1use crate::common::t;
2use crate::common::{format_duration_seconds, ColumnId, UTC_TIMESTAMP_WIDTH};
3use crate::ui::table::Column;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7pub fn init(i18n: &mut HashMap<String, String>) {
8 for col in UserColumn::all() {
9 i18n.entry(col.id().to_string())
10 .or_insert_with(|| col.default_name().to_string());
11 }
12 for col in GroupColumn::all() {
13 i18n.entry(col.id().to_string())
14 .or_insert_with(|| col.default_name().to_string());
15 }
16 for col in RoleColumn::all() {
17 i18n.entry(col.id().to_string())
18 .or_insert_with(|| col.default_name().to_string());
19 }
20}
21
22pub fn format_arn(account_id: &str, resource_type: &str, resource_name: &str) -> String {
23 format!(
24 "arn:aws:iam::{}:{}/{}",
25 account_id, resource_type, resource_name
26 )
27}
28
29pub fn console_url_users(_region: &str) -> String {
30 "https://console.aws.amazon.com/iam/home#/users".to_string()
31}
32
33pub fn console_url_user_detail(region: &str, user_name: &str, section: &str) -> String {
34 format!(
35 "https://{}.console.aws.amazon.com/iam/home?region={}#/users/details/{}?section={}",
36 region, region, user_name, section
37 )
38}
39
40pub fn console_url_roles(_region: &str) -> String {
41 "https://console.aws.amazon.com/iam/home#/roles".to_string()
42}
43
44pub fn console_url_role_detail(region: &str, role_name: &str, section: &str) -> String {
45 format!(
46 "https://{}.console.aws.amazon.com/iam/home?region={}#/roles/details/{}?section={}",
47 region, region, role_name, section
48 )
49}
50
51pub fn console_url_role_policy(region: &str, role_name: &str, policy_name: &str) -> String {
52 format!(
53 "https://{}.console.aws.amazon.com/iam/home?region={}#/roles/details/{}/editPolicy/{}?step=addPermissions",
54 region, region, role_name, policy_name
55 )
56}
57
58pub fn console_url_groups(region: &str) -> String {
59 format!(
60 "https://{}.console.aws.amazon.com/iam/home?region={}#/groups",
61 region, region
62 )
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct IamUser {
67 pub user_name: String,
68 pub path: String,
69 pub groups: String,
70 pub last_activity: String,
71 pub mfa: String,
72 pub password_age: String,
73 pub console_last_sign_in: String,
74 pub access_key_id: String,
75 pub active_key_age: String,
76 pub access_key_last_used: String,
77 pub arn: String,
78 pub creation_time: String,
79 pub console_access: String,
80 pub signing_certs: String,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct IamRole {
85 pub role_name: String,
86 pub path: String,
87 pub trusted_entities: String,
88 pub last_activity: String,
89 pub arn: String,
90 pub creation_time: String,
91 pub description: String,
92 pub max_session_duration: Option<i32>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct IamGroup {
97 pub group_name: String,
98 pub path: String,
99 pub users: String,
100 pub permissions: String,
101 pub creation_time: String,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct Policy {
106 pub policy_name: String,
107 pub policy_type: String,
108 pub attached_via: String,
109 pub attached_entities: String,
110 pub description: String,
111 pub creation_time: String,
112 pub edited_time: String,
113 pub policy_arn: Option<String>,
114}
115
116#[derive(Debug, Clone)]
117pub struct RoleTag {
118 pub key: String,
119 pub value: String,
120}
121
122#[derive(Debug, Clone)]
123pub struct UserTag {
124 pub key: String,
125 pub value: String,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct UserGroup {
130 pub group_name: String,
131 pub attached_policies: String,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct GroupUser {
136 pub user_name: String,
137 pub groups: String,
138 pub last_activity: String,
139 pub creation_time: String,
140}
141
142#[derive(Debug, Clone)]
143pub struct LastAccessedService {
144 pub service: String,
145 pub policies_granting: String,
146 pub last_accessed: String,
147}
148
149#[derive(Debug, Clone, Copy, PartialEq)]
150pub enum UserColumn {
151 UserName,
152 Path,
153 Groups,
154 LastActivity,
155 Mfa,
156 PasswordAge,
157 ConsoleLastSignIn,
158 AccessKeyId,
159 ActiveKeyAge,
160 AccessKeyLastUsed,
161 Arn,
162 CreationTime,
163 ConsoleAccess,
164 SigningCerts,
165}
166
167impl UserColumn {
168 pub fn id(&self) -> &'static str {
169 match self {
170 Self::UserName => "column.iam.user.user_name",
171 Self::Path => "column.iam.user.path",
172 Self::Groups => "column.iam.user.groups",
173 Self::LastActivity => "column.iam.user.last_activity",
174 Self::Mfa => "column.iam.user.mfa",
175 Self::PasswordAge => "column.iam.user.password_age",
176 Self::ConsoleLastSignIn => "column.iam.user.console_last_sign_in",
177 Self::AccessKeyId => "column.iam.user.access_key_id",
178 Self::ActiveKeyAge => "column.iam.user.active_key_age",
179 Self::AccessKeyLastUsed => "column.iam.user.access_key_last_used",
180 Self::Arn => "column.iam.user.arn",
181 Self::CreationTime => "column.iam.user.creation_time",
182 Self::ConsoleAccess => "column.iam.user.console_access",
183 Self::SigningCerts => "column.iam.user.signing_certs",
184 }
185 }
186
187 pub fn default_name(&self) -> &'static str {
188 match self {
189 Self::UserName => "User name",
190 Self::Path => "Path",
191 Self::Groups => "Groups",
192 Self::LastActivity => "Last activity",
193 Self::Mfa => "MFA",
194 Self::PasswordAge => "Password age",
195 Self::ConsoleLastSignIn => "Console last sign-in",
196 Self::AccessKeyId => "Access key ID",
197 Self::ActiveKeyAge => "Active key age",
198 Self::AccessKeyLastUsed => "Access key last used",
199 Self::Arn => "ARN",
200 Self::CreationTime => "Creation time",
201 Self::ConsoleAccess => "Console access",
202 Self::SigningCerts => "Signing certificates",
203 }
204 }
205
206 pub fn name(&self) -> String {
207 let key = self.id();
208 let translated = t(key);
209 if translated == key {
210 self.default_name().to_string()
211 } else {
212 translated
213 }
214 }
215
216 pub fn from_id(id: &str) -> Option<Self> {
217 match id {
218 "column.iam.user.user_name" => Some(Self::UserName),
219 "column.iam.user.path" => Some(Self::Path),
220 "column.iam.user.groups" => Some(Self::Groups),
221 "column.iam.user.last_activity" => Some(Self::LastActivity),
222 "column.iam.user.mfa" => Some(Self::Mfa),
223 "column.iam.user.password_age" => Some(Self::PasswordAge),
224 "column.iam.user.console_last_sign_in" => Some(Self::ConsoleLastSignIn),
225 "column.iam.user.access_key_id" => Some(Self::AccessKeyId),
226 "column.iam.user.active_key_age" => Some(Self::ActiveKeyAge),
227 "column.iam.user.access_key_last_used" => Some(Self::AccessKeyLastUsed),
228 "column.iam.user.arn" => Some(Self::Arn),
229 "column.iam.user.creation_time" => Some(Self::CreationTime),
230 "column.iam.user.console_access" => Some(Self::ConsoleAccess),
231 "column.iam.user.signing_certs" => Some(Self::SigningCerts),
232 _ => None,
233 }
234 }
235
236 pub fn all() -> [UserColumn; 14] {
237 [
238 Self::UserName,
239 Self::Path,
240 Self::Groups,
241 Self::LastActivity,
242 Self::Mfa,
243 Self::PasswordAge,
244 Self::ConsoleLastSignIn,
245 Self::AccessKeyId,
246 Self::ActiveKeyAge,
247 Self::AccessKeyLastUsed,
248 Self::Arn,
249 Self::CreationTime,
250 Self::ConsoleAccess,
251 Self::SigningCerts,
252 ]
253 }
254
255 pub fn ids() -> Vec<ColumnId> {
256 Self::all().iter().map(|c| c.id()).collect()
257 }
258
259 pub fn visible() -> Vec<ColumnId> {
260 vec![
261 Self::UserName.id(),
262 Self::Path.id(),
263 Self::Groups.id(),
264 Self::LastActivity.id(),
265 Self::Mfa.id(),
266 Self::PasswordAge.id(),
267 Self::ConsoleLastSignIn.id(),
268 Self::AccessKeyId.id(),
269 Self::ActiveKeyAge.id(),
270 Self::AccessKeyLastUsed.id(),
271 Self::Arn.id(),
272 ]
273 }
274}
275
276#[derive(Debug, Clone, Copy)]
277pub enum GroupColumn {
278 GroupName,
279 Path,
280 Users,
281 Permissions,
282 CreationTime,
283}
284
285impl GroupColumn {
286 pub fn all() -> [GroupColumn; 5] {
287 [
288 Self::GroupName,
289 Self::Path,
290 Self::Users,
291 Self::Permissions,
292 Self::CreationTime,
293 ]
294 }
295}
296
297#[derive(Debug, Clone, Copy)]
298pub enum RoleColumn {
299 RoleName,
300 Path,
301 TrustedEntities,
302 LastActivity,
303 Arn,
304 CreationTime,
305 Description,
306 MaxSessionDuration,
307}
308
309impl RoleColumn {
310 pub fn from_id(id: &str) -> Option<Self> {
311 match id {
312 "column.iam.role.role_name" => Some(Self::RoleName),
313 "column.iam.role.path" => Some(Self::Path),
314 "column.iam.role.trusted_entities" => Some(Self::TrustedEntities),
315 "column.iam.role.last_activity" => Some(Self::LastActivity),
316 "column.iam.role.arn" => Some(Self::Arn),
317 "column.iam.role.creation_time" => Some(Self::CreationTime),
318 "column.iam.role.description" => Some(Self::Description),
319 "column.iam.role.max_session_duration" => Some(Self::MaxSessionDuration),
320 _ => None,
321 }
322 }
323
324 pub fn id(&self) -> ColumnId {
325 match self {
326 Self::RoleName => "column.iam.role.role_name",
327 Self::Path => "column.iam.role.path",
328 Self::TrustedEntities => "column.iam.role.trusted_entities",
329 Self::LastActivity => "column.iam.role.last_activity",
330 Self::Arn => "column.iam.role.arn",
331 Self::CreationTime => "column.iam.role.creation_time",
332 Self::Description => "column.iam.role.description",
333 Self::MaxSessionDuration => "column.iam.role.max_session_duration",
334 }
335 }
336
337 pub fn default_name(&self) -> &'static str {
338 match self {
339 Self::RoleName => "Role name",
340 Self::Path => "Path",
341 Self::TrustedEntities => "Trusted entities",
342 Self::LastActivity => "Last activity",
343 Self::Arn => "ARN",
344 Self::CreationTime => "Creation time",
345 Self::Description => "Description",
346 Self::MaxSessionDuration => "Max session duration",
347 }
348 }
349
350 pub fn name(&self) -> String {
351 let key = self.id();
352 let translated = t(key);
353 if translated == key {
354 self.default_name().to_string()
355 } else {
356 translated
357 }
358 }
359
360 pub fn all() -> [RoleColumn; 8] {
361 [
362 Self::RoleName,
363 Self::Path,
364 Self::TrustedEntities,
365 Self::LastActivity,
366 Self::Arn,
367 Self::CreationTime,
368 Self::Description,
369 Self::MaxSessionDuration,
370 ]
371 }
372
373 pub fn ids() -> Vec<ColumnId> {
374 Self::all().iter().map(|c| c.id()).collect()
375 }
376
377 pub fn visible() -> Vec<ColumnId> {
378 vec![
379 Self::RoleName.id(),
380 Self::TrustedEntities.id(),
381 Self::CreationTime.id(),
382 ]
383 }
384}
385
386#[derive(Debug, Clone, Copy)]
387pub enum GroupUserColumn {
388 UserName,
389 Groups,
390 LastActivity,
391 CreationTime,
392}
393
394#[derive(Debug, Clone, Copy)]
395pub enum PolicyColumn {
396 PolicyName,
397 Type,
398 AttachedVia,
399 AttachedEntities,
400 Description,
401 CreationTime,
402 EditedTime,
403}
404
405#[derive(Debug, Clone, Copy)]
406pub enum TagColumn {
407 Key,
408 Value,
409}
410
411#[derive(Debug, Clone, Copy)]
412pub enum UserGroupColumn {
413 GroupName,
414 AttachedPolicies,
415}
416
417#[derive(Debug, Clone, Copy)]
418pub enum LastAccessedServiceColumn {
419 Service,
420 PoliciesGranting,
421 LastAccessed,
422}
423
424impl<'a> Column<&'a IamUser> for UserColumn {
425 fn id(&self) -> &'static str {
426 match self {
427 Self::UserName => "column.iam.user.user_name",
428 Self::Path => "column.iam.user.path",
429 Self::Groups => "column.iam.user.groups",
430 Self::LastActivity => "column.iam.user.last_activity",
431 Self::Mfa => "column.iam.user.mfa",
432 Self::PasswordAge => "column.iam.user.password_age",
433 Self::ConsoleLastSignIn => "column.iam.user.console_last_sign_in",
434 Self::AccessKeyId => "column.iam.user.access_key_id",
435 Self::ActiveKeyAge => "column.iam.user.active_key_age",
436 Self::AccessKeyLastUsed => "column.iam.user.access_key_last_used",
437 Self::Arn => "column.iam.user.arn",
438 Self::CreationTime => "column.iam.user.creation_time",
439 Self::ConsoleAccess => "column.iam.user.console_access",
440 Self::SigningCerts => "column.iam.user.signing_certs",
441 }
442 }
443
444 fn default_name(&self) -> &'static str {
445 match self {
446 Self::UserName => "User name",
447 Self::Path => "Path",
448 Self::Groups => "Groups",
449 Self::LastActivity => "Last activity",
450 Self::Mfa => "MFA",
451 Self::PasswordAge => "Password age",
452 Self::ConsoleLastSignIn => "Console last sign-in",
453 Self::AccessKeyId => "Access key ID",
454 Self::ActiveKeyAge => "Active key age",
455 Self::AccessKeyLastUsed => "Access key last used",
456 Self::Arn => "ARN",
457 Self::CreationTime => "Creation time",
458 Self::ConsoleAccess => "Console access",
459 Self::SigningCerts => "Signing certificates",
460 }
461 }
462
463 fn name(&self) -> &str {
464 let key = self.id();
465 let translated = t(key);
466 if translated == key {
467 self.default_name()
468 } else {
469 Box::leak(translated.into_boxed_str())
470 }
471 }
472
473 fn width(&self) -> u16 {
474 let custom = match self {
475 Self::UserName => 20,
476 Self::Path => 15,
477 Self::Groups => 20,
478 Self::LastActivity => 20,
479 Self::Mfa => 10,
480 Self::PasswordAge => 15,
481 Self::ConsoleLastSignIn => 25,
482 Self::AccessKeyId => 25,
483 Self::ActiveKeyAge => 18,
484 Self::AccessKeyLastUsed => UTC_TIMESTAMP_WIDTH as usize,
485 Self::Arn => 50,
486 Self::CreationTime => 30,
487 Self::ConsoleAccess => 15,
488 Self::SigningCerts => 15,
489 };
490 self.name().len().max(custom) as u16
491 }
492
493 fn render(&self, item: &&'a IamUser) -> (String, ratatui::style::Style) {
494 let text = match self {
495 Self::UserName => item.user_name.clone(),
496 Self::Path => item.path.clone(),
497 Self::Groups => item.groups.clone(),
498 Self::LastActivity => item.last_activity.clone(),
499 Self::Mfa => item.mfa.clone(),
500 Self::PasswordAge => item.password_age.clone(),
501 Self::ConsoleLastSignIn => item.console_last_sign_in.clone(),
502 Self::AccessKeyId => item.access_key_id.clone(),
503 Self::ActiveKeyAge => item.active_key_age.clone(),
504 Self::AccessKeyLastUsed => item.access_key_last_used.clone(),
505 Self::Arn => item.arn.clone(),
506 Self::CreationTime => item.creation_time.clone(),
507 Self::ConsoleAccess => item.console_access.clone(),
508 Self::SigningCerts => item.signing_certs.clone(),
509 };
510 (text, ratatui::style::Style::default())
511 }
512}
513
514impl Column<IamGroup> for GroupColumn {
515 fn id(&self) -> &'static str {
516 match self {
517 Self::GroupName => "column.iam.group.group_name",
518 Self::Path => "column.iam.group.path",
519 Self::Users => "column.iam.group.users",
520 Self::Permissions => "column.iam.group.permissions",
521 Self::CreationTime => "column.iam.group.creation_time",
522 }
523 }
524
525 fn default_name(&self) -> &'static str {
526 match self {
527 Self::GroupName => "Group name",
528 Self::Path => "Path",
529 Self::Users => "Users",
530 Self::Permissions => "Permissions",
531 Self::CreationTime => "Creation time",
532 }
533 }
534
535 fn width(&self) -> u16 {
536 let custom = match self {
537 Self::GroupName => 20,
538 Self::Path => 15,
539 Self::Users => 10,
540 Self::Permissions => 20,
541 Self::CreationTime => 30,
542 };
543 self.name().len().max(custom) as u16
544 }
545
546 fn render(&self, item: &IamGroup) -> (String, ratatui::style::Style) {
547 use ratatui::style::{Color, Style};
548 match self {
549 Self::GroupName => (item.group_name.clone(), Style::default()),
550 Self::Permissions if item.permissions == "Defined" => (
551 format!("✅ {}", item.permissions),
552 Style::default().fg(Color::Green),
553 ),
554 Self::Path => (item.path.clone(), Style::default()),
555 Self::Users => (item.users.clone(), Style::default()),
556 Self::Permissions => (item.permissions.clone(), Style::default()),
557 Self::CreationTime => (item.creation_time.clone(), Style::default()),
558 }
559 }
560}
561
562impl Column<IamRole> for RoleColumn {
563 fn id(&self) -> &'static str {
564 match self {
565 Self::RoleName => "column.iam.role.role_name",
566 Self::Path => "column.iam.role.path",
567 Self::TrustedEntities => "column.iam.role.trusted_entities",
568 Self::LastActivity => "column.iam.role.last_activity",
569 Self::Arn => "column.iam.role.arn",
570 Self::CreationTime => "column.iam.role.creation_time",
571 Self::Description => "column.iam.role.description",
572 Self::MaxSessionDuration => "column.iam.role.max_session_duration",
573 }
574 }
575
576 fn default_name(&self) -> &'static str {
577 match self {
578 Self::RoleName => "Role name",
579 Self::Path => "Path",
580 Self::TrustedEntities => "Trusted entities",
581 Self::LastActivity => "Last activity",
582 Self::Arn => "ARN",
583 Self::CreationTime => "Creation time",
584 Self::Description => "Description",
585 Self::MaxSessionDuration => "Max CLI/API session",
586 }
587 }
588
589 fn width(&self) -> u16 {
590 let custom = match self {
591 Self::RoleName => 30,
592 Self::Path => 15,
593 Self::TrustedEntities => 30,
594 Self::LastActivity => 20,
595 Self::Arn => 50,
596 Self::CreationTime => 30,
597 Self::Description => 40,
598 Self::MaxSessionDuration => 22,
599 };
600 self.name().len().max(custom) as u16
601 }
602
603 fn render(&self, item: &IamRole) -> (String, ratatui::style::Style) {
604 let text = match self {
605 Self::RoleName => item.role_name.clone(),
606 Self::Path => item.path.clone(),
607 Self::TrustedEntities => item.trusted_entities.clone(),
608 Self::LastActivity => item.last_activity.clone(),
609 Self::Arn => item.arn.clone(),
610 Self::CreationTime => item.creation_time.clone(),
611 Self::Description => item.description.clone(),
612 Self::MaxSessionDuration => item
613 .max_session_duration
614 .map(format_duration_seconds)
615 .unwrap_or_default(),
616 };
617 (text, ratatui::style::Style::default())
618 }
619}
620
621impl Column<GroupUser> for GroupUserColumn {
622 fn name(&self) -> &str {
623 match self {
624 Self::UserName => "User name",
625 Self::Groups => "Groups",
626 Self::LastActivity => "Last activity",
627 Self::CreationTime => "Creation time",
628 }
629 }
630
631 fn width(&self) -> u16 {
632 let custom = match self {
633 Self::UserName => 20,
634 Self::Groups => 20,
635 Self::LastActivity => 20,
636 Self::CreationTime => 30,
637 };
638 self.name().len().max(custom) as u16
639 }
640
641 fn render(&self, item: &GroupUser) -> (String, ratatui::style::Style) {
642 let text = match self {
643 Self::UserName => item.user_name.clone(),
644 Self::Groups => item.groups.clone(),
645 Self::LastActivity => item.last_activity.clone(),
646 Self::CreationTime => item.creation_time.clone(),
647 };
648 (text, ratatui::style::Style::default())
649 }
650}
651
652impl Column<Policy> for PolicyColumn {
653 fn name(&self) -> &str {
654 match self {
655 Self::PolicyName => "Policy name",
656 Self::Type => "Type",
657 Self::AttachedVia => "Attached via",
658 Self::AttachedEntities => "Attached entities",
659 Self::Description => "Description",
660 Self::CreationTime => "Creation time",
661 Self::EditedTime => "Edited time",
662 }
663 }
664
665 fn width(&self) -> u16 {
666 match self {
667 Self::PolicyName => 30,
668 Self::Type => 15,
669 Self::AttachedVia => 20,
670 Self::AttachedEntities => 20,
671 Self::Description => 40,
672 Self::CreationTime => 30,
673 Self::EditedTime => 30,
674 }
675 }
676
677 fn render(&self, item: &Policy) -> (String, ratatui::style::Style) {
678 let text = match self {
679 Self::PolicyName => item.policy_name.clone(),
680 Self::Type => item.policy_type.clone(),
681 Self::AttachedVia => item.attached_via.clone(),
682 Self::AttachedEntities => item.attached_entities.clone(),
683 Self::Description => item.description.clone(),
684 Self::CreationTime => item.creation_time.clone(),
685 Self::EditedTime => item.edited_time.clone(),
686 };
687 (text, ratatui::style::Style::default())
688 }
689}
690
691impl Column<RoleTag> for TagColumn {
692 fn name(&self) -> &str {
693 match self {
694 Self::Key => "Key",
695 Self::Value => "Value",
696 }
697 }
698
699 fn width(&self) -> u16 {
700 match self {
701 Self::Key => 30,
702 Self::Value => 70,
703 }
704 }
705
706 fn render(&self, item: &RoleTag) -> (String, ratatui::style::Style) {
707 let text = match self {
708 Self::Key => item.key.clone(),
709 Self::Value => item.value.clone(),
710 };
711 (text, ratatui::style::Style::default())
712 }
713}
714
715impl Column<UserTag> for TagColumn {
716 fn name(&self) -> &str {
717 match self {
718 Self::Key => "Key",
719 Self::Value => "Value",
720 }
721 }
722
723 fn width(&self) -> u16 {
724 match self {
725 Self::Key => 30,
726 Self::Value => 70,
727 }
728 }
729
730 fn render(&self, item: &UserTag) -> (String, ratatui::style::Style) {
731 let text = match self {
732 Self::Key => item.key.clone(),
733 Self::Value => item.value.clone(),
734 };
735 (text, ratatui::style::Style::default())
736 }
737}
738
739impl Column<UserGroup> for UserGroupColumn {
740 fn name(&self) -> &str {
741 match self {
742 Self::GroupName => "Group name",
743 Self::AttachedPolicies => "Attached policies",
744 }
745 }
746
747 fn width(&self) -> u16 {
748 match self {
749 Self::GroupName => 40,
750 Self::AttachedPolicies => 60,
751 }
752 }
753
754 fn render(&self, item: &UserGroup) -> (String, ratatui::style::Style) {
755 let text = match self {
756 Self::GroupName => item.group_name.clone(),
757 Self::AttachedPolicies => item.attached_policies.clone(),
758 };
759 (text, ratatui::style::Style::default())
760 }
761}
762
763impl Column<LastAccessedService> for LastAccessedServiceColumn {
764 fn name(&self) -> &str {
765 match self {
766 Self::Service => "Service",
767 Self::PoliciesGranting => "Policies granting permissions",
768 Self::LastAccessed => "Last accessed",
769 }
770 }
771
772 fn width(&self) -> u16 {
773 match self {
774 Self::Service => 30,
775 Self::PoliciesGranting => 40,
776 Self::LastAccessed => 30,
777 }
778 }
779
780 fn render(&self, item: &LastAccessedService) -> (String, ratatui::style::Style) {
781 let text = match self {
782 Self::Service => item.service.clone(),
783 Self::PoliciesGranting => item.policies_granting.clone(),
784 Self::LastAccessed => item.last_accessed.clone(),
785 };
786 (text, ratatui::style::Style::default())
787 }
788}
789
790#[cfg(test)]
791mod tests {
792 use super::*;
793 use crate::common::CyclicEnum;
794 use crate::ui::iam::{GroupTab, State, UserTab};
795
796 #[test]
797 fn test_user_group_creation() {
798 let group = UserGroup {
799 group_name: "Developers".to_string(),
800 attached_policies: "AmazonS3ReadOnlyAccess, AmazonEC2ReadOnlyAccess".to_string(),
801 };
802 assert_eq!(group.group_name, "Developers");
803 assert_eq!(
804 group.attached_policies,
805 "AmazonS3ReadOnlyAccess, AmazonEC2ReadOnlyAccess"
806 );
807 }
808
809 #[test]
810 fn test_iam_state_user_group_memberships_initialization() {
811 let state = State::new();
812 assert_eq!(state.user_group_memberships.items.len(), 0);
813 assert_eq!(state.user_group_memberships.selected, 0);
814 assert_eq!(state.user_group_memberships.filter, "");
815 }
816
817 #[test]
818 fn test_user_tab_groups() {
819 let tab = UserTab::Permissions;
820 assert_eq!(tab.next(), UserTab::Groups);
821 assert_eq!(UserTab::Groups.name(), "Groups");
822 }
823
824 #[test]
825 fn test_group_tab_navigation() {
826 let tab = GroupTab::Users;
827 assert_eq!(tab.next(), GroupTab::Permissions);
828 assert_eq!(tab.next().next(), GroupTab::AccessAdvisor);
829 assert_eq!(tab.next().next().next(), GroupTab::Users);
830 }
831
832 #[test]
833 fn test_group_tab_names() {
834 assert_eq!(GroupTab::Users.name(), "Users");
835 assert_eq!(GroupTab::Permissions.name(), "Permissions");
836 assert_eq!(GroupTab::AccessAdvisor.name(), "Access Advisor");
837 }
838
839 #[test]
840 fn test_iam_state_group_tab_initialization() {
841 let state = State::new();
842 assert_eq!(state.group_tab, GroupTab::Users);
843 }
844}