Files
Intune-Manager/www/index.html
T
marcoandClaude Opus 4.8 ea51bac772
Build & Release MSI / build-msi (push) Canceled after 0s
Policies-Tab: Export/Import (Settings Catalog, Compliance, Config)
Neuer Tab "Policies" zum Sichern und Wiederherstellen von Intune-Policies.

- Backend src/PolicyIO.ps1: typ-getrieben fuer configurationPolicies
  (Settings Catalog), deviceCompliancePolicies und deviceConfigurations
- Export als JSON-Download (einzeln/Bundle) + Server-Archiv unter %APPDATA%
- Import per Datei-Upload, immer Neuanlage (nie ueberschreiben), robust fuer
  Hashtable (PS7) und PSCustomObject (PS5.1); im Read-Only-Modus ausgeblendet
- Routen in src/Api.ps1; Config-Scope DeviceManagementConfiguration.* auch
  nach Settings-Save sichergestellt
- Frontend: Tab + View, Suche/Typ-Filter/Sortierung, Auswahl, Download/Upload
- Version 0.1.25 (Start.ps1, CHANGELOG, README)
- build-local.ps1: x64-.NET-Runtime fuer wix auf ARM64-Windows + echter
  Desktop-Pfad

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 14:25:29 +02:00

1182 lines
63 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Intune Manager</title>
<script>
// Theme vor erstem Render anwenden — verhindert Flash
(function () {
try {
var saved = localStorage.getItem('iam-theme');
var theme = saved || (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
} catch (e) { /* localStorage-Block */ }
})();
</script>
<link rel="stylesheet" href="/styles.css">
<link rel="stylesheet" href="/vendor/flatpickr.min.css">
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='22' fill='%23111111'/%3E%3Ctext x='50' y='70' font-size='62' text-anchor='middle' fill='white' font-family='Inter,Arial' font-weight='600'%3EI%3C/text%3E%3C/svg%3E">
</head>
<body>
<!-- HEADER -->
<header class="topbar">
<div class="brand">
<div id="brandLogo" class="logo"><span class="logo-letter">I</span></div>
<div class="brand-text">
<div class="brand-name">Intune Manager</div>
</div>
</div>
<nav class="topbar-nav" id="topbarNav" aria-label="Hauptnavigation">
<button class="nav-tab active" data-view="apps" aria-selected="true">App Management</button>
<button class="nav-tab" data-view="groups" aria-selected="false">Group Management</button>
<button class="nav-tab" data-view="report" aria-selected="false">App Report</button>
<button class="nav-tab" data-view="devices" aria-selected="false">Geräte</button>
<button class="nav-tab" data-view="policies" aria-selected="false">Policies</button>
</nav>
<div class="header-actions">
<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>
<button id="btnHelp" class="icon-btn icon-btn-header" title="Hilfe" aria-label="Hilfe ö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="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
</button>
<button id="btnThemeToggle" class="theme-toggle" role="switch" aria-checked="false" title="Dark/Light Mode umschalten" aria-label="Dark/Light Mode umschalten">
<span class="theme-track">
<svg class="theme-ico theme-ico-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
<svg class="theme-ico theme-ico-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<span class="theme-knob"></span>
</span>
</button>
<div id="connStatus" class="conn-pill conn-off" title="Verbindungsstatus">
<span class="dot"></span>
<span class="conn-text">Offline</span>
</div>
<button id="btnDisconnect" class="icon-btn icon-btn-header hidden" title="Trennen" aria-label="Verbindung trennen">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
</button>
</div>
</header>
<!-- ONBOARDING (MSAL.js Browser-Popup) -->
<div id="onboarding" class="onboarding hidden">
<div class="onboarding-card">
<!-- View 1: idle -->
<div data-onboarding-view="idle">
<div class="onboarding-icon">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
<path d="M12 2 L4 6 v6 c0 5 3.5 9 8 10 c4.5-1 8-5 8-10 V6 z"/>
<path d="M9 12 l2 2 l4-4"/>
</svg>
</div>
<h1 class="onboarding-title">Mit Microsoft anmelden</h1>
<p class="onboarding-text">
Es öffnet sich ein Microsoft-Anmeldefenster. Wähle den gewünschten Account aus.
</p>
<div id="onboardingSetupHint" class="banner-warning hidden">
Konfiguration unvollständig — bitte zuerst <a href="#" id="onboardingOpenSettings">Einstellungen</a> öffnen und Tenant ID, Client ID und Abteilungs-Präfix setzen.
</div>
<button id="btnConnect" class="btn btn-primary btn-lg">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M11.4 2.4H2.4v9h9v-9zm10.2 0h-9v9h9v-9zm-10.2 10.2H2.4v9h9v-9zm10.2 0h-9v9h9v-9z"/></svg>
Anmeldung starten
</button>
<div class="onboarding-meta">
Lese- und Zuweisungs-Rechte für Intune-Apps und Gruppenmitgliedschaften.
</div>
</div>
<!-- View 2: waiting -->
<div data-onboarding-view="awaiting" class="hidden">
<div class="dc-spinner-wrap">
<div class="spinner"></div>
</div>
<h2 class="onboarding-title">Anmeldung läuft</h2>
<p class="onboarding-text">
Bitte im Microsoft-Anmeldefenster fortfahren. Wir holen es automatisch in den Vordergrund —
falls es trotzdem im Hintergrund liegt, schau in der Taskleiste.
</p>
<button id="btnCancelConnect" class="btn btn-text">Abbrechen</button>
</div>
<!-- View 3: error -->
<div data-onboarding-view="error" class="hidden">
<div class="onboarding-icon onboarding-icon-error">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg>
</div>
<h2 class="onboarding-title">Anmeldung fehlgeschlagen</h2>
<p class="onboarding-text" id="dcError">Unbekannter Fehler.</p>
<button id="btnRetryConnect" class="btn btn-primary btn-lg">Erneut versuchen</button>
</div>
</div>
</div>
<!-- MAIN -->
<main class="layout" id="layout" data-view="apps">
<!-- LEFT: TARGETS -->
<section class="panel panel-targets">
<div class="panel-head">
<div class="panel-title">
<h2>Abteilungen &amp; Benutzer</h2>
<span id="targetCount" class="count-badge">0</span>
</div>
<button id="btnReloadTargets" class="icon-btn" title="Neu laden" aria-label="Neu laden">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/></svg>
</button>
</div>
<div class="mode-segment" role="tablist">
<button class="seg active" data-mode="dept" role="tab">Abteilung</button>
<button class="seg" data-mode="rpa" role="tab">RPA</button>
<button class="seg" data-mode="user" role="tab">Benutzer</button>
</div>
<div class="search-wrap">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="targetSearch" placeholder="Filtern..." class="input input-search" autocomplete="off">
<span id="userSearchHint" class="search-hint hidden">min. 2 Zeichen</span>
</div>
<div class="select-toolbar">
<div class="select-actions">
<button id="btnTargetsSelectAll" class="link-btn">Alle</button>
<span class="dotsep">·</span>
<button id="btnTargetsClear" class="link-btn">Leeren</button>
</div>
</div>
<div id="pinnedTargets" class="pinned-targets hidden" aria-label="Aktuelle Auswahl">
<!-- populated by JS -->
</div>
<div id="targetList" class="list-area target-list">
<!-- populated by JS -->
</div>
</section>
<!-- CENTER: APPS -->
<section class="panel panel-apps">
<div class="panel-head">
<div class="panel-title">
<h2>Apps</h2>
<span id="appCount" class="count-badge">0</span>
</div>
<div class="panel-head-actions">
<button id="btnReloadApps" class="icon-btn" title="Neu laden (Cache umgehen)" aria-label="Neu laden">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/></svg>
</button>
</div>
</div>
<div class="apps-toolbar">
<div class="search-wrap flex-1">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="appSearch" placeholder="Apps durchsuchen..." class="input input-search" autocomplete="off">
</div>
<select id="appFilter" class="select">
<option value="all">Alle Apps</option>
<option value="none">Ohne Zuweisungen</option>
<option value="available">Nur Available</option>
<option value="required">Nur Required</option>
<option value="multipleAvail">Mehrere Available</option>
<option value="multipleReq">Mehrere Required</option>
<option value="allUsers">All Users</option>
<option value="allDevices">All Devices</option>
<option value="srcIntune">Nur Intune-Apps</option>
<!-- Vendor-Optionen werden dynamisch von rebuildVendorFilterOptions() ergaenzt -->
</select>
<select id="appCategoryFilter" class="select" title="Nach App-Kategorie filtern">
<option value="">Alle Kategorien</option>
<!-- Kategorie-Optionen werden dynamisch von rebuildCategoryFilterOptions() befuellt -->
</select>
</div>
<div class="filter-chips">
<label class="check-chip">
<input type="checkbox" id="chkOnlyMember">
<span>Bereits Gruppenmitglied</span>
</label>
<label class="check-chip">
<input type="checkbox" id="chkHideNative">
<span>All Users/Devices ausblenden</span>
</label>
<div class="date-filter-chip" id="dateFilterChip">
<span class="date-filter-label" title="Nur Apps anzeigen die im angegebenen Zeitraum geändert wurden">Geändert</span>
<input type="date" id="dateModFrom" class="date-input" title="Von (inkl.)">
<span class="date-sep">–</span>
<input type="date" id="dateModTo" class="date-input" title="Bis (inkl.)">
<button type="button" id="dateModClear" class="date-clear" hidden title="Datum-Filter zurücksetzen">×</button>
</div>
<div class="legend-group">
<span class="legend-item" title="Alle ausgewählten Empfänger sind bereits Mitglied"><span class="row-status full">✓</span> bereits zugewiesen</span>
<span class="legend-item" title="Teilweise Mitglied"><span class="row-status partial">◐</span> teilweise</span>
</div>
</div>
<div id="appList" class="list-area apps-list">
<!-- populated by JS -->
</div>
</section>
<!-- RIGHT: SESSION -->
<aside class="panel panel-side">
<div class="panel-head">
<div class="panel-title">
<h2>Geplante Zuweisungen</h2>
<span id="sessionCount" class="count-badge accent">0</span>
</div>
</div>
<div class="side-pane" id="sidePaneSession">
<div id="sessionList" class="session-list">
<!-- populated by JS -->
</div>
<div class="session-summary">
<div class="summary-section">
<div class="summary-section-title">Ausgewählte Empfänger</div>
<div class="summary-row sub">
<span class="muted">Abteilungsgruppen</span>
<span id="sumGroupsSel">0</span>
</div>
<div class="summary-row sub">
<span class="muted">Einzelbenutzer</span>
<span id="sumUsersSel">0</span>
</div>
</div>
<div class="summary-section">
<div class="summary-section-title">Zuweisungen erhalten</div>
<div class="summary-row sub">
<span class="muted">Abteilungsgruppen</span>
<span id="sumGroupsOps">0</span>
</div>
<div class="summary-row sub">
<span class="muted">Einzelbenutzer</span>
<span id="sumUsersOps">0</span>
</div>
</div>
<div class="summary-row total">
<span>Gesamt-Vorgänge</span>
<span id="sumTotal">0</span>
</div>
</div>
<button id="btnApply" class="btn btn-apply" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M5 12l5 5L20 7"/></svg>
<span id="btnApplyLabel">Zuweisungen ausführen</span>
</button>
<button id="btnSessionClear" class="link-btn link-btn-danger" hidden>Geplante Zuweisungen leeren</button>
</div>
<div class="side-pane hidden" id="sidePaneExisting">
<div class="side-pane-hint">Apps, in denen die ausgewählten Empfänger bereits Mitglied einer zugewiesenen Gruppe sind.</div>
<div id="existingList" class="existing-list">
<!-- populated by JS -->
</div>
</div>
</aside>
</main>
<!-- GROUP MANAGEMENT VIEW -->
<section class="group-mgmt-view hidden" id="groupMgmtView">
<div class="gex-body group-mgmt-body">
<!-- Tab-Leiste -->
<div class="gex-tab-bar group-mgmt-tabs">
<button class="gex-tab active" data-tab="export">Mitglieder &amp; Export</button>
<button class="gex-tab gex-add-pane" data-tab="add">Benutzer hinzufügen</button>
<button class="gex-tab gex-add-pane" data-tab="import">CSV-Import</button>
<button class="gex-tab" data-tab="user">Benutzer</button>
</div>
<div class="gex-columns group-mgmt-columns">
<!-- Linke Spalte: Gruppensuche -->
<div class="gex-left" id="gexLeft">
<div class="gex-section-title">Gruppe suchen</div>
<div class="search-wrap" style="margin-bottom:10px;">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="gexSearch" class="input input-search" placeholder="Gruppenname eingeben…" autocomplete="off">
</div>
<div id="gexSearchResults" class="gex-results">
<div class="gex-hint">Mindestens 2 Zeichen eingeben.</div>
</div>
</div>
<!-- Rechte Spalte: Export-Tab -->
<div class="gex-right" id="gexPaneExport">
<div class="gex-section-title" id="gexMembersTitle">Mitglieder</div>
<div class="search-wrap" style="margin-bottom:10px;">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="gexMemberFilter" class="input input-search" placeholder="Mitglieder filtern…" autocomplete="off">
</div>
<div id="gexMembersArea" class="gex-members-area">
<div class="gex-hint">Gruppe auswählen um Mitglieder zu laden.</div>
</div>
<div class="group-mgmt-foot">
<span id="gexStats" class="muted" style="margin-right:auto;"></span>
<button class="btn btn-danger btn-sm" id="gexRemoveBtn" disabled style="display:none;">Aus Gruppe entfernen</button>
<button class="btn btn-primary" id="gexExportBtn" disabled>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Als CSV exportieren
</button>
</div>
</div>
<!-- Rechte Spalte: CSV-Import -->
<div class="gex-right hidden" id="gexPaneImport">
<div class="gex-section-title">UPNs einfügen oder CSV hochladen</div>
<div class="csv-import-input-row">
<textarea id="csvImportText" class="csv-import-textarea" placeholder="Eine UPN/E-Mail pro Zeile, z.B.&#10;max.mustermann@firma.de&#10;erika.muster@firma.de&#10;&#10;Oder erste Spalte einer CSV (Header wird erkannt)."></textarea>
<label class="csv-upload-label" title="CSV-Datei auswählen">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
CSV laden
<input type="file" id="csvImportFile" accept=".csv,.txt" style="display:none">
</label>
</div>
<div class="csv-import-actions">
<button class="btn btn-secondary btn-sm" id="csvResolveBtn">Benutzer auflösen</button>
<span id="csvResolveStats" class="muted" style="font-size:12px;"></span>
</div>
<!-- Vorschau-Tabelle -->
<div id="csvPreviewWrap" class="csv-preview-wrap hidden">
<div class="gex-section-title" style="margin-top:10px;">
Vorschau
<span id="csvFoundBadge" class="count-badge" style="background:var(--success,#4ade80);color:#000;margin-left:6px;">0 gefunden</span>
<span id="csvNotFoundBadge" class="count-badge" style="background:var(--error,#f87171);color:#fff;margin-left:4px;">0 nicht gefunden</span>
</div>
<div class="csv-preview-table-wrap">
<table class="csv-preview-table">
<thead><tr><th></th><th>UPN / E-Mail</th><th>Name</th><th>Status</th></tr></thead>
<tbody id="csvPreviewBody"></tbody>
</table>
</div>
</div>
<!-- Ergebnis-Banner (nach Import) -->
<div id="csvResultBanner" class="csv-result-banner hidden">
<div class="csv-result-stats">
<span id="csvResAdded" class="csv-res-chip csv-res-ok">0 hinzugefügt</span>
<span id="csvResExist" class="csv-res-chip csv-res-warn">0 bereits Mitglied</span>
<span id="csvResError" class="csv-res-chip csv-res-err">0 Fehler</span>
<span id="csvResSkipped" class="csv-res-chip csv-res-dim">0 nicht gefunden</span>
</div>
</div>
<div class="group-mgmt-foot">
<span class="muted" id="csvImportNote" style="font-size:11px;">Gruppe links auswählen, dann Benutzer auflösen.</span>
<button class="btn btn-primary" id="csvImportBtn" disabled>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" y1="8" x2="19" y2="14"/><line x1="22" y1="11" x2="16" y2="11"/></svg>
Gefundene importieren
</button>
</div>
</div>
<!-- Rechte Spalte: Benutzer hinzufügen -->
<div class="gex-right hidden" id="gexPaneAdd">
<div class="gex-section-title">Benutzer suchen</div>
<div class="search-wrap" style="margin-bottom:10px;">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="gexUserSearch" class="input input-search" placeholder="Name, UPN oder E-Mail…" autocomplete="off">
</div>
<div id="gexUserResults" class="gex-results gex-add-results">
<div class="gex-hint">Mindestens 2 Zeichen eingeben.</div>
</div>
<div class="gex-section-title" style="margin-top:12px;">
Ausgewählt <span id="gexSelectedCount" class="count-badge">0</span>
</div>
<div id="gexSelectedUsers" class="gex-selected-area">
<div class="gex-hint">Noch keine Benutzer ausgewählt.</div>
</div>
<div class="group-mgmt-foot">
<button class="btn btn-primary" id="gexAddBtn" disabled>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" y1="8" x2="19" y2="14"/><line x1="22" y1="11" x2="16" y2="11"/></svg>
Zu Gruppe hinzufügen
</button>
</div>
</div>
<!-- Benutzer-Tab: User → Gruppen -->
<div class="gex-right hidden" id="gexPaneUser">
<div class="gex-section-title">Benutzer suchen</div>
<div class="search-wrap" style="margin-bottom:10px;">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="gexUmSearch" class="input input-search" placeholder="Name, UPN oder E-Mail…" autocomplete="off">
</div>
<div id="gexUmResults" class="gex-results" style="max-height:160px;overflow-y:auto;margin-bottom:12px;">
<div class="gex-hint">Mindestens 2 Zeichen eingeben.</div>
</div>
<div class="gex-section-title" id="gexUmGroupsTitle" style="display:none;">
Mitglied in <span id="gexUmGroupCount" class="count-badge">0</span> Gruppen
</div>
<div class="search-wrap" id="gexUmFilterWrap" style="display:none;margin-bottom:8px;">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="gexUmFilter" class="input input-search" placeholder="Gruppen filtern…" autocomplete="off">
</div>
<div id="gexUmGroupsList" class="gex-members-area" style="flex:1;overflow-y:auto;"></div>
</div>
</div>
</div>
</section>
<!-- APP REPORT VIEW -->
<section class="app-report-view hidden" id="appReportView">
<div class="app-report-toolbar">
<div class="search-wrap">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="reportSearch" class="input input-search" placeholder="App suchen…" autocomplete="off">
</div>
<span id="reportCount" class="count-badge">0</span>
<button class="btn btn-secondary btn-sm" id="btnReportRefresh" title="Daten neu laden">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/></svg>
Neu laden
</button>
<button class="btn btn-primary btn-sm" id="btnReportExport" disabled>
<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 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Als CSV exportieren
</button>
</div>
<div class="app-report-table-wrap">
<table class="app-report-table" id="reportTable">
<thead>
<tr>
<th class="sortable" data-col="AppName">App-Name <span class="sort-ico"></span></th>
<th class="sortable" data-col="Publisher">Publisher <span class="sort-ico"></span></th>
<th class="sortable" data-col="AppType">Typ <span class="sort-ico"></span></th>
<th class="sortable" data-col="Version">Version <span class="sort-ico"></span></th>
<th class="sortable num" data-col="InstalledDeviceCount">Installiert <span class="sort-ico"></span></th>
<th class="sortable num" data-col="PendingInstallDeviceCount">Ausstehend <span class="sort-ico"></span></th>
<th class="sortable num" data-col="FailedDeviceCount">Fehlgeschlagen <span class="sort-ico"></span></th>
<th class="sortable num" data-col="NotApplicableDeviceCount">Nicht anwendbar <span class="sort-ico"></span></th>
<th class="report-th-export" title="Gerätestatus-Export"></th>
</tr>
</thead>
<tbody id="reportBody">
<tr><td colspan="9" class="report-hint">Klicke auf „Neu laden" um Daten zu laden.</td></tr>
</tbody>
</table>
</div>
</section>
<!-- GERÄTE VIEW -->
<section class="dev-view hidden" id="devView">
<div style="display:flex;align-items:center;gap:8px;padding:10px 16px;border-bottom:1px solid var(--hairline-soft);flex-shrink:0;">
<div style="position:relative;display:flex;align-items:center;flex:1;min-width:0;">
<svg style="position:absolute;left:10px;color:var(--muted);pointer-events:none;" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="devSearch" style="width:100%;box-sizing:border-box;padding:0 12px 0 34px;height:34px;background:var(--canvas);border:1px solid var(--hairline);border-radius:6px;color:var(--ink);font-size:13px;font-family:inherit;" placeholder="Gerätename, Benutzer, Seriennummer…" autocomplete="off">
</div>
<span id="devCount" class="count-badge">0</span>
<select id="devOsFilter" style="height:34px;padding:0 8px;border:1px solid var(--hairline);border-radius:6px;background:var(--canvas);color:var(--ink);font-size:12.5px;cursor:pointer;">
<option value="">Alle OS</option>
<option value="Windows">Windows</option>
<option value="macOS">macOS</option>
<option value="iOS">iOS</option>
<option value="Android">Android</option>
</select>
<select id="devComplianceFilter" style="height:34px;padding:0 8px;border:1px solid var(--hairline);border-radius:6px;background:var(--canvas);color:var(--ink);font-size:12.5px;cursor:pointer;">
<option value="">Alle Compliance</option>
<option value="compliant">Compliant</option>
<option value="noncompliant">Non-Compliant</option>
<option value="unknown">Unbekannt</option>
</select>
</div>
<div class="dev-layout">
<!-- Geräteliste -->
<div class="dev-list-wrap">
<table class="dev-table" id="devTable">
<thead>
<tr>
<th class="sortable" data-col="deviceName">Gerät <span class="sort-ico"></span></th>
<th class="sortable" data-col="userDisplayName">Benutzer <span class="sort-ico"></span></th>
<th class="sortable" data-col="operatingSystem">OS <span class="sort-ico"></span></th>
<th class="sortable" data-col="complianceState">Compliance <span class="sort-ico"></span></th>
<th class="sortable" data-col="lastSyncDateTime">Letzter Sync <span class="sort-ico"></span></th>
</tr>
</thead>
<tbody id="devTableBody"></tbody>
</table>
<div id="devEmpty" class="dev-empty hidden">Keine Geräte gefunden.</div>
<div id="devLoading" class="dev-loading hidden">
<span class="spinner"></span> Lade Geräte…
</div>
</div>
<!-- Detail-Panel -->
<div class="dev-detail hidden" id="devDetail">
<div class="dev-detail-header">
<div class="dev-detail-name" id="devDetailName">—</div>
<div class="dev-detail-sub" id="devDetailSub">—</div>
</div>
<div class="dev-detail-body">
<dl class="dev-info-grid" id="devInfoGrid"></dl>
</div>
<div class="dev-actions" id="devActions">
<div class="dev-actions-title">Aktionen</div>
<div class="dev-action-btns">
<button class="btn btn-secondary btn-sm dev-action-btn" data-action="syncDevice" title="Richtlinien & Apps jetzt abrufen">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/></svg>
Sync
</button>
<button class="btn btn-secondary btn-sm dev-action-btn" data-action="rebootNow" title="Gerät neu starten">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
Neustart
</button>
<button class="btn btn-secondary btn-sm dev-action-btn" data-action="remoteLock" title="Gerät sperren">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
Sperren
</button>
<button class="btn btn-secondary btn-sm dev-action-btn" data-action="collectDiagnostics" title="Diagnose-Daten sammeln">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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"/><polyline points="10 9 9 9 8 9"/></svg>
Diagnose
</button>
<button class="btn btn-secondary btn-sm dev-action-btn" data-action="rotateBitLockerKeys" title="BitLocker-Schlüssel rotieren" id="devBtnBitlocker">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/><circle cx="12" cy="16" r="1"/></svg>
BitLocker
</button>
<button class="btn btn-danger btn-sm dev-action-btn" data-action="autopilotReset" title="Windows Autopilot Reset (Gerät wird zurückgesetzt, bleibt aber enrolliert)" id="devBtnAutopilot">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></svg>
Autopilot Reset
</button>
<button class="btn btn-danger btn-sm dev-action-btn" data-action="retire" title="MDM-Profil entfernen (Retire)">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>
Retire
</button>
<button class="btn btn-danger btn-sm dev-action-btn" data-action="wipe" title="Gerät vollständig zurücksetzen (Wipe)">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>
Wipe
</button>
</div>
</div>
</div>
</div>
</section>
<!-- POLICIES VIEW -->
<section class="pol-view hidden" id="policiesView">
<div class="pol-toolbar">
<div class="search-wrap">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="polSearch" class="input input-search" placeholder="Policy suchen…" autocomplete="off">
</div>
<span id="polCount" class="count-badge">0</span>
<select id="polTypeFilter" class="input" style="height:34px;max-width:220px;">
<option value="">Alle Typen</option>
<option value="settingscatalog">Settings Catalog</option>
<option value="compliance">Compliance</option>
<option value="configuration">Konfigurationsprofil</option>
</select>
<span class="pol-toolbar-spacer" style="flex:1;"></span>
<button class="btn btn-secondary btn-sm" id="btnPolRefresh" title="Policies neu laden">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-3-6.7L21 8"/><path d="M21 3v5h-5"/></svg>
Neu laden
</button>
<button class="btn btn-secondary btn-sm pol-write" id="btnPolImport" title="Policies aus JSON-Datei(en) importieren (Neuanlage)">
<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 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
Import…
</button>
<input type="file" id="polImportFile" accept=".json,application/json" multiple style="display:none;">
<button class="btn btn-primary btn-sm" id="btnPolExport" disabled title="Ausgewählte Policies als JSON exportieren">
<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 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Export (<span id="polSelCount">0</span>)
</button>
</div>
<div class="pol-table-wrap">
<table class="pol-table" id="polTable">
<thead>
<tr>
<th class="pol-col-check"><input type="checkbox" id="polCheckAll" title="Alle auswählen"></th>
<th class="sortable" data-col="name">Name <span class="sort-ico"></span></th>
<th class="sortable" data-col="typeLabel">Typ <span class="sort-ico"></span></th>
<th class="sortable" data-col="platform">Plattform <span class="sort-ico"></span></th>
<th class="sortable" data-col="lastModifiedDateTime">Geändert <span class="sort-ico"></span></th>
</tr>
</thead>
<tbody id="polBody">
<tr><td colspan="5" class="pol-hint">Klicke auf „Neu laden" um Policies zu laden.</td></tr>
</tbody>
</table>
</div>
</section>
<!-- MODAL: Wipe -->
<div class="modal-backdrop hidden" id="wipeModalBackdrop">
<div class="modal" style="max-width:420px;">
<div class="modal-header">
<span class="modal-title">Gerät zurücksetzen (Wipe)</span>
<button class="modal-close" id="wipeModalClose">&times;</button>
</div>
<div class="modal-body" style="padding:16px 20px;">
<p style="margin:0 0 12px;font-size:13.5px;">ACHTUNG: Das Gerät <strong id="wipeDeviceName"></strong> wird vollständig zurückgesetzt und aus dem Enrollment entfernt.</p>
<label style="display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;">
<input type="checkbox" id="wipeKeepUserData">
Benutzerdaten behalten (nur bei Windows Fresh Start)
</label>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="wipeCancel">Abbrechen</button>
<button class="btn btn-danger" id="wipeConfirm">Jetzt zurücksetzen</button>
</div>
</div>
</div>
<!-- TOAST CONTAINER -->
<div id="toasts" class="toasts"></div>
<!-- LOADING OVERLAY -->
<div id="loading" class="loading hidden">
<div class="loading-card">
<div class="spinner"></div>
<div id="loadingText" class="loading-text">Bitte warten...</div>
</div>
</div>
<!-- MODAL: Create Required Group -->
<div id="modalCreateGroup" class="modal hidden">
<div class="modal-backdrop" data-close></div>
<div class="modal-box modal-lg">
<div class="modal-head">
<h3>Zuweisung hinzufügen</h3>
<button class="modal-close" data-close aria-label="Schließen">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="lbl" for="cgAppName">App</label>
<input type="text" id="cgAppName" class="input" readonly>
</div>
<div class="form-group">
<label class="lbl">Typ der Zuweisung</label>
<div class="cg-intent-grid">
<div class="cg-intent-col cg-intent-required">
<div class="cg-intent-head">
<span class="cg-intent-badge req">REQUIRED</span>
<span class="cg-intent-sub">App wird zwingend installiert</span>
</div>
<label class="cg-mode">
<input type="radio" name="cgMode" value="req-group" checked>
<span class="cg-mode-body">
<span class="cg-mode-title">Neue Gruppe</span>
<span class="cg-mode-desc">Neue Required-Gruppe nach Naming-Schema erstellen und zuweisen.</span>
</span>
</label>
<label class="cg-mode">
<input type="radio" name="cgMode" value="req-allusers">
<span class="cg-mode-body">
<span class="cg-mode-title">All Users</span>
<span class="cg-mode-desc">Native Required-Zuweisung an alle lizenzierten Benutzer.</span>
</span>
</label>
<label class="cg-mode">
<input type="radio" name="cgMode" value="req-alldevices">
<span class="cg-mode-body">
<span class="cg-mode-title">All Devices</span>
<span class="cg-mode-desc">Native Required-Zuweisung an alle Geräte des Tenants.</span>
</span>
</label>
</div>
<div class="cg-intent-col cg-intent-available">
<div class="cg-intent-head">
<span class="cg-intent-badge avail">AVAILABLE</span>
<span class="cg-intent-sub">Selbstinstallation via Firmenportal</span>
</div>
<label class="cg-mode">
<input type="radio" name="cgMode" value="avail-group">
<span class="cg-mode-body">
<span class="cg-mode-title">Neue Gruppe</span>
<span class="cg-mode-desc">Neue Available-Gruppe nach Naming-Schema erstellen und zuweisen.</span>
</span>
</label>
<label class="cg-mode">
<input type="radio" name="cgMode" value="avail-allusers">
<span class="cg-mode-body">
<span class="cg-mode-title">All Users</span>
<span class="cg-mode-desc">App erscheint im Firmenportal aller Benutzer.</span>
</span>
</label>
<label class="cg-mode">
<input type="radio" name="cgMode" value="avail-alldevices">
<span class="cg-mode-body">
<span class="cg-mode-title">All Devices</span>
<span class="cg-mode-desc">App ist auf allen Geräten verfügbar.</span>
</span>
</label>
</div>
</div>
</div>
<div class="form-group" id="cgGroupNameGroup">
<label class="lbl" for="cgGroupName">Gruppen-Name (Vorschlag — kann angepasst werden)</label>
<input type="text" id="cgGroupName" class="input">
<div id="cgStatus" class="form-hint"></div>
</div>
<label class="check-chip" id="cgAssignToAppWrap">
<input type="checkbox" id="cgAssignToApp" checked>
<span>Gruppe direkt der App zuweisen</span>
</label>
</div>
<div class="modal-foot">
<button class="btn btn-secondary" data-close>Abbrechen</button>
<button class="btn btn-primary" id="cgCreate">Erstellen</button>
</div>
</div>
</div>
<!-- MODAL: Apply Result -->
<div id="modalResult" class="modal hidden">
<div class="modal-backdrop" data-close></div>
<div class="modal-box modal-lg">
<div class="modal-head">
<h3 id="resHeadline">Ergebnis</h3>
<button class="modal-close" data-close aria-label="Schließen">×</button>
</div>
<div class="modal-body">
<div id="resBanner" class="res-banner"></div>
<div class="result-stats">
<div class="rstat ok"><div class="num" id="resOk">0</div><div class="lbl">Erfolgreich</div></div>
<div class="rstat warn"><div class="num" id="resSkip">0</div><div class="lbl">Übersprungen</div></div>
<div class="rstat err"><div class="num" id="resErr">0</div><div class="lbl">Fehler</div></div>
<div class="rstat tot"><div class="num" id="resTotal">0</div><div class="lbl">Gesamt</div></div>
</div>
<div id="resDetails" class="res-details"></div>
<div id="resReportLink" class="report-link"></div>
<div id="resDebug" class="res-debug"></div>
</div>
<div class="modal-foot">
<button class="btn btn-secondary" data-close>Schließen</button>
</div>
</div>
</div>
<!-- MODAL: Mitglieder einer Gruppe einsehen / einzelne User uebernehmen -->
<div id="modalMembers" class="modal hidden">
<div class="modal-backdrop" data-close></div>
<div class="modal-box modal-lg">
<div class="modal-head">
<h3 id="mmTitle">Mitglieder</h3>
<button class="modal-close" data-close aria-label="Schließen">×</button>
</div>
<div class="modal-body">
<div class="search-wrap" style="margin: 0 0 12px 0;">
<svg class="search-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
<input type="search" id="mmSearch" class="input input-search" placeholder="Mitglieder filtern...">
</div>
<div class="mm-toolbar">
<button class="link-btn" id="mmSelectAll">Alle</button>
<span class="dotsep">·</span>
<button class="link-btn" id="mmClear">Leeren</button>
</div>
<div id="mmList" class="member-list"></div>
</div>
<div class="modal-foot">
<span id="mmInfo" class="muted" style="margin-right: auto;">0 ausgewählt</span>
<button class="btn btn-secondary" data-close>Abbrechen</button>
<button class="btn btn-danger" id="mmRemove" disabled>Aus Gruppe entfernen</button>
<button class="btn btn-primary" id="mmApply">Als Benutzer-Auswahl übernehmen</button>
</div>
</div>
</div>
<!-- MODAL: Confirm Apply -->
<div id="modalConfirm" class="modal hidden">
<div class="modal-backdrop" data-close></div>
<div class="modal-box">
<div class="modal-head">
<h3>Zuweisungen ausführen?</h3>
<button class="modal-close" data-close aria-label="Schließen">×</button>
</div>
<div class="modal-body">
<div id="confirmText" class="confirm-text"></div>
<div class="confirm-warn">Hinzufügen ist sofort wirksam — Mitgliedschaften können später manuell entfernt werden.</div>
</div>
<div class="modal-foot">
<button class="btn btn-secondary" data-close>Abbrechen</button>
<button class="btn btn-apply" id="btnConfirmApply">Ja, ausführen</button>
</div>
</div>
</div>
<!-- MODAL: Hilfe -->
<div id="modalHelp" class="modal hidden">
<div class="modal-backdrop" data-close></div>
<div class="modal-box modal-xl">
<div class="modal-head">
<h3>Hilfe &amp; Funktionsverzeichnis</h3>
<button class="modal-close" data-close aria-label="Schließen">×</button>
</div>
<div class="modal-body help-body">
<aside class="help-toc" id="helpToc">
<div class="help-toc-title">Inhalt</div>
<ul></ul>
</aside>
<div class="help-content md-content" id="helpContent">
<div class="help-loading">Hilfe wird geladen…</div>
</div>
</div>
<div class="modal-foot">
<span id="helpVersion" class="help-version-badge"></span>
<span class="modal-foot-spacer"></span>
<button class="btn btn-primary" data-close>Schließen</button>
</div>
</div>
</div>
<!-- MODAL: App umbenennen -->
<div id="modalRenameApp" class="modal hidden">
<div class="modal-backdrop" data-close></div>
<div class="modal-box">
<div class="modal-head">
<h3>App umbenennen</h3>
<button class="modal-close" data-close aria-label="Schließen">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="lbl">Aktueller Name</label>
<div id="raOldName" class="ra-old-name"></div>
</div>
<div class="form-group">
<label class="lbl" for="raNewName">Neuer Name</label>
<input type="text" id="raNewName" class="input" maxlength="256" autocomplete="off" spellcheck="false">
<div id="raError" class="form-hint form-hint-err"></div>
</div>
<div class="form-hint">Wirkt sofort in Intune. Vendor-verwaltete Apps (PatchMyPC, Robopack, …) können nicht hier umbenannt werden.</div>
</div>
<div class="modal-foot">
<button class="btn btn-secondary" data-close>Abbrechen</button>
<button class="btn btn-primary" id="raSubmit">Umbenennen</button>
</div>
</div>
</div>
<!-- MODAL: Setup-Datei aktualisieren -->
<div id="modalUpdateContent" class="modal hidden">
<div class="modal-backdrop" data-close></div>
<div class="modal-box">
<div class="modal-head">
<h3>Setup-Datei aktualisieren</h3>
<button class="modal-close" data-close aria-label="Schließen">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="lbl">App</label>
<div id="ucAppName" class="ra-old-name"></div>
</div>
<div class="banner-warning">
<strong>Achtung:</strong> Dies lädt eine neue Setup-Datei hoch und aktiviert sie als neue Version der App. Alle zugewiesenen Geräte erhalten die neue Version beim nächsten Intune-Sync. Vorgang ist nicht rückgängig zu machen.
</div>
<div class="form-group">
<label class="lbl" for="ucFilePath">Pfad zur neuen Datei (lokal auf dem Server-Rechner)</label>
<div class="uc-path-row">
<input type="text" id="ucFilePath" class="input mono" spellcheck="false" autocomplete="off" placeholder="C:\Pakete\app-2.0\app.intunewin">
<button type="button" class="btn btn-secondary" id="ucBrowse">Durchsuchen…</button>
</div>
<div class="form-hint" id="ucFileHint">Phase 1: nur <code>.intunewin</code> (win32LobApp). Der Server liest die Datei direkt von diesem Pfad. <em>Durchsuchen</em> öffnet den Datei-Dialog auf dem Rechner, auf dem das Tool läuft.</div>
</div>
<div class="form-group">
<label class="lbl" for="ucVersion">Neue Versionsnummer (optional)</label>
<input type="text" id="ucVersion" class="input mono" spellcheck="false" autocomplete="off" placeholder="z.B. 2.0.1">
</div>
<label class="check-chip"><input type="checkbox" id="ucConfirm"><span>Ich weiß, dass die ausgerollte Version ersetzt wird</span></label>
<div id="ucError" class="form-hint form-hint-err"></div>
<div id="ucProgress" class="content-update-progress hidden"></div>
</div>
<div class="modal-foot">
<button class="btn btn-secondary" data-close>Abbrechen</button>
<button class="btn btn-primary" id="ucSubmit" disabled>Hochladen &amp; aktivieren</button>
</div>
</div>
</div>
<!-- MODAL: Einstellungen -->
<div id="modalSettings" class="modal hidden">
<div class="modal-backdrop" data-close></div>
<div class="modal-box modal-lg">
<div class="modal-head">
<h3>Einstellungen</h3>
<button class="modal-close" data-close aria-label="Schließen">×</button>
</div>
<div class="modal-body">
<div id="settingsForm" class="settings-form">
<div id="setupBanner" class="banner-warning hidden">
<strong>Erste Konfiguration noetig.</strong>
Bitte mindestens Tenant ID, Client ID und Abteilungs-Praefix setzen. Danach <em>Verbindung + Lookups testen</em> ganz unten, dann speichern.
</div>
<section class="settings-sec">
<div class="settings-sec-head">
<h4>Microsoft Graph Verbindung</h4>
<div class="settings-sec-sub">Tenant- und App-Registrierung. Änderungen erzwingen einen Disconnect.</div>
</div>
<div class="form-group">
<label class="lbl" for="setTenantId">Tenant ID (GUID)</label>
<input type="text" id="setTenantId" class="input mono" placeholder="00000000-0000-0000-0000-000000000000" spellcheck="false" autocomplete="off">
<div class="form-hint">Findest du im Entra Admin Center → Übersicht → Tenant-Informationen.</div>
</div>
<div class="form-group">
<label class="lbl" for="setClientId">Client ID – Read/Write (GUID)</label>
<input type="text" id="setClientId" class="input mono" placeholder="00000000-0000-0000-0000-000000000000" spellcheck="false" autocomplete="off">
<div class="form-hint">App-Registrierung mit Schreibrechten. Erforderlich: <em>Allow public client flows</em> = Ja, Redirect URI <code>http://localhost</code>.</div>
</div>
<div class="form-group">
<label class="lbl" for="setClientIdRo">Client ID – Read Only (GUID, optional)</label>
<input type="text" id="setClientIdRo" class="input mono" placeholder="00000000-0000-0000-0000-000000000000" spellcheck="false" autocomplete="off">
<div class="form-hint">Separate App-Registrierung ohne Schreibrechte. Nutzer dieser App sehen alle Daten, können aber nichts ändern. Leer lassen wenn nicht benötigt.</div>
</div>
<div class="form-group">
<label class="lbl" for="setScopes">Scopes Read/Write (eine pro Zeile)</label>
<textarea id="setScopes" class="input mono" rows="4" spellcheck="false" placeholder="Group.ReadWrite.All&#10;GroupMember.ReadWrite.All&#10;User.Read.All&#10;DeviceManagementApps.ReadWrite.All"></textarea>
<div class="form-hint">Die Standard-Liste passt für den vollen Funktionsumfang.</div>
</div>
<div class="form-group">
<label class="lbl" for="setScopesRo">Scopes Read Only (eine pro Zeile)</label>
<textarea id="setScopesRo" class="input mono" rows="4" spellcheck="false" placeholder="Group.Read.All&#10;GroupMember.Read.All&#10;User.Read.All&#10;DeviceManagementApps.Read.All"></textarea>
<div class="form-hint">Scopes für die Read-Only-App-Registrierung.</div>
</div>
</section>
<section class="settings-sec">
<div class="settings-sec-head">
<h4>Abteilungs-Gruppen</h4>
<div class="settings-sec-sub">Ein oder mehrere Präfixe für den Abteilungs-Modus (linke Spalte). Alle Gruppen, deren <code>displayName</code> mit einem dieser Präfixe beginnt, werden geladen.</div>
</div>
<div class="form-group">
<label class="lbl" for="setDeptPrefixes">Präfixe (eine pro Zeile)</label>
<textarea id="setDeptPrefixes" class="input mono" rows="3" spellcheck="false" placeholder="abt-hm&#10;abt-extern"></textarea>
</div>
</section>
<section class="settings-sec">
<div class="settings-sec-head">
<h4>RPA-Gruppen</h4>
<div class="settings-sec-sub">Genaue <code>displayName</code>-Werte der Gruppen, die im RPA-Tab angezeigt werden. Eine pro Zeile.</div>
</div>
<div class="form-group">
<label class="lbl" for="setRpaGroups">RPA-Gruppennamen</label>
<textarea id="setRpaGroups" class="input mono" rows="4" spellcheck="false" placeholder="intune-userrole-windowsclientuser-rpa&#10;intune-userrole-windowsclientuser-rpa-fbt&#10;intune-userrole-windowsclientuser-rpa-pro"></textarea>
</div>
</section>
<section class="settings-sec">
<div class="settings-sec-head">
<h4>Benutzer-Suche</h4>
<div class="settings-sec-sub">In welchen Feldern bei der Benutzersuche gesucht wird (alle markierten werden mit <code>OR</code> verknüpft).</div>
</div>
<div class="form-group form-check-grid">
<label class="check-chip"><input type="checkbox" id="setUsfDisplayName"><span>Display Name</span></label>
<label class="check-chip"><input type="checkbox" id="setUsfUpn"><span>UPN</span></label>
<label class="check-chip"><input type="checkbox" id="setUsfMail"><span>E-Mail</span></label>
<label class="check-chip"><input type="checkbox" id="setUsfDepartment"><span>Department</span></label>
</div>
</section>
<section class="settings-sec">
<div class="settings-sec-head">
<h4>Required-Gruppen-Naming</h4>
<div class="settings-sec-sub">Schema für neue Required-Gruppen aus dem App-Manager. Format: <code>{Präfix}{App-Slug}{Suffix}</code>.</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="lbl" for="setReqPrefix">Präfix</label>
<input type="text" id="setReqPrefix" class="input mono" placeholder="intune-win-app-">
</div>
<div class="form-group">
<label class="lbl" for="setReqSuffix">Suffix</label>
<input type="text" id="setReqSuffix" class="input mono" placeholder="-required">
</div>
</div>
<div class="form-hint">Vorschau: <code id="setReqPreview" class="set-preview"></code></div>
</section>
<section class="settings-sec">
<div class="settings-sec-head">
<h4>Available-Gruppen-Naming</h4>
<div class="settings-sec-sub">Schema für neue Available-Gruppen aus dem App-Manager. Format: <code>{Präfix}{App-Slug}{Suffix}</code>.</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="lbl" for="setAvailPrefix">Präfix</label>
<input type="text" id="setAvailPrefix" class="input mono" placeholder="intune-win-app-">
</div>
<div class="form-group">
<label class="lbl" for="setAvailSuffix">Suffix</label>
<input type="text" id="setAvailSuffix" class="input mono" placeholder="-available">
</div>
</div>
<div class="form-hint">Vorschau: <code id="setAvailPreview" class="set-preview"></code></div>
</section>
<section class="settings-sec">
<div class="settings-sec-head">
<h4>Branding — Logo</h4>
<div class="settings-sec-sub">Eigenes Logo links oben im Header. Wird im Projekt-Verzeichnis als <code>branding-logo.&lt;ext&gt;</code> gespeichert. Max. 2 MB.</div>
</div>
<div class="logo-row">
<div class="logo-preview" id="logoPreview">
<span class="logo-letter">I</span>
</div>
<div class="logo-actions">
<input type="file" id="logoFile" accept="image/png,image/jpeg,image/svg+xml,image/webp,image/gif" class="hidden">
<button type="button" class="btn btn-secondary btn-sm" id="btnLogoPick">Bild auswählen…</button>
<button type="button" class="btn btn-text link-btn-danger" id="btnLogoRemove" hidden>Entfernen</button>
<div class="form-hint" id="logoHint">PNG, JPG, SVG, WEBP, GIF</div>
</div>
</div>
</section>
<section class="settings-sec">
<div class="settings-sec-head settings-sec-head-row">
<div>
<h4>Farben</h4>
<div class="settings-sec-sub">Akzentfarben für Highlights. Soft/Hover/Background-Varianten werden automatisch abgeleitet.</div>
</div>
<button type="button" class="btn btn-secondary btn-sm" id="btnRestoreColors" title="Alle Farben auf die Firmen-Standardwerte zurücksetzen">↺ Standardfarben wiederherstellen</button>
</div>
<div class="color-grid">
<div class="color-row" data-color-key="brand">
<span class="color-swatch" data-swatch="brand"></span>
<div class="color-meta">
<div class="color-name">Brand (Firmenfarbe)</div>
<div class="color-desc">Logo-Hintergrund, Stepper-Indikator</div>
</div>
<input type="color" id="setColBrand" class="color-picker">
<input type="text" id="setColBrandHex" class="input mono color-hex" maxlength="7" spellcheck="false">
<button type="button" class="link-btn" data-color-reset="brand" title="Auf Standard zurücksetzen">↺</button>
</div>
<div class="color-row" data-color-key="accent">
<span class="color-swatch" data-swatch="accent"></span>
<div class="color-meta">
<div class="color-name">Available</div>
<div class="color-desc">Available-Buttons, Links, Akzent-Hover</div>
</div>
<input type="color" id="setColAccent" class="color-picker">
<input type="text" id="setColAccentHex" class="input mono color-hex" maxlength="7" spellcheck="false">
<button type="button" class="link-btn" data-color-reset="accent" title="Auf Standard zurücksetzen">↺</button>
</div>
<div class="color-row" data-color-key="required">
<span class="color-swatch" data-swatch="required"></span>
<div class="color-meta">
<div class="color-name">Required</div>
<div class="color-desc">Required-Buttons und -Badges</div>
</div>
<input type="color" id="setColRequired" class="color-picker">
<input type="text" id="setColRequiredHex" class="input mono color-hex" maxlength="7" spellcheck="false">
<button type="button" class="link-btn" data-color-reset="required">↺</button>
</div>
<div class="color-row" data-color-key="success">
<span class="color-swatch" data-swatch="success"></span>
<div class="color-meta">
<div class="color-name">Erfolg / Bereits zugewiesen</div>
<div class="color-desc">Member-Indikatoren, Erfolgs-Toasts</div>
</div>
<input type="color" id="setColSuccess" class="color-picker">
<input type="text" id="setColSuccessHex" class="input mono color-hex" maxlength="7" spellcheck="false">
<button type="button" class="link-btn" data-color-reset="success">↺</button>
</div>
<div class="color-row" data-color-key="warning">
<span class="color-swatch" data-swatch="warning"></span>
<div class="color-meta">
<div class="color-name">Warnung / Teilweise</div>
<div class="color-desc">Partial-Member, Skip-Statuses</div>
</div>
<input type="color" id="setColWarning" class="color-picker">
<input type="text" id="setColWarningHex" class="input mono color-hex" maxlength="7" spellcheck="false">
<button type="button" class="link-btn" data-color-reset="warning">↺</button>
</div>
<div class="color-row" data-color-key="error">
<span class="color-swatch" data-swatch="error"></span>
<div class="color-meta">
<div class="color-name">Fehler</div>
<div class="color-desc">Fehler-Toasts, Delete-Hover, Validation</div>
</div>
<input type="color" id="setColError" class="color-picker">
<input type="text" id="setColErrorHex" class="input mono color-hex" maxlength="7" spellcheck="false">
<button type="button" class="link-btn" data-color-reset="error">↺</button>
</div>
<div class="color-row" data-color-key="rowSelected">
<span class="color-swatch" data-swatch="rowSelected"></span>
<div class="color-meta">
<div class="color-name">Markierte App-Zeile</div>
<div class="color-desc">Hintergrund der aufgeklappten App-Zeile + linker Akzentstreifen. Schrift/Icons passen sich automatisch dem Kontrast an.</div>
</div>
<input type="color" id="setColRowSelected" class="color-picker">
<input type="text" id="setColRowSelectedHex" class="input mono color-hex" maxlength="7" spellcheck="false">
<button type="button" class="link-btn" data-color-reset="rowSelected">↺</button>
</div>
<div class="color-row" data-color-key="detailPanel">
<span class="color-swatch" data-swatch="detailPanel"></span>
<div class="color-meta">
<div class="color-name">Details-Bereich</div>
<div class="color-desc">Hintergrund des aufgeklappten Detail-Blocks unter der App. Text-Farbe wird automatisch nach Lesbarkeit gewählt.</div>
</div>
<input type="color" id="setColDetailPanel" class="color-picker">
<input type="text" id="setColDetailPanelHex" class="input mono color-hex" maxlength="7" spellcheck="false">
<button type="button" class="link-btn" data-color-reset="detailPanel">↺</button>
</div>
</div>
</section>
<section class="settings-sec">
<div class="settings-sec-head">
<h4>Konfiguration pruefen</h4>
<div class="settings-sec-sub">Prueft die aktuell eingetragenen Werte gegen Microsoft Graph: Abteilungs-Gruppen-Lookup, RPA-Gruppen und Benutzer-Suche. Voraussetzung: Verbindung steht.</div>
</div>
<div class="form-row">
<button type="button" class="btn btn-secondary" id="btnSettingsTest">Verbindung + Lookups testen</button>
<div id="settingsTestResult" class="settings-test-result"></div>
</div>
</section>
<section class="settings-sec settings-sec-meta">
<div class="settings-sec-head">
<h4>Datei</h4>
</div>
<div class="form-hint">Gespeichert in: <code id="setFilePath" class="set-path">…</code></div>
</section>
</div>
</div>
<div class="modal-foot">
<button class="btn btn-text link-btn-danger" id="btnSettingsReset" type="button">Auf Standardwerte zurücksetzen</button>
<span class="modal-foot-spacer"></span>
<button class="btn btn-secondary" data-close>Abbrechen</button>
<button class="btn btn-primary" id="btnSettingsSave">Speichern</button>
</div>
</div>
</div>
<!-- MODAL: Gruppen-Export (leeres Stub — Inhalt jetzt in #groupMgmtView) -->
<div id="modalGroupExport" class="modal hidden">
<div class="modal-backdrop" data-close></div>
<div class="modal-box modal-xl">
<div class="modal-head">
<h3>Gruppen-Verwaltung</h3>
<button class="modal-close" data-close aria-label="Schließen">×</button>
</div>
<div class="modal-body"></div>
</div>
</div>
<!-- Markdown-Renderer (bundled, MIT, marked v15) — wird im App-Detail-Panel
fuer die Beschreibung benutzt. Vor app.js laden damit window.marked
beim ersten render() bereitsteht. -->
<script src="/vendor/marked.min.js"></script>
<!-- Date-Picker (bundled, MIT, flatpickr v4) — ersetzt den nativen
date-input Popup, vermeidet Browser-Quirks (schwarzes Initial-Render). -->
<script src="/vendor/flatpickr.min.js"></script>
<script src="/app.js"></script>
</body>
</html>