1use crate::common::{format_iso_timestamp, translate_column, ColumnId, UTC_TIMESTAMP_WIDTH};
2use crate::ui::table::Column as TableColumn;
3use ratatui::prelude::*;
4use std::collections::HashMap;
5
6pub fn init(i18n: &mut HashMap<String, String>) {
7 for col in Column::all() {
8 i18n.entry(col.id().to_string())
9 .or_insert_with(|| col.default_name().to_string());
10 }
11}
12
13#[derive(Debug, Clone)]
14pub struct FileSystem {
15 pub file_system_id: String,
16 pub file_system_arn: String,
17 pub name: String,
18 pub creation_token: String,
19 pub encrypted: String,
20 pub kms_key_id: String,
21 pub total_size: String,
22 pub size_in_standard: String,
23 pub size_in_ia: String,
24 pub size_in_archive: String,
25 pub provisioned_throughput: String,
26 pub throughput_mode: String,
27 pub life_cycle_state: String,
28 pub number_of_mount_targets: String,
29 pub owner_id: String,
30 pub creation_time: String,
31 pub performance_mode: String,
32 pub availability_zone: String,
33 pub replication_overwrite_protection: String,
34 pub dns_name: String,
35 pub tags: Vec<(String, String)>,
36 pub total_size_bytes: i64,
37 pub size_in_standard_bytes: i64,
38 pub size_in_ia_bytes: i64,
39 pub size_in_archive_bytes: i64,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
43pub enum Column {
44 Name,
45 FileSystemId,
46 CreationToken,
47 Encrypted,
48 KmsKeyId,
49 TotalSize,
50 SizeInStandard,
51 SizeInIa,
52 SizeInArchive,
53 ProvisionedThroughput,
54 ThroughputMode,
55 LifeCycleState,
56 NumberOfMountTargets,
57 OwnerId,
58 CreationTime,
59 PerformanceMode,
60 AvailabilityZone,
61 ReplicationOverwriteProtection,
62}
63
64impl Column {
65 const ID_NAME: &'static str = "column.efs.fs.name";
66 const ID_FILE_SYSTEM_ID: &'static str = "column.efs.fs.file_system_id";
67 const ID_CREATION_TOKEN: &'static str = "column.efs.fs.creation_token";
68 const ID_ENCRYPTED: &'static str = "column.efs.fs.encrypted";
69 const ID_KMS_KEY_ID: &'static str = "column.efs.fs.kms_key_id";
70 const ID_TOTAL_SIZE: &'static str = "column.efs.fs.total_size";
71 const ID_SIZE_IN_STANDARD: &'static str = "column.efs.fs.size_in_standard";
72 const ID_SIZE_IN_IA: &'static str = "column.efs.fs.size_in_ia";
73 const ID_SIZE_IN_ARCHIVE: &'static str = "column.efs.fs.size_in_archive";
74 const ID_PROVISIONED_THROUGHPUT: &'static str = "column.efs.fs.provisioned_throughput";
75 const ID_THROUGHPUT_MODE: &'static str = "column.efs.fs.throughput_mode";
76 const ID_LIFE_CYCLE_STATE: &'static str = "column.efs.fs.life_cycle_state";
77 const ID_NUMBER_OF_MOUNT_TARGETS: &'static str = "column.efs.fs.number_of_mount_targets";
78 const ID_OWNER_ID: &'static str = "column.efs.fs.owner_id";
79 const ID_CREATION_TIME: &'static str = "column.efs.fs.creation_time";
80 const ID_PERFORMANCE_MODE: &'static str = "column.efs.fs.performance_mode";
81 const ID_AVAILABILITY_ZONE: &'static str = "column.efs.fs.availability_zone";
82 const ID_REPLICATION_OVERWRITE_PROTECTION: &'static str =
83 "column.efs.fs.replication_overwrite_protection";
84
85 pub const fn id(&self) -> &'static str {
86 match self {
87 Column::Name => Self::ID_NAME,
88 Column::FileSystemId => Self::ID_FILE_SYSTEM_ID,
89 Column::CreationToken => Self::ID_CREATION_TOKEN,
90 Column::Encrypted => Self::ID_ENCRYPTED,
91 Column::KmsKeyId => Self::ID_KMS_KEY_ID,
92 Column::TotalSize => Self::ID_TOTAL_SIZE,
93 Column::SizeInStandard => Self::ID_SIZE_IN_STANDARD,
94 Column::SizeInIa => Self::ID_SIZE_IN_IA,
95 Column::SizeInArchive => Self::ID_SIZE_IN_ARCHIVE,
96 Column::ProvisionedThroughput => Self::ID_PROVISIONED_THROUGHPUT,
97 Column::ThroughputMode => Self::ID_THROUGHPUT_MODE,
98 Column::LifeCycleState => Self::ID_LIFE_CYCLE_STATE,
99 Column::NumberOfMountTargets => Self::ID_NUMBER_OF_MOUNT_TARGETS,
100 Column::OwnerId => Self::ID_OWNER_ID,
101 Column::CreationTime => Self::ID_CREATION_TIME,
102 Column::PerformanceMode => Self::ID_PERFORMANCE_MODE,
103 Column::AvailabilityZone => Self::ID_AVAILABILITY_ZONE,
104 Column::ReplicationOverwriteProtection => Self::ID_REPLICATION_OVERWRITE_PROTECTION,
105 }
106 }
107
108 pub const fn default_name(&self) -> &'static str {
109 match self {
110 Column::Name => "Name",
111 Column::FileSystemId => "File system ID",
112 Column::CreationToken => "Creation token",
113 Column::Encrypted => "Encrypted",
114 Column::KmsKeyId => "KMS Key ID",
115 Column::TotalSize => "Total size",
116 Column::SizeInStandard => "Size in Standard",
117 Column::SizeInIa => "Size in IA",
118 Column::SizeInArchive => "Size in Archive",
119 Column::ProvisionedThroughput => "Provisioned Throughput (MiB/s)",
120 Column::ThroughputMode => "Throughput mode",
121 Column::LifeCycleState => "File system state",
122 Column::NumberOfMountTargets => "Number of mount targets",
123 Column::OwnerId => "Owner ID",
124 Column::CreationTime => "Creation time",
125 Column::PerformanceMode => "Performance mode",
126 Column::AvailabilityZone => "Availability Zone",
127 Column::ReplicationOverwriteProtection => "Replication overwrite protection",
128 }
129 }
130
131 pub const fn all() -> [Column; 18] {
132 [
133 Column::Name,
134 Column::FileSystemId,
135 Column::CreationToken,
136 Column::Encrypted,
137 Column::KmsKeyId,
138 Column::TotalSize,
139 Column::SizeInStandard,
140 Column::SizeInIa,
141 Column::SizeInArchive,
142 Column::ProvisionedThroughput,
143 Column::ThroughputMode,
144 Column::LifeCycleState,
145 Column::NumberOfMountTargets,
146 Column::OwnerId,
147 Column::CreationTime,
148 Column::PerformanceMode,
149 Column::AvailabilityZone,
150 Column::ReplicationOverwriteProtection,
151 ]
152 }
153
154 pub fn ids() -> Vec<ColumnId> {
155 Self::all().iter().map(|c| c.id()).collect()
156 }
157
158 pub fn default_visible_ids() -> Vec<ColumnId> {
161 vec![
162 Self::ID_NAME,
163 Self::ID_FILE_SYSTEM_ID,
164 Self::ID_ENCRYPTED,
165 Self::ID_TOTAL_SIZE,
166 Self::ID_SIZE_IN_STANDARD,
167 Self::ID_SIZE_IN_IA,
168 Self::ID_SIZE_IN_ARCHIVE,
169 Self::ID_PROVISIONED_THROUGHPUT,
170 Self::ID_LIFE_CYCLE_STATE,
171 Self::ID_CREATION_TIME,
172 Self::ID_AVAILABILITY_ZONE,
173 Self::ID_REPLICATION_OVERWRITE_PROTECTION,
174 ]
175 }
176
177 pub fn from_id(id: &str) -> Option<Self> {
178 match id {
179 Self::ID_NAME => Some(Column::Name),
180 Self::ID_FILE_SYSTEM_ID => Some(Column::FileSystemId),
181 Self::ID_CREATION_TOKEN => Some(Column::CreationToken),
182 Self::ID_ENCRYPTED => Some(Column::Encrypted),
183 Self::ID_KMS_KEY_ID => Some(Column::KmsKeyId),
184 Self::ID_TOTAL_SIZE => Some(Column::TotalSize),
185 Self::ID_SIZE_IN_STANDARD => Some(Column::SizeInStandard),
186 Self::ID_SIZE_IN_IA => Some(Column::SizeInIa),
187 Self::ID_SIZE_IN_ARCHIVE => Some(Column::SizeInArchive),
188 Self::ID_PROVISIONED_THROUGHPUT => Some(Column::ProvisionedThroughput),
189 Self::ID_THROUGHPUT_MODE => Some(Column::ThroughputMode),
190 Self::ID_LIFE_CYCLE_STATE => Some(Column::LifeCycleState),
191 Self::ID_NUMBER_OF_MOUNT_TARGETS => Some(Column::NumberOfMountTargets),
192 Self::ID_OWNER_ID => Some(Column::OwnerId),
193 Self::ID_CREATION_TIME => Some(Column::CreationTime),
194 Self::ID_PERFORMANCE_MODE => Some(Column::PerformanceMode),
195 Self::ID_AVAILABILITY_ZONE => Some(Column::AvailabilityZone),
196 Self::ID_REPLICATION_OVERWRITE_PROTECTION => {
197 Some(Column::ReplicationOverwriteProtection)
198 }
199 _ => None,
200 }
201 }
202
203 pub fn name(&self) -> String {
204 translate_column(self.id(), self.default_name())
205 }
206}
207
208impl TableColumn<FileSystem> for Column {
209 fn name(&self) -> &str {
210 Box::leak(translate_column(self.id(), self.default_name()).into_boxed_str())
211 }
212
213 fn width(&self) -> u16 {
214 let translated = translate_column(self.id(), self.default_name());
215 translated.len().max(match self {
216 Column::Name => 30,
217 Column::FileSystemId => 21,
218 Column::CreationToken => 20,
219 Column::Encrypted => 10,
220 Column::KmsKeyId => 30,
221 Column::TotalSize => 12,
222 Column::SizeInStandard => 16,
223 Column::SizeInIa => 10,
224 Column::SizeInArchive => 14,
225 Column::ProvisionedThroughput => 30,
226 Column::ThroughputMode => 16,
227 Column::LifeCycleState => 16,
228 Column::NumberOfMountTargets => 23,
229 Column::OwnerId => 14,
230 Column::CreationTime => UTC_TIMESTAMP_WIDTH as usize,
231 Column::PerformanceMode => 16,
232 Column::AvailabilityZone => 20,
233 Column::ReplicationOverwriteProtection => 34,
234 }) as u16
235 }
236
237 fn render(&self, item: &FileSystem) -> (String, Style) {
238 let (text, style) = match self {
239 Column::Name => (item.name.clone(), Style::default()),
240 Column::FileSystemId => (item.file_system_id.clone(), Style::default()),
241 Column::CreationToken => (item.creation_token.clone(), Style::default()),
242 Column::Encrypted => {
243 let color = match item.encrypted.as_str() {
244 "Yes" => Color::Green,
245 "No" => Color::Yellow,
246 _ => Color::White,
247 };
248 (item.encrypted.clone(), Style::default().fg(color))
249 }
250 Column::KmsKeyId => (item.kms_key_id.clone(), Style::default()),
251 Column::TotalSize => (item.total_size.clone(), Style::default()),
252 Column::SizeInStandard => (item.size_in_standard.clone(), Style::default()),
253 Column::SizeInIa => (item.size_in_ia.clone(), Style::default()),
254 Column::SizeInArchive => (item.size_in_archive.clone(), Style::default()),
255 Column::ProvisionedThroughput => {
256 (item.provisioned_throughput.clone(), Style::default())
257 }
258 Column::ThroughputMode => (
259 format_throughput_mode(&item.throughput_mode),
260 Style::default(),
261 ),
262 Column::LifeCycleState => {
263 let (text, color) = format_life_cycle_state(&item.life_cycle_state);
264 (text.to_string(), Style::default().fg(color))
265 }
266 Column::NumberOfMountTargets => {
267 (item.number_of_mount_targets.clone(), Style::default())
268 }
269 Column::OwnerId => (item.owner_id.clone(), Style::default()),
270 Column::CreationTime => (format_iso_timestamp(&item.creation_time), Style::default()),
271 Column::PerformanceMode => (
272 format_performance_mode(&item.performance_mode),
273 Style::default(),
274 ),
275 Column::AvailabilityZone => (item.availability_zone.clone(), Style::default()),
276 Column::ReplicationOverwriteProtection => {
277 let text = format_replication_protection(&item.replication_overwrite_protection);
278 let color = match item.replication_overwrite_protection.as_str() {
279 "Enabled" | "ENABLED" => Color::Green,
280 "Disabled" | "DISABLED" => Color::Yellow,
281 "Replicating" | "REPLICATING" => Color::Cyan,
282 _ => Color::White,
283 };
284 (text, Style::default().fg(color))
285 }
286 };
287 (text, style)
288 }
289}
290
291fn format_life_cycle_state(state: &str) -> (&'static str, Color) {
292 match state {
293 "available" | "Available" | "AVAILABLE" => ("✅ Available", Color::Green),
294 "creating" | "Creating" | "CREATING" => ("⏳ Creating", Color::Yellow),
295 "deleting" | "Deleting" | "DELETING" => ("🗑 Deleting", Color::Red),
296 "deleted" | "Deleted" | "DELETED" => ("🗑 Deleted", Color::Red),
297 "updating" | "Updating" | "UPDATING" => ("🔄 Updating", Color::Yellow),
298 "error" | "Error" | "ERROR" => ("⚠ Error", Color::Red),
299 _ => ("Unknown", Color::White),
300 }
301}
302
303fn format_throughput_mode(mode: &str) -> String {
304 match mode {
305 "Bursting" => "Bursting".to_string(),
306 "Provisioned" => "Provisioned".to_string(),
307 "Elastic" => "Elastic".to_string(),
308 s => s.to_string(),
309 }
310}
311
312fn format_performance_mode(mode: &str) -> String {
313 match mode {
314 "GeneralPurpose" => "General Purpose".to_string(),
315 "MaxIo" => "Max I/O".to_string(),
316 s => s.to_string(),
317 }
318}
319
320fn format_replication_protection(val: &str) -> String {
321 match val {
322 "Enabled" => "Enabled".to_string(),
323 "Disabled" => "Disabled".to_string(),
324 "Replicating" => "Replicating".to_string(),
325 s => s.to_string(),
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_column_ids_have_correct_prefix() {
335 for col in Column::all() {
336 assert!(
337 col.id().starts_with("column.efs.fs."),
338 "Column ID '{}' should start with 'column.efs.fs.'",
339 col.id()
340 );
341 }
342 }
343
344 #[test]
345 fn test_from_id_roundtrip() {
346 for col in Column::all() {
347 assert_eq!(Column::from_id(col.id()), Some(col));
348 }
349 }
350
351 #[test]
352 fn test_default_visible_excludes_specified_columns() {
353 let ids = Column::default_visible_ids();
354 assert!(
355 !ids.contains(&Column::ThroughputMode.id()),
356 "ThroughputMode not default"
357 );
358 assert!(
359 !ids.contains(&Column::PerformanceMode.id()),
360 "PerformanceMode not default"
361 );
362 assert!(
363 !ids.contains(&Column::NumberOfMountTargets.id()),
364 "NumberOfMountTargets not default"
365 );
366 assert!(!ids.contains(&Column::OwnerId.id()), "OwnerId not default");
367 assert!(
368 !ids.contains(&Column::CreationToken.id()),
369 "CreationToken not default"
370 );
371 assert!(
372 !ids.contains(&Column::KmsKeyId.id()),
373 "KmsKeyId not default"
374 );
375 }
376
377 #[test]
378 fn test_default_visible_includes_core_columns() {
379 let ids = Column::default_visible_ids();
380 assert!(ids.contains(&Column::Name.id()));
381 assert!(ids.contains(&Column::FileSystemId.id()));
382 assert!(ids.contains(&Column::LifeCycleState.id()));
383 assert!(ids.contains(&Column::CreationTime.id()));
384 assert!(ids.contains(&Column::TotalSize.id()));
385 }
386
387 #[test]
388 fn test_all_18_columns() {
389 assert_eq!(Column::all().len(), 18);
390 }
391}