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}