Skip to main content

rusticity_term/efs/
fs.rs

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    /// Default visible columns — excludes: ThroughputMode, PerformanceMode,
159    /// NumberOfMountTargets, OwnerId, CreationToken, KmsKeyId
160    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}