diff --git a/CHANGELOG.md b/CHANGELOG.md index 0662fcf..2b19ddf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/) — solan --- +## [0.1.33] - 2026-09-13 + +Offboarding-Report und Update-Hinweis. + +### Neu + +- **Offboarding-Report**: Nach dem Ausführen erzeugt das Offboarding einen **HTML-Report** (`/reports/offboard-report-.html`) im Stil des Zuweisungs-Reports — mit KPIs (Geräte / Aktionen / Erfolg / Fehler) und **pro Gerät** einer Tabelle der ausgeführten Löschungen (Dienst · Aktion · Status · Meldung). Das Ergebnis im Dialog ist jetzt ebenfalls **pro Gerät gruppiert**, plus ein Button „HTML-Report öffnen". +- **Update-Hinweis**: Beim Start prüft die App (nicht-blockierend) die neueste Release-Version über **Gitea** und zeigt bei einer neueren Version ein **Badge in der Kopfzeile** mit Link zur Release-Seite (dort MSI herunterladen → In-Place-Upgrade). Nur Lesezugriff; optionaler read-only Token via `settings.json → update.token`, falls das Repo privat ist. Neuer Endpoint `GET /api/update/check`. + +--- + ## [0.1.32] - 2026-09-11 Bulk-Offboarding aus dem Geräte-Tab, Geräte-Ladefehler behoben, bessere Graph-Diagnose. diff --git a/Start.ps1 b/Start.ps1 index 14387f0..9322456 100644 --- a/Start.ps1 +++ b/Start.ps1 @@ -189,7 +189,7 @@ $script:State = [pscustomobject]@{ Session = @() # geplante Zuweisungen } -$script:ToolVersion = "0.1.32" +$script:ToolVersion = "0.1.33" $script:BuildStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Write-Host "" diff --git a/installer/build-local.ps1 b/installer/build-local.ps1 index a1e6947..b24b731 100644 --- a/installer/build-local.ps1 +++ b/installer/build-local.ps1 @@ -12,7 +12,7 @@ if (Test-Path "$x64Dotnet\dotnet.exe") { $env:DOTNET_ROOT = "C:\Program Files\dotnet" } -$version = "0.1.32" +$version = "0.1.33" $src = "\\Mac\Home\ClaudePRJ\Intune Manager" $stage = "$env:TEMP\IntuneManagerStage" $installerDir = "$src\installer" diff --git a/src/Api.ps1 b/src/Api.ps1 index 95e2995..31289c4 100644 --- a/src/Api.ps1 +++ b/src/Api.ps1 @@ -21,6 +21,7 @@ function Invoke-ApiHandler { "POST /api/settings/test" { return Test-SettingsEndpoint -Body $Body } "POST /api/settings/logo" { return Save-LogoEndpoint -Body $Body } "DELETE /api/settings/logo" { return Remove-LogoEndpoint } + "GET /api/update/check" { return Get-UpdateCheckEndpoint } "POST /api/connect" { return Invoke-ConnectEndpoint } "POST /api/connect/token" { return Invoke-ConnectWithTokenEndpoint -Body $Body } "POST /api/connect/start" { return Start-DeviceCodeConnect } @@ -136,6 +137,65 @@ function Invoke-ApiHandler { return $null } +# ============================================================ +# Update-Check (neueste Release-Version von Gitea vergleichen) +# ============================================================ + +# Semver-Vergleich: 1 wenn A>B, -1 wenn A$ico $lbl" + + "$(_e $r.service)" + + "$(_e $r.action)" + + "$msg" + } + if (-not $rowsHtml) { $rowsHtml = "Keine ausgefuehrten Aktionen." } + $stats = "" + if ($okC -gt 0) { $stats += "$okC OK" } + if ($errC -gt 0) { $stats += "$errC Fehler" } + $devBlocks += "
" + + "$(_e $dn)$stats" + + "
" + + "" + + "$rowsHtml
StatusDienstAktionMeldung
" + } + if (-not $devBlocks) { $devBlocks = "
Keine Ergebnisse.
" } + + $html = @" + + + + + +Intune Offboarding-Report - $tsHuman + + + + + +
+
+
+
+ +
+
Intune Offboarding-Report
+
$tsHuman · ausgefuehrt von $(_e $user)
+
+
+
+"@ + if ($account) { $html += "Account $(_e $account)" } + if ($tenant) { $html += "Tenant $(_e $tenant)" } + $html += "Host $(_e $machine)" + $html += @" +
+
+ +
+ +
+
$devCount
Geraete
+
$Total
Aktionen
+
$Success
Erfolgreich
+
$Errors
Fehler
+
+ +
+
Angeforderte Dienste
+
$svcTxt
+
+ +
+
Ergebnisse pro Geraet
+
Jeder Block zeigt die ausgefuehrten Loeschungen/Aenderungen mit Status und Meldung.
+ $devBlocks +
+ +
+ Intune Manager · Web Edition v$(_e $script:ToolVersion) · Offboarding-Report +
+
+ + +"@ + [IO.File]::WriteAllText($path, $html, [System.Text.Encoding]::UTF8) + return $path +} diff --git a/src/Offboard.ps1 b/src/Offboard.ps1 index 521e269..c65156d 100644 --- a/src/Offboard.ps1 +++ b/src/Offboard.ps1 @@ -281,7 +281,19 @@ function Invoke-OffboardExecuteEndpoint { success = $ok; error = $errMsg } } - $okCount = @($results | Where-Object { $_.success }).Count + $okCount = @($results | Where-Object { $_.success }).Count + $errCount = $results.Count - $okCount Write-Host " -> $okCount/$($results.Count) erfolgreich" -ForegroundColor DarkGray - return @{ ok = $true; successCount = $okCount; total = $results.Count; results = @($results) } + + # HTML-Report erzeugen (pro Geraet die ausgefuehrten Loeschungen) und verlinken. + $reportUrl = "" + try { + $reportFile = New-OffboardHtmlReport -Devices $devices -Results $results -Success $okCount -Errors $errCount -Total $results.Count -EntraAction $entraAction -DoIntune $doIntune -DoAutopilot $doAutopilot + if ($reportFile) { $reportUrl = "/reports/" + (Split-Path $reportFile -Leaf) } + Write-Host "[OFFBOARD] HTML-Report: $reportFile" -ForegroundColor DarkGray + } catch { + Write-Host "[OFFBOARD] Report-Erstellung fehlgeschlagen: $($_.Exception.Message)" -ForegroundColor Red + } + + return @{ ok = $true; successCount = $okCount; total = $results.Count; results = @($results); reportUrl = [string]$reportUrl } } diff --git a/www/app.js b/www/app.js index ea1acdf..63946e7 100644 --- a/www/app.js +++ b/www/app.js @@ -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 => + `
${r.success ? '✓' : '✗'} ${escapeHtml(r.service)} ${escapeHtml(r.action)}${r.error ? ': ' + escapeHtml(r.error) : ''}
` + ).join(''); + return `
+
${escapeHtml(name || '—')}${errs ? ` ${errs} Fehler` : ''}
+ ${lines} +
`; + }).join(''); + const reportLink = res.reportUrl + ? ` + + HTML-Report öffnen + ` + : ''; document.getElementById('offResults').innerHTML = - `
Ergebnis (${res.successCount}/${res.total})
` + - results.map(r => `
${r.success ? '✓' : '✗'} ${escapeHtml(r.deviceName)} — ${escapeHtml(r.service)} ${escapeHtml(r.action)}${r.error ? ': ' + escapeHtml(r.error) : ''}
`).join(''); + `
Ergebnis (${res.successCount}/${res.total})
${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)); diff --git a/www/index.html b/www/index.html index 641155b..7c062a9 100644 --- a/www/index.html +++ b/www/index.html @@ -41,6 +41,10 @@
+ @@ -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; } diff --git a/www/styles.css b/www/styles.css index 09484f2..3df22ce 100644 --- a/www/styles.css +++ b/www/styles.css @@ -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;