anvilsign in

collin/anvil

1//! Admin-only dashboard pages (site-level), gated by `User.is_admin`.
2
3use anvil_core::{
4 App,
5 usage,
6};
7use axum::{
8 Router,
9 extract::State,
10 response::{
11 IntoResponse,
12 Response,
13 },
14 routing::get,
15};
16use maud::{
17 Markup,
18 html,
19};
20
21use crate::{
22 auth::CurrentUser,
23 ui::{
24 fmt_size,
25 layout,
26 not_found,
27 server_error,
28 },
29};
30
31pub fn routes(router: Router<App>) -> Router<App> {
32 router.route("/-/admin/usage", get(usage_page))
33}
34
35/// `GET /-/admin/usage` — site-wide disk usage by user and content type.
36/// Non-admins (including anonymous) get a 404, so the page's existence isn't
37/// leaked.
38async fn usage_page(State(app): State<App>, CurrentUser(user): CurrentUser) -> Response {
39 if !user.as_ref().is_some_and(|u| u.is_admin) {
40 return not_found("not found");
41 }
42 let data = match usage::compute(&app).await {
43 Ok(d) => d,
44 Err(e) => return server_error(e),
45 };
46 layout("Disk usage", user.as_ref(), render(&data)).into_response()
47}
48
49fn render(u: &usage::Usage) -> Markup {
50 html! {
51 h1 { "Disk usage" }
52 p.muted {
53 "Actual on-disk bytes per user, by content type — "
54 (fmt_size(u.total() as i64)) " total across the instance."
55 }
56 table.usage {
57 thead { tr {
58 th { "User" }
59 th.num { "Repositories" }
60 th.num { "CI artifacts" }
61 th.num { "Attachments" }
62 th.num { "Total" }
63 } }
64 tbody {
65 @for row in &u.per_user {
66 tr {
67 td { (row.username) }
68 td.num { (fmt_size(row.git as i64)) }
69 td.num { (fmt_size(row.artifacts as i64)) }
70 td.num { (fmt_size(row.attachments as i64)) }
71 td.num { (fmt_size(row.total() as i64)) }
72 }
73 }
74 }
75 tfoot { tr {
76 td { "All users" }
77 td.num { (fmt_size(u.git as i64)) }
78 td.num { (fmt_size(u.artifacts as i64)) }
79 td.num { (fmt_size(u.attachments as i64)) }
80 td.num { (fmt_size(u.total() as i64)) }
81 } }
82 }
83 }
84}