Handle browser-blocked status checks
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:
4
Brain.md
4
Brain.md
@@ -140,4 +140,6 @@ mischlabs/
|
||||
- Erlaubter Admin in der statischen UI:
|
||||
- `preferred_username=mrdiderot` oder `email=mail.misch@pm.me`
|
||||
- Aktuelle Grenze: Weil mischlabs noch rein statisch ueber Nginx laeuft, kann `/admin.html` globale Aenderungen noch nicht serverseitig speichern. Der Editor speichert lokal im Browser (`localStorage`) und kann eine neue `services.json` exportieren. Fuer echte Live-Aenderungen braucht der naechste Schritt ein kleines Backend oder einen Gitea-Commit-Workflow.
|
||||
- Statusdiagnose nutzt Browser-Fetch mit `mode: "no-cors"`. Dadurch erkennt sie Erreichbarkeit/Timeouts, aber bei fremden Subdomains keine echten HTTP-Statuscodes. Fuer echte Fehlerdetails braucht es ebenfalls ein Backend.
|
||||
- Statusdiagnose nutzt Browser-Fetch mit `mode: "no-cors"`. Dadurch erkennt sie Erreichbarkeit/Timeouts, aber bei fremden Subdomains keine echten HTTP-Statuscodes.
|
||||
- Wenn der Browser die Pruefung wegen CORS/CORB blockiert (`Failed to fetch`), wird der Dienst **nicht** mehr als offline markiert, sondern als "Nicht im Browser pruefbar". Das betraf z. B. Vaultwarden/`password.mischlabs.de`.
|
||||
- Fuer echte Fehlerdetails braucht es ein kleines Backend bzw. einen Statusproxy, der serverseitig `HEAD`/`GET` prueft.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>MischLabs Admin</title>
|
||||
<meta name="description" content="MischLabs dashboard administration.">
|
||||
<meta name="theme-color" content="#0f172a">
|
||||
<link rel="stylesheet" href="/style.css?v=10">
|
||||
<link rel="stylesheet" href="/style.css?v=11">
|
||||
</head>
|
||||
<body>
|
||||
<header class="shell hero admin-hero">
|
||||
@@ -57,6 +57,6 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/admin.js?v=1" defer></script>
|
||||
<script src="/admin.js?v=2" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
26
admin.js
26
admin.js
@@ -217,17 +217,20 @@ async function probe(url) {
|
||||
signal: controller.signal
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
status: 'online',
|
||||
ms: Math.round(performance.now() - started),
|
||||
detail: response.type === 'opaque'
|
||||
? 'Erreichbar. HTTP-Details wegen Browser-CORS nicht lesbar.'
|
||||
: `HTTP ${response.status}`
|
||||
};
|
||||
} catch (error) {
|
||||
const isTimeout = error.name === 'AbortError';
|
||||
return {
|
||||
ok: false,
|
||||
status: isTimeout ? 'offline' : 'unknown',
|
||||
ms: Math.round(performance.now() - started),
|
||||
detail: error.name === 'AbortError' ? 'Timeout nach 6 Sekunden.' : error.message
|
||||
detail: isTimeout
|
||||
? 'Timeout nach 6 Sekunden.'
|
||||
: 'Der Browser blockiert diese Pruefung wahrscheinlich wegen CORS/CORB. Der Dienst kann trotzdem online sein.'
|
||||
};
|
||||
} finally {
|
||||
window.clearTimeout(timeout);
|
||||
@@ -238,12 +241,20 @@ async function checkRow(row) {
|
||||
const service = config.services[Number(row.dataset.index)];
|
||||
const diagnostic = row.querySelector('.diagnostic');
|
||||
diagnostic.textContent = 'Pruefe...';
|
||||
row.classList.remove('is-online', 'is-offline');
|
||||
row.classList.remove('is-online', 'is-offline', 'is-unknown');
|
||||
|
||||
const result = await probe(service.url);
|
||||
row.classList.toggle('is-online', result.ok);
|
||||
row.classList.toggle('is-offline', !result.ok);
|
||||
diagnostic.textContent = `${result.ok ? 'Online' : 'Fehler'} - ${result.ms} ms - ${result.detail}`;
|
||||
row.classList.toggle('is-online', result.status === 'online');
|
||||
row.classList.toggle('is-offline', result.status === 'offline');
|
||||
row.classList.toggle('is-unknown', result.status === 'unknown');
|
||||
|
||||
const label = {
|
||||
online: 'Online',
|
||||
offline: 'Fehler',
|
||||
unknown: 'Nicht im Browser pruefbar'
|
||||
}[result.status];
|
||||
|
||||
diagnostic.textContent = `${label} - ${result.ms} ms - ${result.detail}`;
|
||||
}
|
||||
|
||||
function saveLocal() {
|
||||
@@ -315,4 +326,3 @@ downloadConfigButton.addEventListener('click', downloadConfig);
|
||||
boot().catch((error) => {
|
||||
loginMessage.textContent = `Adminseite konnte nicht starten: ${error.message}`;
|
||||
});
|
||||
|
||||
|
||||
17
app.js
17
app.js
@@ -154,13 +154,19 @@ async function probeService(card) {
|
||||
});
|
||||
card.classList.add('is-online');
|
||||
card.classList.remove('is-offline');
|
||||
card.classList.remove('is-unknown');
|
||||
card.querySelector('.status-dot').setAttribute('aria-label', 'Online');
|
||||
return true;
|
||||
} catch {
|
||||
card.classList.add('is-offline');
|
||||
} catch (error) {
|
||||
const isTimeout = error.name === 'AbortError';
|
||||
card.classList.toggle('is-offline', isTimeout);
|
||||
card.classList.toggle('is-unknown', !isTimeout);
|
||||
card.classList.remove('is-online');
|
||||
card.querySelector('.status-dot').setAttribute('aria-label', 'Nicht erreichbar');
|
||||
return false;
|
||||
card.querySelector('.status-dot').setAttribute(
|
||||
'aria-label',
|
||||
isTimeout ? 'Nicht erreichbar' : 'Im Browser nicht pruefbar'
|
||||
);
|
||||
return isTimeout ? false : null;
|
||||
} finally {
|
||||
window.clearTimeout(timeout);
|
||||
}
|
||||
@@ -169,11 +175,12 @@ async function probeService(card) {
|
||||
async function refreshStatus() {
|
||||
const results = await Promise.allSettled(cards.map(probeService));
|
||||
const online = results.filter((result) => result.status === 'fulfilled' && result.value).length;
|
||||
const unknown = results.filter((result) => result.status === 'fulfilled' && result.value === null).length;
|
||||
onlineCount.textContent = String(online);
|
||||
lastChecked.textContent = `Status geprueft: ${new Date().toLocaleTimeString('de-DE', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}`;
|
||||
})}${unknown ? `, ${unknown} im Browser nicht pruefbar` : ''}`;
|
||||
}
|
||||
|
||||
function scheduleStatusRefresh() {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<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="512x512" href="/icons/icon-512.png">
|
||||
<link rel="stylesheet" href="style.css?v=10">
|
||||
<link rel="stylesheet" href="style.css?v=11">
|
||||
</head>
|
||||
<body>
|
||||
<header class="shell hero">
|
||||
@@ -73,6 +73,6 @@
|
||||
<span id="lastChecked">Status wird im Hintergrund geprueft.</span>
|
||||
</footer>
|
||||
|
||||
<script src="/app.js?v=2" defer></script>
|
||||
<script src="/app.js?v=3" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MischLabs - Offline</title>
|
||||
<meta name="theme-color" content="#0f172a">
|
||||
<link rel="stylesheet" href="/style.css?v=10">
|
||||
<link rel="stylesheet" href="/style.css?v=11">
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell hero">
|
||||
|
||||
@@ -387,6 +387,11 @@ main {
|
||||
box-shadow: 0 0 0 4px rgba(251, 113, 133, 0.1);
|
||||
}
|
||||
|
||||
.service-card.is-unknown .status-dot {
|
||||
background: #38bdf8;
|
||||
box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.1);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
margin: 32px 0;
|
||||
padding: 18px;
|
||||
@@ -555,6 +560,10 @@ main {
|
||||
border-color: rgba(251, 113, 133, 0.36);
|
||||
}
|
||||
|
||||
.admin-row.is-unknown {
|
||||
border-color: rgba(56, 189, 248, 0.32);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
6
sw.js
6
sw.js
@@ -1,10 +1,10 @@
|
||||
const CACHE_NAME = 'mischlabs-pwa-v4';
|
||||
const CACHE_NAME = 'mischlabs-pwa-v5';
|
||||
const APP_SHELL = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/offline.html',
|
||||
'/style.css?v=10',
|
||||
'/app.js?v=2',
|
||||
'/style.css?v=11',
|
||||
'/app.js?v=3',
|
||||
'/services.json?v=1',
|
||||
'/manifest.webmanifest?v=3',
|
||||
'/icons/icon-192.png',
|
||||
|
||||
Reference in New Issue
Block a user