dashboard: show when an approval was requested (closes #272)
This commit is contained in:
parent
908cadb151
commit
a9a10b631f
4 changed files with 40 additions and 2 deletions
|
|
@ -208,8 +208,11 @@ agent is stale. Banner pulses on each broker SSE event
|
||||||
Each pending approval renders as a card (`assets/app.js::
|
Each pending approval renders as a card (`assets/app.js::
|
||||||
renderApprovals`) with three stacked sections:
|
renderApprovals`) with three stacked sections:
|
||||||
|
|
||||||
- **identity header** — glyph, `#id`, agent, kind chip, and (for
|
- **identity header** — glyph, `#id`, agent, kind chip, (for
|
||||||
`apply_commit`) the short proposal sha as `<code>`.
|
`apply_commit`) the short proposal sha as `<code>`, and a
|
||||||
|
right-aligned `requested <N> ago` relative time from
|
||||||
|
`ApprovalView.requested_at` — amber once the request has been
|
||||||
|
pending ≥ 1h so a stale approval stands out (issue #272).
|
||||||
- **what-changed body** — the manager's description, then
|
- **what-changed body** — the manager's description, then
|
||||||
drill-in triggers: `↳ view diff` opens the diff in the side
|
drill-in triggers: `↳ view diff` opens the diff in the side
|
||||||
panel; `↳ commit on forge ↗` deep-links the proposal commit
|
panel; `↳ commit on forge ↗` deep-links the proposal commit
|
||||||
|
|
|
||||||
|
|
@ -1193,6 +1193,12 @@
|
||||||
sha_short: ev.sha_short || null,
|
sha_short: ev.sha_short || null,
|
||||||
diff: ev.diff || null,
|
diff: ev.diff || null,
|
||||||
description: ev.description || null,
|
description: ev.description || null,
|
||||||
|
// The ApprovalAdded event carries no requested_at; a live-added
|
||||||
|
// approval was queued just now, so client-now is accurate — and
|
||||||
|
// consistent with how fmtAgo compares everything to client-now.
|
||||||
|
// A later /api/state cold-load swaps in the server value. (#272)
|
||||||
|
requested_at: ev.requested_at != null
|
||||||
|
? ev.requested_at : Math.floor(Date.now() / 1000),
|
||||||
};
|
};
|
||||||
if (existing >= 0) approvalsState.pending[existing] = row;
|
if (existing >= 0) approvalsState.pending[existing] = row;
|
||||||
else approvalsState.pending.push(row);
|
else approvalsState.pending.push(row);
|
||||||
|
|
@ -1364,6 +1370,16 @@
|
||||||
isApply ? 'apply' : isInit ? 'init' : 'spawn'),
|
isApply ? 'apply' : isInit ? 'init' : 'spawn'),
|
||||||
);
|
);
|
||||||
if (isApply && a.sha_short) head.append(el('code', {}, a.sha_short));
|
if (isApply && a.sha_short) head.append(el('code', {}, a.sha_short));
|
||||||
|
// When the approval was requested — relative time, right-aligned.
|
||||||
|
// Goes amber once it's been pending an hour so a stale request is
|
||||||
|
// obvious at a glance. (issue #272)
|
||||||
|
if (a.requested_at != null) {
|
||||||
|
const ageSec = Math.max(0, Math.floor(Date.now() / 1000 - a.requested_at));
|
||||||
|
head.append(el('span', {
|
||||||
|
class: 'approval-ts' + (ageSec >= 3600 ? ' stale' : ''),
|
||||||
|
title: 'requested ' + new Date(a.requested_at * 1000).toLocaleString(),
|
||||||
|
}, 'requested ' + fmtAgo(a.requested_at)));
|
||||||
|
}
|
||||||
li.append(head);
|
li.append(head);
|
||||||
|
|
||||||
// ── what-changed body ────────────────────────────────────────
|
// ── what-changed body ────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,18 @@ code {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.3em;
|
gap: 0.3em;
|
||||||
}
|
}
|
||||||
|
/* When the approval was requested — right-aligned in the head row;
|
||||||
|
goes amber once it has been pending ≥ 1h so a stale request stands
|
||||||
|
out at a glance (issue #272). */
|
||||||
|
.approval-ts {
|
||||||
|
margin-left: auto;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
.approval-ts.stale {
|
||||||
|
color: var(--amber);
|
||||||
|
text-shadow: 0 0 6px rgba(250, 179, 135, 0.5);
|
||||||
|
}
|
||||||
.approval-body {
|
.approval-body {
|
||||||
margin: 0.45em 0;
|
margin: 0.45em 0;
|
||||||
padding-left: 1.3em;
|
padding-left: 1.3em;
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,9 @@ struct ApprovalView {
|
||||||
/// Manager-supplied description shown on the approval card.
|
/// Manager-supplied description shown on the approval card.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
/// Unix seconds the approval was queued. Rendered as a relative
|
||||||
|
/// time on the card so the operator can spot a stale request. (#272)
|
||||||
|
requested_at: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace silent `.unwrap_or_default()` on the data sources behind
|
/// Replace silent `.unwrap_or_default()` on the data sources behind
|
||||||
|
|
@ -606,6 +609,7 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
|
||||||
sha_short: Some(sha),
|
sha_short: Some(sha),
|
||||||
diff: Some(diff),
|
diff: Some(diff),
|
||||||
description: a.description,
|
description: a.description,
|
||||||
|
requested_at: a.requested_at,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hive_sh4re::ApprovalKind::Spawn => ApprovalView {
|
hive_sh4re::ApprovalKind::Spawn => ApprovalView {
|
||||||
|
|
@ -615,6 +619,7 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
|
||||||
sha_short: None,
|
sha_short: None,
|
||||||
diff: None,
|
diff: None,
|
||||||
description: a.description,
|
description: a.description,
|
||||||
|
requested_at: a.requested_at,
|
||||||
},
|
},
|
||||||
hive_sh4re::ApprovalKind::InitConfig => ApprovalView {
|
hive_sh4re::ApprovalKind::InitConfig => ApprovalView {
|
||||||
id: a.id,
|
id: a.id,
|
||||||
|
|
@ -623,6 +628,7 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
|
||||||
sha_short: None,
|
sha_short: None,
|
||||||
diff: None,
|
diff: None,
|
||||||
description: a.description,
|
description: a.description,
|
||||||
|
requested_at: a.requested_at,
|
||||||
},
|
},
|
||||||
hive_sh4re::ApprovalKind::UpdateMetaInputs => ApprovalView {
|
hive_sh4re::ApprovalKind::UpdateMetaInputs => ApprovalView {
|
||||||
id: a.id,
|
id: a.id,
|
||||||
|
|
@ -631,6 +637,7 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
|
||||||
sha_short: None,
|
sha_short: None,
|
||||||
diff: None,
|
diff: None,
|
||||||
description: a.description,
|
description: a.description,
|
||||||
|
requested_at: a.requested_at,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue