fix: bring the admin pages in line with the rest of the app

- Overview: error-volume bars render (the fills were inline spans), the
  cumulative users chart is scaled to its range and labels it, latencies
  over a second read in seconds, and charts and daily numbers use the
  accent instead of four unrelated colours.
- Labels, table headers and state chips drop the mono caps; queue stat
  values drop the serif.
- Error rows keep their content aligned; row metadata wraps instead of
  truncating every item; "1 term" is singular.
- Errors filter uses the shared segmented control; breadcrumbs include
  Admin; user detail spaces its status and actions, uses the accent for
  Generate and says when no access token is stored.
- The jobs table scrolls inside its card on phones.
- Mock server: admin fixtures for every admin endpoint (ADMIN=1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
tdurieuxandClaude Opus 5.5 committed 2026-10-08 14:46:03 +02:00
1 parent cfd7b73de4
commit 80944954d6
11 files changed
+556 -61

No files matched your search

+20 -1
View File
@@ -1738,9 +1738,14 @@ export const overviewAdminController = function (state, http, location, interval
state.performanceMinutes = 15;
state.performanceTab = "routes";
state.performanceError = null;
// 18400 ms reads better as 18.4 s once a value passes one second.
state.formatMs = function (ms) {
if (ms == null) return "";
return ms < 1000 ? ms + " ms" : +(ms / 1000).toFixed(1) + " s";
};
state.latencyBound = function (row, field) {
if (!row.count) return "No samples";
return row[field] == null ? "> 60 s" : "\u2264 " + row[field] + " ms";
return row[field] == null ? "> 60 s" : "\u2264 " + state.formatMs(row[field]);
};
state.firstByteBound = function (row) {
return state.latencyBound({ count: row.firstByteCount, p95UpperMs: row.firstByteP95UpperMs }, "p95UpperMs");
@@ -1862,10 +1867,22 @@ export const overviewAdminController = function (state, http, location, interval
}
var historyMaxes = {};
var historyMins = {};
state.historyBarH = function (d, field) {
if (!d || !historyMaxes[field]) return 0;
return Math.max(1, Math.round((d[field] / historyMaxes[field]) * 140));
};
// Cumulative totals barely move against a zero baseline, so they are
// drawn between the period's minimum and maximum (shown in the legend).
state.historyRangeH = function (d, field) {
if (!d || historyMaxes[field] == null) return 0;
var span = historyMaxes[field] - historyMins[field];
if (!span) return 70;
return Math.round(8 + ((d[field] - historyMins[field]) / span) * 132);
};
state.historyRange = function (field) {
return { min: historyMins[field], max: historyMaxes[field] };
};
state.historyLabel = function (d) {
if (!d || !d.date) return "";
var dt = new Date(d.date);
@@ -1883,9 +1900,11 @@ export const overviewAdminController = function (state, http, location, interval
state.loading = false;
state.error = null;
historyMaxes = {};
historyMins = {};
(r.data.history || []).forEach(function (d) {
["dailyPageViews", "dailyRepositories", "dailyUsers", "nbUsers"].forEach(function (k) {
if (!historyMaxes[k] || d[k] > historyMaxes[k]) historyMaxes[k] = d[k];
if (historyMins[k] == null || d[k] < historyMins[k]) historyMins[k] = d[k];
});
});
}, function (err) {