#![cfg_attr(
not(test),
deny(clippy::print_stdout, clippy::print_stderr, clippy::dbg_macro)
)]
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
symbols::Marker,
text::{Line, Span},
widgets::{
Axis, Block, BorderType, Borders, Cell, Chart, Clear, Dataset, GraphType, List, ListItem,
Padding, Paragraph, Row, Table, Wrap,
},
Frame,
};
use crate::aws::Environment;
use crate::overlay::{centered_overlay, OverlaySize};
use crate::theme::{IconStyle, Theme};
use crate::app::{
Action, ActionFlow, App, ConfirmKind, DetailTab, DisplayRow, LoadState, Mode, Overlay, Scope,
SortKey, ToastKind, ViewMode, ACTIONS,
};
mod action;
mod chrome;
mod detail;
mod dlq;
mod events;
mod footer;
mod header;
mod help;
mod overlays;
mod shell;
mod table;
pub(crate) use action::*;
pub(crate) use chrome::*;
pub(crate) use dlq::*;
pub(crate) use events::*;
pub(crate) use footer::*;
pub(crate) use header::*;
pub(crate) use shell::*;
pub(crate) use table::*;
use detail::*;
use help::*;
use overlays::*;
fn assert_mode_state_coherent(app: &App) {
let missing = match app.mode {
Mode::Detail if app.detail.is_none() => Some("Detail"),
Mode::Dlq if app.dlq.is_none() => Some("Dlq"),
Mode::Action if app.action_flow.is_none() => Some("Action"),
Mode::Form if app.form.is_none() => Some("Form"),
Mode::Picker if app.picker.is_none() => Some("Picker"),
Mode::Shell if app.current_shell.is_none() => Some("Shell"),
_ => return,
};
if let Some(surface) = missing {
missing_mode_state(surface);
}
}
pub(crate) fn draw(f: &mut Frame, app: &mut App) {
assert_mode_state_coherent(app);
if app.mode == Mode::Shell && app.current_shell.is_some() {
draw_shell(f, f.area(), app);
return;
}
if app.dlq.is_some() {
draw_dlq(f, f.area(), app);
} else if app.detail.is_some() {
draw_detail(f, f.area(), app);
} else {
let events_height: u16 = if app.event_panel.visible {
app.event_panel.height
} else {
0
};
let (header_height, merge_pills) = header_layout(app, f.area().width);
let mut constraints: Vec<Constraint> =
vec![Constraint::Length(header_height), Constraint::Min(3)];
if events_height > 0 {
constraints.push(Constraint::Length(events_height));
}
let footer_height: u16 = if app.first_run_hint { 3 } else { 2 };
constraints.push(Constraint::Length(footer_height));
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(constraints)
.split(f.area());
draw_header(f, chunks[0], app, merge_pills);
match app.scope {
Scope::Envs => draw_table(f, chunks[1], app),
Scope::Apps => draw_apps_table(f, chunks[1], app),
}
if app.event_panel.visible {
app.event_panel.area = Some(chunks[2]);
draw_events(f, chunks[2], app);
draw_footer(f, chunks[3], app);
} else {
app.event_panel.area = None;
draw_footer(f, chunks[2], app);
}
}
if app.mode == Mode::Help {
draw_help(f, f.area(), app);
} else {
app.help.max_scroll = 0;
}
if app.mode == Mode::Picker {
draw_picker(f, f.area(), app);
}
if app.mode == Mode::Action {
draw_action(f, f.area(), app);
}
if matches!(app.current_overlay, Some(Overlay::WhyRed { .. })) {
draw_why_red_overlay(f, f.area(), app);
} else if let Some(overlay) = app.current_overlay.as_ref() {
match overlay {
Overlay::Describe(text) => draw_describe(f, f.area(), app, text),
Overlay::Whatsnew(text) => draw_whatsnew(f, f.area(), app, text),
Overlay::History(text) => draw_history_overlay(f, f.area(), app, text),
Overlay::Alarms { body, .. } => draw_alarms_overlay(f, f.area(), app, body),
Overlay::Diff(text) => draw_diff_overlay(f, f.area(), app, text),
Overlay::SavedConfigs(text) => draw_saved_configs_overlay(f, f.area(), app, text),
Overlay::SavedConfigsInteractive {
items,
cursor,
confirm_delete,
} => draw_saved_configs_interactive(f, f.area(), app, items, *cursor, *confirm_delete),
Overlay::TextDump { title, body } => {
draw_text_dump_overlay(f, f.area(), app, title, body)
}
Overlay::LogTail { .. } => draw_log_tail_overlay(f, f.area(), app),
Overlay::EventTail { .. } => draw_event_tail_overlay(f, f.area(), app),
Overlay::WhyRed { .. } => {}
Overlay::AppsActionMenu {
app_name,
env_names,
cursor,
} => draw_apps_action_menu(f, f.area(), app, app_name, env_names, *cursor),
Overlay::ReportBug { body } => draw_report_bug_overlay(f, f.area(), app, body),
Overlay::About(opened) => draw_about(f, f.area(), app, *opened),
}
}
if app.mode == Mode::Palette {
draw_palette(f, f.area(), app);
}
if app.mode == Mode::Form {
draw_form(f, f.area(), app);
}
if !app.toasts.is_empty() {
draw_toasts(f, f.area(), app);
}
}
pub(crate) fn missing_mode_state(surface: &'static str) {
debug_assert!(
false,
"mode is {surface} but its state is None — a mode was entered \
without its state, or the state was cleared without leaving the \
mode"
);
static LOGGED: std::sync::Mutex<Option<std::collections::BTreeSet<&'static str>>> =
std::sync::Mutex::new(None);
if let Ok(mut g) = LOGGED.lock() {
let seen = g.get_or_insert_with(std::collections::BTreeSet::new);
if seen.insert(surface) {
tracing::error!(
surface,
"mode is active but its state is None — rendering an empty pane"
);
}
}
}
#[cfg(test)]
mod tests;