-
-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
src/
main.rs clap CLI entry point, dispatches to tui::run() or a subcommand
config.rs ~/.config/admintoolkit/ path helpers, shared by every module
secret.rs AES-256-GCM at-rest encryption for stored passwords/tokens
ssh_exec.rs shared SSH session + exec helpers (ssh2)
ssh_tunnel.rs local-port-forward over SSH, for "connect to a DB via jump host"
<tool>/ one module per tool — NO ratatui/crossterm imports allowed here
mod.rs
config.rs the tool's on-disk config (serde_json), CRUD helpers
client.rs / exec.rs the tool's actual work: DB client, SSH commands, HTTP API calls
tui/
mod.rs the App state machine: Screen enum, dispatch tables
home.rs the launcher menu (Screen::Home)
<tool>_screen.rs one screen per tool: owns UI state, calls into `<tool>/`
widgets.rs shared UI primitives (Input, buttons, tabs, modal, History panel)
theme.rs color palette system, F9 to cycle
mouse.rs click/scroll hit-testing helpers
host_picker.rs reusable "pick a host from ~/.ssh/config" modal
file_picker.rs reusable filesystem browser modal (e.g. picking an SSH key file)
priv_picker.rs reusable multi-select checkbox grid (DB privilege picker)
Existing tools as of writing: easyssh_mgr (SSH Server Manager),
sshuser (SSH User Manager), cloudflare, godaddy, mysql_mgr,
postgres_mgr, clickhouse, logs_mgr, kerneltune, sslcert.
A <tool>/ module never imports ratatui or crossterm. It knows
nothing about screens, key events, or drawing — it's a library that could in
principle be used from a completely different UI. All of that logic lives in
tui/<tool>_screen.rs. This is why, for example, mysql_mgr::client can be
unit-tested (in principle) without a terminal, and why the SSH User Manager
can expose the exact same sshuser module through both the TUI and a
clap subcommand (see CLI Subcommands) — the module doesn't care who's
calling it.
enum Screen {
Home, EasySsh, SshUser, GoDaddy, Cloudflare,
Mysql, Postgresql, ClickHouse, Logs, KernelTune, SslCert,
}
struct App {
screen: Screen,
home: home::HomeState,
easyssh: Option<easyssh_screen::EasySshScreen>,
sshuser: Option<sshuser_screen::SshUserScreen>,
// ... one Option<XyzScreen> field per tool
should_quit: bool,
last_click: Option<(u16, u16, Instant)>,
mouse_enabled: bool,
}Every tool's screen is None until the user opens it once
(App::enter), then stays alive for the rest of the process — so a tool
that's mid-form-fill keeps its state if you back out to Home and come back.
Four methods on App each do the same match self.screen { ... } fan-out,
one arm per Screen variant, forwarding to the active screen's own method
of the same name:
App method |
Screen method it calls | Contract |
|---|---|---|
tick(&mut self) |
s.tick() |
called every frame (~10/s); poll async work, advance spinners |
handle_key(&mut self, key) |
s.handle_key(key) -> bool |
true return means "pop back to Home" |
handle_mouse(&mut self, me) |
s.handle_mouse(me, area) |
area is the full terminal Rect; screen does its own hit-testing |
draw(&self, f) |
s.draw(f, area) |
same area
|
That table is the contract a new screen must implement — see Adding a New Tool for the exact method signatures to copy.
Two things are handled globally in App::handle_key before the per-screen
match, so no screen needs to reimplement them: Ctrl+C (quit) and F9
(cycle color theme). F12 (toggle mouse capture) is also global.
DEFAULT_ITEMS: &[HomeItem] is the canonical tool list — title, one-line
description, the binary name shown next to it, and which Screen it opens.
The user's on-screen order can differ from this (drag-reorder with
Shift+J/Shift+K, persisted to menu_order.json), but DEFAULT_ITEMS
itself is what a fresh install shows and what any newly-added tool falls
back to (appended at the end) if it's missing from a saved order file. This
is the other file that needs one new entry when adding a tool.
Most tools never need this — they call into their <tool>/ module and get
a Result back while staying in the alternate screen. The one exception is
the SSH Server Manager's "connect" action, which needs to hand the real
TTY to an interactive ssh session (so the user gets a real shell, not a
scraped one). That's what PendingAction::RunInteractive and
App::take_pending_action/run_pending_action in tui/mod.rs are for:
leave the alternate screen, run the program with inherited stdio, come back,
force a redraw. Only reach for this if a tool genuinely needs to hand off
the terminal — everything else (SSH exec, DB queries, HTTP calls) should run
synchronously or on a background thread with a channel back to the screen
(see mysql_screen.rs's use of std::sync::mpsc for the pattern used when
a call might take a while and shouldn't freeze redraws).