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}