Skip to main content

rusticity_term/ec2/
mod.rs

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