This commit is contained in:
@@ -87,10 +87,10 @@
|
||||
"logKey": "orgWebBuild"
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"id": "calibre-sync",
|
||||
|
||||
{
|
||||
|
||||
"id": "calibre-sync",
|
||||
|
||||
"type": "log",
|
||||
|
||||
@@ -99,11 +99,63 @@
|
||||
"integration": "scripts",
|
||||
|
||||
"logKey": "calibreSync"
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"id": "org-web-build",
|
||||
|
||||
"type": "buildStatus",
|
||||
|
||||
"title": "Website build",
|
||||
|
||||
"integration": "builds",
|
||||
|
||||
"lastRunKey": "org_web"
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"links": [
|
||||
|
||||
{ "id": "timesheet", "label": "Timesheet", "href": "/time-tracking/", "group": "platform" },
|
||||
|
||||
{ "id": "website", "label": "Website", "href": "https://zainezq.com", "group": "platform", "external": true },
|
||||
|
||||
{ "id": "author", "label": "Author", "href": "https://author.zainezq.com", "group": "platform", "external": true },
|
||||
|
||||
{ "id": "files", "label": "Files", "href": "https://filebrowser.zainezq.com", "group": "infra", "external": true },
|
||||
|
||||
{ "id": "library", "label": "Library", "href": "https://calibre.zainezq.com", "group": "infra", "external": true },
|
||||
|
||||
{ "id": "news", "label": "News", "href": "https://miniflux.zainezq.com", "group": "infra", "external": true },
|
||||
|
||||
{ "id": "rdp", "label": "RDP", "href": "https://guac.zainezq.com", "group": "infra", "external": true },
|
||||
|
||||
{ "id": "portainer", "label": "Portainer", "href": "https://portainer.zainezq.com", "group": "infra", "external": true },
|
||||
|
||||
{ "id": "nextcloud", "label": "Nextcloud", "href": "https://nextcloud.zainezq.com", "group": "infra", "external": true },
|
||||
|
||||
{ "id": "pgadmin", "label": "Databases", "href": "https://pgadmin.zainezq.com", "group": "infra", "external": true },
|
||||
|
||||
{ "id": "gitea", "label": "Gitea", "href": "https://gitea.zainezq.com", "group": "infra", "external": true }
|
||||
|
||||
],
|
||||
|
||||
"builds": [
|
||||
|
||||
{
|
||||
|
||||
"id": "org_web",
|
||||
|
||||
"label": "Website Build",
|
||||
|
||||
"badge": "Web",
|
||||
|
||||
"confirmTitle": "Update Website",
|
||||
|
||||
"confirmBody": "This will rebuild and deploy the website. Continue?",
|
||||
|
||||
"runLabel": "Run website build",
|
||||
|
||||
@@ -247,15 +247,48 @@ function updateWidgetCard(widget, status) {
|
||||
}
|
||||
}
|
||||
|
||||
function setBadge(el, text, tone) {
|
||||
if (!el) return;
|
||||
el.textContent = text;
|
||||
el.className = `badge ${tone} widget-badge`;
|
||||
}
|
||||
|
||||
function renderBuilds(builds) {
|
||||
const grid = document.getElementById('buildGrid');
|
||||
grid.innerHTML = '';
|
||||
function setBadge(el, text, tone) {
|
||||
if (!el) return;
|
||||
el.textContent = text;
|
||||
el.className = `badge ${tone} widget-badge`;
|
||||
}
|
||||
|
||||
function safeDomId(value) {
|
||||
return String(value).replace(/[^a-zA-Z0-9_-]/g, '-');
|
||||
}
|
||||
|
||||
function buildDomIds(build) {
|
||||
if (build.id === 'org_web') {
|
||||
return {
|
||||
btnId: 'buildBtn',
|
||||
killId: 'killWebBtn',
|
||||
resultId: 'buildResult',
|
||||
logId: 'logOutput',
|
||||
lastRunId: 'webLastRunMeta',
|
||||
};
|
||||
}
|
||||
if (build.id === 'emacs') {
|
||||
return {
|
||||
btnId: 'emacsBtn',
|
||||
killId: 'killEmacsBtn',
|
||||
resultId: 'emacsResult',
|
||||
logId: 'emacsLogs',
|
||||
lastRunId: 'emacsLastRunMeta',
|
||||
};
|
||||
}
|
||||
const id = safeDomId(build.id);
|
||||
return {
|
||||
btnId: `${id}Btn`,
|
||||
killId: `${id}CancelBtn`,
|
||||
resultId: `${id}Result`,
|
||||
logId: `${id}Logs`,
|
||||
lastRunId: `${id}LastRunMeta`,
|
||||
};
|
||||
}
|
||||
|
||||
function renderBuilds(builds) {
|
||||
const grid = document.getElementById('buildGrid');
|
||||
grid.innerHTML = '';
|
||||
for (const build of builds) {
|
||||
grid.appendChild(createBuildCard(build));
|
||||
}
|
||||
@@ -270,15 +303,11 @@ function renderBuilds(builds) {
|
||||
|
||||
function createBuildCard(build) {
|
||||
const article = document.createElement('article');
|
||||
article.className = 'panel run-card';
|
||||
article.dataset.buildId = build.id;
|
||||
article.setAttribute('data-build-id', build.id);
|
||||
|
||||
const btnId = build.id === 'org_web' ? 'buildBtn' : 'emacsBtn';
|
||||
const killId = build.id === 'org_web' ? 'killWebBtn' : 'killEmacsBtn';
|
||||
const resultId = build.id === 'org_web' ? 'buildResult' : 'emacsResult';
|
||||
const logId = build.id === 'org_web' ? 'logOutput' : 'emacsLogs';
|
||||
const lastRunId = build.id === 'org_web' ? 'webLastRunMeta' : 'emacsLastRunMeta';
|
||||
article.className = 'panel run-card';
|
||||
article.dataset.buildId = build.id;
|
||||
article.setAttribute('data-build-id', build.id);
|
||||
|
||||
const { btnId, killId, resultId, logId, lastRunId } = buildDomIds(build);
|
||||
|
||||
article.innerHTML = `
|
||||
<div class="panel-header">
|
||||
@@ -383,17 +412,16 @@ async function fetchUptime() {
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function fetchLastRuns() {
|
||||
try {
|
||||
const res = await fetch(manifest.status.lastRuns);
|
||||
const data = await res.json();
|
||||
for (const build of manifest.builds) {
|
||||
const metaId = build.id === 'org_web' ? 'webLastRunMeta' : 'emacsLastRunMeta';
|
||||
renderLastRun(metaId, data[build.lastRunKey]);
|
||||
updateBuildStatusFromLastRun(build, data[build.lastRunKey]);
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
async function fetchLastRuns() {
|
||||
try {
|
||||
const res = await fetch(manifest.status.lastRuns);
|
||||
const data = await res.json();
|
||||
for (const build of manifest.builds) {
|
||||
renderLastRun(buildDomIds(build).lastRunId, data[build.lastRunKey]);
|
||||
updateBuildStatusFromLastRun(build, data[build.lastRunKey]);
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function updateBuildStatusFromLastRun(build, info) {
|
||||
if (!build.statusDotId || !info || info.lastRun === 'never') return;
|
||||
|
||||
@@ -74,6 +74,10 @@ test('manifest defines unique link and build ids', () => {
|
||||
assert.ok(manifest.links.some(l => l.id === 'timesheet' && l.href === '/time-tracking/'));
|
||||
assert.ok(manifest.builds.some(b => b.id === 'org_web'));
|
||||
assert.ok(manifest.builds.some(b => b.id === 'emacs'));
|
||||
assert.ok(manifest.builds.some(b => b.id === 'adventure_resources'));
|
||||
assert.ok(manifest.builds.some(b => b.id === 'guacamole_start'));
|
||||
assert.ok(manifest.builds.some(b => b.id === 'guacamole_stop'));
|
||||
assert.ok(manifest.builds.some(b => b.id === 'nostalgia_prod'));
|
||||
});
|
||||
|
||||
test('manifest build cards expose stable dom ids', () => {
|
||||
@@ -88,6 +92,22 @@ test('manifest build cards expose stable dom ids', () => {
|
||||
assert.equal(build.start.path, '/api/rerun-emacs');
|
||||
assert.equal(build.lastRunKey, 'emacs');
|
||||
}
|
||||
if (build.id === 'adventure_resources') {
|
||||
assert.equal(build.start.path, '/api/adventure/resources/free');
|
||||
assert.equal(build.lastRunKey, 'adventure_resources');
|
||||
}
|
||||
if (build.id === 'guacamole_start') {
|
||||
assert.equal(build.start.path, '/api/guacamole/start');
|
||||
assert.equal(build.lastRunKey, 'guacamole_start');
|
||||
}
|
||||
if (build.id === 'guacamole_stop') {
|
||||
assert.equal(build.start.path, '/api/guacamole/stop');
|
||||
assert.equal(build.lastRunKey, 'guacamole_stop');
|
||||
}
|
||||
if (build.id === 'nostalgia_prod') {
|
||||
assert.equal(build.start.path, '/api/nostalgia/production');
|
||||
assert.equal(build.lastRunKey, 'nostalgia_prod');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -101,7 +121,19 @@ test('dashboard module references manifest-driven API endpoints', () => {
|
||||
'/api/build-web/status',
|
||||
'/api/rerun-emacs',
|
||||
'/api/rerun-emacs/logs',
|
||||
'/api/rerun-emacs/status'
|
||||
'/api/rerun-emacs/status',
|
||||
'/api/adventure/resources/free',
|
||||
'/api/adventure/resources/free/logs',
|
||||
'/api/adventure/resources/free/status',
|
||||
'/api/guacamole/start',
|
||||
'/api/guacamole/start/logs',
|
||||
'/api/guacamole/start/status',
|
||||
'/api/guacamole/stop',
|
||||
'/api/guacamole/stop/logs',
|
||||
'/api/guacamole/stop/status',
|
||||
'/api/nostalgia/production',
|
||||
'/api/nostalgia/production/logs',
|
||||
'/api/nostalgia/production/status'
|
||||
];
|
||||
|
||||
for (const endpoint of expectedEndpoints) {
|
||||
@@ -123,6 +155,7 @@ test('dashboard module references manifest-driven API endpoints', () => {
|
||||
assert.match(dashboardJs, /data\/manifest\.json/);
|
||||
assert.match(dashboardJs, /\/api\/zone\/status/);
|
||||
assert.match(dashboardJs, /renderWidgets/);
|
||||
assert.match(dashboardJs, /buildDomIds/);
|
||||
assert.doesNotMatch(dashboardJs, /build-roam/);
|
||||
assert.doesNotMatch(dashboardJs, /run-combined/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user