Architecture
Ferrodock is 100% Rust:
- egui / eframe for the native UI
- bollard for the Docker Engine API
- tokio for async I/O
Layout
src/
├── main.rs window setup
├── docker/ Docker Engine API, one file per resource (no UI code)
├── worker/ background thread: Docker calls, log and stats streams
├── theme/ colour palettes, widget styling, fonts
└── app/
├── mod.rs wires the store, the shell and the pages together
├── store/ everything currently known about the engine
├── page.rs the Page trait every sidebar section implements
├── pages/ containers · compose · images · volumes · networks
├── components/ table, details pane, dialogs, charts, toasts
├── shell.rs shared page header and "Docker isn't running" screen
└── sidebar.rs
How data flows
- The UI sends a request to the worker over a channel: refresh a list, run an action, or watch a container.
- The worker runs it on a tokio runtime and sends responses back. The UI thread never waits on Docker.
- Responses land in the store, which holds containers, projects, images, volumes, networks and the live logs and stats.
- Pages draw from the store. To change something, a page returns an intent: run a task, or ask for confirmation first. The app turns intents into worker requests.
Adding a page
Implement the Page trait in a new file under src/app/pages/ and register
it in pages::all(). The sidebar entry, header, search box and details pane
all work through the trait, so nothing else needs to change.
pub trait Page {
fn title(&self) -> &'static str;
fn resource(&self) -> Resource;
fn count(&self, store: &Store) -> usize;
fn summary(&self, store: &Store) -> String;
fn show(&mut self, ui: &mut egui::Ui, ctx: &mut PageCtx);
fn has_selection(&self, store: &Store) -> bool;
fn show_details(&mut self, ui: &mut egui::Ui, ctx: &mut PageCtx);
fn clear_selection(&mut self);
// Optional: search_hint, toolbar, watched_container
}