Web dashboard step 6: jobs and stats

Job catalogue and daily-epub job run, the systemd job unit and polkit rule,
SystemdRunner/MockRunner, the Jobs pages with status and journal tail, the
stats_data refactor with a byte-identical CLI, the stats page and the
overview sparklines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
This commit is contained in:
2026-09-03 18:01:21 +00:00
co-authored by Claude Fable 5.1
parent d36f203e4c
commit aa3de51d9c
21 changed files with 2958 additions and 112 deletions
+27 -1
View File
@@ -101,6 +101,23 @@ impl AppState {
}
}
/// `new`, with a specific [`crate::web::JobRunner`]: `SystemdRunner` in
/// `serve`, a `MockRunner` in router tests (dashboard plan §14.4).
pub fn with_jobs(
db: Db,
config: Config,
config_path: Option<PathBuf>,
jobs: Arc<dyn crate::web::JobRunner>,
) -> Self {
let mut state = Self::new(db, config, config_path);
state.web = Arc::new(crate::web::WebState {
jobs,
started_at: Timestamp::now(),
config_mtime: std::sync::Mutex::new(None),
});
state
}
pub fn config(&self) -> Arc<Config> {
match self.config.read() {
Ok(config) => Arc::clone(&config),
@@ -199,7 +216,16 @@ pub async fn serve(
}
});
let app = router(AppState::new(db, config, config_path));
// The Jobs page starts `daily-epub-job@<name>.service` through systemd +
// polkit (§14); `server.jobs_enabled = false` swaps in the runner whose
// page says so.
let jobs: Arc<dyn crate::web::JobRunner> = if config.server.jobs_enabled {
Arc::new(crate::jobs::SystemdRunner::default())
} else {
tracing::info!("server.jobs_enabled is false; the Jobs page cannot start units");
Arc::new(crate::web::DisabledRunner)
};
let app = router(AppState::with_jobs(db, config, config_path, jobs));
axum::serve(
listener,
app.into_make_service_with_connect_info::<std::net::SocketAddr>(),