Skip to main content

rusticity_term/iam/
actions.rs

1use crate::app::App;
2use crate::common::{CyclicEnum, InputFocus};
3use crate::ui::iam::{
4    filtered_iam_policies, filtered_iam_roles, filtered_iam_users, filtered_last_accessed,
5    filtered_tags as filtered_iam_tags, filtered_user_tags, GroupTab, RoleTab, UserTab,
6    GROUP_FILTER_CONTROLS, HISTORY_FILTER, POLICY_FILTER_CONTROLS, POLICY_TYPE_DROPDOWN,
7    ROLE_FILTER_CONTROLS, USER_LAST_ACCESSED_FILTER_CONTROLS, USER_SIMPLE_FILTER_CONTROLS,
8};
9
10// ── Filter reset ──────────────────────────────────────────────────────────────
11
12pub fn apply_filter_reset_users(app: &mut App) {
13    if app.iam_state.current_user.is_some() {
14        app.iam_state.user_tags.reset();
15        app.iam_state.policies.reset();
16    } else {
17        app.iam_state.users.reset();
18    }
19}
20
21pub fn apply_filter_reset_roles(app: &mut App) {
22    if app.iam_state.current_role.is_some() {
23        app.iam_state.tags.reset();
24        app.iam_state.policies.reset();
25    } else {
26        app.iam_state.roles.reset();
27    }
28}
29
30pub fn apply_filter_reset_groups(app: &mut App) {
31    if app.iam_state.current_group.is_some() {
32        app.iam_state.policies.reset();
33        app.iam_state.group_users.reset();
34    } else {
35        app.iam_state.groups.reset();
36    }
37}
38
39pub fn reset_on_service_select_roles(app: &mut App) {
40    app.iam_state.roles.reset();
41}
42
43pub fn reset_on_service_select_users(app: &mut App) {
44    app.iam_state.users.reset();
45}
46
47pub fn reset_on_service_select_groups(app: &mut App) {
48    app.iam_state.groups.reset();
49}
50
51// ── Filter focus ──────────────────────────────────────────────────────────────
52
53pub fn next_filter_focus_roles(app: &mut App) {
54    if app.iam_state.current_role.is_some() {
55        app.iam_state.policy_input_focus = app
56            .iam_state
57            .policy_input_focus
58            .next(&POLICY_FILTER_CONTROLS);
59    } else {
60        app.iam_state.role_input_focus = app.iam_state.role_input_focus.next(&ROLE_FILTER_CONTROLS);
61    }
62}
63
64pub fn prev_filter_focus_roles(app: &mut App) {
65    if app.iam_state.current_role.is_some() {
66        app.iam_state.policy_input_focus = app
67            .iam_state
68            .policy_input_focus
69            .prev(&POLICY_FILTER_CONTROLS);
70    } else {
71        app.iam_state.role_input_focus = app.iam_state.role_input_focus.prev(&ROLE_FILTER_CONTROLS);
72    }
73}
74
75pub fn next_filter_focus_users(app: &mut App) {
76    if app.iam_state.user_tab == UserTab::Permissions {
77        app.iam_state.policy_input_focus = app
78            .iam_state
79            .policy_input_focus
80            .next(&POLICY_FILTER_CONTROLS);
81    } else if app.iam_state.user_tab == UserTab::LastAccessed {
82        app.iam_state.last_accessed_input_focus = app
83            .iam_state
84            .last_accessed_input_focus
85            .next(&USER_LAST_ACCESSED_FILTER_CONTROLS);
86    } else {
87        app.iam_state.user_input_focus = app
88            .iam_state
89            .user_input_focus
90            .next(&USER_SIMPLE_FILTER_CONTROLS);
91    }
92}
93
94pub fn prev_filter_focus_users(app: &mut App) {
95    if app.iam_state.user_tab == UserTab::Permissions {
96        app.iam_state.policy_input_focus = app
97            .iam_state
98            .policy_input_focus
99            .prev(&POLICY_FILTER_CONTROLS);
100    } else if app.iam_state.user_tab == UserTab::LastAccessed {
101        app.iam_state.last_accessed_input_focus = app
102            .iam_state
103            .last_accessed_input_focus
104            .prev(&USER_LAST_ACCESSED_FILTER_CONTROLS);
105    } else {
106        app.iam_state.user_input_focus = app
107            .iam_state
108            .user_input_focus
109            .prev(&USER_SIMPLE_FILTER_CONTROLS);
110    }
111}
112
113pub fn next_filter_focus_groups(app: &mut App) {
114    app.iam_state.group_input_focus = app.iam_state.group_input_focus.next(&GROUP_FILTER_CONTROLS);
115}
116
117pub fn prev_filter_focus_groups(app: &mut App) {
118    app.iam_state.group_input_focus = app.iam_state.group_input_focus.prev(&GROUP_FILTER_CONTROLS);
119}
120
121pub fn is_pagination_focused_roles(app: &App) -> bool {
122    app.iam_state.current_role.is_none() && app.iam_state.role_input_focus == InputFocus::Pagination
123}
124
125pub fn is_filter_focused_roles(app: &App) -> bool {
126    app.iam_state.current_role.is_none() && app.iam_state.role_input_focus == InputFocus::Filter
127}
128
129pub fn is_filter_focused_policy_view(app: &App) -> bool {
130    app.iam_state.policy_input_focus == InputFocus::Filter
131}
132
133pub fn is_pagination_focused_policy_view(app: &App) -> bool {
134    app.iam_state.policy_input_focus == InputFocus::Pagination
135}
136
137// ── Navigation ────────────────────────────────────────────────────────────────
138
139pub fn next_item_users(app: &mut App) {
140    if app.iam_state.current_user.is_some() {
141        if app.iam_state.user_tab == UserTab::Tags {
142            let filtered = filtered_user_tags(app);
143            if !filtered.is_empty() {
144                app.iam_state.user_tags.next_item(filtered.len());
145            }
146        } else {
147            let filtered = filtered_iam_policies(app);
148            if !filtered.is_empty() {
149                app.iam_state.policies.next_item(filtered.len());
150            }
151        }
152    } else {
153        let filtered = filtered_iam_users(app);
154        if !filtered.is_empty() {
155            app.iam_state.users.next_item(filtered.len());
156        }
157    }
158}
159
160pub fn next_item_roles(app: &mut App) {
161    if app.iam_state.current_role.is_some() {
162        match app.iam_state.role_tab {
163            RoleTab::TrustRelationships => {
164                let lines = app.iam_state.trust_policy_document.lines().count();
165                let max_scroll = lines.saturating_sub(1);
166                app.iam_state.trust_policy_scroll =
167                    (app.iam_state.trust_policy_scroll + 1).min(max_scroll);
168            }
169            RoleTab::RevokeSessions => {
170                app.iam_state.revoke_sessions_scroll =
171                    (app.iam_state.revoke_sessions_scroll + 1).min(19);
172            }
173            RoleTab::Tags => {
174                let filtered = filtered_iam_tags(app);
175                if !filtered.is_empty() {
176                    app.iam_state.tags.next_item(filtered.len());
177                }
178            }
179            RoleTab::LastAccessed => {
180                let filtered = filtered_last_accessed(app);
181                if !filtered.is_empty() {
182                    app.iam_state
183                        .last_accessed_services
184                        .next_item(filtered.len());
185                }
186            }
187            _ => {
188                let filtered = filtered_iam_policies(app);
189                if !filtered.is_empty() {
190                    app.iam_state.policies.next_item(filtered.len());
191                }
192            }
193        }
194    } else {
195        let filtered = filtered_iam_roles(app);
196        if !filtered.is_empty() {
197            app.iam_state.roles.next_item(filtered.len());
198        }
199    }
200}
201
202pub fn next_item_groups(app: &mut App) {
203    if app.iam_state.current_group.is_some() {
204        match app.iam_state.group_tab {
205            GroupTab::Users => {
206                let filtered: Vec<_> = app
207                    .iam_state
208                    .group_users
209                    .items
210                    .iter()
211                    .filter(|u| {
212                        app.iam_state.group_users.filter.is_empty()
213                            || u.user_name
214                                .to_lowercase()
215                                .contains(&app.iam_state.group_users.filter.to_lowercase())
216                    })
217                    .collect();
218                if !filtered.is_empty() {
219                    app.iam_state.group_users.next_item(filtered.len());
220                }
221            }
222            GroupTab::Permissions => {
223                let filtered = filtered_iam_policies(app);
224                if !filtered.is_empty() {
225                    app.iam_state.policies.next_item(filtered.len());
226                }
227            }
228            GroupTab::AccessAdvisor => {
229                let filtered = filtered_last_accessed(app);
230                if !filtered.is_empty() {
231                    app.iam_state
232                        .last_accessed_services
233                        .next_item(filtered.len());
234                }
235            }
236        }
237    } else {
238        let filtered: Vec<_> = app
239            .iam_state
240            .groups
241            .items
242            .iter()
243            .filter(|g| {
244                app.iam_state.groups.filter.is_empty()
245                    || g.group_name
246                        .to_lowercase()
247                        .contains(&app.iam_state.groups.filter.to_lowercase())
248            })
249            .collect();
250        if !filtered.is_empty() {
251            app.iam_state.groups.next_item(filtered.len());
252        }
253    }
254}
255
256pub fn prev_item_users(app: &mut App) {
257    app.iam_state.users.prev_item();
258}
259
260pub fn prev_item_roles(app: &mut App) {
261    if app.iam_state.current_role.is_some() {
262        match app.iam_state.role_tab {
263            RoleTab::TrustRelationships => {
264                app.iam_state.trust_policy_scroll =
265                    app.iam_state.trust_policy_scroll.saturating_sub(1);
266            }
267            RoleTab::RevokeSessions => {
268                app.iam_state.revoke_sessions_scroll =
269                    app.iam_state.revoke_sessions_scroll.saturating_sub(1);
270            }
271            RoleTab::Tags => app.iam_state.tags.prev_item(),
272            RoleTab::LastAccessed => app.iam_state.last_accessed_services.prev_item(),
273            _ => app.iam_state.policies.prev_item(),
274        }
275    } else {
276        app.iam_state.roles.prev_item();
277    }
278}
279
280pub fn prev_item_groups(app: &mut App) {
281    if app.iam_state.current_group.is_some() {
282        match app.iam_state.group_tab {
283            GroupTab::Users => app.iam_state.group_users.prev_item(),
284            GroupTab::Permissions => app.iam_state.policies.prev_item(),
285            GroupTab::AccessAdvisor => app.iam_state.last_accessed_services.prev_item(),
286        }
287    } else {
288        app.iam_state.groups.prev_item();
289    }
290}
291
292pub fn page_down_filter_input_roles(app: &mut App) {
293    let page_size = app.iam_state.roles.page_size.value();
294    let filtered_count = filtered_iam_roles(app).len();
295    app.iam_state.role_input_focus.handle_page_down(
296        &mut app.iam_state.roles.selected,
297        &mut app.iam_state.roles.scroll_offset,
298        page_size,
299        filtered_count,
300    );
301}
302
303pub fn page_down_filter_input_policy_view(app: &mut App) {
304    let page_size = app.iam_state.policies.page_size.value();
305    let filtered_count = filtered_iam_policies(app).len();
306    app.iam_state.policy_input_focus.handle_page_down(
307        &mut app.iam_state.policies.selected,
308        &mut app.iam_state.policies.scroll_offset,
309        page_size,
310        filtered_count,
311    );
312}
313
314pub fn page_down_normal_users(app: &mut App) {
315    let len = filtered_iam_users(app).len();
316    crate::app::nav_page_down(&mut app.iam_state.users.selected, len, 10);
317}
318
319pub fn page_down_normal_roles(app: &mut App) {
320    if app.iam_state.current_role.is_some() {
321        let filtered = filtered_iam_policies(app);
322        if !filtered.is_empty() {
323            app.iam_state.policies.page_down(filtered.len());
324        }
325    } else {
326        let filtered = filtered_iam_roles(app);
327        app.iam_state.roles.page_down(filtered.len());
328    }
329}
330
331pub fn page_down_normal_groups(app: &mut App) {
332    if app.iam_state.current_group.is_some() {
333        match app.iam_state.group_tab {
334            GroupTab::Users => {
335                let filtered: Vec<_> = app
336                    .iam_state
337                    .group_users
338                    .items
339                    .iter()
340                    .filter(|u| {
341                        app.iam_state.group_users.filter.is_empty()
342                            || u.user_name
343                                .to_lowercase()
344                                .contains(&app.iam_state.group_users.filter.to_lowercase())
345                    })
346                    .collect();
347                if !filtered.is_empty() {
348                    app.iam_state.group_users.page_down(filtered.len());
349                }
350            }
351            GroupTab::Permissions => {
352                let filtered = filtered_iam_policies(app);
353                if !filtered.is_empty() {
354                    app.iam_state.policies.page_down(filtered.len());
355                }
356            }
357            GroupTab::AccessAdvisor => {
358                let filtered = filtered_last_accessed(app);
359                if !filtered.is_empty() {
360                    app.iam_state
361                        .last_accessed_services
362                        .page_down(filtered.len());
363                }
364            }
365        }
366    } else {
367        let filtered: Vec<_> = app
368            .iam_state
369            .groups
370            .items
371            .iter()
372            .filter(|g| {
373                app.iam_state.groups.filter.is_empty()
374                    || g.group_name
375                        .to_lowercase()
376                        .contains(&app.iam_state.groups.filter.to_lowercase())
377            })
378            .collect();
379        if !filtered.is_empty() {
380            app.iam_state.groups.page_down(filtered.len());
381        }
382    }
383}
384
385pub fn page_up_filter_input_roles(app: &mut App) {
386    let page_size = app.iam_state.roles.page_size.value();
387    app.iam_state.role_input_focus.handle_page_up(
388        &mut app.iam_state.roles.selected,
389        &mut app.iam_state.roles.scroll_offset,
390        page_size,
391    );
392}
393
394pub fn page_up_filter_input_policy_view(app: &mut App) {
395    let page_size = app.iam_state.policies.page_size.value();
396    app.iam_state.policy_input_focus.handle_page_up(
397        &mut app.iam_state.policies.selected,
398        &mut app.iam_state.policies.scroll_offset,
399        page_size,
400    );
401}
402
403pub fn page_up_normal_users(app: &mut App) {
404    app.iam_state.users.page_up();
405}
406
407pub fn page_up_normal_roles(app: &mut App) {
408    if app.iam_state.current_role.is_some() {
409        app.iam_state.policies.page_up();
410    } else {
411        app.iam_state.roles.page_up();
412    }
413}
414
415pub fn go_to_page_users(app: &mut App, page: usize) {
416    let filtered_count = filtered_iam_users(app).len();
417    app.iam_state.users.goto_page(page, filtered_count);
418}
419
420pub fn go_to_page_roles(app: &mut App, page: usize) {
421    let filtered_count = filtered_iam_roles(app).len();
422    app.iam_state.roles.goto_page(page, filtered_count);
423}
424
425pub fn scroll_up_policy_view(app: &mut App) {
426    app.iam_state.policy_scroll = app.iam_state.policy_scroll.saturating_sub(10);
427}
428
429pub fn scroll_down_policy_view(app: &mut App) {
430    let lines = app.iam_state.policy_document.lines().count();
431    let max_scroll = lines.saturating_sub(1);
432    app.iam_state.policy_scroll = (app.iam_state.policy_scroll + 10).min(max_scroll);
433}
434
435pub fn scroll_up_trust_policy(app: &mut App) {
436    app.iam_state.trust_policy_scroll = app.iam_state.trust_policy_scroll.saturating_sub(10);
437}
438
439pub fn scroll_down_trust_policy(app: &mut App) {
440    let lines = app.iam_state.trust_policy_document.lines().count();
441    let max_scroll = lines.saturating_sub(1);
442    app.iam_state.trust_policy_scroll = (app.iam_state.trust_policy_scroll + 10).min(max_scroll);
443}
444
445pub fn scroll_up_revoke_sessions(app: &mut App) {
446    app.iam_state.revoke_sessions_scroll = app.iam_state.revoke_sessions_scroll.saturating_sub(10);
447}
448
449pub fn scroll_down_revoke_sessions(app: &mut App) {
450    app.iam_state.revoke_sessions_scroll = (app.iam_state.revoke_sessions_scroll + 10).min(19);
451}
452
453pub fn scroll_down_policy_view_one(app: &mut App) {
454    let lines = app.iam_state.policy_document.lines().count();
455    let max_scroll = lines.saturating_sub(1);
456    app.iam_state.policy_scroll = (app.iam_state.policy_scroll + 1).min(max_scroll);
457}
458
459pub fn scroll_up_policy_view_one(app: &mut App) {
460    app.iam_state.policy_scroll = app.iam_state.policy_scroll.saturating_sub(1);
461}
462
463// ── FilterInput next_item (dropdown cycling) ──────────────────────────────────
464
465pub fn next_item_filter_input_users(app: &mut App) {
466    if app.iam_state.user_tab == UserTab::Permissions
467        && app.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
468    {
469        cycle_policy_type_next(app);
470    } else if app.iam_state.user_tab == UserTab::LastAccessed
471        && app.iam_state.last_accessed_input_focus == HISTORY_FILTER
472    {
473        app.iam_state.last_accessed_history_filter =
474            app.iam_state.last_accessed_history_filter.next();
475        app.iam_state.last_accessed_services.reset();
476    }
477}
478
479pub fn prev_item_filter_input_users(app: &mut App) {
480    if app.iam_state.user_tab == UserTab::Permissions
481        && app.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
482    {
483        cycle_policy_type_prev(app);
484    } else if app.iam_state.user_tab == UserTab::LastAccessed
485        && app.iam_state.last_accessed_input_focus == HISTORY_FILTER
486    {
487        app.iam_state.last_accessed_history_filter =
488            app.iam_state.last_accessed_history_filter.prev();
489        app.iam_state.last_accessed_services.reset();
490    }
491}
492
493pub fn next_item_filter_input_roles(app: &mut App) {
494    if app.iam_state.role_tab == RoleTab::Permissions
495        && app.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
496    {
497        cycle_policy_type_next(app);
498    }
499}
500
501pub fn prev_item_filter_input_roles(app: &mut App) {
502    if app.iam_state.role_tab == RoleTab::Permissions
503        && app.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
504    {
505        cycle_policy_type_prev(app);
506    }
507}
508
509fn cycle_policy_type_next(app: &mut App) {
510    let types = ["All types", "AWS managed", "Customer managed"];
511    let current_idx = types
512        .iter()
513        .position(|&t| t == app.iam_state.policy_type_filter)
514        .unwrap_or(0);
515    app.iam_state.policy_type_filter = types[(current_idx + 1) % types.len()].to_string();
516    app.iam_state.policies.reset();
517}
518
519fn cycle_policy_type_prev(app: &mut App) {
520    let types = ["All types", "AWS managed", "Customer managed"];
521    let current_idx = types
522        .iter()
523        .position(|&t| t == app.iam_state.policy_type_filter)
524        .unwrap_or(0);
525    let prev_idx = if current_idx == 0 {
526        types.len() - 1
527    } else {
528        current_idx - 1
529    };
530    app.iam_state.policy_type_filter = types[prev_idx].to_string();
531    app.iam_state.policies.reset();
532}
533
534// ── Expand / collapse ─────────────────────────────────────────────────────────
535
536pub fn expand_row_users(app: &mut App) {
537    if app.iam_state.current_user.is_some() {
538        if app.iam_state.user_tab == UserTab::Tags {
539            if app.iam_state.user_tags.expanded_item != Some(app.iam_state.user_tags.selected) {
540                app.iam_state.user_tags.expanded_item = Some(app.iam_state.user_tags.selected);
541            }
542        } else if app.iam_state.policies.expanded_item != Some(app.iam_state.policies.selected) {
543            app.iam_state.policies.toggle_expand();
544        }
545    } else if !app.iam_state.users.is_expanded() {
546        app.iam_state.users.toggle_expand();
547    }
548}
549
550pub fn expand_row_roles(app: &mut App) {
551    if app.iam_state.current_role.is_some() {
552        match app.iam_state.role_tab {
553            RoleTab::Tags => {
554                if !app.iam_state.tags.is_expanded() {
555                    app.iam_state.tags.expand();
556                }
557            }
558            RoleTab::LastAccessed => {
559                if !app.iam_state.last_accessed_services.is_expanded() {
560                    app.iam_state.last_accessed_services.expand();
561                }
562            }
563            _ => {
564                if !app.iam_state.policies.is_expanded() {
565                    app.iam_state.policies.expand();
566                }
567            }
568        }
569    } else if !app.iam_state.roles.is_expanded() {
570        app.iam_state.roles.expand();
571    }
572}
573
574pub fn expand_row_groups(app: &mut App) {
575    if app.iam_state.current_group.is_some() {
576        match app.iam_state.group_tab {
577            GroupTab::Users => {
578                if !app.iam_state.group_users.is_expanded() {
579                    app.iam_state.group_users.expand();
580                }
581            }
582            GroupTab::Permissions => {
583                if !app.iam_state.policies.is_expanded() {
584                    app.iam_state.policies.expand();
585                }
586            }
587            GroupTab::AccessAdvisor => {
588                if !app.iam_state.last_accessed_services.is_expanded() {
589                    app.iam_state.last_accessed_services.expand();
590                }
591            }
592        }
593    } else if !app.iam_state.groups.is_expanded() {
594        app.iam_state.groups.expand();
595    }
596}
597
598pub fn prev_pane_users(app: &mut App) {
599    if app.iam_state.users.has_expanded_item() {
600        app.iam_state.users.collapse();
601    }
602}
603
604pub fn prev_pane_roles(app: &mut App) {
605    if app.view_mode == crate::app::ViewMode::PolicyView {
606        app.view_mode = crate::app::ViewMode::Detail;
607        app.iam_state.current_policy = None;
608        app.iam_state.policy_document.clear();
609        app.iam_state.policy_scroll = 0;
610    } else if app.iam_state.current_role.is_some() {
611        if app.iam_state.role_tab == RoleTab::Tags && app.iam_state.tags.has_expanded_item() {
612            app.iam_state.tags.collapse();
613        } else if app.iam_state.role_tab == RoleTab::LastAccessed
614            && app.iam_state.last_accessed_services.expanded_item.is_some()
615        {
616            app.iam_state.last_accessed_services.collapse();
617        } else if app.iam_state.policies.has_expanded_item() {
618            app.iam_state.policies.collapse();
619        }
620    } else if app.iam_state.roles.has_expanded_item() {
621        app.iam_state.roles.collapse();
622    }
623}
624
625pub fn prev_pane_groups(app: &mut App) {
626    if app.iam_state.current_group.is_some() {
627        if app.iam_state.group_tab == GroupTab::Users
628            && app.iam_state.group_users.has_expanded_item()
629        {
630            app.iam_state.group_users.collapse();
631        } else if app.iam_state.group_tab == GroupTab::Permissions
632            && app.iam_state.policies.has_expanded_item()
633        {
634            app.iam_state.policies.collapse();
635        } else if app.iam_state.group_tab == GroupTab::AccessAdvisor
636            && app.iam_state.last_accessed_services.expanded_item.is_some()
637        {
638            app.iam_state.last_accessed_services.collapse();
639        }
640    } else if app.iam_state.groups.has_expanded_item() {
641        app.iam_state.groups.collapse();
642    }
643}
644
645pub fn collapse_row_users(app: &mut App) {
646    if app.iam_state.current_user.is_some() {
647        match app.iam_state.user_tab {
648            UserTab::Permissions => app.iam_state.policies.collapse(),
649            UserTab::Groups => app.iam_state.user_group_memberships.collapse(),
650            UserTab::Tags => app.iam_state.user_tags.collapse(),
651            _ => {}
652        }
653    } else {
654        app.iam_state.users.collapse();
655    }
656}
657
658pub fn collapse_row_roles(app: &mut App) {
659    if app.iam_state.current_role.is_some() {
660        match app.iam_state.role_tab {
661            RoleTab::Permissions => app.iam_state.policies.collapse(),
662            RoleTab::Tags => app.iam_state.tags.collapse(),
663            _ => {}
664        }
665    } else {
666        app.iam_state.roles.collapse();
667    }
668}
669
670pub fn collapse_row_groups(app: &mut App) {
671    app.iam_state.groups.collapse();
672}
673
674// ── Go back ───────────────────────────────────────────────────────────────────
675
676pub fn go_back_users(app: &mut App) {
677    app.iam_state.current_user = None;
678    app.iam_state.policies.items.clear();
679    app.iam_state.policies.reset();
680    app.update_current_tab_breadcrumb();
681}
682
683pub fn go_back_groups(app: &mut App) {
684    app.iam_state.current_group = None;
685    app.update_current_tab_breadcrumb();
686}
687
688pub fn go_back_roles(app: &mut App) {
689    if app.view_mode == crate::app::ViewMode::PolicyView {
690        app.view_mode = crate::app::ViewMode::Detail;
691        app.iam_state.current_policy = None;
692        app.iam_state.policy_document.clear();
693        app.iam_state.policy_scroll = 0;
694        app.update_current_tab_breadcrumb();
695    } else if app.iam_state.current_role.is_some() {
696        app.iam_state.current_role = None;
697        app.iam_state.policies.items.clear();
698        app.iam_state.policies.reset();
699        app.update_current_tab_breadcrumb();
700    }
701}
702
703// ── Detail tabs ───────────────────────────────────────────────────────────────
704
705pub fn next_detail_tab_roles(app: &mut App) {
706    app.iam_state.role_tab = app.iam_state.role_tab.next();
707    if app.iam_state.role_tab == RoleTab::Tags {
708        app.iam_state.tags.loading = true;
709    }
710}
711
712pub fn prev_detail_tab_roles(app: &mut App) {
713    app.iam_state.role_tab = app.iam_state.role_tab.prev();
714}
715
716pub fn next_detail_tab_users(app: &mut App) {
717    app.iam_state.user_tab = app.iam_state.user_tab.next();
718    if app.iam_state.user_tab == UserTab::Tags {
719        app.iam_state.user_tags.loading = true;
720    }
721}
722
723pub fn prev_detail_tab_users(app: &mut App) {
724    app.iam_state.user_tab = app.iam_state.user_tab.prev();
725}
726
727pub fn next_detail_tab_groups(app: &mut App) {
728    app.iam_state.group_tab = app.iam_state.group_tab.next();
729}
730
731pub fn prev_detail_tab_groups(app: &mut App) {
732    app.iam_state.group_tab = app.iam_state.group_tab.prev();
733}
734
735// ── Select item ───────────────────────────────────────────────────────────────
736
737pub fn select_item_users(app: &mut App) {
738    if app.iam_state.current_user.is_some() {
739        if app.iam_state.user_tab == UserTab::Permissions {
740            let filtered = filtered_iam_policies(app);
741            if let Some(policy) = app.iam_state.policies.get_selected(&filtered) {
742                app.iam_state.current_policy = Some(policy.policy_name.clone());
743                app.iam_state.policy_scroll = 0;
744                app.view_mode = crate::app::ViewMode::PolicyView;
745                app.iam_state.policies.loading = true;
746                app.update_current_tab_breadcrumb();
747            }
748        }
749    } else {
750        let filtered = filtered_iam_users(app);
751        if let Some(user) = app.iam_state.users.get_selected(&filtered) {
752            app.iam_state.current_user = Some(user.user_name.clone());
753            app.iam_state.user_tab = UserTab::Permissions;
754            app.iam_state.policies.reset();
755            app.update_current_tab_breadcrumb();
756        }
757    }
758}
759
760pub fn select_item_roles(app: &mut App) {
761    if app.iam_state.current_role.is_some() {
762        if app.iam_state.role_tab == RoleTab::Permissions {
763            let filtered = filtered_iam_policies(app);
764            if let Some(policy) = app.iam_state.policies.get_selected(&filtered) {
765                app.iam_state.current_policy = Some(policy.policy_name.clone());
766                app.iam_state.policy_scroll = 0;
767                app.view_mode = crate::app::ViewMode::PolicyView;
768                app.iam_state.policies.loading = true;
769                app.update_current_tab_breadcrumb();
770            }
771        }
772    } else {
773        let filtered = filtered_iam_roles(app);
774        if let Some(role) = app.iam_state.roles.get_selected(&filtered) {
775            app.iam_state.current_role = Some(role.role_name.clone());
776            app.iam_state.role_tab = RoleTab::Permissions;
777            app.iam_state.policies.reset();
778            app.update_current_tab_breadcrumb();
779        }
780    }
781}
782
783pub fn select_item_groups(app: &mut App) {
784    if app.iam_state.current_group.is_none() {
785        let filtered: Vec<_> = app
786            .iam_state
787            .groups
788            .items
789            .iter()
790            .filter(|g| {
791                app.iam_state.groups.filter.is_empty()
792                    || g.group_name
793                        .to_lowercase()
794                        .contains(&app.iam_state.groups.filter.to_lowercase())
795            })
796            .collect();
797        if let Some(group) = app.iam_state.groups.get_selected(&filtered) {
798            app.iam_state.current_group = Some(group.group_name.clone());
799            app.update_current_tab_breadcrumb();
800        }
801    }
802}
803
804// ── Yank ──────────────────────────────────────────────────────────────────────
805
806pub fn yank_users(app: &App) {
807    // In policy view, copy the policy document JSON
808    if app.view_mode == crate::app::ViewMode::PolicyView {
809        if !app.iam_state.policy_document.is_empty() {
810            app.clipboard.request_text(&app.iam_state.policy_document);
811        }
812        return;
813    }
814    if app.iam_state.current_user.is_some() {
815        if let Some(user_name) = &app.iam_state.current_user {
816            if let Some(user) = app
817                .iam_state
818                .users
819                .items
820                .iter()
821                .find(|u| u.user_name == *user_name)
822            {
823                app.clipboard.request_text(&user.arn);
824            }
825        }
826    } else {
827        let filtered = filtered_iam_users(app);
828        if let Some(user) = app.iam_state.users.get_selected(&filtered) {
829            app.clipboard.request_text(&user.arn);
830        }
831    }
832}
833
834pub fn yank_roles(app: &App) {
835    // In policy view, copy the policy document JSON
836    if app.view_mode == crate::app::ViewMode::PolicyView {
837        if !app.iam_state.policy_document.is_empty() {
838            app.clipboard.request_text(&app.iam_state.policy_document);
839        }
840        return;
841    }
842    if app.iam_state.current_role.is_some() {
843        if let Some(role_name) = &app.iam_state.current_role {
844            if let Some(role) = app
845                .iam_state
846                .roles
847                .items
848                .iter()
849                .find(|r| r.role_name == *role_name)
850            {
851                app.clipboard.request_text(&role.arn);
852            }
853        }
854    } else {
855        let filtered = filtered_iam_roles(app);
856        if let Some(role) = app.iam_state.roles.get_selected(&filtered) {
857            app.clipboard.request_text(&role.arn);
858        }
859    }
860}
861
862pub fn yank_groups(app: &App) {
863    use crate::iam;
864    if let Some(group_name) = &app.iam_state.current_group {
865        let arn = iam::format_arn(&app.config.account_id, "group", group_name);
866        app.clipboard.request_text(&arn);
867    } else {
868        let filtered: Vec<_> = app
869            .iam_state
870            .groups
871            .items
872            .iter()
873            .filter(|g| {
874                app.iam_state.groups.filter.is_empty()
875                    || g.group_name
876                        .to_lowercase()
877                        .contains(&app.iam_state.groups.filter.to_lowercase())
878            })
879            .collect();
880        if let Some(group) = app.iam_state.groups.get_selected(&filtered) {
881            let arn = iam::format_arn(&app.config.account_id, "group", &group.group_name);
882            app.clipboard.request_text(&arn);
883        }
884    }
885}
886
887// ── Column preferences ────────────────────────────────────────────────────────
888
889pub fn toggle_column_users(app: &mut App) {
890    crate::preferences::bindings::activate_for(app, crate::app::Service::IamUsers);
891}
892
893pub fn toggle_column_roles(app: &mut App) {
894    crate::preferences::bindings::activate_for(app, crate::app::Service::IamRoles);
895}
896
897pub fn toggle_column_groups(app: &mut App) {
898    crate::preferences::bindings::activate_for(app, crate::app::Service::IamUserGroups);
899}
900
901// ── Prefs cycling ─────────────────────────────────────────────────────────────
902
903pub fn next_preferences_users(app: &mut App) {
904    crate::preferences::bindings::move_section_for(app, crate::app::Service::IamUsers, true);
905}
906
907pub fn prev_preferences_users(app: &mut App) {
908    crate::preferences::bindings::move_section_for(app, crate::app::Service::IamUsers, false);
909}
910
911pub fn next_preferences_roles(app: &mut App) {
912    crate::preferences::bindings::move_section_for(app, crate::app::Service::IamRoles, true);
913}
914
915pub fn prev_preferences_roles(app: &mut App) {
916    crate::preferences::bindings::move_section_for(app, crate::app::Service::IamRoles, false);
917}
918
919pub fn next_preferences_groups(app: &mut App) {
920    crate::preferences::bindings::move_section_for(app, crate::app::Service::IamUserGroups, true);
921}
922
923pub fn prev_preferences_groups(app: &mut App) {
924    crate::preferences::bindings::move_section_for(app, crate::app::Service::IamUserGroups, false);
925}
926
927pub fn column_selector_max_users(app: &App) -> usize {
928    crate::preferences::bindings::model_for(app, crate::app::Service::IamUsers).max_index()
929}
930
931pub fn column_count_users(app: &App) -> usize {
932    crate::preferences::bindings::model_for(app, crate::app::Service::IamUsers).column_count()
933}
934
935pub fn column_selector_max_roles(app: &App) -> usize {
936    crate::preferences::bindings::model_for(app, crate::app::Service::IamRoles).max_index()
937}
938
939pub fn column_count_roles(app: &App) -> usize {
940    crate::preferences::bindings::model_for(app, crate::app::Service::IamRoles).column_count()
941}
942
943pub fn column_selector_max_groups(app: &App) -> usize {
944    crate::preferences::bindings::model_for(app, crate::app::Service::IamUserGroups).max_index()
945}
946
947pub fn column_count_groups(app: &App) -> usize {
948    crate::preferences::bindings::model_for(app, crate::app::Service::IamUserGroups).column_count()
949}
950
951// ── Breadcrumb / console URL ──────────────────────────────────────────────────
952
953pub fn breadcrumb_users() -> Vec<String> {
954    vec!["IAM".to_string(), "Users".to_string()]
955}
956
957pub fn breadcrumb_roles(app: &App) -> Vec<String> {
958    let mut parts = vec!["IAM".to_string(), "Roles".to_string()];
959    if let Some(role_name) = &app.iam_state.current_role {
960        parts.push(role_name.clone());
961        if let Some(policy_name) = &app.iam_state.current_policy {
962            parts.push(policy_name.clone());
963        }
964    }
965    parts
966}
967
968pub fn breadcrumb_groups(app: &App) -> Vec<String> {
969    let mut parts = vec!["IAM".to_string(), "User Groups".to_string()];
970    if let Some(group_name) = &app.iam_state.current_group {
971        parts.push(group_name.clone());
972    }
973    parts
974}
975
976pub fn console_url_users(app: &App) -> String {
977    use crate::iam;
978    if let Some(user_name) = &app.iam_state.current_user {
979        let section = match app.iam_state.user_tab {
980            UserTab::Permissions => "permissions",
981            UserTab::Groups => "groups",
982            UserTab::Tags => "tags",
983            UserTab::SecurityCredentials => "security_credentials",
984            UserTab::LastAccessed => "access_advisor",
985        };
986        iam::console_url_user_detail(&app.config.region, user_name, section)
987    } else {
988        iam::console_url_users(&app.config.region)
989    }
990}
991
992pub fn console_url_roles(app: &App) -> String {
993    use crate::iam;
994    if let Some(policy_name) = &app.iam_state.current_policy {
995        if let Some(role_name) = &app.iam_state.current_role {
996            return iam::console_url_role_policy(&app.config.region, role_name, policy_name);
997        }
998    }
999    if let Some(role_name) = &app.iam_state.current_role {
1000        let section = match app.iam_state.role_tab {
1001            RoleTab::Permissions => "permissions",
1002            RoleTab::TrustRelationships => "trust_relationships",
1003            RoleTab::Tags => "tags",
1004            RoleTab::LastAccessed => "access_advisor",
1005            RoleTab::RevokeSessions => "revoke_sessions",
1006        };
1007        iam::console_url_role_detail(&app.config.region, role_name, section)
1008    } else {
1009        iam::console_url_roles(&app.config.region)
1010    }
1011}
1012
1013pub fn console_url_groups(app: &App) -> String {
1014    use crate::iam;
1015    iam::console_url_groups(&app.config.region)
1016}