Skip to main content

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    const ID_INSTANCE_ID: &'static str = "column.ec2.instance.instance_id";
131    const ID_NAME: &'static str = "column.ec2.instance.name";
132    const ID_INSTANCE_STATE: &'static str = "column.ec2.instance.state";
133    const ID_INSTANCE_TYPE: &'static str = "column.ec2.instance.instance_type";
134    const ID_STATUS_CHECK: &'static str = "column.ec2.instance.status_check";
135    const ID_ALARM_STATUS: &'static str = "column.ec2.instance.alarm_status";
136    const ID_AVAILABILITY_ZONE: &'static str = "column.ec2.instance.availability_zone";
137    const ID_PUBLIC_IPV4_DNS: &'static str = "column.ec2.instance.public_ipv4_dns";
138    const ID_PUBLIC_IPV4_ADDRESS: &'static str = "column.ec2.instance.public_ipv4_address";
139    const ID_ELASTIC_IP: &'static str = "column.ec2.instance.elastic_ip";
140    const ID_IPV6_IPS: &'static str = "column.ec2.instance.ipv6_ips";
141    const ID_MONITORING: &'static str = "column.ec2.instance.monitoring";
142    const ID_SECURITY_GROUP_NAME: &'static str = "column.ec2.instance.security_group_name";
143    const ID_KEY_NAME: &'static str = "column.ec2.instance.key_name";
144    const ID_LAUNCH_TIME: &'static str = "column.ec2.instance.launch_time";
145    const ID_PLATFORM_DETAILS: &'static str = "column.ec2.instance.platform_details";
146    const ID_PRIVATE_DNS_NAME: &'static str = "column.ec2.instance.private_dns_name";
147    const ID_PRIVATE_IP_ADDRESS: &'static str = "column.ec2.instance.private_ip_address";
148    const ID_SECURITY_GROUP_IDS: &'static str = "column.ec2.instance.security_group_ids";
149    const ID_OWNER_ID: &'static str = "column.ec2.instance.owner_id";
150    const ID_VOLUME_ID: &'static str = "column.ec2.instance.volume_id";
151    const ID_ROOT_DEVICE_NAME: &'static str = "column.ec2.instance.root_device_name";
152    const ID_ROOT_DEVICE_TYPE: &'static str = "column.ec2.instance.root_device_type";
153    const ID_EBS_OPTIMIZED: &'static str = "column.ec2.instance.ebs_optimized";
154    const ID_IMAGE_ID: &'static str = "column.ec2.instance.image_id";
155    const ID_KERNEL_ID: &'static str = "column.ec2.instance.kernel_id";
156    const ID_RAMDISK_ID: &'static str = "column.ec2.instance.ramdisk_id";
157    const ID_AMI_LAUNCH_INDEX: &'static str = "column.ec2.instance.ami_launch_index";
158    const ID_RESERVATION_ID: &'static str = "column.ec2.instance.reservation_id";
159    const ID_VPC_ID: &'static str = "column.ec2.instance.vpc_id";
160    const ID_SUBNET_IDS: &'static str = "column.ec2.instance.subnet_ids";
161    const ID_INSTANCE_LIFECYCLE: &'static str = "column.ec2.instance.instance_lifecycle";
162    const ID_ARCHITECTURE: &'static str = "column.ec2.instance.architecture";
163    const ID_VIRTUALIZATION_TYPE: &'static str = "column.ec2.instance.virtualization_type";
164    const ID_PLATFORM: &'static str = "column.ec2.instance.platform";
165    const ID_IAM_INSTANCE_PROFILE_ARN: &'static str =
166        "column.ec2.instance.iam_instance_profile_arn";
167    const ID_TENANCY: &'static str = "column.ec2.instance.tenancy";
168    const ID_AFFINITY: &'static str = "column.ec2.instance.affinity";
169    const ID_HOST_ID: &'static str = "column.ec2.instance.host_id";
170    const ID_PLACEMENT_GROUP: &'static str = "column.ec2.instance.placement_group";
171    const ID_PARTITION_NUMBER: &'static str = "column.ec2.instance.partition_number";
172    const ID_CAPACITY_RESERVATION_ID: &'static str = "column.ec2.instance.capacity_reservation_id";
173    const ID_STATE_TRANSITION_REASON_CODE: &'static str =
174        "column.ec2.instance.state_transition_reason_code";
175    const ID_STATE_TRANSITION_REASON_MESSAGE: &'static str =
176        "column.ec2.instance.state_transition_reason_message";
177    const ID_STOP_HIBERNATION_BEHAVIOR: &'static str =
178        "column.ec2.instance.stop_hibernation_behavior";
179    const ID_OUTPOST_ARN: &'static str = "column.ec2.instance.outpost_arn";
180    const ID_PRODUCT_CODES: &'static str = "column.ec2.instance.product_codes";
181    const ID_AVAILABILITY_ZONE_ID: &'static str = "column.ec2.instance.availability_zone_id";
182    const ID_IMDSV2: &'static str = "column.ec2.instance.imdsv2";
183    const ID_USAGE_OPERATION: &'static str = "column.ec2.instance.usage_operation";
184    const ID_MANAGED: &'static str = "column.ec2.instance.managed";
185    const ID_OPERATOR: &'static str = "column.ec2.instance.operator";
186
187    pub const fn id(&self) -> &'static str {
188        match self {
189            Column::InstanceId => Self::ID_INSTANCE_ID,
190            Column::Name => Self::ID_NAME,
191            Column::InstanceState => Self::ID_INSTANCE_STATE,
192            Column::InstanceType => Self::ID_INSTANCE_TYPE,
193            Column::StatusCheck => Self::ID_STATUS_CHECK,
194            Column::AlarmStatus => Self::ID_ALARM_STATUS,
195            Column::AvailabilityZone => Self::ID_AVAILABILITY_ZONE,
196            Column::PublicIpv4Dns => Self::ID_PUBLIC_IPV4_DNS,
197            Column::PublicIpv4Address => Self::ID_PUBLIC_IPV4_ADDRESS,
198            Column::ElasticIp => Self::ID_ELASTIC_IP,
199            Column::Ipv6Ips => Self::ID_IPV6_IPS,
200            Column::Monitoring => Self::ID_MONITORING,
201            Column::SecurityGroupName => Self::ID_SECURITY_GROUP_NAME,
202            Column::KeyName => Self::ID_KEY_NAME,
203            Column::LaunchTime => Self::ID_LAUNCH_TIME,
204            Column::PlatformDetails => Self::ID_PLATFORM_DETAILS,
205            Column::PrivateDnsName => Self::ID_PRIVATE_DNS_NAME,
206            Column::PrivateIpAddress => Self::ID_PRIVATE_IP_ADDRESS,
207            Column::SecurityGroupIds => Self::ID_SECURITY_GROUP_IDS,
208            Column::OwnerId => Self::ID_OWNER_ID,
209            Column::VolumeId => Self::ID_VOLUME_ID,
210            Column::RootDeviceName => Self::ID_ROOT_DEVICE_NAME,
211            Column::RootDeviceType => Self::ID_ROOT_DEVICE_TYPE,
212            Column::EbsOptimized => Self::ID_EBS_OPTIMIZED,
213            Column::ImageId => Self::ID_IMAGE_ID,
214            Column::KernelId => Self::ID_KERNEL_ID,
215            Column::RamdiskId => Self::ID_RAMDISK_ID,
216            Column::AmiLaunchIndex => Self::ID_AMI_LAUNCH_INDEX,
217            Column::ReservationId => Self::ID_RESERVATION_ID,
218            Column::VpcId => Self::ID_VPC_ID,
219            Column::SubnetIds => Self::ID_SUBNET_IDS,
220            Column::InstanceLifecycle => Self::ID_INSTANCE_LIFECYCLE,
221            Column::Architecture => Self::ID_ARCHITECTURE,
222            Column::VirtualizationType => Self::ID_VIRTUALIZATION_TYPE,
223            Column::Platform => Self::ID_PLATFORM,
224            Column::IamInstanceProfileArn => Self::ID_IAM_INSTANCE_PROFILE_ARN,
225            Column::Tenancy => Self::ID_TENANCY,
226            Column::Affinity => Self::ID_AFFINITY,
227            Column::HostId => Self::ID_HOST_ID,
228            Column::PlacementGroup => Self::ID_PLACEMENT_GROUP,
229            Column::PartitionNumber => Self::ID_PARTITION_NUMBER,
230            Column::CapacityReservationId => Self::ID_CAPACITY_RESERVATION_ID,
231            Column::StateTransitionReasonCode => Self::ID_STATE_TRANSITION_REASON_CODE,
232            Column::StateTransitionReasonMessage => Self::ID_STATE_TRANSITION_REASON_MESSAGE,
233            Column::StopHibernationBehavior => Self::ID_STOP_HIBERNATION_BEHAVIOR,
234            Column::OutpostArn => Self::ID_OUTPOST_ARN,
235            Column::ProductCodes => Self::ID_PRODUCT_CODES,
236            Column::AvailabilityZoneId => Self::ID_AVAILABILITY_ZONE_ID,
237            Column::Imdsv2 => Self::ID_IMDSV2,
238            Column::UsageOperation => Self::ID_USAGE_OPERATION,
239            Column::Managed => Self::ID_MANAGED,
240            Column::Operator => Self::ID_OPERATOR,
241        }
242    }
243
244    pub const fn default_name(&self) -> &'static str {
245        match self {
246            Column::InstanceId => "Instance ID",
247            Column::Name => "Name",
248            Column::InstanceState => "Instance state",
249            Column::InstanceType => "Instance type",
250            Column::StatusCheck => "Status check",
251            Column::AlarmStatus => "Alarm status",
252            Column::AvailabilityZone => "Availability Zone",
253            Column::PublicIpv4Dns => "Public IPv4 DNS",
254            Column::PublicIpv4Address => "Public IPv4 address",
255            Column::ElasticIp => "Elastic IP",
256            Column::Ipv6Ips => "IPv6 IPs",
257            Column::Monitoring => "Monitoring",
258            Column::SecurityGroupName => "Security group name",
259            Column::KeyName => "Key name",
260            Column::LaunchTime => "Launch time",
261            Column::PlatformDetails => "Platform details",
262            Column::PrivateDnsName => "Private DNS name",
263            Column::PrivateIpAddress => "Private IP address",
264            Column::SecurityGroupIds => "Security group IDs",
265            Column::OwnerId => "Owner ID",
266            Column::VolumeId => "Volume ID",
267            Column::RootDeviceName => "Root device name",
268            Column::RootDeviceType => "Root device type",
269            Column::EbsOptimized => "EBS optimized",
270            Column::ImageId => "Image ID",
271            Column::KernelId => "Kernel ID",
272            Column::RamdiskId => "RAM disk ID",
273            Column::AmiLaunchIndex => "AMI launch index",
274            Column::ReservationId => "Reservation ID",
275            Column::VpcId => "VPC ID",
276            Column::SubnetIds => "Subnet IDs",
277            Column::InstanceLifecycle => "Instance lifecycle",
278            Column::Architecture => "Architecture",
279            Column::VirtualizationType => "Virtualization type",
280            Column::Platform => "Platform",
281            Column::IamInstanceProfileArn => "IAM instance profile ARN",
282            Column::Tenancy => "Tenancy",
283            Column::Affinity => "Affinity",
284            Column::HostId => "Host ID",
285            Column::PlacementGroup => "Placement group",
286            Column::PartitionNumber => "Partition number",
287            Column::CapacityReservationId => "Capacity Reservation ID",
288            Column::StateTransitionReasonCode => "State transition reason code",
289            Column::StateTransitionReasonMessage => "State transition reason message",
290            Column::StopHibernationBehavior => "Stop-hibernation behavior",
291            Column::OutpostArn => "Outpost ARN",
292            Column::ProductCodes => "Product codes",
293            Column::AvailabilityZoneId => "Availability Zone ID",
294            Column::Imdsv2 => "IMDSv2",
295            Column::UsageOperation => "Usage operation",
296            Column::Managed => "Managed",
297            Column::Operator => "Operator",
298        }
299    }
300
301    pub fn name(&self) -> String {
302        translate_column(self.id(), self.default_name())
303    }
304
305    pub fn from_id(id: &str) -> Option<Self> {
306        match id {
307            Self::ID_INSTANCE_ID => Some(Column::InstanceId),
308            Self::ID_NAME => Some(Column::Name),
309            Self::ID_INSTANCE_STATE => Some(Column::InstanceState),
310            Self::ID_INSTANCE_TYPE => Some(Column::InstanceType),
311            Self::ID_STATUS_CHECK => Some(Column::StatusCheck),
312            Self::ID_ALARM_STATUS => Some(Column::AlarmStatus),
313            Self::ID_AVAILABILITY_ZONE => Some(Column::AvailabilityZone),
314            Self::ID_PUBLIC_IPV4_DNS => Some(Column::PublicIpv4Dns),
315            Self::ID_PUBLIC_IPV4_ADDRESS => Some(Column::PublicIpv4Address),
316            Self::ID_ELASTIC_IP => Some(Column::ElasticIp),
317            Self::ID_IPV6_IPS => Some(Column::Ipv6Ips),
318            Self::ID_MONITORING => Some(Column::Monitoring),
319            Self::ID_SECURITY_GROUP_NAME => Some(Column::SecurityGroupName),
320            Self::ID_KEY_NAME => Some(Column::KeyName),
321            Self::ID_LAUNCH_TIME => Some(Column::LaunchTime),
322            Self::ID_PLATFORM_DETAILS => Some(Column::PlatformDetails),
323            Self::ID_PRIVATE_DNS_NAME => Some(Column::PrivateDnsName),
324            Self::ID_PRIVATE_IP_ADDRESS => Some(Column::PrivateIpAddress),
325            Self::ID_SECURITY_GROUP_IDS => Some(Column::SecurityGroupIds),
326            Self::ID_OWNER_ID => Some(Column::OwnerId),
327            Self::ID_VOLUME_ID => Some(Column::VolumeId),
328            Self::ID_ROOT_DEVICE_NAME => Some(Column::RootDeviceName),
329            Self::ID_ROOT_DEVICE_TYPE => Some(Column::RootDeviceType),
330            Self::ID_EBS_OPTIMIZED => Some(Column::EbsOptimized),
331            Self::ID_IMAGE_ID => Some(Column::ImageId),
332            Self::ID_KERNEL_ID => Some(Column::KernelId),
333            Self::ID_RAMDISK_ID => Some(Column::RamdiskId),
334            Self::ID_AMI_LAUNCH_INDEX => Some(Column::AmiLaunchIndex),
335            Self::ID_RESERVATION_ID => Some(Column::ReservationId),
336            Self::ID_VPC_ID => Some(Column::VpcId),
337            Self::ID_SUBNET_IDS => Some(Column::SubnetIds),
338            Self::ID_INSTANCE_LIFECYCLE => Some(Column::InstanceLifecycle),
339            Self::ID_ARCHITECTURE => Some(Column::Architecture),
340            Self::ID_VIRTUALIZATION_TYPE => Some(Column::VirtualizationType),
341            Self::ID_PLATFORM => Some(Column::Platform),
342            Self::ID_IAM_INSTANCE_PROFILE_ARN => Some(Column::IamInstanceProfileArn),
343            Self::ID_TENANCY => Some(Column::Tenancy),
344            Self::ID_AFFINITY => Some(Column::Affinity),
345            Self::ID_HOST_ID => Some(Column::HostId),
346            Self::ID_PLACEMENT_GROUP => Some(Column::PlacementGroup),
347            Self::ID_PARTITION_NUMBER => Some(Column::PartitionNumber),
348            Self::ID_CAPACITY_RESERVATION_ID => Some(Column::CapacityReservationId),
349            Self::ID_STATE_TRANSITION_REASON_CODE => Some(Column::StateTransitionReasonCode),
350            Self::ID_STATE_TRANSITION_REASON_MESSAGE => Some(Column::StateTransitionReasonMessage),
351            Self::ID_STOP_HIBERNATION_BEHAVIOR => Some(Column::StopHibernationBehavior),
352            Self::ID_OUTPOST_ARN => Some(Column::OutpostArn),
353            Self::ID_PRODUCT_CODES => Some(Column::ProductCodes),
354            Self::ID_AVAILABILITY_ZONE_ID => Some(Column::AvailabilityZoneId),
355            Self::ID_IMDSV2 => Some(Column::Imdsv2),
356            Self::ID_USAGE_OPERATION => Some(Column::UsageOperation),
357            Self::ID_MANAGED => Some(Column::Managed),
358            Self::ID_OPERATOR => Some(Column::Operator),
359            _ => None,
360        }
361    }
362
363    pub const fn all() -> [Column; 52] {
364        [
365            Column::InstanceId,
366            Column::Name,
367            Column::InstanceState,
368            Column::InstanceType,
369            Column::StatusCheck,
370            Column::AlarmStatus,
371            Column::AvailabilityZone,
372            Column::PublicIpv4Dns,
373            Column::PublicIpv4Address,
374            Column::ElasticIp,
375            Column::Ipv6Ips,
376            Column::Monitoring,
377            Column::SecurityGroupName,
378            Column::KeyName,
379            Column::LaunchTime,
380            Column::PlatformDetails,
381            Column::PrivateDnsName,
382            Column::PrivateIpAddress,
383            Column::SecurityGroupIds,
384            Column::OwnerId,
385            Column::VolumeId,
386            Column::RootDeviceName,
387            Column::RootDeviceType,
388            Column::EbsOptimized,
389            Column::ImageId,
390            Column::KernelId,
391            Column::RamdiskId,
392            Column::AmiLaunchIndex,
393            Column::ReservationId,
394            Column::VpcId,
395            Column::SubnetIds,
396            Column::InstanceLifecycle,
397            Column::Architecture,
398            Column::VirtualizationType,
399            Column::Platform,
400            Column::IamInstanceProfileArn,
401            Column::Tenancy,
402            Column::Affinity,
403            Column::HostId,
404            Column::PlacementGroup,
405            Column::PartitionNumber,
406            Column::CapacityReservationId,
407            Column::StateTransitionReasonCode,
408            Column::StateTransitionReasonMessage,
409            Column::StopHibernationBehavior,
410            Column::OutpostArn,
411            Column::ProductCodes,
412            Column::AvailabilityZoneId,
413            Column::Imdsv2,
414            Column::UsageOperation,
415            Column::Managed,
416            Column::Operator,
417        ]
418    }
419
420    pub fn ids() -> Vec<ColumnId> {
421        Self::all().iter().map(|c| c.id()).collect()
422    }
423
424    pub fn to_column(&self) -> Box<dyn TableColumn<Instance>> {
425        struct InstanceColumn {
426            variant: Column,
427        }
428
429        impl TableColumn<Instance> for InstanceColumn {
430            fn name(&self) -> &str {
431                Box::leak(self.variant.name().into_boxed_str())
432            }
433
434            fn width(&self) -> u16 {
435                let translated = translate_column(self.variant.id(), self.variant.default_name());
436                translated.len().max(match self.variant {
437                    Column::InstanceId => 20,
438                    Column::Name => 30,
439                    Column::InstanceState => 18,
440                    Column::InstanceType => 15,
441                    Column::StatusCheck => 15,
442                    Column::AlarmStatus => 15,
443                    Column::AvailabilityZone => 20,
444                    Column::PublicIpv4Dns => 40,
445                    Column::PublicIpv4Address => 20,
446                    Column::ElasticIp => 20,
447                    Column::Ipv6Ips => 30,
448                    Column::Monitoring => 15,
449                    Column::SecurityGroupName => 30,
450                    Column::KeyName => 20,
451                    Column::LaunchTime => UTC_TIMESTAMP_WIDTH as usize,
452                    Column::PlatformDetails => 30,
453                    Column::PrivateDnsName => 40,
454                    Column::PrivateIpAddress => 20,
455                    Column::SecurityGroupIds => 30,
456                    Column::OwnerId => 15,
457                    Column::VolumeId => 25,
458                    Column::RootDeviceName => 20,
459                    Column::RootDeviceType => 18,
460                    Column::EbsOptimized => 15,
461                    Column::ImageId => 25,
462                    Column::KernelId => 25,
463                    Column::RamdiskId => 25,
464                    Column::AmiLaunchIndex => 18,
465                    Column::ReservationId => 20,
466                    Column::VpcId => 25,
467                    Column::SubnetIds => 30,
468                    Column::InstanceLifecycle => 20,
469                    Column::Architecture => 15,
470                    Column::VirtualizationType => 20,
471                    Column::Platform => 15,
472                    Column::IamInstanceProfileArn => 50,
473                    Column::Tenancy => 15,
474                    Column::Affinity => 15,
475                    Column::HostId => 25,
476                    Column::PlacementGroup => 25,
477                    Column::PartitionNumber => 18,
478                    Column::CapacityReservationId => 30,
479                    Column::StateTransitionReasonCode => 30,
480                    Column::StateTransitionReasonMessage => 50,
481                    Column::StopHibernationBehavior => 28,
482                    Column::OutpostArn => 40,
483                    Column::ProductCodes => 30,
484                    Column::AvailabilityZoneId => 20,
485                    Column::Imdsv2 => 15,
486                    Column::UsageOperation => 25,
487                    Column::Managed => 15,
488                    Column::Operator => 15,
489                }) as u16
490            }
491
492            fn render(&self, item: &Instance) -> (String, Style) {
493                match self.variant {
494                    Column::InstanceId => (item.instance_id.clone(), Style::default()),
495                    Column::Name => (item.name.clone(), Style::default()),
496                    Column::InstanceState => {
497                        let (formatted, color) = format_state(&item.state);
498                        (formatted, Style::default().fg(color))
499                    }
500                    Column::InstanceType => (item.instance_type.clone(), Style::default()),
501                    Column::StatusCheck => (item.status_checks.clone(), Style::default()),
502                    Column::AlarmStatus => (item.alarm_status.clone(), Style::default()),
503                    Column::AvailabilityZone => (item.availability_zone.clone(), Style::default()),
504                    Column::PublicIpv4Dns => (item.public_ipv4_dns.clone(), Style::default()),
505                    Column::PublicIpv4Address => {
506                        (item.public_ipv4_address.clone(), Style::default())
507                    }
508                    Column::ElasticIp => (item.elastic_ip.clone(), Style::default()),
509                    Column::Ipv6Ips => (item.ipv6_ips.clone(), Style::default()),
510                    Column::Monitoring => (item.monitoring.clone(), Style::default()),
511                    Column::SecurityGroupName => (item.security_groups.clone(), Style::default()),
512                    Column::KeyName => (item.key_name.clone(), Style::default()),
513                    Column::LaunchTime => (item.launch_time.clone(), Style::default()),
514                    Column::PlatformDetails => (item.platform_details.clone(), Style::default()),
515                    Column::PrivateDnsName => (item.private_dns_name.clone(), Style::default()),
516                    Column::PrivateIpAddress => (item.private_ip_address.clone(), Style::default()),
517                    Column::SecurityGroupIds => (item.security_group_ids.clone(), Style::default()),
518                    Column::OwnerId => (item.owner_id.clone(), Style::default()),
519                    Column::VolumeId => (item.volume_id.clone(), Style::default()),
520                    Column::RootDeviceName => (item.root_device_name.clone(), Style::default()),
521                    Column::RootDeviceType => (item.root_device_type.clone(), Style::default()),
522                    Column::EbsOptimized => (item.ebs_optimized.clone(), Style::default()),
523                    Column::ImageId => (item.image_id.clone(), Style::default()),
524                    Column::KernelId => (item.kernel_id.clone(), Style::default()),
525                    Column::RamdiskId => (item.ramdisk_id.clone(), Style::default()),
526                    Column::AmiLaunchIndex => (item.ami_launch_index.clone(), Style::default()),
527                    Column::ReservationId => (item.reservation_id.clone(), Style::default()),
528                    Column::VpcId => (item.vpc_id.clone(), Style::default()),
529                    Column::SubnetIds => (item.subnet_ids.clone(), Style::default()),
530                    Column::InstanceLifecycle => {
531                        (item.instance_lifecycle.clone(), Style::default())
532                    }
533                    Column::Architecture => (item.architecture.clone(), Style::default()),
534                    Column::VirtualizationType => {
535                        (item.virtualization_type.clone(), Style::default())
536                    }
537                    Column::Platform => (item.platform.clone(), Style::default()),
538                    Column::IamInstanceProfileArn => {
539                        (item.iam_instance_profile_arn.clone(), Style::default())
540                    }
541                    Column::Tenancy => (item.tenancy.clone(), Style::default()),
542                    Column::Affinity => (item.affinity.clone(), Style::default()),
543                    Column::HostId => (item.host_id.clone(), Style::default()),
544                    Column::PlacementGroup => (item.placement_group.clone(), Style::default()),
545                    Column::PartitionNumber => (item.partition_number.clone(), Style::default()),
546                    Column::CapacityReservationId => {
547                        (item.capacity_reservation_id.clone(), Style::default())
548                    }
549                    Column::StateTransitionReasonCode => {
550                        (item.state_transition_reason_code.clone(), Style::default())
551                    }
552                    Column::StateTransitionReasonMessage => (
553                        item.state_transition_reason_message.clone(),
554                        Style::default(),
555                    ),
556                    Column::StopHibernationBehavior => {
557                        (item.stop_hibernation_behavior.clone(), Style::default())
558                    }
559                    Column::OutpostArn => (item.outpost_arn.clone(), Style::default()),
560                    Column::ProductCodes => (item.product_codes.clone(), Style::default()),
561                    Column::AvailabilityZoneId => {
562                        (item.availability_zone_id.clone(), Style::default())
563                    }
564                    Column::Imdsv2 => (item.imdsv2.clone(), Style::default()),
565                    Column::UsageOperation => (item.usage_operation.clone(), Style::default()),
566                    Column::Managed => (item.managed.clone(), Style::default()),
567                    Column::Operator => (item.operator.clone(), Style::default()),
568                }
569            }
570        }
571
572        Box::new(InstanceColumn { variant: *self })
573    }
574}
575
576pub fn format_state(state: &str) -> (String, Color) {
577    match state {
578        "running" => ("✅ Running".to_string(), Color::Green),
579        "stopped" => ("🛑 Stopped".to_string(), Color::Red),
580        "terminated" => ("❌ Terminated".to_string(), Color::DarkGray),
581        "pending" => ("❎ Pending".to_string(), Color::Yellow),
582        "shutting-down" => ("🔴 Shutting-down".to_string(), Color::Yellow),
583        "stopping" => ("🚫 Stopping".to_string(), Color::Yellow),
584        _ => (state.to_string(), Color::White),
585    }
586}
587
588#[cfg(test)]
589mod tests {
590    use super::*;
591
592    #[test]
593    fn test_column_names() {
594        assert_eq!(Column::Name.name(), "Name");
595        assert_eq!(Column::InstanceId.name(), "Instance ID");
596        assert_eq!(Column::InstanceState.name(), "Instance state");
597        assert_eq!(Column::InstanceType.name(), "Instance type");
598    }
599
600    #[test]
601    fn test_column_all() {
602        let columns = Column::ids();
603        assert_eq!(columns.len(), 52);
604        assert_eq!(columns[0], Column::InstanceId.id());
605    }
606
607    #[test]
608    fn test_format_state() {
609        let (formatted, color) = format_state("running");
610        assert_eq!(formatted, "✅ Running");
611        assert_eq!(color, Color::Green);
612
613        let (formatted, color) = format_state("stopped");
614        assert_eq!(formatted, "🛑 Stopped");
615        assert_eq!(color, Color::Red);
616
617        let (formatted, color) = format_state("terminated");
618        assert_eq!(formatted, "❌ Terminated");
619        assert_eq!(color, Color::DarkGray);
620
621        let (formatted, color) = format_state("pending");
622        assert_eq!(formatted, "❎ Pending");
623        assert_eq!(color, Color::Yellow);
624
625        let (formatted, color) = format_state("shutting-down");
626        assert_eq!(formatted, "🔴 Shutting-down");
627        assert_eq!(color, Color::Yellow);
628
629        let (formatted, color) = format_state("stopping");
630        assert_eq!(formatted, "🚫 Stopping");
631        assert_eq!(color, Color::Yellow);
632    }
633
634    #[test]
635    fn test_column_from_id() {
636        assert_eq!(
637            Column::from_id("column.ec2.instance.name"),
638            Some(Column::Name)
639        );
640        assert_eq!(
641            Column::from_id("column.ec2.instance.instance_id"),
642            Some(Column::InstanceId)
643        );
644        assert_eq!(
645            Column::from_id("column.ec2.instance.state"),
646            Some(Column::InstanceState)
647        );
648        assert_eq!(Column::from_id("invalid"), None);
649    }
650
651    #[test]
652    fn test_column_ids_unique() {
653        let ids = Column::ids();
654        let mut seen = std::collections::HashSet::new();
655        for id in ids {
656            assert!(seen.insert(id), "Duplicate column ID: {}", id);
657        }
658    }
659
660    #[test]
661    fn test_column_ids_have_correct_prefix() {
662        for col in Column::all() {
663            assert!(
664                col.id().starts_with("column.ec2.instance."),
665                "Column ID '{}' should start with 'column.ec2.instance.'",
666                col.id()
667            );
668        }
669    }
670
671    #[test]
672    fn test_launch_time_uses_utc_timestamp_width() {
673        let col = Column::LaunchTime.to_column();
674        assert_eq!(col.width(), UTC_TIMESTAMP_WIDTH);
675    }
676
677    #[test]
678    fn test_utc_timestamp_width_constant_is_27() {
679        assert_eq!(UTC_TIMESTAMP_WIDTH, 27);
680    }
681}