feat: Add premium server-side Dienste-Status (Realtime) Board to admin panel
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 11s
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 11s
This commit is contained in:
112
admin.js
112
admin.js
@@ -25,6 +25,8 @@ const dangerState = document.querySelector('#dangerState');
|
||||
const diskStatus = document.querySelector('#diskStatus');
|
||||
const watchtowerStatus = document.querySelector('#watchtowerStatus');
|
||||
const containerStatus = document.querySelector('#containerStatus');
|
||||
const servicesStatusBoard = document.querySelector('#servicesStatusBoard');
|
||||
const refreshServicesButton = document.querySelector('#refreshServicesButton');
|
||||
|
||||
let config = null;
|
||||
let tokenSet = null;
|
||||
@@ -298,6 +300,106 @@ function formatBytes(bytes) {
|
||||
return `${value.toFixed(value >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`;
|
||||
}
|
||||
|
||||
function renderServicesStatus(services = []) {
|
||||
if (!servicesStatusBoard) return;
|
||||
if (!Array.isArray(services) || services.length === 0) {
|
||||
servicesStatusBoard.innerHTML = '<div class="ops-line">Keine Dienst-Statusdaten vom Server erhalten.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a map of status by URL for easy lookup
|
||||
const statusMap = new Map();
|
||||
services.forEach((s) => {
|
||||
if (s && s.url) {
|
||||
statusMap.set(s.url, s);
|
||||
}
|
||||
});
|
||||
|
||||
// We want to render:
|
||||
// 1. The main Landing Page (mischlabs.de)
|
||||
// 2. All services defined in the config (from config.services)
|
||||
const itemsToRender = [];
|
||||
|
||||
// Main landing page check
|
||||
const mainSiteUrl = 'https://mischlabs.de';
|
||||
const mainSiteStatus = statusMap.get(mainSiteUrl);
|
||||
if (mainSiteStatus) {
|
||||
itemsToRender.push({
|
||||
name: 'MischLabs Landing Page',
|
||||
domain: 'mischlabs.de',
|
||||
url: mainSiteUrl,
|
||||
status: mainSiteStatus
|
||||
});
|
||||
}
|
||||
|
||||
// Services from editor config
|
||||
if (config && Array.isArray(config.services)) {
|
||||
config.services.forEach((service) => {
|
||||
const serviceStatus = statusMap.get(service.url);
|
||||
if (serviceStatus) {
|
||||
itemsToRender.push({
|
||||
name: service.name,
|
||||
domain: service.domain,
|
||||
url: service.url,
|
||||
status: serviceStatus
|
||||
});
|
||||
} else {
|
||||
// Fallback if URL didn't match exactly but we can find a substring/partial match,
|
||||
// or just show it as unknown
|
||||
const fallbackStatus = services.find(s => s && s.url && s.url.includes(service.domain));
|
||||
itemsToRender.push({
|
||||
name: service.name,
|
||||
domain: service.domain,
|
||||
url: service.url,
|
||||
status: fallbackStatus || { url: service.url, ok: false, error: 'Nicht geprueft' }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const offlineCount = itemsToRender.filter(item => !item.status.ok).length;
|
||||
const totalCount = itemsToRender.length;
|
||||
const summaryEl = document.querySelector('#servicesStatusSummary');
|
||||
if (summaryEl) {
|
||||
if (offlineCount > 0) {
|
||||
summaryEl.innerHTML = `<span style="color: var(--down); font-weight: 700;">${offlineCount} offline</span> / ${totalCount} gesamt`;
|
||||
} else {
|
||||
summaryEl.innerHTML = `<span style="color: var(--ok); font-weight: 700;">Alle online</span> (${totalCount})`;
|
||||
}
|
||||
}
|
||||
|
||||
servicesStatusBoard.innerHTML = itemsToRender.map((item) => {
|
||||
const s = item.status;
|
||||
let statusClass = 'is-online';
|
||||
let statusLabel = `HTTP ${s.statusCode || '200'}`;
|
||||
let latencyText = `${s.ms ?? '--'} ms`;
|
||||
|
||||
if (s.error || s.ok === false) {
|
||||
statusClass = 'is-offline';
|
||||
statusLabel = s.statusCode ? `HTTP ${s.statusCode}` : 'Offline';
|
||||
latencyText = s.error || 'Verbindung fehlgeschlagen';
|
||||
} else if (s.ms > 1000) {
|
||||
statusClass = 'is-warning';
|
||||
statusLabel = 'Traege';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="status-board-item ${statusClass}">
|
||||
<div class="status-board-item-header">
|
||||
<h4>${item.name}</h4>
|
||||
<span class="status-board-item-latency">${latencyText}</span>
|
||||
</div>
|
||||
<div class="status-board-item-body">
|
||||
<span class="status-board-item-domain">${item.domain}</span>
|
||||
<div class="status-board-item-info">
|
||||
<span class="status-board-item-status">${statusLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderDisks(disks = []) {
|
||||
diskStatus.innerHTML = disks.map((disk) => {
|
||||
if (disk.error) {
|
||||
@@ -565,6 +667,9 @@ async function refreshOps(options = {}) {
|
||||
diskStatus.textContent = 'Lade...';
|
||||
watchtowerStatus.textContent = 'Lade...';
|
||||
containerStatus.textContent = 'Lade...';
|
||||
if (servicesStatusBoard) {
|
||||
servicesStatusBoard.textContent = 'Lade...';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -574,10 +679,14 @@ async function refreshOps(options = {}) {
|
||||
renderDisks(status.disks);
|
||||
renderWatchtower(status.watchtower);
|
||||
renderContainers(status.containers);
|
||||
renderServicesStatus(status.services);
|
||||
} catch (error) {
|
||||
diskStatus.textContent = `Statusproxy nicht erreichbar: ${error.message}`;
|
||||
watchtowerStatus.textContent = 'Nicht verfuegbar.';
|
||||
containerStatus.textContent = 'Nicht verfuegbar.';
|
||||
if (servicesStatusBoard) {
|
||||
servicesStatusBoard.textContent = `Nicht verfuegbar: ${error.message}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,6 +753,9 @@ resetLocalButton.addEventListener('click', () => {
|
||||
|
||||
downloadConfigButton.addEventListener('click', downloadConfig);
|
||||
refreshOpsButton.addEventListener('click', refreshOps);
|
||||
if (refreshServicesButton) {
|
||||
refreshServicesButton.addEventListener('click', () => refreshOps());
|
||||
}
|
||||
runWatchtowerButton.addEventListener('click', runWatchtowerOnce);
|
||||
dangerModeButton.addEventListener('click', unlockDangerMode);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user