pinakes-tui: cover more API routes in the TUI crate
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Id14b6f82d3b9f3c27bee9c214a1bdedc6a6a6964
This commit is contained in:
parent
0dda2aec8f
commit
bb69f2fa37
8 changed files with 1866 additions and 67 deletions
174
crates/pinakes-tui/src/ui/admin.rs
Normal file
174
crates/pinakes-tui/src/ui/admin.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Row, Table, Tabs},
|
||||
};
|
||||
|
||||
use super::format_date;
|
||||
use crate::app::AppState;
|
||||
|
||||
pub fn render(f: &mut Frame, state: &AppState, area: Rect) {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(3), Constraint::Min(0)])
|
||||
.split(area);
|
||||
|
||||
render_tab_bar(f, state, chunks[0]);
|
||||
|
||||
match state.admin_tab {
|
||||
0 => render_users(f, state, chunks[1]),
|
||||
1 => render_devices(f, state, chunks[1]),
|
||||
_ => render_webhooks(f, state, chunks[1]),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_tab_bar(f: &mut Frame, state: &AppState, area: Rect) {
|
||||
let titles: Vec<Line> = vec!["Users", "Sync Devices", "Webhooks"]
|
||||
.into_iter()
|
||||
.map(|t| Line::from(Span::styled(t, Style::default().fg(Color::White))))
|
||||
.collect();
|
||||
|
||||
let tabs = Tabs::new(titles)
|
||||
.block(Block::default().borders(Borders::ALL).title(" Admin "))
|
||||
.select(state.admin_tab)
|
||||
.style(Style::default().fg(Color::Gray))
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
|
||||
f.render_widget(tabs, area);
|
||||
}
|
||||
|
||||
fn render_users(f: &mut Frame, state: &AppState, area: Rect) {
|
||||
let header = Row::new(vec!["Username", "Role", "Created"]).style(
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
|
||||
let rows: Vec<Row> = state
|
||||
.users_list
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, user)| {
|
||||
let style = if i == state.users_selected {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan)
|
||||
} else {
|
||||
let role_color = match user.role.as_str() {
|
||||
"admin" => Color::Red,
|
||||
"editor" => Color::Yellow,
|
||||
_ => Color::White,
|
||||
};
|
||||
Style::default().fg(role_color)
|
||||
};
|
||||
Row::new(vec![
|
||||
user.username.clone(),
|
||||
user.role.clone(),
|
||||
format_date(&user.created_at).to_string(),
|
||||
])
|
||||
.style(style)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let title = format!(" Users ({}) ", state.users_list.len());
|
||||
|
||||
let table = Table::new(rows, [
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Percentage(40),
|
||||
])
|
||||
.header(header)
|
||||
.block(Block::default().borders(Borders::ALL).title(title));
|
||||
|
||||
f.render_widget(table, area);
|
||||
}
|
||||
|
||||
fn render_devices(f: &mut Frame, state: &AppState, area: Rect) {
|
||||
let header = Row::new(vec!["Name", "Type", "Last Seen"]).style(
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
|
||||
let rows: Vec<Row> = state
|
||||
.sync_devices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, dev)| {
|
||||
let style = if i == state.sync_devices_selected {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
Row::new(vec![
|
||||
dev.name.clone(),
|
||||
dev.device_type.clone().unwrap_or_else(|| "-".into()),
|
||||
dev
|
||||
.last_seen
|
||||
.as_deref()
|
||||
.map_or("-", format_date)
|
||||
.to_string(),
|
||||
])
|
||||
.style(style)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let title = format!(" Sync Devices ({}) ", state.sync_devices.len());
|
||||
|
||||
let table = Table::new(rows, [
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Percentage(40),
|
||||
])
|
||||
.header(header)
|
||||
.block(Block::default().borders(Borders::ALL).title(title));
|
||||
|
||||
f.render_widget(table, area);
|
||||
}
|
||||
|
||||
fn render_webhooks(f: &mut Frame, state: &AppState, area: Rect) {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0)])
|
||||
.split(area);
|
||||
|
||||
let header = Row::new(vec!["URL", "Events"]).style(
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
|
||||
let rows: Vec<Row> = state
|
||||
.webhooks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, wh)| {
|
||||
let style = if i == state.webhooks_selected {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
let events = if wh.events.is_empty() {
|
||||
"-".to_string()
|
||||
} else {
|
||||
wh.events.join(", ")
|
||||
};
|
||||
Row::new(vec![wh.url.clone(), events]).style(style)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let title = format!(" Webhooks ({}) ", state.webhooks.len());
|
||||
|
||||
let table = Table::new(rows, [
|
||||
Constraint::Percentage(50),
|
||||
Constraint::Percentage(50),
|
||||
])
|
||||
.header(header)
|
||||
.block(Block::default().borders(Borders::ALL).title(title));
|
||||
|
||||
f.render_widget(table, chunks[0]);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue