admin panel bombarded like never before
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
// src/components/admin.rs
|
||||
pub mod admin_panel;
|
||||
pub mod admin_panel_admin;
|
||||
|
||||
pub use admin_panel::*;
|
||||
pub use admin_panel_admin::*;
|
||||
|
||||
@@ -98,8 +98,8 @@ fn render_admin_panel_non_admin(
|
||||
.block(Block::default().title("Profiles"))
|
||||
.highlight_style(Style::default().bg(theme.highlight).fg(theme.bg));
|
||||
|
||||
let mut list_state_clone = admin_state.list_state.clone();
|
||||
f.render_stateful_widget(list, content_chunks[0], &mut list_state_clone);
|
||||
let mut profile_list_state_clone = admin_state.profile_list_state.clone();
|
||||
f.render_stateful_widget(list, content_chunks[0], &mut profile_list_state_clone);
|
||||
|
||||
// Profile details - Use selection info from admin_state
|
||||
if let Some(profile) = admin_state
|
||||
|
||||
127
client/src/components/admin/admin_panel_admin.rs
Normal file
127
client/src/components/admin/admin_panel_admin.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
// src/components/admin/admin_panel_admin.rs
|
||||
|
||||
// Add necessary imports that were previously in admin_panel.rs
|
||||
// and are used by this function
|
||||
use crate::config::colors::themes::Theme;
|
||||
use crate::state::pages::admin::{AdminFocus, AdminState};
|
||||
use crate::state::app::state::AppState;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
style::{Style, Stylize},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, BorderType, Borders, List, ListItem, Paragraph},
|
||||
Frame,
|
||||
};
|
||||
|
||||
/// Renders the view specific to admin users.
|
||||
/// Implements the two-pane layout for profile and table browsing.
|
||||
// Make it public (`pub`) if called from outside this module,
|
||||
// or keep it private (no `pub`) if only called from admin_panel.rs
|
||||
// For now, let's assume it's only called from admin_panel.rs
|
||||
pub(super) fn render_admin_panel_admin( // Use pub(super) or pub based on need
|
||||
f: &mut Frame,
|
||||
admin_state: &mut AdminState,
|
||||
app_state: &AppState,
|
||||
content_chunks: &[Rect],
|
||||
theme: &Theme,
|
||||
) {
|
||||
let profile_tree = &app_state.profile_tree;
|
||||
let selected_profile_index = admin_state.get_selected_profile_index();
|
||||
|
||||
// --- Profile List (Left Pane) ---
|
||||
let profile_items: Vec<ListItem> = profile_tree
|
||||
.profiles
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, p)| {
|
||||
let is_selected = selected_profile_index == Some(i);
|
||||
let style = if is_selected && admin_state.current_focus == AdminFocus::Profiles {
|
||||
Style::default().fg(theme.bg).bg(theme.highlight)
|
||||
} else if is_selected {
|
||||
Style::default().fg(theme.fg).bg(theme.inactive_highlight_bg)
|
||||
} else {
|
||||
Style::default().fg(theme.fg)
|
||||
};
|
||||
ListItem::new(Line::from(Span::styled(&p.name, style)))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let profile_list_block = Block::default()
|
||||
.title(" Profiles ")
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Plain)
|
||||
.border_style(if admin_state.current_focus == AdminFocus::Profiles {
|
||||
Style::default().fg(theme.accent)
|
||||
} else {
|
||||
Style::default().fg(theme.border)
|
||||
});
|
||||
|
||||
let profile_list = List::new(profile_items).block(profile_list_block);
|
||||
|
||||
f.render_stateful_widget(
|
||||
profile_list,
|
||||
content_chunks[0],
|
||||
&mut admin_state.profile_list_state,
|
||||
);
|
||||
|
||||
// --- Table List & Details (Right Pane) ---
|
||||
let table_list_block = Block::default()
|
||||
.title(" Tables & Dependencies ")
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Plain)
|
||||
.border_style(if admin_state.current_focus == AdminFocus::Tables {
|
||||
Style::default().fg(theme.accent)
|
||||
} else {
|
||||
Style::default().fg(theme.border)
|
||||
});
|
||||
|
||||
if let Some(profile_idx) = selected_profile_index {
|
||||
if let Some(profile) = profile_tree.profiles.get(profile_idx) {
|
||||
let selected_table_index = admin_state.get_selected_table_index();
|
||||
let table_items: Vec<ListItem> = profile
|
||||
.tables
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, table)| {
|
||||
let is_selected = selected_table_index == Some(i);
|
||||
let base_style = if is_selected && admin_state.current_focus == AdminFocus::Tables {
|
||||
Style::default().fg(theme.bg).bg(theme.highlight)
|
||||
} else if is_selected {
|
||||
Style::default().fg(theme.fg).bg(theme.inactive_highlight_bg)
|
||||
} else {
|
||||
Style::default().fg(theme.fg)
|
||||
};
|
||||
|
||||
let mut line_spans = vec![Span::styled(&table.name, base_style)];
|
||||
if !table.depends_on.is_empty() {
|
||||
line_spans.push(Span::styled(" -> ", base_style.fg(theme.secondary)));
|
||||
line_spans.push(Span::styled(
|
||||
format!("[{}]", table.depends_on.join(", ")),
|
||||
base_style.fg(theme.secondary),
|
||||
));
|
||||
}
|
||||
ListItem::new(Line::from(line_spans))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let table_list = List::new(table_items);
|
||||
|
||||
f.render_stateful_widget(
|
||||
table_list.block(table_list_block),
|
||||
content_chunks[1],
|
||||
&mut admin_state.table_list_state,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let placeholder = Paragraph::new("Select a profile to see tables.")
|
||||
.style(Style::default().fg(theme.secondary))
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
f.render_widget(
|
||||
placeholder.block(table_list_block),
|
||||
content_chunks[1],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,10 +81,10 @@ pub fn render_autocomplete_dropdown(
|
||||
let list = List::new(items);
|
||||
|
||||
// State for managing selection highlight (still needed for logic)
|
||||
let mut list_state = ListState::default();
|
||||
list_state.select(selected_index);
|
||||
let mut profile_list_state = ListState::default();
|
||||
profile_list_state.select(selected_index);
|
||||
|
||||
// Render the list statefully *over* the background block
|
||||
f.render_stateful_widget(list, dropdown_area, &mut list_state);
|
||||
f.render_stateful_widget(list, dropdown_area, &mut profile_list_state);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ pub struct Theme {
|
||||
pub warning: Color,
|
||||
pub border: Color,
|
||||
pub highlight_bg: Color,
|
||||
pub inactive_highlight_bg: Color,// admin panel no idea what it really is
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
@@ -33,6 +34,7 @@ impl Theme {
|
||||
warning: Color::Rgb(255, 182, 193), // Pastel pink
|
||||
border: Color::Rgb(220, 220, 220), // Light gray border
|
||||
highlight_bg: Color::Rgb(70, 70, 70), // Darker grey for highlight background
|
||||
inactive_highlight_bg: Color::Rgb(50, 50, 50),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +49,7 @@ impl Theme {
|
||||
warning: Color::Rgb(255, 99, 71), // Bright red
|
||||
border: Color::Rgb(100, 100, 100), // Medium gray border
|
||||
highlight_bg: Color::Rgb(180, 180, 180), // Lighter grey for highlight background
|
||||
inactive_highlight_bg: Color::Rgb(50, 50, 50),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +64,7 @@ impl Theme {
|
||||
warning: Color::Rgb(255, 0, 0), // Red
|
||||
border: Color::Rgb(0, 0, 0), // Black border
|
||||
highlight_bg: Color::Rgb(180, 180, 180), // Lighter grey for highlight background
|
||||
inactive_highlight_bg: Color::Rgb(50, 50, 50),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,64 +2,178 @@
|
||||
|
||||
use ratatui::widgets::ListState;
|
||||
|
||||
// Define the focus states for the admin panel panes
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum AdminFocus {
|
||||
#[default] // Default focus is on the profiles list
|
||||
Profiles,
|
||||
Tables,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct AdminState {
|
||||
pub profiles: Vec<String>,
|
||||
pub list_state: ListState,
|
||||
pub profile_list_state: ListState, // State for the profiles list
|
||||
pub table_list_state: ListState, // State for the tables list
|
||||
pub current_focus: AdminFocus, // Tracks which pane is focused
|
||||
}
|
||||
|
||||
impl AdminState {
|
||||
/// Gets the index of the currently selected item.
|
||||
pub fn get_selected_index(&self) -> Option<usize> {
|
||||
self.list_state.selected()
|
||||
self.profile_list_state.selected()
|
||||
}
|
||||
|
||||
/// Gets the name of the currently selected profile.
|
||||
pub fn get_selected_profile_name(&self) -> Option<&String> {
|
||||
self.list_state.selected().and_then(|i| self.profiles.get(i))
|
||||
self.profile_list_state.selected().and_then(|i| self.profiles.get(i))
|
||||
}
|
||||
|
||||
/// Populates the profile list and updates/resets the selection.
|
||||
pub fn set_profiles(&mut self, new_profiles: Vec<String>) {
|
||||
let current_selection_index = self.list_state.selected();
|
||||
let current_selection_index = self.profile_list_state.selected();
|
||||
self.profiles = new_profiles;
|
||||
|
||||
if self.profiles.is_empty() {
|
||||
self.list_state.select(None);
|
||||
self.profile_list_state.select(None);
|
||||
} else {
|
||||
let new_selection = match current_selection_index {
|
||||
Some(index) => Some(index.min(self.profiles.len() - 1)),
|
||||
None => Some(0),
|
||||
};
|
||||
self.list_state.select(new_selection);
|
||||
self.profile_list_state.select(new_selection);
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects the next profile in the list, wrapping around.
|
||||
pub fn next(&mut self) {
|
||||
if self.profiles.is_empty() {
|
||||
self.list_state.select(None);
|
||||
self.profile_list_state.select(None);
|
||||
return;
|
||||
}
|
||||
let i = match self.list_state.selected() {
|
||||
let i = match self.profile_list_state.selected() {
|
||||
Some(i) => if i >= self.profiles.len() - 1 { 0 } else { i + 1 },
|
||||
None => 0,
|
||||
};
|
||||
self.list_state.select(Some(i));
|
||||
self.profile_list_state.select(Some(i));
|
||||
}
|
||||
|
||||
/// Selects the previous profile in the list, wrapping around.
|
||||
pub fn previous(&mut self) {
|
||||
if self.profiles.is_empty() {
|
||||
self.list_state.select(None);
|
||||
self.profile_list_state.select(None);
|
||||
return;
|
||||
}
|
||||
let i = match self.list_state.selected() {
|
||||
let i = match self.profile_list_state.selected() {
|
||||
Some(i) => if i == 0 { self.profiles.len() - 1 } else { i - 1 },
|
||||
None => self.profiles.len() - 1,
|
||||
};
|
||||
self.list_state.select(Some(i));
|
||||
self.profile_list_state.select(Some(i));
|
||||
}
|
||||
|
||||
/// Gets the index of the currently selected profile.
|
||||
pub fn get_selected_profile_index(&self) -> Option<usize> {
|
||||
self.profile_list_state.selected()
|
||||
}
|
||||
|
||||
/// Gets the index of the currently selected table.
|
||||
pub fn get_selected_table_index(&self) -> Option<usize> {
|
||||
self.table_list_state.selected()
|
||||
}
|
||||
|
||||
/// Selects a profile by index and resets table selection.
|
||||
pub fn select_profile(&mut self, index: Option<usize>) {
|
||||
self.profile_list_state.select(index);
|
||||
self.table_list_state.select(None); // Reset table selection
|
||||
}
|
||||
|
||||
/// Selects a table by index.
|
||||
pub fn select_table(&mut self, index: Option<usize>) {
|
||||
self.table_list_state.select(index);
|
||||
}
|
||||
|
||||
/// Selects the next profile, wrapping around.
|
||||
/// `profile_count` should be the total number of profiles available.
|
||||
pub fn next_profile(&mut self, profile_count: usize) {
|
||||
if profile_count == 0 {
|
||||
return;
|
||||
}
|
||||
let i = match self.get_selected_profile_index() {
|
||||
Some(i) => {
|
||||
if i >= profile_count - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.select_profile(Some(i)); // Use the helper method
|
||||
}
|
||||
|
||||
/// Selects the previous profile, wrapping around.
|
||||
/// `profile_count` should be the total number of profiles available.
|
||||
pub fn previous_profile(&mut self, profile_count: usize) {
|
||||
if profile_count == 0 {
|
||||
return;
|
||||
}
|
||||
let i = match self.get_selected_profile_index() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
profile_count - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0, // Or profile_count - 1 if you prefer wrapping from None
|
||||
};
|
||||
self.select_profile(Some(i)); // Use the helper method
|
||||
}
|
||||
|
||||
/// Selects the next table, wrapping around.
|
||||
/// `table_count` should be the number of tables in the *currently selected* profile.
|
||||
pub fn next_table(&mut self, table_count: usize) {
|
||||
if table_count == 0 {
|
||||
return;
|
||||
}
|
||||
let i = match self.get_selected_table_index() {
|
||||
Some(i) => {
|
||||
if i >= table_count - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.select_table(Some(i));
|
||||
}
|
||||
|
||||
/// Selects the previous table, wrapping around.
|
||||
/// `table_count` should be the number of tables in the *currently selected* profile.
|
||||
pub fn previous_table(&mut self, table_count: usize) {
|
||||
if table_count == 0 {
|
||||
return;
|
||||
}
|
||||
let i = match self.get_selected_table_index() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
table_count - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0, // Or table_count - 1
|
||||
};
|
||||
self.select_table(Some(i));
|
||||
}
|
||||
|
||||
/// Toggles focus between the profile list and the table list.
|
||||
pub fn toggle_focus(&mut self) {
|
||||
self.current_focus = match self.current_focus {
|
||||
AdminFocus::Profiles => AdminFocus::Tables,
|
||||
AdminFocus::Tables => AdminFocus::Profiles,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user