Skip to main content

rusticity_term/efs/
actions.rs

1use crate::app::App;
2use crate::common::{ColumnId, InputFocus, PageSize};
3use crate::ui::efs::{
4    filtered_access_points, filtered_efs_file_systems, DetailTab, FILTER_CONTROLS,
5};
6
7/// Number of access-point columns visible in the preferences selector.
8fn ap_col_count(app: &App) -> usize {
9    app.efs_state.ap_column_ids.len()
10}
11
12/// Number of mount-target columns visible in the preferences selector.
13fn mt_col_count(app: &App) -> usize {
14    app.efs_state.mt_column_ids.len()
15}
16
17/// Toggle a column's visibility within `visible`, keeping at least one visible.
18fn toggle_visible(visible: &mut Vec<ColumnId>, all: &[ColumnId], idx: usize) {
19    if idx > 0 && idx <= all.len() {
20        let col = all[idx - 1];
21        if let Some(pos) = visible.iter().position(|c| *c == col) {
22            if visible.len() > 1 {
23                visible.remove(pos);
24            }
25        } else {
26            visible.push(col);
27        }
28    }
29}
30
31/// Apply a page-size selection when `idx` lands in the page-size section that
32/// follows `n` columns (indices `n+3 ..= n+6`). Returns to the first page.
33fn set_page_size_by_idx<T>(table: &mut crate::table::TableState<T>, idx: usize, n: usize) {
34    let ps = if idx == n + 3 {
35        PageSize::Ten
36    } else if idx == n + 4 {
37        PageSize::TwentyFive
38    } else if idx == n + 5 {
39        PageSize::Fifty
40    } else if idx == n + 6 {
41        PageSize::OneHundred
42    } else {
43        return;
44    };
45    table.set_page_size(ps);
46}
47
48// ── Navigation ────────────────────────────────────────────────────────────────
49
50pub fn next_item(app: &mut App) {
51    if app.efs_state.current_file_system.is_some() {
52        match app.efs_state.detail_tab {
53            DetailTab::Tags => {
54                let tag_count = current_fs_tag_count(app);
55                if tag_count > 0 {
56                    app.efs_state.tags_table.next_item(tag_count);
57                }
58            }
59            DetailTab::AccessPoints => {
60                let n = filtered_access_points(app).len();
61                if n > 0 {
62                    app.efs_state.access_points.next_item(n);
63                }
64            }
65            DetailTab::Network => {
66                let n = app.efs_state.mount_targets.items.len();
67                if n > 0 {
68                    app.efs_state.mount_targets.next_item(n);
69                }
70            }
71            _ => {}
72        }
73    } else {
74        let filtered = filtered_efs_file_systems(app);
75        if !filtered.is_empty() {
76            app.efs_state.file_systems.next_item(filtered.len());
77        }
78    }
79}
80
81fn current_fs_tag_count(app: &App) -> usize {
82    app.efs_state
83        .current_file_system
84        .as_deref()
85        .and_then(|id| {
86            app.efs_state
87                .file_systems
88                .items
89                .iter()
90                .find(|f| f.file_system_id == id)
91        })
92        .map(|fs| fs.tags.len())
93        .unwrap_or(0)
94}
95
96pub fn prev_item(app: &mut App) {
97    if app.efs_state.current_file_system.is_some() {
98        match app.efs_state.detail_tab {
99            DetailTab::Tags => app.efs_state.tags_table.prev_item(),
100            DetailTab::AccessPoints => app.efs_state.access_points.prev_item(),
101            DetailTab::Network => app.efs_state.mount_targets.prev_item(),
102            _ => {}
103        }
104    } else {
105        app.efs_state.file_systems.prev_item();
106    }
107}
108
109pub fn page_down_filter_input(app: &mut App) {
110    if app.efs_state.current_file_system.is_some()
111        && app.efs_state.detail_tab == DetailTab::AccessPoints
112    {
113        let n = filtered_access_points(app).len();
114        app.efs_state.access_points.page_down(n);
115        return;
116    }
117    if app.efs_state.input_focus == InputFocus::Filter {
118        let filtered = filtered_efs_file_systems(app);
119        app.efs_state.file_systems.page_down(filtered.len());
120    } else {
121        let page_size = app.efs_state.file_systems.page_size.value();
122        let filtered_count = filtered_efs_file_systems(app).len();
123        app.efs_state.input_focus.handle_page_down(
124            &mut app.efs_state.file_systems.selected,
125            &mut app.efs_state.file_systems.scroll_offset,
126            page_size,
127            filtered_count,
128        );
129    }
130}
131
132pub fn page_down_normal(app: &mut App) {
133    if app.efs_state.current_file_system.is_some() {
134        match app.efs_state.detail_tab {
135            DetailTab::AccessPoints => {
136                let n = filtered_access_points(app).len();
137                app.efs_state.access_points.page_down(n);
138            }
139            DetailTab::Network => {
140                let n = app.efs_state.mount_targets.items.len();
141                app.efs_state.mount_targets.page_down(n);
142            }
143            _ => {}
144        }
145        return;
146    }
147    let filtered = filtered_efs_file_systems(app);
148    app.efs_state.file_systems.page_down(filtered.len());
149}
150
151pub fn page_up_filter_input(app: &mut App) {
152    if app.efs_state.current_file_system.is_some()
153        && app.efs_state.detail_tab == DetailTab::AccessPoints
154    {
155        app.efs_state.access_points.page_up();
156        return;
157    }
158    if app.efs_state.input_focus == InputFocus::Filter {
159        app.efs_state.file_systems.page_up();
160    } else {
161        let page_size = app.efs_state.file_systems.page_size.value();
162        app.efs_state.input_focus.handle_page_up(
163            &mut app.efs_state.file_systems.selected,
164            &mut app.efs_state.file_systems.scroll_offset,
165            page_size,
166        );
167    }
168}
169
170pub fn page_up_normal(app: &mut App) {
171    if app.efs_state.current_file_system.is_some() {
172        match app.efs_state.detail_tab {
173            DetailTab::AccessPoints => app.efs_state.access_points.page_up(),
174            DetailTab::Network => app.efs_state.mount_targets.page_up(),
175            _ => {}
176        }
177        return;
178    }
179    app.efs_state.file_systems.page_up();
180}
181
182pub fn expand_row(app: &mut App) {
183    if app.efs_state.current_file_system.is_some() {
184        match app.efs_state.detail_tab {
185            DetailTab::Tags => app.efs_state.tags_table.toggle_expand(),
186            DetailTab::AccessPoints => app.efs_state.access_points.toggle_expand(),
187            DetailTab::Network => app.efs_state.mount_targets.toggle_expand(),
188            _ => {}
189        }
190    } else {
191        app.efs_state.file_systems.toggle_expand();
192    }
193}
194
195pub fn prev_pane(app: &mut App) {
196    collapse_row(app);
197}
198
199pub fn collapse_row(app: &mut App) {
200    if app.efs_state.current_file_system.is_some() {
201        match app.efs_state.detail_tab {
202            DetailTab::Tags => app.efs_state.tags_table.collapse(),
203            DetailTab::AccessPoints => app.efs_state.access_points.collapse(),
204            DetailTab::Network => app.efs_state.mount_targets.collapse(),
205            _ => {}
206        }
207    } else {
208        app.efs_state.file_systems.collapse();
209    }
210}
211
212pub fn go_to_page(app: &mut App, page: usize) {
213    if app.efs_state.current_file_system.is_some()
214        && app.efs_state.detail_tab == DetailTab::AccessPoints
215    {
216        let n = filtered_access_points(app).len();
217        app.efs_state.access_points.goto_page(page, n);
218        return;
219    }
220    let filtered_count = filtered_efs_file_systems(app).len();
221    app.efs_state.file_systems.goto_page(page, filtered_count);
222}
223
224// ── Filter ────────────────────────────────────────────────────────────────────
225
226pub fn get_active_filter_mut(app: &mut App) -> Option<&mut String> {
227    if app.efs_state.current_file_system.is_some()
228        && app.efs_state.detail_tab == DetailTab::AccessPoints
229    {
230        return Some(&mut app.efs_state.access_points.filter);
231    }
232    Some(&mut app.efs_state.file_systems.filter)
233}
234
235pub fn apply_filter_reset(app: &mut App) {
236    if app.efs_state.current_file_system.is_some()
237        && app.efs_state.detail_tab == DetailTab::AccessPoints
238    {
239        app.efs_state.access_points.reset();
240        return;
241    }
242    app.efs_state.file_systems.reset();
243}
244
245pub fn start_filter(app: &mut App) {
246    app.efs_state.input_focus = InputFocus::Filter;
247}
248
249pub fn next_filter_focus(app: &mut App) {
250    app.efs_state.input_focus = app.efs_state.input_focus.next(&FILTER_CONTROLS);
251}
252
253pub fn prev_filter_focus(app: &mut App) {
254    app.efs_state.input_focus = app.efs_state.input_focus.prev(&FILTER_CONTROLS);
255}
256
257pub fn is_pagination_focused(app: &App) -> bool {
258    app.efs_state.input_focus == InputFocus::Pagination
259}
260
261// ── Yank / console URL / breadcrumb ──────────────────────────────────────────
262
263pub fn yank(app: &App) {
264    use crate::app::copy_to_clipboard;
265    if let Some(fs_id) = &app.efs_state.current_file_system {
266        // On the File system policy tab, copy the policy JSON.
267        if app.efs_state.detail_tab == crate::ui::efs::DetailTab::FileSystemPolicy
268            && !app.efs_state.policy_document.is_empty()
269        {
270            copy_to_clipboard(&app.efs_state.policy_document);
271            return;
272        }
273        // Otherwise in detail view — copy the ARN
274        if let Some(fs) = app
275            .efs_state
276            .file_systems
277            .items
278            .iter()
279            .find(|f| &f.file_system_id == fs_id)
280        {
281            copy_to_clipboard(&fs.file_system_arn);
282        }
283    } else {
284        // In list view — copy the file system ID
285        let filtered = filtered_efs_file_systems(app);
286        if let Some(fs) = app.efs_state.file_systems.get_selected(&filtered) {
287            copy_to_clipboard(&fs.file_system_id);
288        }
289    }
290}
291
292// ── Select / go back ──────────────────────────────────────────────────────────
293
294pub fn select_item(app: &mut App) {
295    if app.efs_state.current_file_system.is_none() {
296        let filtered = filtered_efs_file_systems(app);
297        if let Some(fs) = app.efs_state.file_systems.get_selected(&filtered) {
298            app.efs_state.current_file_system = Some(fs.file_system_id.clone());
299            app.efs_state.detail_tab = crate::ui::efs::DetailTab::MeteredSize;
300        }
301    }
302}
303
304pub fn go_back(app: &mut App) {
305    app.efs_state.current_file_system = None;
306    app.efs_state.detail_tab = crate::ui::efs::DetailTab::MeteredSize;
307}
308
309pub fn next_detail_tab(app: &mut App) {
310    use crate::common::CyclicEnum;
311    app.efs_state.detail_tab = app.efs_state.detail_tab.next();
312    trigger_tab_load(app);
313}
314
315pub fn prev_detail_tab(app: &mut App) {
316    use crate::common::CyclicEnum;
317    app.efs_state.detail_tab = app.efs_state.detail_tab.prev();
318    trigger_tab_load(app);
319}
320
321/// When switching to the Monitoring tab, mark metrics as loading so the event
322/// loop fetches fresh CloudWatch data and reset the chart scroll. When switching
323/// to the File system policy tab, mark the policy as loading.
324fn trigger_tab_load(app: &mut App) {
325    use crate::ui::monitoring::MonitoringState;
326    match app.efs_state.detail_tab {
327        crate::ui::efs::DetailTab::Monitoring => {
328            app.efs_state.set_metrics_loading(true);
329            app.efs_state.set_monitoring_scroll(0);
330            app.efs_state.clear_metrics();
331        }
332        crate::ui::efs::DetailTab::FileSystemPolicy => {
333            app.efs_state.policy_loading = true;
334            app.efs_state.policy_scroll = 0;
335        }
336        crate::ui::efs::DetailTab::AccessPoints => {
337            app.efs_state.ap_loading = true;
338            app.efs_state.access_points.reset();
339        }
340        crate::ui::efs::DetailTab::Network => {
341            app.efs_state.mt_loading = true;
342            app.efs_state.mount_targets.reset();
343        }
344        crate::ui::efs::DetailTab::Replication => {
345            app.efs_state.replication_loading = true;
346        }
347        _ => {}
348    }
349}
350
351/// Number of charts in the EFS Monitoring tab; the last scrollable index is
352/// `EFS_MONITORING_CHARTS - 1`.
353const EFS_MONITORING_CHARTS: usize = 6;
354
355/// Scroll the active detail tab down (Ctrl+D / ScrollDown).
356pub fn scroll_down_detail(app: &mut App) {
357    use crate::ui::monitoring::MonitoringState;
358    match app.efs_state.detail_tab {
359        crate::ui::efs::DetailTab::Monitoring => {
360            let max = EFS_MONITORING_CHARTS.saturating_sub(1);
361            app.efs_state
362                .set_monitoring_scroll((app.efs_state.monitoring_scroll() + 1).min(max));
363        }
364        crate::ui::efs::DetailTab::FileSystemPolicy => {
365            let max = app
366                .efs_state
367                .policy_document
368                .lines()
369                .count()
370                .saturating_sub(1);
371            app.efs_state.policy_scroll = (app.efs_state.policy_scroll + 1).min(max);
372        }
373        _ => {}
374    }
375}
376
377/// Scroll the active detail tab up (Ctrl+U / ScrollUp).
378pub fn scroll_up_detail(app: &mut App) {
379    use crate::ui::monitoring::MonitoringState;
380    match app.efs_state.detail_tab {
381        crate::ui::efs::DetailTab::Monitoring => {
382            app.efs_state
383                .set_monitoring_scroll(app.efs_state.monitoring_scroll().saturating_sub(1));
384        }
385        crate::ui::efs::DetailTab::FileSystemPolicy => {
386            app.efs_state.policy_scroll = app.efs_state.policy_scroll.saturating_sub(1);
387        }
388        _ => {}
389    }
390}
391
392pub fn breadcrumb(app: &App) -> Vec<String> {
393    let mut parts = vec!["EFS".to_string()];
394    if let Some(fs_id) = &app.efs_state.current_file_system {
395        parts.push(fs_id.clone());
396    } else {
397        parts.push("File Systems".to_string());
398    }
399    parts
400}
401
402pub fn console_url(app: &App) -> String {
403    use crate::efs;
404    // In detail view, link to the current file system's active tab.
405    if let Some(fs_id) = &app.efs_state.current_file_system {
406        return efs::console_url_file_system_with_tab(
407            &app.config.region,
408            fs_id,
409            app.efs_state.detail_tab,
410        );
411    }
412    let filtered = filtered_efs_file_systems(app);
413    if let Some(fs) = app.efs_state.file_systems.get_selected(&filtered) {
414        efs::console_url_file_system(&app.config.region, &fs.file_system_id)
415    } else {
416        efs::console_url_file_systems(&app.config.region)
417    }
418}
419
420// ── Column preferences ────────────────────────────────────────────────────────
421
422pub fn toggle_column(app: &mut App) {
423    let idx = app.column_selector_index;
424    if app.efs_state.current_file_system.is_some() {
425        match app.efs_state.detail_tab {
426            DetailTab::Tags => {
427                // Key/Value columns are always visible — only page size configurable.
428                set_page_size_by_idx(&mut app.efs_state.tags_table, idx, 2);
429                return;
430            }
431            DetailTab::AccessPoints => {
432                let all = app.efs_state.ap_column_ids.clone();
433                toggle_visible(&mut app.efs_state.ap_visible_column_ids, &all, idx);
434                set_page_size_by_idx(&mut app.efs_state.access_points, idx, all.len());
435                return;
436            }
437            DetailTab::Network => {
438                let all = app.efs_state.mt_column_ids.clone();
439                toggle_visible(&mut app.efs_state.mt_visible_column_ids, &all, idx);
440                set_page_size_by_idx(&mut app.efs_state.mount_targets, idx, all.len());
441                return;
442            }
443            _ => return,
444        }
445    }
446    // File systems list.
447    if idx > 0 && idx <= app.efs_column_ids.len() {
448        if let Some(col) = app.efs_column_ids.get(idx - 1) {
449            if let Some(pos) = app.efs_visible_column_ids.iter().position(|c| c == col) {
450                if app.efs_visible_column_ids.len() > 1 {
451                    app.efs_visible_column_ids.remove(pos);
452                }
453            } else {
454                app.efs_visible_column_ids.push(*col);
455            }
456        }
457    } else {
458        set_page_size_by_idx(
459            &mut app.efs_state.file_systems,
460            idx,
461            app.efs_column_ids.len(),
462        );
463    }
464}
465
466/// Number of toggleable columns for the active view (used by the prefs cursor).
467fn active_prefs_col_count(app: &App) -> usize {
468    if app.efs_state.current_file_system.is_some() {
469        match app.efs_state.detail_tab {
470            DetailTab::Tags => 2,
471            DetailTab::AccessPoints => ap_col_count(app),
472            DetailTab::Network => mt_col_count(app),
473            _ => app.efs_column_ids.len(),
474        }
475    } else {
476        app.efs_column_ids.len()
477    }
478}
479
480pub fn next_preferences(app: &mut App) {
481    let page_size_idx = active_prefs_col_count(app) + 2;
482    if app.column_selector_index < page_size_idx {
483        app.column_selector_index = page_size_idx;
484    } else {
485        app.column_selector_index = 0;
486    }
487}
488
489pub fn prev_preferences(app: &mut App) {
490    let page_size_idx = active_prefs_col_count(app) + 2;
491    if app.column_selector_index >= page_size_idx {
492        app.column_selector_index = 0;
493    } else {
494        app.column_selector_index = page_size_idx;
495    }
496}
497
498pub fn column_selector_max(app: &App) -> usize {
499    active_prefs_col_count(app) + 6
500}
501
502pub fn column_count(app: &App) -> usize {
503    active_prefs_col_count(app)
504}