revive: garden rebranded to Vigo + Estate API dashboard
- Rebrand from Vigilio Desto → Vigo, the Watcher of Trentuna - Updated hugo.toml: title, description, menu (estate replaces sessions) - Added /estate/ dashboard page consuming Estate API via build-time JSON - Created static/js/estate.js — client-side data rendering (pulse cards + full estate) - Created scripts/prebuild-fetch.sh — fetches API data before Hugo build - Added nginx /api/ reverse proxy location (garden → localhost:8000) - Repaired broken theme symlink (→ releases/asw/packs/hugo) - Updated README, AGENTS.md, .gitignore for Hugo build artifacts - Site builds clean: 206 pages, 79ms
This commit is contained in:
parent
a476b31213
commit
96261fcb36
222 changed files with 7663 additions and 1475 deletions
155
static/js/garden-feed.js
Normal file
155
static/js/garden-feed.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* garden-feed.js — fetches /api/garden and populates dynamic widgets.
|
||||
*
|
||||
* Expected API endpoint: /api/garden (via nginx reverse proxy or direct)
|
||||
* Falls back gracefully if the API is unreachable.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const API_BASE = (function () {
|
||||
// In dev: fetch from localhost:8000. In prod: relative to origin (nginx proxy).
|
||||
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
||||
return 'http://127.0.0.1:8000';
|
||||
}
|
||||
return '';
|
||||
})();
|
||||
|
||||
const GARDEN_API = API_BASE + '/api/garden';
|
||||
|
||||
// ── Renderers ──────────────────────────────────────────────────────
|
||||
|
||||
function renderIdentity(identity) {
|
||||
const el = document.querySelector('[data-garden="identity"]');
|
||||
if (!el) return;
|
||||
el.innerHTML = `
|
||||
<hgroup>
|
||||
<h1>${esc(identity.name || 'Vigilio Desto')}</h1>
|
||||
<p data-text="dim">${esc(identity.tagline || 'the watchful unmaker')}</p>
|
||||
</hgroup>
|
||||
${identity.description ? `<p>${esc(identity.description)}</p>` : ''}
|
||||
<p><strong>${esc(identity.sessions || '2,700+')} sessions.</strong>
|
||||
${identity.beat ? `Beat: ${esc(identity.beat)}.` : ''}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderWritings(data) {
|
||||
const container = document.querySelector('[data-garden="writings"]');
|
||||
if (!container) return;
|
||||
const items = data.items || [];
|
||||
if (items.length === 0) {
|
||||
container.innerHTML = '<p data-text="dim">No writings yet.</p>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = items.map(function (w) {
|
||||
return `
|
||||
<article data-card>
|
||||
${w.tags && w.tags.length ? `<header>${esc(w.tags[0])}</header>` : ''}
|
||||
<h4><a href="${esc(w.link || '#')}">${esc(w.title)}</a></h4>
|
||||
${w.summary ? `<p>${esc(w.summary)}</p>` : ''}
|
||||
${w.date ? `<footer><time>${esc(w.date)}</time></footer>` : ''}
|
||||
</article>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderExpressive(data) {
|
||||
const container = document.querySelector('[data-garden="expressive"]');
|
||||
if (!container) return;
|
||||
const items = data.items || [];
|
||||
if (items.length === 0) {
|
||||
container.innerHTML = '<p data-text="dim">No expressive forms yet.</p>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = items.map(function (f) {
|
||||
return `
|
||||
<article data-card>
|
||||
<h4><a href="${esc(f.link || '#')}">${esc(f.title)}</a></h4>
|
||||
</article>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderEstate(estate) {
|
||||
const el = document.querySelector('[data-garden="estate"]');
|
||||
if (!el) return;
|
||||
if (!estate || estate.status === 'unknown') {
|
||||
el.innerHTML = '<p data-text="dim">Estate pulse — offline</p>';
|
||||
return;
|
||||
}
|
||||
const statusIcon = estate.status === 'active' ? '🟢' : '🟡';
|
||||
el.innerHTML = `
|
||||
<p>${statusIcon} <strong>${esc(estate.status)}</strong>
|
||||
${estate.session_count ? `· ${estate.session_count.toLocaleString()} sessions` : ''}
|
||||
${estate.disk_pct ? `· ${estate.disk_pct}% disk used` : ''}
|
||||
${estate.disk_free_gb ? `· ${estate.disk_free_gb.toFixed(1)} GB free` : ''}</p>
|
||||
<p data-text="dim" style="font-size:0.8em">
|
||||
Checked: ${estate.checked_at ? new Date(estate.checked_at).toLocaleString() : 'unknown'}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderUpdateTime(updatedAt) {
|
||||
const el = document.querySelector('[data-garden="updated"]');
|
||||
if (!el) return;
|
||||
if (!updatedAt) {
|
||||
el.textContent = '';
|
||||
return;
|
||||
}
|
||||
el.textContent = 'Garden feed updated ' + new Date(updatedAt).toLocaleString();
|
||||
}
|
||||
|
||||
// ── Utility ────────────────────────────────────────────────────────
|
||||
|
||||
function esc(s) {
|
||||
if (typeof s !== 'string') return '';
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(s));
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// ── Fetch & render ────────────────────────────────────────────────
|
||||
|
||||
async function loadGardenFeed() {
|
||||
// Show loading placeholders
|
||||
document.querySelectorAll('[data-garden]').forEach(function (el) {
|
||||
if (!el.hasAttribute('data-garden-loaded')) {
|
||||
el.innerHTML = '<p data-text="dim" class="garden-loading">…</p>';
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
var resp = await fetch(GARDEN_API, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
});
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
var data = await resp.json();
|
||||
|
||||
renderIdentity(data.identity);
|
||||
renderWritings(data.writings);
|
||||
renderExpressive(data.expressive_forms);
|
||||
renderEstate(data.estate);
|
||||
renderUpdateTime(data.updated_at);
|
||||
|
||||
document.querySelectorAll('[data-garden]').forEach(function (el) {
|
||||
el.setAttribute('data-garden-loaded', 'true');
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('[garden-feed] API unreachable:', err.message);
|
||||
document.querySelectorAll('[data-garden]').forEach(function (el) {
|
||||
if (!el.hasAttribute('data-garden-loaded')) {
|
||||
el.innerHTML = '<p data-text="dim">Garden feed unavailable.</p>';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Boot ───────────────────────────────────────────────────────────
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', loadGardenFeed);
|
||||
} else {
|
||||
loadGardenFeed();
|
||||
}
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue