This commit is contained in:
@@ -102,6 +102,58 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
"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",
|
"confirmTitle": "Update Website",
|
||||||
|
|
||||||
"confirmBody": "This will rebuild and deploy the website. Continue?",
|
"confirmBody": "This will rebuild and deploy the website. Continue?",
|
||||||
|
|||||||
@@ -253,6 +253,39 @@ function setBadge(el, text, tone) {
|
|||||||
el.className = `badge ${tone} widget-badge`;
|
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) {
|
function renderBuilds(builds) {
|
||||||
const grid = document.getElementById('buildGrid');
|
const grid = document.getElementById('buildGrid');
|
||||||
grid.innerHTML = '';
|
grid.innerHTML = '';
|
||||||
@@ -274,11 +307,7 @@ function createBuildCard(build) {
|
|||||||
article.dataset.buildId = build.id;
|
article.dataset.buildId = build.id;
|
||||||
article.setAttribute('data-build-id', build.id);
|
article.setAttribute('data-build-id', build.id);
|
||||||
|
|
||||||
const btnId = build.id === 'org_web' ? 'buildBtn' : 'emacsBtn';
|
const { btnId, killId, resultId, logId, lastRunId } = buildDomIds(build);
|
||||||
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.innerHTML = `
|
article.innerHTML = `
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
@@ -388,8 +417,7 @@ async function fetchLastRuns() {
|
|||||||
const res = await fetch(manifest.status.lastRuns);
|
const res = await fetch(manifest.status.lastRuns);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
for (const build of manifest.builds) {
|
for (const build of manifest.builds) {
|
||||||
const metaId = build.id === 'org_web' ? 'webLastRunMeta' : 'emacsLastRunMeta';
|
renderLastRun(buildDomIds(build).lastRunId, data[build.lastRunKey]);
|
||||||
renderLastRun(metaId, data[build.lastRunKey]);
|
|
||||||
updateBuildStatusFromLastRun(build, data[build.lastRunKey]);
|
updateBuildStatusFromLastRun(build, data[build.lastRunKey]);
|
||||||
}
|
}
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
|
|||||||
@@ -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.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 === 'org_web'));
|
||||||
assert.ok(manifest.builds.some(b => b.id === 'emacs'));
|
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', () => {
|
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.start.path, '/api/rerun-emacs');
|
||||||
assert.equal(build.lastRunKey, '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/build-web/status',
|
||||||
'/api/rerun-emacs',
|
'/api/rerun-emacs',
|
||||||
'/api/rerun-emacs/logs',
|
'/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) {
|
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, /data\/manifest\.json/);
|
||||||
assert.match(dashboardJs, /\/api\/zone\/status/);
|
assert.match(dashboardJs, /\/api\/zone\/status/);
|
||||||
assert.match(dashboardJs, /renderWidgets/);
|
assert.match(dashboardJs, /renderWidgets/);
|
||||||
|
assert.match(dashboardJs, /buildDomIds/);
|
||||||
assert.doesNotMatch(dashboardJs, /build-roam/);
|
assert.doesNotMatch(dashboardJs, /build-roam/);
|
||||||
assert.doesNotMatch(dashboardJs, /run-combined/);
|
assert.doesNotMatch(dashboardJs, /run-combined/);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user