| 1 | //! Admin-only dashboard pages (site-level), gated by `User.is_admin`. |
| 2 | |
| 3 | use anvil_core::{ |
| 4 | App, |
| 5 | usage, |
| 6 | }; |
| 7 | use axum::{ |
| 8 | Router, |
| 9 | extract::State, |
| 10 | response::{ |
| 11 | IntoResponse, |
| 12 | Response, |
| 13 | }, |
| 14 | routing::get, |
| 15 | }; |
| 16 | use maud::{ |
| 17 | Markup, |
| 18 | html, |
| 19 | }; |
| 20 | |
| 21 | use crate::{ |
| 22 | auth::CurrentUser, |
| 23 | ui::{ |
| 24 | fmt_size, |
| 25 | layout, |
| 26 | not_found, |
| 27 | server_error, |
| 28 | }, |
| 29 | }; |
| 30 | |
| 31 | pub 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. |
| 38 | async 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 | |
| 49 | fn 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 | } |