dashboard frontend: consume rfc3339 timestamps from the api

This commit is contained in:
damocles 2026-07-02 21:35:03 +02:00 committed by mara
commit cafef519a9
5 changed files with 46 additions and 33 deletions

View file

@ -16,7 +16,7 @@
import { $, el, form, Panel, appendLinkified } from './common.js';
import { themedToast } from './modal.js';
import { fmtAgo, fmtDuration } from './util.js';
import { epochSec, fmtAgo, fmtDuration } from './util.js';
import { questionsState, QUESTION_HISTORY_LIMIT } from './state.js';
// Registered by the dashboard entry at boot; defaults to a no-op so the
@ -70,7 +70,7 @@ function renderOperatorInbox() {
`✓ mark all read (${operatorInbox.length})`);
mark.addEventListener('click', markOperatorInboxRead);
root.append(el('div', { class: 'inbox-toolbar' }, mark));
const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19);
const fmt = (ts) => new Date(ts).toISOString().replace('T', ' ').slice(0, 19);
const ul = el('ul', { class: 'inbox' });
for (const m of operatorInbox) {
const body = el('span', { class: 'msg-body' });
@ -333,11 +333,12 @@ export function renderApprovals() {
// Goes amber once it's been pending an hour so a stale request is
// obvious at a glance (see docs/web-ui.md::Approval card).
if (a.requested_at != null) {
const ageSec = Math.max(0, Math.floor(Date.now() / 1000 - a.requested_at));
const requestedSec = epochSec(a.requested_at);
const ageSec = Math.max(0, Math.floor(Date.now() / 1000 - requestedSec));
head.append(el('span', {
class: 'approval-ts' + (ageSec >= 3600 ? ' stale' : ''),
title: 'requested ' + new Date(a.requested_at * 1000).toLocaleString(),
'data-requested-at': String(a.requested_at),
title: 'requested ' + new Date(a.requested_at).toLocaleString(),
'data-requested-at': String(requestedSec),
}, 'requested ' + fmtAgo(a.requested_at)));
}
li.append(head);
@ -556,7 +557,7 @@ function questionRowFingerprint(q) {
// Event listeners attached here (keydown on textarea, submit on form) are
// preserved in the reused node — no re-attachment needed.
function buildQuestionLi(q) {
const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19);
const fmt = (ts) => new Date(ts).toISOString().replace('T', ' ').slice(0, 19);
const targetLabel = q.target || 'operator';
const li = el('li', { class: 'question' + (q.target ? ' question-peer' : '') });
const head = el('div', { class: 'q-head' },
@ -570,10 +571,10 @@ function buildQuestionLi(q) {
// Tag the chip with its deadline so the global 1s ticker
// can refresh the text without re-rendering the questions section.
const ttlEl = el('span', {
class: 'q-ttl', 'data-deadline': String(q.deadline_at),
class: 'q-ttl', 'data-deadline': String(epochSec(q.deadline_at)),
});
ttlEl.textContent = formatTtl(
q.deadline_at - Math.floor(Date.now() / 1000),
epochSec(q.deadline_at) - Math.floor(Date.now() / 1000),
);
head.append(' ', ttlEl);
}
@ -694,7 +695,7 @@ export function renderQuestions() {
// <li> nodes (preserving textarea/checkbox state) and only rebuilds
// cache-miss rows, so we no longer wipe the DOM at the start.
const openDetails = snapshotOpenDetails(root);
const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19);
const fmt = (ts) => new Date(ts).toISOString().replace('T', ' ').slice(0, 19);
const allPending = questionsState.pending;
// Filter chips. Always include `all` / `operator` / `peer`; add