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}