Show full GlobalConfig on /config page with secrets masked
Adds Display for SecretString (always renders "[secret]"), then
rewrites the config handler to produce a flat key-value row list
covering every field in GlobalConfig. Secret values are never
exposed — only their presence and key name.
diff --git a/quire-core/src/secret.rs b/quire-core/src/secret.rs
index ee604de..f1a6de5 100644
--- a/quire-core/src/secret.rs
+++ b/quire-core/src/secret.rs
@@ -79,6 +79,12 @@ impl std::fmt::Debug for SecretString {
}
}
+impl std::fmt::Display for SecretString {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("[secret]")
+ }
+}
+
impl From<String> for SecretString {
fn from(value: String) -> Self {
Self(SecretSource::Plain(value))
diff --git a/quire-server/src/quire/web/handlers.rs b/quire-server/src/quire/web/handlers.rs
index 58fc415..a1addd1 100644
--- a/quire-server/src/quire/web/handlers.rs
+++ b/quire-server/src/quire/web/handlers.rs
@@ -282,16 +282,36 @@ pub async fn run_detail(
pub async fn config(State(quire): State<Quire>) -> Response {
let cfg = &quire.config;
- let mut secret_names: Vec<String> = cfg.secrets.keys().cloned().collect();
- secret_names.sort();
+ let mut rows: Vec<(String, String)> = Vec::new();
+
+ rows.push(("port".into(), cfg.port.to_string()));
+ rows.push((
+ "ci.executor".into(),
+ format!("{:?}", cfg.ci.executor).to_lowercase(),
+ ));
+
+ match &cfg.sentry {
+ Some(s) => rows.push(("sentry.dsn".into(), s.dsn.to_string())),
+ None => rows.push(("sentry".into(), "disabled".into())),
+ }
+
+ match &cfg.github.mirror_token {
+ Some(t) => rows.push(("github.mirror-token".into(), t.to_string())),
+ None => rows.push(("github.mirror-token".into(), "not set".into())),
+ }
+
+ let mut secret_keys: Vec<&String> = cfg.secrets.keys().collect();
+ secret_keys.sort();
+ for key in secret_keys {
+ rows.push((
+ format!("secrets.{key}"),
+ cfg.secrets[key].to_string(),
+ ));
+ }
let tmpl = ConfigTemplate {
crumbs: vec![Crumb::new("config")],
- port: cfg.port,
- sentry_enabled: cfg.sentry.is_some(),
- secret_names,
- executor: format!("{:?}", cfg.ci.executor).to_lowercase(),
- github_mirror_token: cfg.github.mirror_token.is_some(),
+ rows,
};
render(&tmpl)
}
diff --git a/quire-server/src/quire/web/templates.rs b/quire-server/src/quire/web/templates.rs
index 4eca0f3..f145331 100644
--- a/quire-server/src/quire/web/templates.rs
+++ b/quire-server/src/quire/web/templates.rs
@@ -233,11 +233,7 @@ impl DetailShEvent {
#[template(path = "config.html")]
pub struct ConfigTemplate {
pub crumbs: Vec<Crumb>,
- pub port: u16,
- pub sentry_enabled: bool,
- pub secret_names: Vec<String>,
- pub executor: String,
- pub github_mirror_token: bool,
+ pub rows: Vec<(String, String)>,
}
impl ConfigTemplate {
diff --git a/quire-server/templates/config.html b/quire-server/templates/config.html
index 5f2ca87..b18f46c 100644
--- a/quire-server/templates/config.html
+++ b/quire-server/templates/config.html
@@ -31,32 +31,12 @@
<h2 class="ci-heading">config</h2>
<table class="ci-table">
<tbody>
+ {% for (key, value) in rows %}
<tr>
- <th>port</th>
- <td>{{ port }}</td>
- </tr>
- <tr>
- <th>executor</th>
- <td>{{ executor }}</td>
- </tr>
- <tr>
- <th>sentry</th>
- <td>{% if sentry_enabled %}enabled{% else %}disabled{% endif %}</td>
- </tr>
- <tr>
- <th>github mirror token</th>
- <td>{% if github_mirror_token %}set{% else %}not set{% endif %}</td>
- </tr>
- <tr>
- <th>secrets</th>
- <td>
- {% if secret_names.is_empty() %}
- <span class="empty">none</span>
- {% else %}
- {% for name in secret_names %}{{ name }}{% if !loop.last %}, {% endif %}{% endfor %}
- {% endif %}
- </td>
+ <th>{{ key }}</th>
+ <td>{{ value }}</td>
</tr>
+ {% endfor %}
</tbody>
</table>
{% endblock %}