fix: refresh dashboard update runs live
Container-Image veröffentlichen / container (push) Multi-Arch-Image veröffentlicht und verifiziert
Container-Image veröffentlichen / container (push) Multi-Arch-Image veröffentlicht und verifiziert
This commit is contained in:
1 parent
bb60c06c0e
commit
fc59092030
11 files changed
+241
-29
No files matched your search
@@ -348,12 +348,18 @@
|
||||
|
||||
const countdowns = [...document.querySelectorAll('[data-countdown]')];
|
||||
const currentIPv4Fields = [...document.querySelectorAll('[data-current-ipv4-placeholder]')];
|
||||
if (countdowns.length === 0 && currentIPv4Fields.length === 0) return;
|
||||
const dashboardIPv4 = document.querySelector('[data-dashboard-ipv4]');
|
||||
const dashboardIPv6 = document.querySelector('[data-dashboard-ipv6]');
|
||||
const recentRuns = document.querySelector('[data-recent-runs]');
|
||||
const runNowButton = document.querySelector('[data-run-now-button]');
|
||||
if (countdowns.length === 0 && currentIPv4Fields.length === 0 && !dashboardIPv4 && !dashboardIPv6 && !recentRuns && !runNowButton) return;
|
||||
|
||||
let nextRun = Number(countdowns[0]?.dataset.countdown || 0);
|
||||
let running = countdowns[0]?.dataset.running === 'true';
|
||||
let enabled = countdowns[0]?.dataset.enabled !== 'false';
|
||||
let lastRefresh = 0;
|
||||
let refreshing = false;
|
||||
let recentRunsSignature = '';
|
||||
|
||||
const formatRemaining = (seconds) => {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
@@ -363,23 +369,100 @@
|
||||
return `${String(minutes).padStart(2, '0')}:${String(rest).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const runStatusClass = (value) => {
|
||||
const status = String(value || '').toLowerCase();
|
||||
if (status.includes('ok') || status.includes('unverändert') || status.includes('aktualisiert') || status.includes('angelegt')) return 'status-good';
|
||||
if (status.includes('error') || status.includes('fehler') || status.includes('partial')) return 'status-bad';
|
||||
return 'status-warn';
|
||||
};
|
||||
|
||||
const formatRunTime = (unixTime) => {
|
||||
const date = new Date(Number(unixTime || 0) * 1000);
|
||||
if (Number.isNaN(date.getTime())) return '–';
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${day}.${month}.${date.getFullYear()} · ${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
const renderRecentRuns = (runs) => {
|
||||
if (!recentRuns || !Array.isArray(runs)) return;
|
||||
const signature = JSON.stringify(runs.map((run) => [run.id, run.started_at_unix, run.status, run.summary]));
|
||||
if (signature === recentRunsSignature) return;
|
||||
recentRunsSignature = signature;
|
||||
|
||||
if (runs.length === 0) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'empty-state';
|
||||
empty.textContent = 'Noch kein Update-Lauf protokolliert.';
|
||||
recentRuns.replaceChildren(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
const list = document.createElement('div');
|
||||
list.className = 'data-list';
|
||||
for (const run of runs) {
|
||||
const status = String(run.status || 'unbekannt');
|
||||
const statusClass = runStatusClass(status);
|
||||
const summary = String(run.summary || '').trim() || (status === 'running' ? 'Aktualisierung läuft' : 'Update-Lauf');
|
||||
const row = document.createElement('div');
|
||||
row.className = 'data-row';
|
||||
|
||||
const dot = document.createElement('span');
|
||||
dot.className = `state-dot ${statusClass}`;
|
||||
const details = document.createElement('span');
|
||||
const title = document.createElement('strong');
|
||||
title.textContent = summary;
|
||||
const time = document.createElement('small');
|
||||
time.textContent = formatRunTime(run.started_at_unix);
|
||||
details.append(title, time);
|
||||
const pill = document.createElement('span');
|
||||
pill.className = `status-pill ${statusClass}`;
|
||||
pill.textContent = status;
|
||||
row.append(dot, details, pill);
|
||||
list.append(row);
|
||||
}
|
||||
recentRuns.replaceChildren(list);
|
||||
};
|
||||
|
||||
const renderScheduleState = (state) => {
|
||||
const lastIPv4 = typeof state.last_ipv4 === 'string' ? state.last_ipv4.trim() : '';
|
||||
const lastIPv6 = typeof state.last_ipv6 === 'string' ? state.last_ipv6.trim() : '';
|
||||
if (lastIPv4) {
|
||||
for (const field of currentIPv4Fields) field.placeholder = lastIPv4;
|
||||
}
|
||||
if (dashboardIPv4) dashboardIPv4.textContent = lastIPv4 || 'Nicht ermittelt';
|
||||
if (dashboardIPv6) dashboardIPv6.textContent = state.ipv6_enabled === false ? 'Deaktiviert' : (lastIPv6 || 'Nicht ermittelt');
|
||||
if (runNowButton) {
|
||||
runNowButton.disabled = !enabled || running;
|
||||
runNowButton.textContent = running ? 'Update läuft' : 'Update jetzt ausführen';
|
||||
if (!enabled) runNowButton.title = 'Updater ist deaktiviert';
|
||||
else runNowButton.removeAttribute('title');
|
||||
}
|
||||
if (recentRuns) recentRuns.setAttribute('aria-busy', String(running));
|
||||
renderRecentRuns(state.recent_runs);
|
||||
};
|
||||
|
||||
const refreshSchedule = async () => {
|
||||
if (document.hidden) return;
|
||||
if (document.hidden || refreshing) return;
|
||||
const now = Date.now();
|
||||
if (now - lastRefresh < 2500) return;
|
||||
lastRefresh = now;
|
||||
refreshing = true;
|
||||
try {
|
||||
const response = await fetch('/api/v1/scheduler', {headers: {'Accept': 'application/json'}, cache: 'no-store'});
|
||||
const endpoint = recentRuns ? '/api/v1/scheduler?runs=1' : '/api/v1/scheduler';
|
||||
const response = await fetch(endpoint, {headers: {'Accept': 'application/json'}, cache: 'no-store'});
|
||||
if (!response.ok) return;
|
||||
const state = await response.json();
|
||||
nextRun = Number(state.next_run_unix || 0);
|
||||
running = Boolean(state.running);
|
||||
enabled = state.enabled !== false;
|
||||
if (typeof state.last_ipv4 === 'string' && state.last_ipv4.trim()) {
|
||||
for (const field of currentIPv4Fields) field.placeholder = state.last_ipv4.trim();
|
||||
}
|
||||
renderScheduleState(state);
|
||||
} catch {
|
||||
// Visible scheduler data keeps its last known state when the request fails.
|
||||
} finally {
|
||||
refreshing = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user