git-ents.gitmain
⌘K
foforge
commit e7ea7e4
feat: list authorized signers on the repository Settings tab

The Settings page now reads the refs/meta/auth signer set and renders it as an Authorized signers card, mirroring git ents auth list.

Assisted-by: Claude:claude-opus-4-8

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

crates/git-ents-server/src/web/mod.rs @@ -73,7 +73,7 @@ Some((&"releases", &[])) => pages::releases_page(repo, &meta).await.into_response(), Some((&"checks", &[])) => pages::checks_page(&meta).into_response(), Some((&"issues", &[])) => pages::issues_page(&meta).into_response(), - Some((&"settings", &[])) => pages::settings_page(&meta).into_response(), + Some((&"settings", &[])) => pages::settings_page(repo, &meta).await.into_response(), _ => not_found().into_response(), } }
crates/git-ents-server/src/web/pages.rs @@ -703,8 +703,9 @@ /// The Settings tab. Persisting changes needs a config store that does not /// exist yet, so the controls reflect the repository's current real values and /// are presented read-only. -pub(super) fn settings_page(meta: &RepoMeta) -> Markup { +pub(super) async fn settings_page(repo: &Path, meta: &RepoMeta) -> Markup { let name = meta.name(); + let signers = load_signers(repo).await; repo_shell( meta, Tab::Settings, @@ -738,6 +739,33 @@ (feature_row("Wiki", "A separate documentation space.", false)) } + div.card { + div.card-header { + "Authorized signers" + @if let Ok(signers) = &signers { span.count { (signers.len()) } } + } + p.shell-note { + "Keys on " code { "refs/meta/auth" } " whose signed pushes are accepted " + "(" code { "git ents auth list" } ")." + } + @match &signers { + Err(err) => div.card-row.muted { "Could not read signers: " (err) } + Ok(signers) if signers.is_empty() => { + div.card-row.muted { + "No authorized signers — pushes are open until the first key is added." + } + } + Ok(signers) => { + @for signer in signers { + div.card-row { + code { (signer.fingerprint) } + span.muted { (signer_label(&signer.key)) } + } + } + } + } + } + div.card { div.card-header { "Visibility" } (visibility_row("Public", "Anyone can read this repository.", true)) @@ -766,6 +794,29 @@ ) } +/// Load the authorized signer set off the async runtime, since `signers::load` +/// shells out to git and reads the object database synchronously. +async fn load_signers(repo: &Path) -> Result<Vec<git_ents::signers::Signer>, String> { + let repo = repo.to_owned(); + tokio::task::spawn_blocking(move || git_ents::signers::load(&repo)) + .await + .map_err(|err| err.to_string())? + .map_err(|err| err.to_string()) +} + +/// A short label for a signer's key: its type and trailing comment, dropping the +/// long base64 body that would not fit on the row. +fn signer_label(key: &str) -> String { + let mut parts = key.split_whitespace(); + let kind = parts.next().unwrap_or_default(); + let comment = parts.nth(1).unwrap_or_default(); + if comment.is_empty() { + kind.to_owned() + } else { + format!("{kind} · {comment}") + } +} + /// A Features row with a static toggle reflecting `on`. fn feature_row(title: &str, desc: &str, on: bool) -> Markup { html! {