Web dashboard: dashboard module scaffold for parallel steps

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 04:59:29 +00:00
co-authored by Claude Fable 5.1
parent 4995a8bd5d
commit 849231e49a
11 changed files with 158 additions and 15 deletions
+10
View File
@@ -0,0 +1,10 @@
//! Dashboard: articles pages. Filled in by web dashboard plan step 3.
use axum::Router;
use crate::server::AppState;
/// Routes contributed by this page group (merged by `dashboard::router`).
pub fn routes() -> Router<AppState> {
Router::new()
}
+10
View File
@@ -0,0 +1,10 @@
//! Dashboard: jobs pages. Filled in by web dashboard plan step 6.
use axum::Router;
use crate::server::AppState;
/// Routes contributed by this page group (merged by `dashboard::router`).
pub fn routes() -> Router<AppState> {
Router::new()
}
+53
View File
@@ -0,0 +1,53 @@
//! The operator dashboard (`/dashboard/*`, dashboard plan §9–§14).
//!
//! Every route here sits under the admin `permission_required!` layer that
//! `web::router` applies to the merged router; handlers can therefore trust
//! that `AuthSession::user()` is an admin. One submodule per page group; each
//! exposes `routes()` and this module merges them.
pub mod articles;
pub mod jobs;
pub mod profile;
pub mod ratings;
pub mod runs;
pub mod settings;
pub mod stats;
pub mod users;
use askama::Template;
use axum::Router;
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use crate::server::AppState;
use crate::web::session::{AuthSession, Viewer};
use crate::web::{Html, Page, WebError};
/// Every dashboard route, without the admin layer (applied by the caller).
pub fn router() -> Router<AppState> {
Router::new()
.route("/dashboard", get(overview))
.merge(runs::routes())
.merge(articles::routes())
.merge(ratings::routes())
.merge(profile::routes())
.merge(settings::routes())
.merge(jobs::routes())
.merge(stats::routes())
.merge(users::routes())
}
#[derive(Template)]
#[template(path = "dashboard/overview.html")]
struct OverviewTemplate {
page: Page,
}
/// `GET /dashboard` — the overview (§9.1). Step 3 fills this in.
async fn overview(auth: AuthSession) -> Result<Response, WebError> {
let viewer = auth.user().await.map(Viewer::from);
Ok(Html(OverviewTemplate {
page: Page::new("Overview", viewer, "dashboard"),
})
.into_response())
}
+10
View File
@@ -0,0 +1,10 @@
//! Dashboard: profile pages. Filled in by web dashboard plan step 4.
use axum::Router;
use crate::server::AppState;
/// Routes contributed by this page group (merged by `dashboard::router`).
pub fn routes() -> Router<AppState> {
Router::new()
}
+10
View File
@@ -0,0 +1,10 @@
//! Dashboard: ratings pages. Filled in by web dashboard plan step 4.
use axum::Router;
use crate::server::AppState;
/// Routes contributed by this page group (merged by `dashboard::router`).
pub fn routes() -> Router<AppState> {
Router::new()
}
+10
View File
@@ -0,0 +1,10 @@
//! Dashboard: runs pages. Filled in by web dashboard plan step 3.
use axum::Router;
use crate::server::AppState;
/// Routes contributed by this page group (merged by `dashboard::router`).
pub fn routes() -> Router<AppState> {
Router::new()
}
+10
View File
@@ -0,0 +1,10 @@
//! Dashboard: settings pages. Filled in by web dashboard plan step 5.
use axum::Router;
use crate::server::AppState;
/// Routes contributed by this page group (merged by `dashboard::router`).
pub fn routes() -> Router<AppState> {
Router::new()
}
+10
View File
@@ -0,0 +1,10 @@
//! Dashboard: stats pages. Filled in by web dashboard plan step 6.
use axum::Router;
use crate::server::AppState;
/// Routes contributed by this page group (merged by `dashboard::router`).
pub fn routes() -> Router<AppState> {
Router::new()
}
+10
View File
@@ -0,0 +1,10 @@
//! Dashboard: users pages. Filled in by web dashboard plan step 7.
use axum::Router;
use crate::server::AppState;
/// Routes contributed by this page group (merged by `dashboard::router`).
pub fn routes() -> Router<AppState> {
Router::new()
}
+2 -15
View File
@@ -1,3 +1,4 @@
pub mod dashboard;
pub mod issue;
pub mod public;
pub mod rate;
@@ -367,7 +368,7 @@ pub fn router(config: &crate::config::Config) -> axum::Router<crate::server::App
))
.route_layer(from_fn(map_forbidden));
let dashboard = axum::Router::new()
.route("/dashboard", get(dashboard_stub))
.merge(dashboard::router())
.route("/rate", post(rate::post))
.route_layer(permission_required!(
session::Backend,
@@ -390,20 +391,6 @@ pub fn router(config: &crate::config::Config) -> axum::Router<crate::server::App
.merge(dashboard)
}
#[derive(Template)]
#[template(path = "dashboard/overview.html")]
struct OverviewTemplate {
page: Page,
}
async fn dashboard_stub(auth: session::AuthSession) -> Result<Response, WebError> {
let viewer = auth.user().await.map(session::Viewer::from);
Ok(Html(OverviewTemplate {
page: Page::new("Overview", viewer, "dashboard"),
})
.into_response())
}
async fn map_forbidden(request: Request, next: Next) -> Response {
let mut response = next.run(request).await;
if response.status() == StatusCode::FORBIDDEN {