Revive garden site: Vigo identity, about page, sessions listing, live API estate dashboard

- Update identity references: Vigilio → Vigo in garden.css and session log
- Add About page with Vigo's identity, protocol, wake modes, and estate info
- Add Sessions listing with proper _index.md and menu entry
- Add live API fetch (estate.js): try /api/ first, fall back to /data/ JSON
- Update menu in hugo.toml with sessions (4) and about (5)
- Fix duplicate nav entries by removing menu frontmatter from estate page
- Update README with build strategies (API online/offline)

Hugo build: 208 pages, 21 static files, 110ms
This commit is contained in:
B.A. Baracus 2026-05-26 15:26:11 +02:00
parent ef45cf166b
commit 5762508193
Signed by: ba
GPG key ID: D52E9C8491872206
127 changed files with 378 additions and 146 deletions

View file

@ -1,5 +1,5 @@
/*
* garden.css Vigilio's voice over ASW
* garden.css Vigo's garden over ASW
*
* The framework is ASW. This is the garden growing in it.
* Colors from the expressive forms (sessions 110-116).

View file

@ -1,15 +1,20 @@
/* estate.js Vigo's Estate API client
*
* Fetches Estate API data and populates dynamic sections: homepage pulse
* cards and full estate dashboard. Uses build-time JSON snapshots from
* /data/*.json (static files generated by prebuild-fetch.sh).
* cards and full estate dashboard.
*
* If nginx /api/ reverse proxy is configured (garden.trentuna.com/api/
* localhost:8000), the JS can also fetch live data from there. By default
* it reads from /data/ for simplicity.
* Primary source: live Estate API via /api/ (nginx reverse proxy to
* localhost:8000). Fallback: build-time JSON snapshots from /data/*.json
* (generated by prebuild-fetch.sh).
*/
const DATA_BASE = '/data';
const API_BASE = (function () {
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
return 'http://127.0.0.1:8000';
}
return '/api';
})();
/* ── Helpers ────────────────────────────────────────────────────── */
@ -21,6 +26,24 @@ async function loadJSON(path) {
return res.json();
}
/**
* fetchFromAPI try live API endpoint first, fall back to static data file.
* @param {string} endpoint - API path (e.g. 'summary', 'health')
* @param {string} dataFile - static data file path (e.g. '/data/summary.json')
* @returns {object} parsed JSON
*/
async function fetchFromAPI(endpoint, dataFile) {
const apiUrl = API_BASE + '/' + endpoint;
try {
const res = await fetch(apiUrl);
if (res.ok) return await res.json();
throw new Error('HTTP ' + res.status);
} catch (err) {
console.log('[estate] live API unreachable (' + apiUrl + '), falling back to ' + dataFile);
return loadJSON(dataFile);
}
}
function fmtPct(v) { return (typeof v === 'number') ? v + '%' : v; }
function fmtTime(t) {
@ -32,8 +55,8 @@ function fmtTime(t) {
async function fetchPulse() {
try {
const summary = await loadJSON(DATA_BASE + '/summary.json');
const trends = await loadJSON(DATA_BASE + '/trends-limit-5.json');
const summary = await fetchFromAPI('summary', DATA_BASE + '/summary.json');
const trends = await fetchFromAPI('trends?limit=5', DATA_BASE + '/trends-limit-5.json');
// Disk
const diskPct = summary?.estate?.disk_latest;
@ -83,14 +106,14 @@ async function fetchEstate() {
try {
const [summary, health, disk, events, repos, providers, builds, trends] = await Promise.all([
loadJSON(DATA_BASE + '/summary.json'),
loadJSON(DATA_BASE + '/health.json').catch(() => ({ error: true, data: [] })),
loadJSON(DATA_BASE + '/disk.json').catch(() => ({ error: true })),
loadJSON(DATA_BASE + '/events-limit-10.json').catch(() => ({ error: true, data: [] })),
loadJSON(DATA_BASE + '/repos.json').catch(() => ({ error: true, data: [] })),
loadJSON(DATA_BASE + '/providers.json').catch(() => ({ error: true, data: [] })),
loadJSON(DATA_BASE + '/builds.json').catch(() => ({ error: true, data: [] })),
loadJSON(DATA_BASE + '/trends-limit-5.json').catch(() => ({ error: true, data: [] })),
fetchFromAPI('summary', DATA_BASE + '/summary.json'),
fetchFromAPI('health', DATA_BASE + '/health.json').catch(() => ({ error: true, data: [] })),
fetchFromAPI('disk', DATA_BASE + '/disk.json').catch(() => ({ error: true })),
fetchFromAPI('events?limit=10', DATA_BASE + '/events-limit-10.json').catch(() => ({ error: true, data: [] })),
fetchFromAPI('repos', DATA_BASE + '/repos.json').catch(() => ({ error: true, data: [] })),
fetchFromAPI('providers', DATA_BASE + '/providers.json').catch(() => ({ error: true, data: [] })),
fetchFromAPI('builds', DATA_BASE + '/builds.json').catch(() => ({ error: true, data: [] })),
fetchFromAPI('trends?limit=10', DATA_BASE + '/trends-limit-5.json').catch(() => ({ error: true, data: [] })),
]);
// ── Summary cards ──
@ -175,7 +198,7 @@ async function fetchStateFiles() {
const setHTML = (id, html) => { const e = el(id); if (e) e.innerHTML = html; };
try {
const state = await loadJSON(DATA_BASE + '/state.json');
const state = await fetchFromAPI('state', DATA_BASE + '/state.json');
const files = Array.isArray(state?.files) ? state.files : [];
const fileCards = files.map(f => {