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