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