lightweight com.atproto.sync.listReposByCollection
45
fork

Configure Feed

Select the types of activity you want to include in your feed.

split admin into files

phil f3722049 3770bdc7

+88 -82
+4 -4
src/server/admin.rs src/server/admin/status.rs
··· 16 16 use crate::storage::{DbRef, backfill_progress, error::StorageError}; 17 17 use crate::sync::resync::dispatcher::{DispatcherSnapshot, DispatcherState}; 18 18 19 - use super::AdminConfig; 19 + use super::super::AdminConfig; 20 20 21 21 #[derive(Serialize)] 22 - pub(super) struct AdminStatus { 22 + pub(in crate::server) struct AdminStatus { 23 23 // ── Persistent counters (survive restarts) ──────────────────────────── 24 24 first_startup_ms: u64, 25 25 startup_count: u64, ··· 49 49 throttled_hosts: Option<HashMap<String, HashMap<String, u64>>>, 50 50 } 51 51 52 - pub(super) enum AdminStatusError { 52 + pub(in crate::server) enum AdminStatusError { 53 53 Unauthorized, 54 54 Storage, 55 55 } ··· 140 140 /// 141 141 /// Accepts any username. Uses constant-time byte comparison to avoid leaking 142 142 /// whether a guessed password shares a prefix with the real one. 143 - pub(super) fn check_basic_auth(headers: &HeaderMap, expected_password: &str) -> bool { 143 + pub(in crate::server) fn check_basic_auth(headers: &HeaderMap, expected_password: &str) -> bool { 144 144 let Some(auth) = headers.get(header::AUTHORIZATION) else { 145 145 return false; 146 146 };
+5
src/server/admin/mod.rs
··· 1 + mod page; 2 + pub(super) mod status; 3 + 4 + pub(super) use page::admin_page; 5 + pub(super) use status::admin_status;
+34
src/server/admin/page.css
··· 1 + *{box-sizing:border-box;margin:0;padding:0} 2 + body{font-family:system-ui,-apple-system,sans-serif;background:#f8f8f8;color:#222;padding:1.5rem;max-width:1100px;margin:0 auto} 3 + header{margin-bottom:1.5rem;display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:0.5rem} 4 + h1{font-size:1.4rem;font-weight:600;letter-spacing:-0.02em} 5 + .sub{color:#888;font-size:0.82rem} 6 + .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:1rem} 7 + .card{background:#fff;border-radius:6px;padding:1rem 1.25rem;border:1px solid #e8e8e8} 8 + .card h2{font-size:0.78rem;text-transform:uppercase;letter-spacing:0.06em;color:#999;margin-bottom:0.6rem;font-weight:600} 9 + .row{display:flex;justify-content:space-between;align-items:center;padding:0.25rem 0} 10 + .row .k{color:#666;font-size:0.88rem} 11 + .row .v{font-weight:600;font-variant-numeric:tabular-nums;font-size:0.88rem} 12 + .bar-row{margin:0.35rem 0} 13 + .bar-head{display:flex;justify-content:space-between;font-size:0.82rem;margin-bottom:3px} 14 + .bar-head .k{color:#666} 15 + .bar-head .v{font-weight:600;font-variant-numeric:tabular-nums} 16 + .track{height:5px;background:#eee;border-radius:3px;overflow:hidden} 17 + .fill{height:100%;border-radius:3px;transition:width 0.6s ease} 18 + table{width:100%;border-collapse:collapse;font-size:0.82rem;margin-top:0.25rem} 19 + th{text-align:left;font-weight:500;color:#999;padding:0.2rem 0.5rem 0.2rem 0;border-bottom:1px solid #eee;font-size:0.78rem} 20 + td{padding:0.2rem 0.5rem 0.2rem 0;border-bottom:1px solid #f5f5f5;font-family:ui-monospace,'SF Mono',monospace;font-size:0.8rem} 21 + td.num{text-align:right;font-family:system-ui,-apple-system,sans-serif;font-variant-numeric:tabular-nums} 22 + .badge{display:inline-block;padding:0.1rem 0.45rem;border-radius:3px;font-size:0.75rem;font-weight:600} 23 + .bg{background:#e8f5e9;color:#2e7d32} 24 + .by{background:#fff8e1;color:#f57f17} 25 + .dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:#ccc;margin-right:0.4rem;vertical-align:middle;transition:background 0.3s} 26 + .dot.on{background:#4caf50} 27 + .empty{color:#bbb;font-style:italic} 28 + .sec-head{margin-top:0.6rem;font-size:0.75rem;color:#999;text-transform:uppercase;letter-spacing:0.04em;padding-bottom:0.2rem;border-bottom:1px solid #eee} 29 + details summary{cursor:pointer;font-size:0.82rem;color:#555} 30 + details summary:hover{color:#222} 31 + details[open] summary{margin-bottom:0.3rem} 32 + details table{margin-top:0.15rem} 33 + .detail-paths{padding:0.2rem 0 0.2rem 1rem;font-size:0.78rem;color:#666} 34 + .detail-paths div{padding:0.1rem 0;font-family:ui-monospace,'SF Mono',monospace}
+41
src/server/admin/page.rs
··· 1 + use axum::{Extension, http::HeaderMap, response::Html}; 2 + 3 + use super::super::AdminConfig; 4 + use super::status::{AdminStatusError, check_basic_auth}; 5 + 6 + /// `GET /admin` — HTML dashboard that polls `/admin/status` for live stats. 7 + /// 8 + /// Protected by the same HTTP Basic Auth as the JSON endpoint. 9 + pub async fn admin_page( 10 + Extension(config): Extension<AdminConfig>, 11 + headers: HeaderMap, 12 + ) -> Result<Html<&'static str>, AdminStatusError> { 13 + if !check_basic_auth(&headers, &config.admin_password) { 14 + return Err(AdminStatusError::Unauthorized); 15 + } 16 + Ok(Html(PAGE)) 17 + } 18 + 19 + const PAGE: &str = concat!( 20 + "<!DOCTYPE html>\n\ 21 + <html lang=\"en\">\n\ 22 + <head>\n\ 23 + <meta charset=\"utf-8\">\n\ 24 + <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\ 25 + <title>lightrail admin</title>\n\ 26 + <style>\n", 27 + include_str!("page.css"), 28 + "\n</style>\n\ 29 + </head>\n\ 30 + <body>\n\ 31 + <header>\n\ 32 + <div><h1>lightrail</h1><div class=\"sub\" id=\"info\"></div></div>\n\ 33 + <div class=\"sub\"><span class=\"dot\" id=\"dot\"></span><span id=\"ts\">loading&hellip;</span></div>\n\ 34 + </header>\n\ 35 + <div class=\"grid\" id=\"g\"></div>\n\ 36 + <script>\n", 37 + include_str!("page.js"), 38 + "\n</script>\n\ 39 + </body>\n\ 40 + </html>\n", 41 + );
+3 -75
src/server/admin_page.rs src/server/admin/page.js
··· 1 - use axum::{Extension, http::HeaderMap, response::Html}; 2 - 3 - use super::AdminConfig; 4 - use super::admin::{AdminStatusError, check_basic_auth}; 5 - 6 - /// `GET /admin` — HTML dashboard that polls `/admin/status` for live stats. 7 - /// 8 - /// Protected by the same HTTP Basic Auth as the JSON endpoint. 9 - pub async fn admin_page( 10 - Extension(config): Extension<AdminConfig>, 11 - headers: HeaderMap, 12 - ) -> Result<Html<&'static str>, AdminStatusError> { 13 - if !check_basic_auth(&headers, &config.admin_password) { 14 - return Err(AdminStatusError::Unauthorized); 15 - } 16 - Ok(Html(PAGE)) 17 - } 18 - 19 - const PAGE: &str = r##"<!DOCTYPE html> 20 - <html lang="en"> 21 - <head> 22 - <meta charset="utf-8"> 23 - <meta name="viewport" content="width=device-width, initial-scale=1"> 24 - <title>lightrail admin</title> 25 - <style> 26 - *{box-sizing:border-box;margin:0;padding:0} 27 - body{font-family:system-ui,-apple-system,sans-serif;background:#f8f8f8;color:#222;padding:1.5rem;max-width:1100px;margin:0 auto} 28 - header{margin-bottom:1.5rem;display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:0.5rem} 29 - h1{font-size:1.4rem;font-weight:600;letter-spacing:-0.02em} 30 - .sub{color:#888;font-size:0.82rem} 31 - .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:1rem} 32 - .card{background:#fff;border-radius:6px;padding:1rem 1.25rem;border:1px solid #e8e8e8} 33 - .card h2{font-size:0.78rem;text-transform:uppercase;letter-spacing:0.06em;color:#999;margin-bottom:0.6rem;font-weight:600} 34 - .row{display:flex;justify-content:space-between;align-items:center;padding:0.25rem 0} 35 - .row .k{color:#666;font-size:0.88rem} 36 - .row .v{font-weight:600;font-variant-numeric:tabular-nums;font-size:0.88rem} 37 - .bar-row{margin:0.35rem 0} 38 - .bar-head{display:flex;justify-content:space-between;font-size:0.82rem;margin-bottom:3px} 39 - .bar-head .k{color:#666} 40 - .bar-head .v{font-weight:600;font-variant-numeric:tabular-nums} 41 - .track{height:5px;background:#eee;border-radius:3px;overflow:hidden} 42 - .fill{height:100%;border-radius:3px;transition:width 0.6s ease} 43 - table{width:100%;border-collapse:collapse;font-size:0.82rem;margin-top:0.25rem} 44 - th{text-align:left;font-weight:500;color:#999;padding:0.2rem 0.5rem 0.2rem 0;border-bottom:1px solid #eee;font-size:0.78rem} 45 - td{padding:0.2rem 0.5rem 0.2rem 0;border-bottom:1px solid #f5f5f5;font-family:ui-monospace,'SF Mono',monospace;font-size:0.8rem} 46 - td.num{text-align:right;font-family:system-ui,-apple-system,sans-serif;font-variant-numeric:tabular-nums} 47 - .badge{display:inline-block;padding:0.1rem 0.45rem;border-radius:3px;font-size:0.75rem;font-weight:600} 48 - .bg{background:#e8f5e9;color:#2e7d32} 49 - .by{background:#fff8e1;color:#f57f17} 50 - .dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:#ccc;margin-right:0.4rem;vertical-align:middle;transition:background 0.3s} 51 - .dot.on{background:#4caf50} 52 - .empty{color:#bbb;font-style:italic} 53 - .sec-head{margin-top:0.6rem;font-size:0.75rem;color:#999;text-transform:uppercase;letter-spacing:0.04em;padding-bottom:0.2rem;border-bottom:1px solid #eee} 54 - details summary{cursor:pointer;font-size:0.82rem;color:#555} 55 - details summary:hover{color:#222} 56 - details[open] summary{margin-bottom:0.3rem} 57 - details table{margin-top:0.15rem} 58 - .detail-paths{padding:0.2rem 0 0.2rem 1rem;font-size:0.78rem;color:#666} 59 - .detail-paths div{padding:0.1rem 0;font-family:ui-monospace,'SF Mono',monospace} 60 - </style> 61 - </head> 62 - <body> 63 - <header> 64 - <div><h1>lightrail</h1><div class="sub" id="info"></div></div> 65 - <div class="sub"><span class="dot" id="dot"></span><span id="ts">loading&hellip;</span></div> 66 - </header> 67 - <div class="grid" id="g"></div> 68 - <script> 69 1 var $=function(s){return document.getElementById(s)}; 70 2 var N=function(n){return typeof n==='number'?n.toLocaleString():'\u2014'}; 71 3 ··· 129 61 +(function(){ 130 62 var fhMax=Math.max(d.distinct_accounts_commit_strict,d.distinct_accounts_commit_lenient,d.distinct_accounts_desynced,1); 131 63 var fhPct=function(v){return Math.min(100,v/fhMax*100)}; 132 - return barRow('Events processed (strict)',d.distinct_accounts_commit_strict,fhPct(d.distinct_accounts_commit_strict),'#4a90d9') 133 - +barRow('Events processed (lenient)',d.distinct_accounts_commit_lenient,fhPct(d.distinct_accounts_commit_lenient),'#ffb74d') 134 - +barRow('Became desynced',d.distinct_accounts_desynced,fhPct(d.distinct_accounts_desynced),'#e57373'); 64 + return barRow('with events processed (strict)',d.distinct_accounts_commit_strict,fhPct(d.distinct_accounts_commit_strict),'#4a90d9') 65 + +barRow('with events processed (lenient)',d.distinct_accounts_commit_lenient,fhPct(d.distinct_accounts_commit_lenient),'#ffb74d') 66 + +barRow('became desynced',d.distinct_accounts_desynced,fhPct(d.distinct_accounts_desynced),'#e57373'); 135 67 })() 136 68 +row('PDS hosts seen (firehose)','~'+N(d.distinct_pds_hosts)) 137 69 ); ··· 216 148 217 149 poll(); 218 150 setInterval(poll,5000); 219 - </script> 220 - </body> 221 - </html> 222 - "##;
+1 -3
src/server/mod.rs
··· 4 4 //! `IntoRouter` helper. 5 5 6 6 mod admin; 7 - mod admin_page; 8 7 mod get_repo_status; 9 8 mod hello; 10 9 mod list_repos; 11 10 mod list_repos_by_collection; 12 11 13 - use admin::admin_status; 14 - use admin_page::admin_page; 12 + use admin::{admin_page, admin_status}; 15 13 use get_repo_status::get_repo_status; 16 14 use list_repos::list_repos; 17 15 use list_repos_by_collection::list_repos_by_collection;