1use crate::common::{translate_column, ColumnId};
2use crate::ui::table::Column as TableColumn;
3use ratatui::prelude::*;
4use rusticity_core::efs::EfsMountTarget as MountTarget;
5use std::collections::HashMap;
6
7pub fn init(i18n: &mut HashMap<String, String>) {
8 for col in Column::all() {
9 i18n.entry(col.id().to_string())
10 .or_insert_with(|| col.default_name().to_string());
11 }
12}
13
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum Column {
16 AvailabilityZone,
17 MountTargetId,
18 OwnerId,
19 FileSystemId,
20 SubnetId,
21 VpcId,
22 State,
23 Ipv4Address,
24 Ipv6Address,
25 NetworkInterfaceId,
26 SecurityGroups,
27}
28
29impl Column {
30 const ID_AVAILABILITY_ZONE: &'static str = "column.efs.mt.availability_zone";
31 const ID_MOUNT_TARGET_ID: &'static str = "column.efs.mt.mount_target_id";
32 const ID_OWNER_ID: &'static str = "column.efs.mt.owner_id";
33 const ID_FILE_SYSTEM_ID: &'static str = "column.efs.mt.file_system_id";
34 const ID_SUBNET_ID: &'static str = "column.efs.mt.subnet_id";
35 const ID_VPC_ID: &'static str = "column.efs.mt.vpc_id";
36 const ID_STATE: &'static str = "column.efs.mt.state";
37 const ID_IPV4_ADDRESS: &'static str = "column.efs.mt.ipv4_address";
38 const ID_IPV6_ADDRESS: &'static str = "column.efs.mt.ipv6_address";
39 const ID_NETWORK_INTERFACE_ID: &'static str = "column.efs.mt.network_interface_id";
40 const ID_SECURITY_GROUPS: &'static str = "column.efs.mt.security_groups";
41
42 pub const fn id(&self) -> &'static str {
43 match self {
44 Column::AvailabilityZone => Self::ID_AVAILABILITY_ZONE,
45 Column::MountTargetId => Self::ID_MOUNT_TARGET_ID,
46 Column::OwnerId => Self::ID_OWNER_ID,
47 Column::FileSystemId => Self::ID_FILE_SYSTEM_ID,
48 Column::SubnetId => Self::ID_SUBNET_ID,
49 Column::VpcId => Self::ID_VPC_ID,
50 Column::State => Self::ID_STATE,
51 Column::Ipv4Address => Self::ID_IPV4_ADDRESS,
52 Column::Ipv6Address => Self::ID_IPV6_ADDRESS,
53 Column::NetworkInterfaceId => Self::ID_NETWORK_INTERFACE_ID,
54 Column::SecurityGroups => Self::ID_SECURITY_GROUPS,
55 }
56 }
57
58 pub const fn default_name(&self) -> &'static str {
59 match self {
60 Column::AvailabilityZone => "Availability zone (AZ-ID)",
61 Column::MountTargetId => "Mount target ID",
62 Column::OwnerId => "Owner ID",
63 Column::FileSystemId => "File system ID",
64 Column::SubnetId => "Subnet ID",
65 Column::VpcId => "VPC ID",
66 Column::State => "File system state",
67 Column::Ipv4Address => "IPv4 address",
68 Column::Ipv6Address => "IPv6 address",
69 Column::NetworkInterfaceId => "Network interface ID",
70 Column::SecurityGroups => "Security groups",
71 }
72 }
73
74 pub const fn all() -> [Column; 11] {
75 [
76 Column::AvailabilityZone,
77 Column::MountTargetId,
78 Column::OwnerId,
79 Column::FileSystemId,
80 Column::SubnetId,
81 Column::VpcId,
82 Column::State,
83 Column::Ipv4Address,
84 Column::Ipv6Address,
85 Column::NetworkInterfaceId,
86 Column::SecurityGroups,
87 ]
88 }
89
90 pub fn ids() -> Vec<ColumnId> {
91 Self::all().iter().map(|c| c.id()).collect()
92 }
93
94 pub fn default_visible_ids() -> Vec<ColumnId> {
96 Self::all()
97 .iter()
98 .filter(|c| !matches!(c, Column::OwnerId | Column::FileSystemId))
99 .map(|c| c.id())
100 .collect()
101 }
102
103 pub fn from_id(id: &str) -> Option<Self> {
104 match id {
105 Self::ID_AVAILABILITY_ZONE => Some(Column::AvailabilityZone),
106 Self::ID_MOUNT_TARGET_ID => Some(Column::MountTargetId),
107 Self::ID_OWNER_ID => Some(Column::OwnerId),
108 Self::ID_FILE_SYSTEM_ID => Some(Column::FileSystemId),
109 Self::ID_SUBNET_ID => Some(Column::SubnetId),
110 Self::ID_VPC_ID => Some(Column::VpcId),
111 Self::ID_STATE => Some(Column::State),
112 Self::ID_IPV4_ADDRESS => Some(Column::Ipv4Address),
113 Self::ID_IPV6_ADDRESS => Some(Column::Ipv6Address),
114 Self::ID_NETWORK_INTERFACE_ID => Some(Column::NetworkInterfaceId),
115 Self::ID_SECURITY_GROUPS => Some(Column::SecurityGroups),
116 _ => None,
117 }
118 }
119
120 pub fn name(&self) -> String {
121 translate_column(self.id(), self.default_name())
122 }
123}
124
125impl TableColumn<MountTarget> for Column {
126 fn name(&self) -> &str {
127 Box::leak(translate_column(self.id(), self.default_name()).into_boxed_str())
128 }
129
130 fn width(&self) -> u16 {
131 let translated = translate_column(self.id(), self.default_name());
132 translated.len().max(match self {
133 Column::AvailabilityZone => 25,
134 Column::MountTargetId => 22,
135 Column::OwnerId => 14,
136 Column::FileSystemId => 21,
137 Column::SubnetId => 24,
138 Column::VpcId => 22,
139 Column::State => 18,
140 Column::Ipv4Address => 15,
141 Column::Ipv6Address => 20,
142 Column::NetworkInterfaceId => 22,
143 Column::SecurityGroups => 24,
144 }) as u16
145 }
146
147 fn render(&self, item: &MountTarget) -> (String, Style) {
148 match self {
149 Column::AvailabilityZone => {
150 let text = if item.availability_zone_id.is_empty() {
151 item.availability_zone_name.clone()
152 } else {
153 format!(
154 "{} ({})",
155 item.availability_zone_name, item.availability_zone_id
156 )
157 };
158 (text, Style::default())
159 }
160 Column::MountTargetId => (item.mount_target_id.clone(), Style::default()),
161 Column::OwnerId => (item.owner_id.clone(), Style::default()),
162 Column::FileSystemId => (item.file_system_id.clone(), Style::default()),
163 Column::SubnetId => (item.subnet_id.clone(), Style::default()),
164 Column::VpcId => (item.vpc_id.clone(), Style::default()),
165 Column::State => {
166 let (text, color) = format_state(&item.life_cycle_state);
167 (text.to_string(), Style::default().fg(color))
168 }
169 Column::Ipv4Address => (item.ip_address.clone(), Style::default()),
170 Column::Ipv6Address => (item.ipv6_address.clone(), Style::default()),
171 Column::NetworkInterfaceId => (item.network_interface_id.clone(), Style::default()),
172 Column::SecurityGroups => (item.security_groups.clone(), Style::default()),
173 }
174 }
175}
176
177fn format_state(state: &str) -> (&'static str, Color) {
178 match state {
179 "available" | "Available" | "AVAILABLE" => ("✅ Available", Color::Green),
180 "creating" | "Creating" | "CREATING" => ("⏳ Creating", Color::Yellow),
181 "deleting" | "Deleting" | "DELETING" => ("🗑 Deleting", Color::Red),
182 "deleted" | "Deleted" | "DELETED" => ("🗑 Deleted", Color::Red),
183 "updating" | "Updating" | "UPDATING" => ("🔄 Updating", Color::Yellow),
184 "error" | "Error" | "ERROR" => ("⚠ Error", Color::Red),
185 _ => ("Unknown", Color::White),
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_mt_column_ids_have_prefix() {
195 for col in Column::all() {
196 assert!(col.id().starts_with("column.efs.mt."));
197 }
198 }
199
200 #[test]
201 fn test_mt_from_id_roundtrip() {
202 for col in Column::all() {
203 assert_eq!(Column::from_id(col.id()), Some(col));
204 }
205 }
206
207 #[test]
208 fn test_mt_owner_and_fs_id_hidden_by_default() {
209 let visible = Column::default_visible_ids();
210 assert!(!visible.contains(&Column::OwnerId.id()));
211 assert!(!visible.contains(&Column::FileSystemId.id()));
212 assert_eq!(visible.len(), 9);
214 assert!(visible.contains(&Column::AvailabilityZone.id()));
215 assert!(visible.contains(&Column::SecurityGroups.id()));
216 }
217
218 #[test]
219 fn test_mt_all_eleven_columns() {
220 assert_eq!(Column::all().len(), 11);
221 }
222}