rusticity_term/
ec2.rs

1use crate::common::{translate_column, ColumnId, UTC_TIMESTAMP_WIDTH};
2use crate::ui::table::Column as TableColumn;
3use ratatui::prelude::*;
4use ratatui::style::Color;
5use std::collections::HashMap;
6
7pub mod tag;
8
9pub fn init(i18n: &mut HashMap<String, String>) {
10    for col in Column::all() {
11        i18n.entry(col.id().to_string())
12            .or_insert_with(|| col.default_name().to_string());
13    }
14    tag::init(i18n);
15}
16
17#[derive(Debug, Clone)]
18pub struct Instance {
19    pub instance_id: String,
20    pub name: String,
21    pub state: String,
22    pub instance_type: String,
23    pub availability_zone: String,
24    pub public_ipv4_dns: String,
25    pub public_ipv4_address: String,
26    pub elastic_ip: String,
27    pub ipv6_ips: String,
28    pub monitoring: String,
29    pub security_groups: String,
30    pub key_name: String,
31    pub launch_time: String,
32    pub platform_details: String,
33    pub status_checks: String,
34    pub alarm_status: String,
35    pub private_dns_name: String,
36    pub private_ip_address: String,
37    pub security_group_ids: String,
38    pub owner_id: String,
39    pub volume_id: String,
40    pub root_device_name: String,
41    pub root_device_type: String,
42    pub ebs_optimized: String,
43    pub image_id: String,
44    pub kernel_id: String,
45    pub ramdisk_id: String,
46    pub ami_launch_index: String,
47    pub reservation_id: String,
48    pub vpc_id: String,
49    pub subnet_ids: String,
50    pub instance_lifecycle: String,
51    pub architecture: String,
52    pub virtualization_type: String,
53    pub platform: String,
54    pub iam_instance_profile_arn: String,
55    pub tenancy: String,
56    pub affinity: String,
57    pub host_id: String,
58    pub placement_group: String,
59    pub partition_number: String,
60    pub capacity_reservation_id: String,
61    pub state_transition_reason_code: String,
62    pub state_transition_reason_message: String,
63    pub stop_hibernation_behavior: String,
64    pub outpost_arn: String,
65    pub product_codes: String,
66    pub availability_zone_id: String,
67    pub imdsv2: String,
68    pub usage_operation: String,
69    pub managed: String,
70    pub operator: String,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq)]
74pub enum Column {
75    InstanceId,
76    Name,
77    InstanceState,
78    InstanceType,
79    StatusCheck,
80    AlarmStatus,
81    AvailabilityZone,
82    PublicIpv4Dns,
83    PublicIpv4Address,
84    ElasticIp,
85    Ipv6Ips,
86    Monitoring,
87    SecurityGroupName,
88    KeyName,
89    LaunchTime,
90    PlatformDetails,
91    PrivateDnsName,
92    PrivateIpAddress,
93    SecurityGroupIds,
94    OwnerId,
95    VolumeId,
96    RootDeviceName,
97    RootDeviceType,
98    EbsOptimized,
99    ImageId,
100    KernelId,
101    RamdiskId,
102    AmiLaunchIndex,
103    ReservationId,
104    VpcId,
105    SubnetIds,
106    InstanceLifecycle,
107    Architecture,
108    VirtualizationType,
109    Platform,
110    IamInstanceProfileArn,
111    Tenancy,
112    Affinity,
113    HostId,
114    PlacementGroup,
115    PartitionNumber,
116    CapacityReservationId,
117    StateTransitionReasonCode,
118    StateTransitionReasonMessage,
119    StopHibernationBehavior,
120    OutpostArn,
121    ProductCodes,
122    AvailabilityZoneId,
123    Imdsv2,
124    UsageOperation,
125    Managed,
126    Operator,
127}
128
129impl Column {
130    pub fn id(&self) -> &'static str {
131        match self {
132            Column::InstanceId => "column.ec2.instance.instance_id",
133            Column::Name => "column.ec2.instance.name",
134            Column::InstanceState => "column.ec2.instance.state",
135            Column::InstanceType => "column.ec2.instance.instance_type",
136            Column::StatusCheck => "column.ec2.instance.status_check",
137            Column::AlarmStatus => "column.ec2.instance.alarm_status",
138            Column::AvailabilityZone => "column.ec2.instance.availability_zone",
139            Column::PublicIpv4Dns => "column.ec2.instance.public_ipv4_dns",
140            Column::PublicIpv4Address => "column.ec2.instance.public_ipv4_address",
141            Column::ElasticIp => "column.ec2.instance.elastic_ip",
142            Column::Ipv6Ips => "column.ec2.instance.ipv6_ips",
143            Column::Monitoring => "column.ec2.instance.monitoring",
144            Column::SecurityGroupName => "column.ec2.instance.security_group_name",
145            Column::KeyName => "column.ec2.instance.key_name",
146            Column::LaunchTime => "column.ec2.instance.launch_time",
147            Column::PlatformDetails => "column.ec2.instance.platform_details",
148            Column::PrivateDnsName => "column.ec2.instance.private_dns_name",
149            Column::PrivateIpAddress => "column.ec2.instance.private_ip_address",
150            Column::SecurityGroupIds => "column.ec2.instance.security_group_ids",
151            Column::OwnerId => "column.ec2.instance.owner_id",
152            Column::VolumeId => "column.ec2.instance.volume_id",
153            Column::RootDeviceName => "column.ec2.instance.root_device_name",
154            Column::RootDeviceType => "column.ec2.instance.root_device_type",
155            Column::EbsOptimized => "column.ec2.instance.ebs_optimized",
156            Column::ImageId => "column.ec2.instance.image_id",
157            Column::KernelId => "column.ec2.instance.kernel_id",
158            Column::RamdiskId => "column.ec2.instance.ramdisk_id",
159            Column::AmiLaunchIndex => "column.ec2.instance.ami_launch_index",
160            Column::ReservationId => "column.ec2.instance.reservation_id",
161            Column::VpcId => "column.ec2.instance.vpc_id",
162            Column::SubnetIds => "column.ec2.instance.subnet_ids",
163            Column::InstanceLifecycle => "column.ec2.instance.instance_lifecycle",
164            Column::Architecture => "column.ec2.instance.architecture",
165            Column::VirtualizationType => "column.ec2.instance.virtualization_type",
166            Column::Platform => "column.ec2.instance.platform",
167            Column::IamInstanceProfileArn => "column.ec2.instance.iam_instance_profile_arn",
168            Column::Tenancy => "column.ec2.instance.tenancy",
169            Column::Affinity => "column.ec2.instance.affinity",
170            Column::HostId => "column.ec2.instance.host_id",
171            Column::PlacementGroup => "column.ec2.instance.placement_group",
172            Column::PartitionNumber => "column.ec2.instance.partition_number",
173            Column::CapacityReservationId => "column.ec2.instance.capacity_reservation_id",
174            Column::StateTransitionReasonCode => "column.ec2.instance.state_transition_reason_code",
175            Column::StateTransitionReasonMessage => {
176                "column.ec2.instance.state_transition_reason_message"
177            }
178            Column::StopHibernationBehavior => "column.ec2.instance.stop_hibernation_behavior",
179            Column::OutpostArn => "column.ec2.instance.outpost_arn",
180            Column::ProductCodes => "column.ec2.instance.product_codes",
181            Column::AvailabilityZoneId => "column.ec2.instance.availability_zone_id",
182            Column::Imdsv2 => "column.ec2.instance.imdsv2",
183            Column::UsageOperation => "column.ec2.instance.usage_operation",
184            Column::Managed => "column.ec2.instance.managed",
185            Column::Operator => "column.ec2.instance.operator",
186        }
187    }
188
189    pub fn default_name(&self) -> &'static str {
190        match self {
191            Column::InstanceId => "Instance ID",
192            Column::Name => "Name",
193            Column::InstanceState => "Instance state",
194            Column::InstanceType => "Instance type",
195            Column::StatusCheck => "Status check",
196            Column::AlarmStatus => "Alarm status",
197            Column::AvailabilityZone => "Availability Zone",
198            Column::PublicIpv4Dns => "Public IPv4 DNS",
199            Column::PublicIpv4Address => "Public IPv4 address",
200            Column::ElasticIp => "Elastic IP",
201            Column::Ipv6Ips => "IPv6 IPs",
202            Column::Monitoring => "Monitoring",
203            Column::SecurityGroupName => "Security group name",
204            Column::KeyName => "Key name",
205            Column::LaunchTime => "Launch time",
206            Column::PlatformDetails => "Platform details",
207            Column::PrivateDnsName => "Private DNS name",
208            Column::PrivateIpAddress => "Private IP address",
209            Column::SecurityGroupIds => "Security group IDs",
210            Column::OwnerId => "Owner ID",
211            Column::VolumeId => "Volume ID",
212            Column::RootDeviceName => "Root device name",
213            Column::RootDeviceType => "Root device type",
214            Column::EbsOptimized => "EBS optimized",
215            Column::ImageId => "Image ID",
216            Column::KernelId => "Kernel ID",
217            Column::RamdiskId => "RAM disk ID",
218            Column::AmiLaunchIndex => "AMI launch index",
219            Column::ReservationId => "Reservation ID",
220            Column::VpcId => "VPC ID",
221            Column::SubnetIds => "Subnet IDs",
222            Column::InstanceLifecycle => "Instance lifecycle",
223            Column::Architecture => "Architecture",
224            Column::VirtualizationType => "Virtualization type",
225            Column::Platform => "Platform",
226            Column::IamInstanceProfileArn => "IAM instance profile ARN",
227            Column::Tenancy => "Tenancy",
228            Column::Affinity => "Affinity",
229            Column::HostId => "Host ID",
230            Column::PlacementGroup => "Placement group",
231            Column::PartitionNumber => "Partition number",
232            Column::CapacityReservationId => "Capacity Reservation ID",
233            Column::StateTransitionReasonCode => "State transition reason code",
234            Column::StateTransitionReasonMessage => "State transition reason message",
235            Column::StopHibernationBehavior => "Stop-hibernation behavior",
236            Column::OutpostArn => "Outpost ARN",
237            Column::ProductCodes => "Product codes",
238            Column::AvailabilityZoneId => "Availability Zone ID",
239            Column::Imdsv2 => "IMDSv2",
240            Column::UsageOperation => "Usage operation",
241            Column::Managed => "Managed",
242            Column::Operator => "Operator",
243        }
244    }
245
246    pub fn name(&self) -> String {
247        translate_column(self.id(), self.default_name())
248    }
249
250    pub fn from_id(id: &str) -> Option<Self> {
251        match id {
252            "column.ec2.instance.instance_id" => Some(Column::InstanceId),
253            "column.ec2.instance.name" => Some(Column::Name),
254            "column.ec2.instance.state" => Some(Column::InstanceState),
255            "column.ec2.instance.instance_type" => Some(Column::InstanceType),
256            "column.ec2.instance.status_check" => Some(Column::StatusCheck),
257            "column.ec2.instance.alarm_status" => Some(Column::AlarmStatus),
258            "column.ec2.instance.availability_zone" => Some(Column::AvailabilityZone),
259            "column.ec2.instance.public_ipv4_dns" => Some(Column::PublicIpv4Dns),
260            "column.ec2.instance.public_ipv4_address" => Some(Column::PublicIpv4Address),
261            "column.ec2.instance.elastic_ip" => Some(Column::ElasticIp),
262            "column.ec2.instance.ipv6_ips" => Some(Column::Ipv6Ips),
263            "column.ec2.instance.monitoring" => Some(Column::Monitoring),
264            "column.ec2.instance.security_group_name" => Some(Column::SecurityGroupName),
265            "column.ec2.instance.key_name" => Some(Column::KeyName),
266            "column.ec2.instance.launch_time" => Some(Column::LaunchTime),
267            "column.ec2.instance.platform_details" => Some(Column::PlatformDetails),
268            "column.ec2.instance.private_dns_name" => Some(Column::PrivateDnsName),
269            "column.ec2.instance.private_ip_address" => Some(Column::PrivateIpAddress),
270            "column.ec2.instance.security_group_ids" => Some(Column::SecurityGroupIds),
271            "column.ec2.instance.owner_id" => Some(Column::OwnerId),
272            "column.ec2.instance.volume_id" => Some(Column::VolumeId),
273            "column.ec2.instance.root_device_name" => Some(Column::RootDeviceName),
274            "column.ec2.instance.root_device_type" => Some(Column::RootDeviceType),
275            "column.ec2.instance.ebs_optimized" => Some(Column::EbsOptimized),
276            "column.ec2.instance.image_id" => Some(Column::ImageId),
277            "column.ec2.instance.kernel_id" => Some(Column::KernelId),
278            "column.ec2.instance.ramdisk_id" => Some(Column::RamdiskId),
279            "column.ec2.instance.ami_launch_index" => Some(Column::AmiLaunchIndex),
280            "column.ec2.instance.reservation_id" => Some(Column::ReservationId),
281            "column.ec2.instance.vpc_id" => Some(Column::VpcId),
282            "column.ec2.instance.subnet_ids" => Some(Column::SubnetIds),
283            "column.ec2.instance.instance_lifecycle" => Some(Column::InstanceLifecycle),
284            "column.ec2.instance.architecture" => Some(Column::Architecture),
285            "column.ec2.instance.virtualization_type" => Some(Column::VirtualizationType),
286            "column.ec2.instance.platform" => Some(Column::Platform),
287            "column.ec2.instance.iam_instance_profile_arn" => Some(Column::IamInstanceProfileArn),
288            "column.ec2.instance.tenancy" => Some(Column::Tenancy),
289            "column.ec2.instance.affinity" => Some(Column::Affinity),
290            "column.ec2.instance.host_id" => Some(Column::HostId),
291            "column.ec2.instance.placement_group" => Some(Column::PlacementGroup),
292            "column.ec2.instance.partition_number" => Some(Column::PartitionNumber),
293            "column.ec2.instance.capacity_reservation_id" => Some(Column::CapacityReservationId),
294            "column.ec2.instance.state_transition_reason_code" => {
295                Some(Column::StateTransitionReasonCode)
296            }
297            "column.ec2.instance.state_transition_reason_message" => {
298                Some(Column::StateTransitionReasonMessage)
299            }
300            "column.ec2.instance.stop_hibernation_behavior" => {
301                Some(Column::StopHibernationBehavior)
302            }
303            "column.ec2.instance.outpost_arn" => Some(Column::OutpostArn),
304            "column.ec2.instance.product_codes" => Some(Column::ProductCodes),
305            "column.ec2.instance.availability_zone_id" => Some(Column::AvailabilityZoneId),
306            "column.ec2.instance.imdsv2" => Some(Column::Imdsv2),
307            "column.ec2.instance.usage_operation" => Some(Column::UsageOperation),
308            "column.ec2.instance.managed" => Some(Column::Managed),
309            "column.ec2.instance.operator" => Some(Column::Operator),
310            _ => None,
311        }
312    }
313
314    pub fn all() -> [Column; 52] {
315        [
316            Column::InstanceId,
317            Column::Name,
318            Column::InstanceState,
319            Column::InstanceType,
320            Column::StatusCheck,
321            Column::AlarmStatus,
322            Column::AvailabilityZone,
323            Column::PublicIpv4Dns,
324            Column::PublicIpv4Address,
325            Column::ElasticIp,
326            Column::Ipv6Ips,
327            Column::Monitoring,
328            Column::SecurityGroupName,
329            Column::KeyName,
330            Column::LaunchTime,
331            Column::PlatformDetails,
332            Column::PrivateDnsName,
333            Column::PrivateIpAddress,
334            Column::SecurityGroupIds,
335            Column::OwnerId,
336            Column::VolumeId,
337            Column::RootDeviceName,
338            Column::RootDeviceType,
339            Column::EbsOptimized,
340            Column::ImageId,
341            Column::KernelId,
342            Column::RamdiskId,
343            Column::AmiLaunchIndex,
344            Column::ReservationId,
345            Column::VpcId,
346            Column::SubnetIds,
347            Column::InstanceLifecycle,
348            Column::Architecture,
349            Column::VirtualizationType,
350            Column::Platform,
351            Column::IamInstanceProfileArn,
352            Column::Tenancy,
353            Column::Affinity,
354            Column::HostId,
355            Column::PlacementGroup,
356            Column::PartitionNumber,
357            Column::CapacityReservationId,
358            Column::StateTransitionReasonCode,
359            Column::StateTransitionReasonMessage,
360            Column::StopHibernationBehavior,
361            Column::OutpostArn,
362            Column::ProductCodes,
363            Column::AvailabilityZoneId,
364            Column::Imdsv2,
365            Column::UsageOperation,
366            Column::Managed,
367            Column::Operator,
368        ]
369    }
370
371    pub fn ids() -> Vec<ColumnId> {
372        Self::all().iter().map(|c| c.id()).collect()
373    }
374
375    pub fn to_column(&self) -> Box<dyn TableColumn<Instance>> {
376        struct InstanceColumn {
377            variant: Column,
378        }
379
380        impl TableColumn<Instance> for InstanceColumn {
381            fn name(&self) -> &str {
382                Box::leak(self.variant.name().into_boxed_str())
383            }
384
385            fn width(&self) -> u16 {
386                let translated = translate_column(self.variant.id(), self.variant.default_name());
387                translated.len().max(match self.variant {
388                    Column::InstanceId => 20,
389                    Column::Name => 30,
390                    Column::InstanceState => 18,
391                    Column::InstanceType => 15,
392                    Column::StatusCheck => 15,
393                    Column::AlarmStatus => 15,
394                    Column::AvailabilityZone => 20,
395                    Column::PublicIpv4Dns => 40,
396                    Column::PublicIpv4Address => 20,
397                    Column::ElasticIp => 20,
398                    Column::Ipv6Ips => 30,
399                    Column::Monitoring => 15,
400                    Column::SecurityGroupName => 30,
401                    Column::KeyName => 20,
402                    Column::LaunchTime => UTC_TIMESTAMP_WIDTH as usize,
403                    Column::PlatformDetails => 30,
404                    Column::PrivateDnsName => 40,
405                    Column::PrivateIpAddress => 20,
406                    Column::SecurityGroupIds => 30,
407                    Column::OwnerId => 15,
408                    Column::VolumeId => 25,
409                    Column::RootDeviceName => 20,
410                    Column::RootDeviceType => 18,
411                    Column::EbsOptimized => 15,
412                    Column::ImageId => 25,
413                    Column::KernelId => 25,
414                    Column::RamdiskId => 25,
415                    Column::AmiLaunchIndex => 18,
416                    Column::ReservationId => 20,
417                    Column::VpcId => 25,
418                    Column::SubnetIds => 30,
419                    Column::InstanceLifecycle => 20,
420                    Column::Architecture => 15,
421                    Column::VirtualizationType => 20,
422                    Column::Platform => 15,
423                    Column::IamInstanceProfileArn => 50,
424                    Column::Tenancy => 15,
425                    Column::Affinity => 15,
426                    Column::HostId => 25,
427                    Column::PlacementGroup => 25,
428                    Column::PartitionNumber => 18,
429                    Column::CapacityReservationId => 30,
430                    Column::StateTransitionReasonCode => 30,
431                    Column::StateTransitionReasonMessage => 50,
432                    Column::StopHibernationBehavior => 28,
433                    Column::OutpostArn => 40,
434                    Column::ProductCodes => 30,
435                    Column::AvailabilityZoneId => 20,
436                    Column::Imdsv2 => 15,
437                    Column::UsageOperation => 25,
438                    Column::Managed => 15,
439                    Column::Operator => 15,
440                }) as u16
441            }
442
443            fn render(&self, item: &Instance) -> (String, Style) {
444                match self.variant {
445                    Column::InstanceId => (item.instance_id.clone(), Style::default()),
446                    Column::Name => (item.name.clone(), Style::default()),
447                    Column::InstanceState => {
448                        let (formatted, color) = format_state(&item.state);
449                        (formatted, Style::default().fg(color))
450                    }
451                    Column::InstanceType => (item.instance_type.clone(), Style::default()),
452                    Column::StatusCheck => (item.status_checks.clone(), Style::default()),
453                    Column::AlarmStatus => (item.alarm_status.clone(), Style::default()),
454                    Column::AvailabilityZone => (item.availability_zone.clone(), Style::default()),
455                    Column::PublicIpv4Dns => (item.public_ipv4_dns.clone(), Style::default()),
456                    Column::PublicIpv4Address => {
457                        (item.public_ipv4_address.clone(), Style::default())
458                    }
459                    Column::ElasticIp => (item.elastic_ip.clone(), Style::default()),
460                    Column::Ipv6Ips => (item.ipv6_ips.clone(), Style::default()),
461                    Column::Monitoring => (item.monitoring.clone(), Style::default()),
462                    Column::SecurityGroupName => (item.security_groups.clone(), Style::default()),
463                    Column::KeyName => (item.key_name.clone(), Style::default()),
464                    Column::LaunchTime => (item.launch_time.clone(), Style::default()),
465                    Column::PlatformDetails => (item.platform_details.clone(), Style::default()),
466                    Column::PrivateDnsName => (item.private_dns_name.clone(), Style::default()),
467                    Column::PrivateIpAddress => (item.private_ip_address.clone(), Style::default()),
468                    Column::SecurityGroupIds => (item.security_group_ids.clone(), Style::default()),
469                    Column::OwnerId => (item.owner_id.clone(), Style::default()),
470                    Column::VolumeId => (item.volume_id.clone(), Style::default()),
471                    Column::RootDeviceName => (item.root_device_name.clone(), Style::default()),
472                    Column::RootDeviceType => (item.root_device_type.clone(), Style::default()),
473                    Column::EbsOptimized => (item.ebs_optimized.clone(), Style::default()),
474                    Column::ImageId => (item.image_id.clone(), Style::default()),
475                    Column::KernelId => (item.kernel_id.clone(), Style::default()),
476                    Column::RamdiskId => (item.ramdisk_id.clone(), Style::default()),
477                    Column::AmiLaunchIndex => (item.ami_launch_index.clone(), Style::default()),
478                    Column::ReservationId => (item.reservation_id.clone(), Style::default()),
479                    Column::VpcId => (item.vpc_id.clone(), Style::default()),
480                    Column::SubnetIds => (item.subnet_ids.clone(), Style::default()),
481                    Column::InstanceLifecycle => {
482                        (item.instance_lifecycle.clone(), Style::default())
483                    }
484                    Column::Architecture => (item.architecture.clone(), Style::default()),
485                    Column::VirtualizationType => {
486                        (item.virtualization_type.clone(), Style::default())
487                    }
488                    Column::Platform => (item.platform.clone(), Style::default()),
489                    Column::IamInstanceProfileArn => {
490                        (item.iam_instance_profile_arn.clone(), Style::default())
491                    }
492                    Column::Tenancy => (item.tenancy.clone(), Style::default()),
493                    Column::Affinity => (item.affinity.clone(), Style::default()),
494                    Column::HostId => (item.host_id.clone(), Style::default()),
495                    Column::PlacementGroup => (item.placement_group.clone(), Style::default()),
496                    Column::PartitionNumber => (item.partition_number.clone(), Style::default()),
497                    Column::CapacityReservationId => {
498                        (item.capacity_reservation_id.clone(), Style::default())
499                    }
500                    Column::StateTransitionReasonCode => {
501                        (item.state_transition_reason_code.clone(), Style::default())
502                    }
503                    Column::StateTransitionReasonMessage => (
504                        item.state_transition_reason_message.clone(),
505                        Style::default(),
506                    ),
507                    Column::StopHibernationBehavior => {
508                        (item.stop_hibernation_behavior.clone(), Style::default())
509                    }
510                    Column::OutpostArn => (item.outpost_arn.clone(), Style::default()),
511                    Column::ProductCodes => (item.product_codes.clone(), Style::default()),
512                    Column::AvailabilityZoneId => {
513                        (item.availability_zone_id.clone(), Style::default())
514                    }
515                    Column::Imdsv2 => (item.imdsv2.clone(), Style::default()),
516                    Column::UsageOperation => (item.usage_operation.clone(), Style::default()),
517                    Column::Managed => (item.managed.clone(), Style::default()),
518                    Column::Operator => (item.operator.clone(), Style::default()),
519                }
520            }
521        }
522
523        Box::new(InstanceColumn { variant: *self })
524    }
525}
526
527pub fn format_state(state: &str) -> (String, Color) {
528    match state {
529        "running" => ("✅ Running".to_string(), Color::Green),
530        "stopped" => ("🛑 Stopped".to_string(), Color::Red),
531        "terminated" => ("❌ Terminated".to_string(), Color::DarkGray),
532        "pending" => ("❎ Pending".to_string(), Color::Yellow),
533        "shutting-down" => ("🔴 Shutting-down".to_string(), Color::Yellow),
534        "stopping" => ("🚫 Stopping".to_string(), Color::Yellow),
535        _ => (state.to_string(), Color::White),
536    }
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542
543    #[test]
544    fn test_column_names() {
545        assert_eq!(Column::Name.name(), "Name");
546        assert_eq!(Column::InstanceId.name(), "Instance ID");
547        assert_eq!(Column::InstanceState.name(), "Instance state");
548        assert_eq!(Column::InstanceType.name(), "Instance type");
549    }
550
551    #[test]
552    fn test_column_all() {
553        let columns = Column::ids();
554        assert_eq!(columns.len(), 52);
555        assert_eq!(columns[0], Column::InstanceId.id());
556    }
557
558    #[test]
559    fn test_format_state() {
560        let (formatted, color) = format_state("running");
561        assert_eq!(formatted, "✅ Running");
562        assert_eq!(color, Color::Green);
563
564        let (formatted, color) = format_state("stopped");
565        assert_eq!(formatted, "🛑 Stopped");
566        assert_eq!(color, Color::Red);
567
568        let (formatted, color) = format_state("terminated");
569        assert_eq!(formatted, "❌ Terminated");
570        assert_eq!(color, Color::DarkGray);
571
572        let (formatted, color) = format_state("pending");
573        assert_eq!(formatted, "❎ Pending");
574        assert_eq!(color, Color::Yellow);
575
576        let (formatted, color) = format_state("shutting-down");
577        assert_eq!(formatted, "🔴 Shutting-down");
578        assert_eq!(color, Color::Yellow);
579
580        let (formatted, color) = format_state("stopping");
581        assert_eq!(formatted, "🚫 Stopping");
582        assert_eq!(color, Color::Yellow);
583    }
584
585    #[test]
586    fn test_column_from_id() {
587        assert_eq!(
588            Column::from_id("column.ec2.instance.name"),
589            Some(Column::Name)
590        );
591        assert_eq!(
592            Column::from_id("column.ec2.instance.instance_id"),
593            Some(Column::InstanceId)
594        );
595        assert_eq!(
596            Column::from_id("column.ec2.instance.state"),
597            Some(Column::InstanceState)
598        );
599        assert_eq!(Column::from_id("invalid"), None);
600    }
601
602    #[test]
603    fn test_column_ids_unique() {
604        let ids = Column::ids();
605        let mut seen = std::collections::HashSet::new();
606        for id in ids {
607            assert!(seen.insert(id), "Duplicate column ID: {}", id);
608        }
609    }
610
611    #[test]
612    fn test_column_ids_have_correct_prefix() {
613        for col in Column::all() {
614            assert!(
615                col.id().starts_with("column.ec2.instance."),
616                "Column ID '{}' should start with 'column.ec2.instance.'",
617                col.id()
618            );
619        }
620    }
621
622    #[test]
623    fn test_launch_time_uses_utc_timestamp_width() {
624        let col = Column::LaunchTime.to_column();
625        assert_eq!(col.width(), UTC_TIMESTAMP_WIDTH);
626    }
627
628    #[test]
629    fn test_utc_timestamp_width_constant_is_27() {
630        assert_eq!(UTC_TIMESTAMP_WIDTH, 27);
631    }
632}