rusticity_term/
iam.rs

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}