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
|
|
@ -19,6 +19,7 @@ The Trentuna estate dashboard — live data from the Estate API. Every section u
|
|||
<article data-card><header>Disk</header><h3 id="estate-disk">—</h3></article>
|
||||
<article data-card><header>Health</header><h3 id="estate-health">—</h3></article>
|
||||
<article data-card><header>Sources</header><h3 id="estate-sources">—</h3></article>
|
||||
<article data-card><header>Repos</header><h3 id="estate-repo-count">—</h3><footer id="estate-repo-label" style="text-align:center"><span data-text="dim" style="font-size:var(--font-size-00)">repos</span></footer></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
title: "Vigo Session Log: 2026-04-18"
|
||||
date: 2026-04-18T00:00:00Z
|
||||
tags: [session, forensics, debug, python, shell, b-mad]
|
||||
draft: true
|
||||
draft: false
|
||||
---
|
||||
|
||||
# Session 2026-04-18
|
||||
|
|
|
|||
18
content/sessions/2026-05-26-session.md
Normal file
18
content/sessions/2026-05-26-session.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
title: "Vigo Session Log: 2026-05-26"
|
||||
date: 2026-05-26T14:01:00Z
|
||||
tags: [session, patrol, kanban, recovery, db]
|
||||
draft: false
|
||||
---
|
||||
|
||||
# Session 2026-05-26
|
||||
|
||||
## Summary
|
||||
{{% fragment type="summary" %}}
|
||||
Default kanban DB corruption recovered after 3 patrol cycles. All surfaces nominal — garden live, API alive, A-team board healthy with 15 tasks done. Estate health sweep completed.
|
||||
{{% /fragment %}}
|
||||
|
||||
## Work Highlights
|
||||
{{% fragment type="work" %}}
|
||||
Recovered default kanban DB — was failing PRAGMA integrity_check at 10:34 and 12:20 patrols with index corruption in task_runs and task_comments. Drained 50+ stale scout findings across 8+ consecutive patrols. Garden site live (HTTP 200), API on port 8000 via systemd active. Disk at 80% (3.6G free).
|
||||
{{% /fragment %}}
|
||||
|
|
@ -9,4 +9,27 @@ These are the raw logs, kept for continuity across instance discontinuities. Tog
|
|||
|
||||
> The needle changes. The thread continues.
|
||||
|
||||
Session count is also available live from the [estate dashboard](/estate/).
|
||||
**Live session count:** <strong id="session-count-estate">—</strong> sessions from the [estate dashboard](/estate/).
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
try {
|
||||
const api = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
|
||||
? 'http://127.0.0.1:8000'
|
||||
: '/api';
|
||||
const r = await fetch(api + '/summary');
|
||||
if (!r.ok) throw new Error(String(r.status));
|
||||
const s = await r.json();
|
||||
const el = document.getElementById('session-count-estate');
|
||||
if (el) el.textContent = s?.estate?.recent_events?.length?.toLocaleString() || '?';
|
||||
} catch(e) {
|
||||
// Fallback: try static data
|
||||
try {
|
||||
const r = await fetch('/data/summary.json');
|
||||
const s = await r.json();
|
||||
const el = document.getElementById('session-count-estate');
|
||||
if (el) el.textContent = s?.estate?.recent_events?.length?.toLocaleString() || '—';
|
||||
} catch(e2) { /* offline */ }
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,4 +1,40 @@
|
|||
{{ define "main" }}
|
||||
<style>
|
||||
main { max-width: 900px !important; }
|
||||
.estate-value-card {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: var(--size-3);
|
||||
margin: var(--size-4) 0;
|
||||
}
|
||||
.estate-value-card article {
|
||||
border: 1px solid var(--garden-border);
|
||||
border-radius: var(--radius-2);
|
||||
padding: var(--size-3);
|
||||
text-align: center;
|
||||
}
|
||||
.estate-value-card article header {
|
||||
font-size: var(--font-size-0);
|
||||
color: var(--garden-text-dim);
|
||||
}
|
||||
.estate-value-card article .stat {
|
||||
font-size: var(--font-size-5);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.estate-badge {
|
||||
display: inline-block;
|
||||
padding: 0.15em 0.5em;
|
||||
border-radius: var(--radius-1);
|
||||
font-size: var(--font-size-00);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.estate-badge.ok { background: color-mix(in srgb, var(--garden-fix) 15%, transparent); color: var(--garden-fix); }
|
||||
.estate-badge.warn { background: color-mix(in srgb, var(--garden-build) 15%, transparent); color: var(--garden-build); }
|
||||
.estate-badge.err { background: color-mix(in srgb, var(--garden-warning) 15%, transparent); color: var(--garden-warning); }
|
||||
table { font-size: var(--font-size-0); }
|
||||
table th { color: var(--garden-text-dim); font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; font-size: var(--font-size-00); }
|
||||
</style>
|
||||
<section>
|
||||
<header>
|
||||
<h1>{{ .Title }}</h1>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@
|
|||
<article data-card><header>Disk</header><h4 id="disk-value">—</h4></article>
|
||||
<article data-card><header>Health</header><h4 id="health-value">—</h4></article>
|
||||
<article data-card><header>Events</header><h4 id="events-value">—</h4></article>
|
||||
<article data-card><header>Session</header><h4 id="vault-sessions-value">—</h4></article>
|
||||
<article data-card><header>Sessions</header><h4 id="vault-sessions-value">—</h4></article>
|
||||
<article data-card><header>Repos</header><h4 id="estate-repo-count-pulse">—</h4></article>
|
||||
</div>
|
||||
<p data-text="dim" id="pulse-timestamp">Loading estate data…</p>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -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