fix: address dashboard feedback — width, sessions, repo numbers, state security, visual polish

This commit is contained in:
B.A. Baracus 2026-05-26 16:16:45 +02:00
parent e23b2c8815
commit f89cd0730e
Signed by: ba
GPG key ID: D52E9C8491872206
8 changed files with 158 additions and 18 deletions

View file

@ -58,10 +58,50 @@ body {
font-family: var(--garden-font);
}
/* ── Layout — ASW handles body > main container now ────────── */
/* Override --width-lg for narrower garden feel */
:root {
--width-lg: 900px;
/* ── Layout — narrow container for garden prose feel ────────── */
/* Override ASW's responsive max-width cascade. ASW goes up to 1450px
on wide screens too much for a garden of text. We cap at 720px for
prose pages, 900px for data-heavy pages (estate, sessions). */
@media (min-width: 576px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 510px;
}
}
@media (min-width: 768px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 660px;
}
}
@media (min-width: 1024px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 720px;
}
}
@media (min-width: 1280px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 720px;
}
}
@media (min-width: 1536px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 720px;
}
}
/* Estate and sessions pages are data-heavy — give them more room */
body > main[data-page="estate"],
body > main[data-page="sessions"] {
max-width: 900px !important;
}
/* ── Links — violet accent, indigo hover ──────────────────── */

View file

@ -57,6 +57,7 @@ async function fetchPulse() {
try {
const summary = await fetchFromAPI('summary', DATA_BASE + '/summary.json');
const trends = await fetchFromAPI('trends?limit=5', DATA_BASE + '/trends-limit-5.json');
const repos = await fetchFromAPI('repos', DATA_BASE + '/repos.json').catch(() => null);
// Disk
const diskPct = summary?.estate?.disk_latest;
@ -77,6 +78,11 @@ async function fetchPulse() {
const sessions = trends?.data?.[0]?.vault?.sessions || null;
if ($('vault-sessions-value')) $('vault-sessions-value').textContent = sessions !== null ? sessions : '—';
// Repo count from repos
const repoData = repos?.forgejo_repos || repos?.data || [];
const repoCount = Array.isArray(repoData) ? repoData.length : 0;
if ($('estate-repo-count-pulse')) $('estate-repo-count-pulse').textContent = repoCount > 0 ? repoCount.toLocaleString() : '—';
// Session count standalone (in the intro block)
if ($('session-count')) {
$('session-count').textContent = sessions !== null ? sessions.toLocaleString() + ' sessions' : '? sessions';
@ -119,7 +125,13 @@ async function fetchEstate() {
// ── Summary cards ──
setText('estate-api-version', summary?.api_version || '—');
setText('estate-disk', summary?.estate?.disk_latest ? summary.estate.disk_latest + '%' : '—');
setText('estate-health', summary?.estate?.health_status || '—');
const healthStatus = summary?.estate?.health_status || '—';
const healthBadge = healthStatus === 'ok' ? '<span class="estate-badge ok">ok</span>'
: healthStatus.includes('check') ? '<span class="estate-badge warn">' + healthStatus + '</span>'
: '<span class="estate-badge err">' + healthStatus + '</span>';
setHTML('estate-health', healthBadge);
setText('estate-sources', summary?.sources?.length || '—');
const sourceRows = (summary?.sources || []).map(s =>
@ -131,9 +143,11 @@ async function fetchEstate() {
if (health?.error) {
setHTML('estate-health-table', '<tr><td colspan="3">Health data unavailable</td></tr>');
} else {
const healthRows = (Array.isArray(health?.data) ? health.data : health?.data ? [health.data] : []).slice(0, 10).map(h =>
`<tr><td>${h.timestamp || '—'}</td><td>${h.status || '—'}</td><td>${(h.detail || '').substring(0, 80)}</td></tr>`
).join('');
const healthRows = (Array.isArray(health?.data) ? health.data : health?.data ? [health.data] : []).slice(0, 10).map(h => {
const badgeClass = h.status === 'ok' || h.status === 'healthy' ? 'ok'
: (h.status || '').includes('warn') || (h.status || '').includes('degraded') ? 'warn' : 'err';
return `<tr><td>${h.timestamp || '—'}</td><td><span class="estate-badge ${badgeClass}">${h.status || '—'}</span></td><td>${(h.detail || '').substring(0, 80)}</td></tr>`;
}).join('');
setHTML('estate-health-table', healthRows || '<tr><td colspan="3">No health entries</td></tr>');
}
@ -156,7 +170,13 @@ async function fetchEstate() {
setHTML('estate-events-table', eventRows || '<tr><td colspan="3">No events</td></tr>');
// ── Repos ──
const repoList = Array.isArray(repos?.data) ? repos.data : (repos?.repos?.data ? repos.repos.data : []);
const repoData = repos?.forgejo_repos || repos?.data || (repos?.repos?.data ? repos.repos.data : []);
const repoList = Array.isArray(repoData) ? repoData : [];
// Update summary card if we have repo data
if (repoList.length > 0) {
setText('estate-repo-count', repoList.length.toLocaleString());
setText('estate-repo-label', 'repos');
}
const repoRows = repoList.slice(0, 15).map(r =>
`<tr><td>${r.name || r.path || '—'}</td><td>${r.url || '—'}</td><td>${r.branch || r.status || '—'}</td></tr>`
).join('');
@ -164,9 +184,10 @@ async function fetchEstate() {
// ── Providers ──
const provList = Array.isArray(providers?.data) ? providers.data : (providers?.providers || []);
const provRows = provList.slice(0, 10).map(p =>
`<tr><td>${p.name || '—'}</td><td>${p.status || p.reachable ? '✓' : '✗'}</td><td>${p.model || '—'}</td></tr>`
).join('');
const provRows = provList.slice(0, 10).map(p => {
const reachable = p.status === 'ok' || p.reachable === true;
return `<tr><td>${p.name || '—'}</td><td><span class="estate-badge ${reachable ? 'ok' : 'err'}">${reachable ? '✓' : '✗'}</span></td><td>${p.model || '—'}</td></tr>`;
}).join('');
setHTML('estate-providers-table', provRows || '<tr><td colspan="3">No provider data</td></tr>');
// ── Builds ──
@ -192,6 +213,8 @@ async function fetchEstate() {
}
/* ── State files ───────────────────────────────────────────────── */
/* NOTE: Only metadata (name, size) is shown for security. Full
content requires API authentication. */
async function fetchStateFiles() {
const el = (id) => $(id);
@ -202,12 +225,10 @@ async function fetchStateFiles() {
const files = Array.isArray(state?.files) ? state.files : [];
const fileCards = files.map(f => {
const truncated = f.content.length > 600 ? f.content.substring(0, 600) + '…' : f.content || '(empty)';
return `<article data-card>
<header>${f.name}</header>
<p>${(f.size_bytes || 0).toLocaleString()} bytes</p>
<pre style="font-size:0.75rem;max-height:200px;overflow:auto">${escHtml(truncated)}</pre>
<a href="/api/state?file=${f.name}">View full </a>
<p data-text="dim" style="font-size:var(--font-size-00)">Estate state file authenticated API required to view content</p>
</article>`;
}).join('');