Skip to content

Architecture

or1k edited this page Aug 6, 2026 · 1 revision

Architecture

Layout

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.

The rule that keeps this maintainable

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.

App: the dispatch hub (src/tui/mod.rs)

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.

Home menu (src/tui/home.rs)

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.

Suspending the TUI for an interactive subprocess

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).

Clone this wiki locally