v0.1.33 — Offboarding-Report und Update-Hinweis
Build & Release MSI / build-msi (push) Canceled after 0s

- Offboarding: HTML-Report pro Geraet (New-OffboardHtmlReport) + reportUrl;
  Dialog-Ergebnis pro Geraet gruppiert + Report-Link
- Update-Check: GET /api/update/check (Gitea releases/latest, Compare-SemVer);
  Kopfzeilen-Badge bei neuer Version, Link zur Release-Seite; optionaler read-only Token

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 15:59:30 +02:00
co-authored by Claude Opus 4.8
parent 968f47b94f
commit e4efb0ac32
8 changed files with 349 additions and 6 deletions
+42 -2
View File
@@ -4784,6 +4784,24 @@ document.getElementById('btnRestoreColors')?.addEventListener('click', () => {
// Init
// =============================================================
// Prueft (nicht-blockierend) auf eine neuere Release-Version und zeigt bei Bedarf
// das Update-Badge in der Kopfzeile mit Link zur Release-Seite.
async function checkForUpdate() {
try {
const res = await api('/api/update/check');
if (res && res.updateAvailable && res.releaseUrl) {
const badge = document.getElementById('updateBadge');
const txt = document.getElementById('updateBadgeText');
if (badge && txt) {
txt.textContent = `Update ${res.latest}`;
badge.href = res.releaseUrl;
badge.title = `Neue Version ${res.latest} verfügbar (aktuell ${res.current}) — Release-Seite öffnen`;
badge.classList.remove('hidden');
}
}
} catch (e) { /* still: Update-Check ist optional */ }
}
(async function init() {
renderConnection();
renderTargets();
@@ -4807,6 +4825,7 @@ document.getElementById('btnRestoreColors')?.addEventListener('click', () => {
}
await refreshStatus();
refreshTenantSwitcher();
checkForUpdate(); // nicht-blockierend
if (State.connected) autoLoadAfterConnect();
// First-Run: wenn kritische Felder leer sind, Settings-Modal automatisch oeffnen.
if (setupNeeded) {
@@ -7012,9 +7031,30 @@ async function offExecute() {
try {
const res = await api('/api/offboard/execute', { method: 'POST', body: { devices: payload, services }, timeoutMs: 180000 });
const results = res.results || [];
// Ergebnis pro Gerät gruppieren
const byDev = new Map();
for (const r of results) {
if (!byDev.has(r.deviceName)) byDev.set(r.deviceName, []);
byDev.get(r.deviceName).push(r);
}
const devHtml = [...byDev.entries()].map(([name, rows]) => {
const errs = rows.filter(r => !r.success).length;
const lines = rows.map(r =>
`<div class="off-result ${r.success ? 'ok' : 'fail'}">${r.success ? '✓' : '✗'} ${escapeHtml(r.service)} ${escapeHtml(r.action)}${r.error ? ': ' + escapeHtml(r.error) : ''}</div>`
).join('');
return `<div class="off-result-dev">
<div class="off-result-devname">${escapeHtml(name || '—')}${errs ? ` <span class="off-result-badge">${errs} Fehler</span>` : ''}</div>
${lines}
</div>`;
}).join('');
const reportLink = res.reportUrl
? `<a href="${escapeHtml(res.reportUrl)}" target="_blank" rel="noopener" class="btn btn-secondary btn-sm" style="margin-top:12px;display:inline-flex;align-items:center;gap:6px;">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg>
HTML-Report öffnen
</a>`
: '';
document.getElementById('offResults').innerHTML =
`<div class="lbl">Ergebnis (${res.successCount}/${res.total})</div>` +
results.map(r => `<div class="off-result ${r.success ? 'ok' : 'fail'}">${r.success ? '✓' : '✗'} ${escapeHtml(r.deviceName)} — ${escapeHtml(r.service)} ${escapeHtml(r.action)}${r.error ? ': ' + escapeHtml(r.error) : ''}</div>`).join('');
`<div class="lbl">Ergebnis (${res.successCount}/${res.total})</div>${devHtml}${reportLink}`;
toast(`${res.successCount}/${res.total} Aktionen erfolgreich.`, res.successCount === res.total ? 'ok' : 'warn', 'Offboarding');
// Erfolgreich vollständig offgeboardete Geräte aus der Liste nehmen.
const doneNames = new Set(results.filter(r => r.success).map(r => r.deviceName));
+7
View File
@@ -41,6 +41,10 @@
</nav>
<div class="header-actions">
<a id="updateBadge" class="update-badge hidden" href="#" target="_blank" rel="noopener" title="Update verfügbar — Release-Seite öffnen">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-3-6.7"/><polyline points="21 3 21 9 15 9"/></svg>
<span id="updateBadgeText">Update</span>
</a>
<button id="btnSettings" class="icon-btn icon-btn-header" title="Einstellungen" aria-label="Einstellungen öffnen">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.86l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.86-.34 1.7 1.7 0 0 0-1.04 1.57V21a2 2 0 0 1-4 0v-.09a1.7 1.7 0 0 0-1.1-1.57 1.7 1.7 0 0 0-1.86.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.7 1.7 0 0 0 .34-1.86 1.7 1.7 0 0 0-1.57-1.04H3a2 2 0 0 1 0-4h.09A1.7 1.7 0 0 0 4.66 9a1.7 1.7 0 0 0-.34-1.86l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.7 1.7 0 0 0 1.86.34H9a1.7 1.7 0 0 0 1.04-1.57V3a2 2 0 0 1 4 0v.09c0 .68.42 1.29 1.04 1.57.62.27 1.36.13 1.86-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.7 1.7 0 0 0-.34 1.86V9a1.7 1.7 0 0 0 1.57 1.04H21a2 2 0 0 1 0 4h-.09a1.7 1.7 0 0 0-1.51 1z"/></svg>
</button>
@@ -1070,6 +1074,9 @@
.off-result { font-size:13px; padding:3px 0; overflow-wrap:anywhere; }
.off-result.ok { color:var(--success); }
.off-result.fail { color:var(--error); }
.off-result-dev { border:1px solid var(--hairline-soft, var(--hairline)); border-radius:8px; padding:8px 12px; margin-bottom:8px; }
.off-result-devname { font-weight:600; font-size:13px; margin-bottom:4px; display:flex; align-items:center; gap:8px; }
.off-result-badge { font-size:10.5px; font-weight:700; padding:1px 8px; border-radius:999px; background:rgba(245,80,80,0.18); color:var(--error); }
#offResults .lbl { display:block; font-size:12px; color:var(--body); margin:10px 0 4px; }
</style>
+18
View File
@@ -335,6 +335,24 @@ h1, h2, h3 {
.header-actions { display: flex; align-items: center; gap: 12px; }
/* Update-Hinweis-Badge (neue Version verfuegbar) */
.update-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 11px;
border-radius: 999px;
background: var(--accent-soft);
color: var(--brand-accent);
border: 1px solid var(--brand-accent);
font-size: 12px;
font-weight: 600;
text-decoration: none;
white-space: nowrap;
transition: filter var(--tx, 0.15s ease-out);
}
.update-badge:hover { filter: brightness(0.96); }
/* Theme-Toggle — Slide-Switch */
.theme-toggle {
appearance: none;