Auto-refresh Watchtower status
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 12s
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 12s
This commit is contained in:
@@ -83,6 +83,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/admin.js?v=10" defer></script>
|
<script src="/admin.js?v=11" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
25
admin.js
25
admin.js
@@ -35,6 +35,7 @@ let latestContainers = [];
|
|||||||
let dangerToken = null;
|
let dangerToken = null;
|
||||||
let dangerExpiresAt = 0;
|
let dangerExpiresAt = 0;
|
||||||
let dangerTimer = null;
|
let dangerTimer = null;
|
||||||
|
let opsAutoRefreshTimer = null;
|
||||||
|
|
||||||
function base64UrlEncode(buffer) {
|
function base64UrlEncode(buffer) {
|
||||||
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
|
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
|
||||||
@@ -322,14 +323,21 @@ function renderDisks(disks = []) {
|
|||||||
function renderWatchtower(watchtower) {
|
function renderWatchtower(watchtower) {
|
||||||
latestWatchtower = watchtower;
|
latestWatchtower = watchtower;
|
||||||
const officialSession = watchtower?.lastSession;
|
const officialSession = watchtower?.lastSession;
|
||||||
|
const statusGenerated = watchtower?.generatedAt
|
||||||
|
? new Date(watchtower.generatedAt).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
||||||
|
: '--';
|
||||||
const officialHtml = officialSession
|
const officialHtml = officialSession
|
||||||
? `
|
? `
|
||||||
<div class="ops-line"><strong>Offizieller Lauf</strong><span>${officialSession.finishedAt ? new Date(officialSession.finishedAt).toLocaleString('de-DE') : '--'}</span></div>
|
<div class="ops-line"><strong>Offizieller Lauf</strong><span>${officialSession.finishedAt ? new Date(officialSession.finishedAt).toLocaleString('de-DE') : '--'}</span></div>
|
||||||
<div class="ops-line"><strong>Geprueft</strong><span>${officialSession.scanned ?? '--'}</span></div>
|
<div class="ops-line"><strong>Geprueft</strong><span>${officialSession.scanned ?? '--'}</span></div>
|
||||||
<div class="ops-line"><strong>Aktualisiert</strong><span>${officialSession.updated ?? '--'}</span></div>
|
<div class="ops-line"><strong>Aktualisiert</strong><span>${officialSession.updated ?? '--'}</span></div>
|
||||||
<div class="ops-line"><strong>Fehler</strong><span>${officialSession.failed ?? '--'}</span></div>
|
<div class="ops-line"><strong>Fehler</strong><span>${officialSession.failed ?? '--'}</span></div>
|
||||||
|
<div class="ops-line"><strong>Status aktualisiert</strong><span>${statusGenerated}</span></div>
|
||||||
`
|
`
|
||||||
: `<div class="ops-line"><strong>Offizieller Lauf</strong><span>${watchtower?.tail?.slice(-2)?.join(' | ') || 'Noch nicht gefunden'}</span></div>`;
|
: `
|
||||||
|
<div class="ops-line"><strong>Offizieller Lauf</strong><span>${watchtower?.tail?.slice(-2)?.join(' | ') || 'Noch nicht gefunden'}</span></div>
|
||||||
|
<div class="ops-line"><strong>Status aktualisiert</strong><span>${statusGenerated}</span></div>
|
||||||
|
`;
|
||||||
|
|
||||||
if (lastManualWatchtowerRun) {
|
if (lastManualWatchtowerRun) {
|
||||||
const run = lastManualWatchtowerRun;
|
const run = lastManualWatchtowerRun;
|
||||||
@@ -551,10 +559,13 @@ async function pollWatchtowerRun(runId, attempt = 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshOps() {
|
async function refreshOps(options = {}) {
|
||||||
|
const silent = Boolean(options.silent);
|
||||||
|
if (!silent) {
|
||||||
diskStatus.textContent = 'Lade...';
|
diskStatus.textContent = 'Lade...';
|
||||||
watchtowerStatus.textContent = 'Lade...';
|
watchtowerStatus.textContent = 'Lade...';
|
||||||
containerStatus.textContent = 'Lade...';
|
containerStatus.textContent = 'Lade...';
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/status', { cache: 'no-store' });
|
const response = await fetch('/api/status', { cache: 'no-store' });
|
||||||
@@ -570,6 +581,15 @@ async function refreshOps() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startOpsAutoRefresh() {
|
||||||
|
window.clearInterval(opsAutoRefreshTimer);
|
||||||
|
opsAutoRefreshTimer = window.setInterval(() => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
refreshOps({ silent: true });
|
||||||
|
}
|
||||||
|
}, 60000);
|
||||||
|
}
|
||||||
|
|
||||||
async function boot() {
|
async function boot() {
|
||||||
await handleCallback();
|
await handleCallback();
|
||||||
tokenSet = getStoredTokens();
|
tokenSet = getStoredTokens();
|
||||||
@@ -593,6 +613,7 @@ async function boot() {
|
|||||||
adminUser.textContent = `Angemeldet als ${claims.preferred_username || claims.email}`;
|
adminUser.textContent = `Angemeldet als ${claims.preferred_username || claims.email}`;
|
||||||
renderEditor();
|
renderEditor();
|
||||||
refreshOps();
|
refreshOps();
|
||||||
|
startOpsAutoRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
loginButton.addEventListener('click', login);
|
loginButton.addEventListener('click', login);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const DOCKER_SOCKET = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
|
|||||||
const WATCHTOWER_CONTAINER = process.env.WATCHTOWER_CONTAINER || 'watchtower';
|
const WATCHTOWER_CONTAINER = process.env.WATCHTOWER_CONTAINER || 'watchtower';
|
||||||
const WATCHTOWER_RUN_IMAGE = process.env.WATCHTOWER_RUN_IMAGE || 'containrrr/watchtower';
|
const WATCHTOWER_RUN_IMAGE = process.env.WATCHTOWER_RUN_IMAGE || 'containrrr/watchtower';
|
||||||
const WATCHTOWER_CONFIG_PATH = process.env.WATCHTOWER_CONFIG_PATH || '/volume2/docker/watchtower/config.json';
|
const WATCHTOWER_CONFIG_PATH = process.env.WATCHTOWER_CONFIG_PATH || '/volume2/docker/watchtower/config.json';
|
||||||
|
const WATCHTOWER_LOG_TAIL = Number(process.env.WATCHTOWER_LOG_TAIL || 1200);
|
||||||
const AUTHORITY = process.env.AUTHORITY || 'https://auth.mischlabs.de/realms/mischlabs';
|
const AUTHORITY = process.env.AUTHORITY || 'https://auth.mischlabs.de/realms/mischlabs';
|
||||||
const ADMIN_CLIENT_ID = process.env.ADMIN_CLIENT_ID || 'mischlabs-admin';
|
const ADMIN_CLIENT_ID = process.env.ADMIN_CLIENT_ID || 'mischlabs-admin';
|
||||||
const ADMIN_USERS = (process.env.ADMIN_USERS || 'mrdiderot').split(',').map((user) => user.trim().toLowerCase()).filter(Boolean);
|
const ADMIN_USERS = (process.env.ADMIN_USERS || 'mrdiderot').split(',').map((user) => user.trim().toLowerCase()).filter(Boolean);
|
||||||
@@ -523,7 +524,7 @@ function parseWatchtower(lines) {
|
|||||||
current.warnings.push(line.replace(/^.*msg="/, '').replace(/"$/, ''));
|
current.warnings.push(line.replace(/^.*msg="/, '').replace(/"$/, ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
const done = line.match(/Session done(?:\\"|") Failed=(\d+) Scanned=(\d+) Updated=(\d+)/);
|
const done = line.match(/Session done.*Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/);
|
||||||
if (done) {
|
if (done) {
|
||||||
current = current || { startedAt: time, found: [], warnings: [] };
|
current = current || { startedAt: time, found: [], warnings: [] };
|
||||||
current.finishedAt = time;
|
current.finishedAt = time;
|
||||||
@@ -538,7 +539,7 @@ function parseWatchtower(lines) {
|
|||||||
if (sessions.length) return sessions.at(-1);
|
if (sessions.length) return sessions.at(-1);
|
||||||
|
|
||||||
const lastDoneLine = [...lines].reverse().find((line) => line.includes('Session done'));
|
const lastDoneLine = [...lines].reverse().find((line) => line.includes('Session done'));
|
||||||
const done = lastDoneLine?.match(/Failed=(\d+) Scanned=(\d+) Updated=(\d+)/);
|
const done = lastDoneLine?.match(/Failed=(\d+)\s+Scanned=(\d+)\s+Updated=(\d+)/);
|
||||||
if (done) {
|
if (done) {
|
||||||
const time = lastDoneLine.match(/time="([^"]+)"/)?.[1] || null;
|
const time = lastDoneLine.match(/time="([^"]+)"/)?.[1] || null;
|
||||||
return {
|
return {
|
||||||
@@ -599,7 +600,7 @@ async function statusPayload() {
|
|||||||
const [containers, disks, logs, services] = await Promise.all([
|
const [containers, disks, logs, services] = await Promise.all([
|
||||||
getContainers().catch((error) => ({ error: error.message })),
|
getContainers().catch((error) => ({ error: error.message })),
|
||||||
getDisks(),
|
getDisks(),
|
||||||
dockerLogs(WATCHTOWER_CONTAINER).catch((error) => [`watchtower logs unavailable: ${error.message}`]),
|
dockerLogs(WATCHTOWER_CONTAINER, WATCHTOWER_LOG_TAIL).catch((error) => [`watchtower logs unavailable: ${error.message}`]),
|
||||||
getServices()
|
getServices()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -609,6 +610,8 @@ async function statusPayload() {
|
|||||||
disks,
|
disks,
|
||||||
watchtower: {
|
watchtower: {
|
||||||
container: WATCHTOWER_CONTAINER,
|
container: WATCHTOWER_CONTAINER,
|
||||||
|
generatedAt: new Date().toISOString(),
|
||||||
|
logTail: WATCHTOWER_LOG_TAIL,
|
||||||
lastSession: Array.isArray(logs) ? parseWatchtower(logs) : null,
|
lastSession: Array.isArray(logs) ? parseWatchtower(logs) : null,
|
||||||
tail: Array.isArray(logs) ? logs.slice(-20) : logs
|
tail: Array.isArray(logs) ? logs.slice(-20) : logs
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user