Skip to main content

rusticity_term/ui/
rds.rs

1use crate::app::App;
2use crate::common::{render_pagination_text, ColumnId, InputFocus, SortDirection};
3use crate::keymap::Mode;
4use crate::rds::instance::Column;
5use crate::table::{TableState, TableWindow};
6use crate::ui::filter::{render_simple_filter, SimpleFilterConfig};
7use crate::ui::table::{expanded_from_columns, render_table, TableConfig};
8use crate::ui::{format_title, titled_block};
9use ratatui::{prelude::*, widgets::Paragraph};
10use rusticity_core::rds::RdsInstance;
11
12pub const FILTER_CONTROLS: [InputFocus; 2] = [InputFocus::Filter, InputFocus::Pagination];
13
14pub struct State {
15    pub instances: TableState<RdsInstance>,
16    pub input_focus: InputFocus,
17    pub column_ids: Vec<ColumnId>,
18    pub visible_column_ids: Vec<ColumnId>,
19}
20
21impl Default for State {
22    fn default() -> Self {
23        Self {
24            instances: TableState::new(),
25            input_focus: InputFocus::Filter,
26            column_ids: Column::ids(),
27            visible_column_ids: Column::default_visible_ids(),
28        }
29    }
30}
31
32pub fn filtered_instances(app: &App) -> Vec<&RdsInstance> {
33    let filter = app.rds_state.instances.filter.to_lowercase();
34    let mut instances = app.rds_state.instances.filtered(|instance| {
35        [
36            &instance.identifier,
37            &instance.engine,
38            &instance.engine_version,
39            &instance.status,
40            &instance.instance_class,
41            &instance.region,
42            &instance.availability_zone,
43            &instance.endpoint,
44            &instance.arn,
45            &instance.cluster_identifier,
46            &instance.vpc,
47            &instance.subnet_group,
48        ]
49        .iter()
50        .any(|value| value.to_lowercase().contains(&filter))
51    });
52    instances.sort_by(|a, b| a.identifier.cmp(&b.identifier));
53    instances
54}
55
56pub fn render_instances(frame: &mut Frame, app: &App, area: Rect) {
57    let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]).split(area);
58    let state = &app.rds_state;
59    let view = state
60        .instances
61        .prepare_view(filtered_instances(app), TableWindow::SelectedPage);
62    let pagination = render_pagination_text(view.current_page, view.total_pages);
63    render_simple_filter(
64        frame,
65        chunks[0],
66        SimpleFilterConfig {
67            filter_text: &state.instances.filter,
68            placeholder: "Search by DB identifier, engine, status, class, AZ, or endpoint",
69            pagination: &pagination,
70            mode: app.mode,
71            is_input_focused: state.input_focus == InputFocus::Filter,
72            is_pagination_focused: state.input_focus == InputFocus::Pagination,
73        },
74    );
75    if state.instances.loading && state.instances.items.is_empty() {
76        frame.render_widget(
77            Paragraph::new("Loading databases...")
78                .block(titled_block("Databases"))
79                .alignment(Alignment::Center),
80            chunks[1],
81        );
82        return;
83    }
84    let columns: Vec<_> = state
85        .visible_column_ids
86        .iter()
87        .filter_map(|id| Column::from_id(id))
88        .map(Column::into_table_column)
89        .collect();
90    render_table(
91        frame,
92        TableConfig {
93            items: view.items,
94            selected_index: view.selected_index,
95            expanded_index: view.expanded_index,
96            columns: &columns,
97            sort_column: &Column::Identifier.name(),
98            sort_direction: SortDirection::Asc,
99            title: format_title(&format!("Databases ({})", view.total_items)),
100            area: chunks[1],
101            get_expanded_content: Some(Box::new(|instance| {
102                expanded_from_columns(&columns, instance)
103            })),
104            is_active: app.mode == Mode::Normal,
105        },
106    );
107}