Improve ops status diagnostics
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:
@@ -6,7 +6,7 @@
|
|||||||
<title>MischLabs Admin</title>
|
<title>MischLabs Admin</title>
|
||||||
<meta name="description" content="MischLabs dashboard administration.">
|
<meta name="description" content="MischLabs dashboard administration.">
|
||||||
<meta name="theme-color" content="#0f172a">
|
<meta name="theme-color" content="#0f172a">
|
||||||
<link rel="stylesheet" href="/style.css?v=12">
|
<link rel="stylesheet" href="/style.css?v=13">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="shell hero admin-hero">
|
<header class="shell hero admin-hero">
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="notice">
|
<div class="notice">
|
||||||
Hinweis: Ohne Backend speichert diese Adminseite Aenderungen lokal im Browser und kann eine neue `services.json` exportieren. Fuer globale Live-Aenderungen bauen wir als naechsten Schritt ein kleines Backend oder einen Gitea-Commit-Workflow.
|
Hinweis: Betriebsdaten kommen aus dem MischLabs-Statusproxy. Kachel-Aenderungen werden aktuell lokal im Browser gespeichert oder als `services.json` exportiert; fuer globale Live-Aenderungen folgt spaeter ein Gitea-Commit-Workflow.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin-user" id="adminUser"></div>
|
<div class="admin-user" id="adminUser"></div>
|
||||||
@@ -80,6 +80,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/admin.js?v=3" defer></script>
|
<script src="/admin.js?v=4" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
10
admin.js
10
admin.js
@@ -290,7 +290,10 @@ function formatBytes(bytes) {
|
|||||||
function renderDisks(disks = []) {
|
function renderDisks(disks = []) {
|
||||||
diskStatus.innerHTML = disks.map((disk) => {
|
diskStatus.innerHTML = disks.map((disk) => {
|
||||||
if (disk.error) {
|
if (disk.error) {
|
||||||
return `<div class="ops-line"><strong>${disk.label}</strong><span>${disk.error}</span></div>`;
|
const hint = disk.mount
|
||||||
|
? `Mount fehlt: ${disk.mount}`
|
||||||
|
: disk.error;
|
||||||
|
return `<div class="ops-line is-warning"><strong>${disk.label}</strong><span>${hint}</span></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const level = disk.usedPercent >= 90 ? 'danger' : disk.usedPercent >= 80 ? 'warn' : 'ok';
|
const level = disk.usedPercent >= 90 ? 'danger' : disk.usedPercent >= 80 ? 'warn' : 'ok';
|
||||||
@@ -309,7 +312,10 @@ function renderDisks(disks = []) {
|
|||||||
function renderWatchtower(watchtower) {
|
function renderWatchtower(watchtower) {
|
||||||
const session = watchtower?.lastSession;
|
const session = watchtower?.lastSession;
|
||||||
if (!session) {
|
if (!session) {
|
||||||
watchtowerStatus.textContent = 'Kein Watchtower-Lauf gefunden.';
|
const tail = watchtower?.tail?.slice(-4)?.join(' | ');
|
||||||
|
watchtowerStatus.textContent = tail
|
||||||
|
? `Kein Lauf geparst. Letzte Logs: ${tail}`
|
||||||
|
: 'Kein Watchtower-Lauf gefunden.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<link rel="apple-touch-icon" href="/icons/apple-touch-icon-v2.png">
|
<link rel="apple-touch-icon" href="/icons/apple-touch-icon-v2.png">
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png">
|
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png">
|
||||||
<link rel="icon" type="image/png" sizes="512x512" href="/icons/icon-512.png">
|
<link rel="icon" type="image/png" sizes="512x512" href="/icons/icon-512.png">
|
||||||
<link rel="stylesheet" href="style.css?v=12">
|
<link rel="stylesheet" href="style.css?v=13">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="shell hero">
|
<header class="shell hero">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>MischLabs - Offline</title>
|
<title>MischLabs - Offline</title>
|
||||||
<meta name="theme-color" content="#0f172a">
|
<meta name="theme-color" content="#0f172a">
|
||||||
<link rel="stylesheet" href="/style.css?v=12">
|
<link rel="stylesheet" href="/style.css?v=13">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="shell hero">
|
<main class="shell hero">
|
||||||
|
|||||||
33
server.js
33
server.js
@@ -11,8 +11,8 @@ const PUBLIC_DIR = fs.existsSync(dockerPublicDir) ? dockerPublicDir : __dirname;
|
|||||||
const DOCKER_SOCKET = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
|
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 DISK_TARGETS = [
|
const DISK_TARGETS = [
|
||||||
{ id: 'volume1', label: 'Volume 1', path: process.env.STATUS_VOLUME1_PATH || '/host/volume1' },
|
{ id: 'volume1', label: 'Volume 1', path: process.env.STATUS_VOLUME1_PATH || '/host/volume1', mount: '/volume1:/host/volume1:ro' },
|
||||||
{ id: 'volume2', label: 'Volume 2', path: process.env.STATUS_VOLUME2_PATH || '/host/volume2' },
|
{ id: 'volume2', label: 'Volume 2', path: process.env.STATUS_VOLUME2_PATH || '/host/volume2', mount: '/volume2:/host/volume2:ro' },
|
||||||
{ id: 'root', label: 'Root', path: '/' }
|
{ id: 'root', label: 'Root', path: '/' }
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -164,7 +164,13 @@ function parseWatchtower(lines) {
|
|||||||
const timeMatch = line.match(/time="([^"]+)"/);
|
const timeMatch = line.match(/time="([^"]+)"/);
|
||||||
const time = timeMatch?.[1] || null;
|
const time = timeMatch?.[1] || null;
|
||||||
|
|
||||||
if (line.includes('Running a one time update.') || line.includes('Checking all containers')) {
|
if (
|
||||||
|
line.includes('Running a one time update.')
|
||||||
|
|| line.includes('Checking all containers')
|
||||||
|
|| line.includes('Found new')
|
||||||
|
|| line.includes('Stopping /')
|
||||||
|
|| line.includes('Creating /')
|
||||||
|
) {
|
||||||
current = current || { startedAt: time, found: [], warnings: [] };
|
current = current || { startedAt: time, found: [], warnings: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +184,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+) Scanned=(\d+) Updated=(\d+)/);
|
||||||
if (done) {
|
if (done) {
|
||||||
current = current || { startedAt: time, found: [], warnings: [] };
|
current = current || { startedAt: time, found: [], warnings: [] };
|
||||||
current.finishedAt = time;
|
current.finishedAt = time;
|
||||||
@@ -190,7 +196,24 @@ function parseWatchtower(lines) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessions.at(-1) || null;
|
if (sessions.length) return sessions.at(-1);
|
||||||
|
|
||||||
|
const lastDoneLine = [...lines].reverse().find((line) => line.includes('Session done'));
|
||||||
|
const done = lastDoneLine?.match(/Failed=(\d+) Scanned=(\d+) Updated=(\d+)/);
|
||||||
|
if (done) {
|
||||||
|
const time = lastDoneLine.match(/time="([^"]+)"/)?.[1] || null;
|
||||||
|
return {
|
||||||
|
startedAt: null,
|
||||||
|
finishedAt: time,
|
||||||
|
failed: Number(done[1]),
|
||||||
|
scanned: Number(done[2]),
|
||||||
|
updated: Number(done[3]),
|
||||||
|
found: [],
|
||||||
|
warnings: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function probeUrl(target) {
|
function probeUrl(target) {
|
||||||
|
|||||||
@@ -555,6 +555,10 @@ main {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ops-line.is-warning span {
|
||||||
|
color: #fde68a;
|
||||||
|
}
|
||||||
|
|
||||||
.usage-bar {
|
.usage-bar {
|
||||||
height: 7px;
|
height: 7px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
4
sw.js
4
sw.js
@@ -1,9 +1,9 @@
|
|||||||
const CACHE_NAME = 'mischlabs-pwa-v6';
|
const CACHE_NAME = 'mischlabs-pwa-v7';
|
||||||
const APP_SHELL = [
|
const APP_SHELL = [
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/index.html',
|
||||||
'/offline.html',
|
'/offline.html',
|
||||||
'/style.css?v=12',
|
'/style.css?v=13',
|
||||||
'/app.js?v=3',
|
'/app.js?v=3',
|
||||||
'/services.json?v=1',
|
'/services.json?v=1',
|
||||||
'/manifest.webmanifest?v=3',
|
'/manifest.webmanifest?v=3',
|
||||||
|
|||||||
Reference in New Issue
Block a user