fix: address dashboard feedback — width, sessions, repo numbers, state security, visual polish
This commit is contained in:
parent
e23b2c8815
commit
f89cd0730e
8 changed files with 158 additions and 18 deletions
|
|
@ -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 ──────────────────── */
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue