- Neuer Toolbar-Button "Gruppen zuweisen" im Policy-Tab: mehreren ausgewählten Policies (typübergreifend) in einem Schritt dieselben Include-/Exclude-Gruppen zuweisen. - Zwei Modi: "Hinzufügen" (bestehende Zuweisungen werden gelesen und gemergt, da Graph /assign Full-Replace ist) und "Ersetzen". - Integrierte Include-Ziele "Alle Geräte" (allDevicesAssignmentTarget) und "Alle Benutzer" (allLicensedUsersAssignmentTarget), dedupliziert und mit Exclude-Gruppen kombinierbar. - Invoke-PolicyAssignEndpoint um mode/allDevices/allUsers erweitert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1545 lines
85 KiB
HTML
1545 lines
85 KiB
HTML
<!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>
|
||
<button class="nav-tab" data-view="offboard" aria-selected="false">Offboarding</button>
|
||
</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>
|
||
<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>
|
||
<select id="tenantSwitch" class="tenant-switch hidden" title="Aktiven Mandanten wechseln"></select>
|
||
<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 und Client ID 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>Gruppen & 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">Gruppe</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="appPlatformFilter" class="select" title="Nach Plattform filtern">
|
||
<option value="all">Alle Plattformen</option>
|
||
<option value="windows">Windows</option>
|
||
<option value="macos">macOS</option>
|
||
</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">Gruppen</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">Gruppen</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 & Export</button>
|
||
<button class="gex-tab gex-add-pane" data-tab="add">Benutzer | Gruppen 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. max.mustermann@firma.de erika.muster@firma.de 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-addmode">
|
||
<button type="button" class="gex-addmode-btn active" data-addmode="user">Benutzer</button>
|
||
<button type="button" class="gex-addmode-btn" data-addmode="group">Gruppen</button>
|
||
</div>
|
||
<div class="gex-section-title" id="gexAddSearchTitle">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 id="devGroupWrap" style="position:relative;">
|
||
<input type="search" id="devGroupSearch" placeholder="Nach Gruppe filtern…" autocomplete="off"
|
||
style="height:34px;padding:0 24px 0 10px;border:1px solid var(--hairline);border-radius:6px;background:var(--canvas);color:var(--ink);font-size:12.5px;width:200px;box-sizing:border-box;">
|
||
<button id="devGroupClear" type="button" title="Gruppen-Filter entfernen" hidden
|
||
style="position:absolute;right:6px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--muted);cursor:pointer;font-size:16px;line-height:1;padding:0;">×</button>
|
||
<div id="devGroupResults" class="dev-group-results hidden"></div>
|
||
</div>
|
||
<button id="btnDevExport" class="btn btn-secondary btn-sm" disabled title="Angezeigte Geräte als CSV 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>
|
||
CSV
|
||
</button>
|
||
<button id="btnDevOffboard" class="btn btn-danger btn-sm dev-offboard-btn" disabled title="Ausgewählte Geräte offboarden (Intune / Autopilot / Entra)">
|
||
<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="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>
|
||
Offboarden (<span id="devOffboardCount">0</span>)
|
||
</button>
|
||
</div>
|
||
|
||
<div class="dev-layout">
|
||
<!-- Geräteliste -->
|
||
<div class="dev-list-wrap">
|
||
<table class="dev-table" id="devTable">
|
||
<thead>
|
||
<tr>
|
||
<th class="dev-col-check"><input type="checkbox" id="devCheckAll" title="Alle sichtbaren auswählen"></th>
|
||
<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="managementType">Verwaltung <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>
|
||
<option value="administrativetemplate">Administrative Vorlage</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-secondary btn-sm" id="btnPolGitSnapshot" title="Alle Policies als Snapshot in den konfigurierten Git-Ordner schreiben und committen">
|
||
<svg width="13" height="13" 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"/><line x1="12" y1="3" x2="12" y2="9"/><line x1="12" y1="15" x2="12" y2="21"/><circle cx="12" cy="3" r="1"/><circle cx="12" cy="21" r="1"/></svg>
|
||
Git-Snapshot
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm pol-write" id="btnPolAssignGroups" disabled title="Ausgewählten Policies Gruppen zuweisen (Bulk)">
|
||
<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="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||
Gruppen zuweisen (<span id="polAssignSelCount">0</span>)
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm pol-write" id="btnPolConsolidate" disabled title="Ausgewählte Settings-Catalog-Policies zu einer neuen Policy zusammenführen">
|
||
<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="M7 3v6a3 3 0 0 0 3 3h4a3 3 0 0 1 3 3v6"/><polyline points="3 7 7 3 11 7"/><polyline points="13 17 17 21 21 17"/></svg>
|
||
Konsolidieren
|
||
</button>
|
||
<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>
|
||
|
||
<!-- ======================= OFFBOARDING ======================= -->
|
||
<section class="off-view hidden" id="offView">
|
||
<div class="off-bar">
|
||
<select id="offSearchType" class="input" style="height:34px;max-width:170px;">
|
||
<option value="name">Gerätename</option>
|
||
<option value="serial">Seriennummer</option>
|
||
</select>
|
||
<input id="offSearch" class="input" placeholder="Suchen… (Enter)" style="max-width:320px;height:34px;">
|
||
<button class="btn btn-secondary btn-sm" id="offSearchBtn">Suchen</button>
|
||
<span class="off-bar-spacer" style="flex:1;"></span>
|
||
<span id="offSelCount" class="muted">0 ausgewählt</span>
|
||
<button class="btn btn-danger btn-sm" id="offBtn" disabled>Offboarding…</button>
|
||
</div>
|
||
<div class="off-note muted">
|
||
Entfernt Geräte aus <b>Intune</b>, <b>Autopilot</b> und <b>Entra ID</b>. BitLocker-/FileVault-Keys und
|
||
LAPS-Passwörter werden vor dem Löschen angezeigt. Benötigt zusätzliche Berechtigungen & Admin-Rollen
|
||
(siehe <code>docs/App-Registration.md</code>) — im Read-Only-Modus gesperrt.
|
||
</div>
|
||
<div class="off-table-wrap">
|
||
<table class="pol-table off-table" id="offTable">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:34px;"><input type="checkbox" id="offCheckAll"></th>
|
||
<th>Gerät</th>
|
||
<th>Seriennr.</th>
|
||
<th>OS</th>
|
||
<th>Benutzer</th>
|
||
<th>Letzter Sync</th>
|
||
<th>Dienste</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="offBody">
|
||
<tr><td colspan="7" class="pol-hint">Nach Geräten suchen…</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">×</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-existing">
|
||
<span class="cg-mode-body">
|
||
<span class="cg-mode-title">Bestehende Gruppe</span>
|
||
<span class="cg-mode-desc">Eine vorhandene Entra-Gruppe als Required 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-existing">
|
||
<span class="cg-mode-body">
|
||
<span class="cg-mode-title">Bestehende Gruppe</span>
|
||
<span class="cg-mode-desc">Eine vorhandene Entra-Gruppe als Available 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>
|
||
<div class="form-group" id="cgExistingGroup" hidden>
|
||
<label class="lbl" for="cgExistingSearch">Bestehende Gruppe suchen</label>
|
||
<input type="search" id="cgExistingSearch" class="input" placeholder="Gruppenname… (min. 2 Zeichen)" autocomplete="off">
|
||
<div id="cgExistingResults" class="cg-existing-results"></div>
|
||
<div id="cgExistingSelected" 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: Policy-Import (optional umbenennen) -->
|
||
<div id="modalPolImport" class="modal hidden">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-box modal-lg">
|
||
<div class="modal-head">
|
||
<h3>Policies importieren</h3>
|
||
<button class="modal-close" data-close aria-label="Schließen">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-hint" style="margin-bottom:12px;">
|
||
Namen können vor dem Import angepasst werden. Die Policies werden als
|
||
<b>Neuanlage</b> im verbundenen Tenant erstellt — bestehende bleiben unverändert.
|
||
</div>
|
||
<div id="polImportList"></div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<span id="polImportInfo" class="muted" style="margin-right:auto;"></span>
|
||
<button class="btn btn-secondary" data-close>Abbrechen</button>
|
||
<button class="btn btn-primary" id="polImportConfirm">Importieren</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MODAL: Policies konsolidieren (Settings Catalog) -->
|
||
<div id="modalPolConsolidate" class="modal hidden">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-box modal-lg">
|
||
<div class="modal-head">
|
||
<h3>Policies zusammenführen</h3>
|
||
<button class="modal-close" data-close aria-label="Schließen">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-hint" style="margin-bottom:12px;">
|
||
Die Einstellungen der ausgewählten <b>Settings-Catalog</b>-Policies werden zu
|
||
<b>einer neuen</b> Policy vereint (Neuanlage — die Originale bleiben unverändert).
|
||
</div>
|
||
<div id="polConsSummary" class="pol-cons-summary"></div>
|
||
<div id="polConsConflicts"></div>
|
||
<div class="form-group" style="margin-top:12px;">
|
||
<label class="lbl" for="polConsName">Name der neuen Policy</label>
|
||
<input type="text" id="polConsName" class="input" placeholder="z. B. Konsolidiert – Defender macOS">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="lbl" for="polConsDesc">Beschreibung (optional)</label>
|
||
<input type="text" id="polConsDesc" class="input">
|
||
</div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<span id="polConsInfo" class="muted" style="margin-right:auto;"></span>
|
||
<button class="btn btn-secondary" data-close>Abbrechen</button>
|
||
<button class="btn btn-primary" id="polConsConfirm" disabled>Zusammenführen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MODAL: Policy-Zuweisung nach Import -->
|
||
<div id="modalPolAssign" class="modal hidden">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-box modal-lg">
|
||
<div class="modal-head">
|
||
<h3>Importierte Policies zuweisen</h3>
|
||
<button class="modal-close" data-close aria-label="Schließen">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-hint" style="margin-bottom:12px;">
|
||
Optional: jeder importierten Policy Include- und/oder Exclude-Gruppen im aktuellen Tenant zuweisen.
|
||
Felder leer lassen = keine Zuweisung. Original-Zuweisungen des Quell-Tenants werden nicht übernommen.
|
||
</div>
|
||
<div id="polAssignList"></div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<span id="polAssignInfo" class="muted" style="margin-right:auto;"></span>
|
||
<button class="btn btn-secondary" data-close>Überspringen</button>
|
||
<button class="btn btn-primary" id="polAssignApply">Zuweisen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bulk: mehreren ausgewählten Policies dieselben Gruppen zuweisen -->
|
||
<div id="modalPolBulkAssign" class="modal hidden">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-box modal-lg">
|
||
<div class="modal-head">
|
||
<h3>Gruppen zuweisen</h3>
|
||
<button class="modal-close" data-close aria-label="Schließen">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="form-hint" style="margin-bottom:12px;">
|
||
Die gewählten Include-/Exclude-Gruppen werden auf <b><span id="polBulkCount">0</span></b> ausgewählte Policy(s) angewendet.
|
||
</div>
|
||
<div id="polBulkNames" class="pol-bulk-names"></div>
|
||
<div class="pol-assign-pickers" style="margin-top:12px;">
|
||
<div class="pol-assign-picker" data-kind="include">
|
||
<label class="lbl">Include-Gruppen</label>
|
||
<div class="pol-assign-chips" data-scope="bulk" data-kind="include"></div>
|
||
<div class="pol-assign-search-wrap">
|
||
<input type="text" class="input pol-bulk-search" data-kind="include" placeholder="Gruppe suchen…" autocomplete="off">
|
||
<div class="pol-assign-dropdown pol-bulk-dropdown hidden" data-kind="include"></div>
|
||
</div>
|
||
</div>
|
||
<div class="pol-assign-picker" data-kind="exclude">
|
||
<label class="lbl">Exclude-Gruppen</label>
|
||
<div class="pol-assign-chips" data-scope="bulk" data-kind="exclude"></div>
|
||
<div class="pol-assign-search-wrap">
|
||
<input type="text" class="input pol-bulk-search" data-kind="exclude" placeholder="Gruppe suchen…" autocomplete="off">
|
||
<div class="pol-assign-dropdown pol-bulk-dropdown hidden" data-kind="exclude"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="pol-bulk-builtin" style="margin-top:14px;">
|
||
<label class="lbl">Integrierte Ziele (Include)</label>
|
||
<label class="pol-bulk-check"><input type="checkbox" id="polBulkAllDevices"> Alle Geräte</label>
|
||
<label class="pol-bulk-check"><input type="checkbox" id="polBulkAllUsers"> Alle Benutzer</label>
|
||
</div>
|
||
<div class="pol-bulk-mode" style="margin-top:16px;">
|
||
<label class="lbl">Modus</label>
|
||
<label class="pol-bulk-radio"><input type="radio" name="polBulkMode" value="add" checked>
|
||
<span><b>Hinzufügen</b> — bestehende Zuweisungen bleiben erhalten, neue Gruppen kommen dazu.</span></label>
|
||
<label class="pol-bulk-radio"><input type="radio" name="polBulkMode" value="replace">
|
||
<span><b>Ersetzen</b> — alle bisherigen Zuweisungen der Policy werden durch die gewählten Gruppen ersetzt.</span></label>
|
||
</div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<span id="polBulkInfo" class="muted" style="margin-right:auto;"></span>
|
||
<button class="btn btn-secondary" data-close>Abbrechen</button>
|
||
<button class="btn btn-primary" id="polBulkApply" disabled>Zuweisen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MODAL: Geräte-Offboarding -->
|
||
<div id="modalOffboard" class="modal hidden">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-box modal-lg">
|
||
<div class="modal-head">
|
||
<h3>Geräte offboarden</h3>
|
||
<button class="modal-close" data-close aria-label="Schließen">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="off-warn">⚠ Entfernt die Geräte aus den gewählten Diensten. Löschen ist <b>nicht umkehrbar</b>. Recovery-Keys unbedingt vorher sichern.</div>
|
||
<div id="offConfirmList" class="off-confirm-list"></div>
|
||
|
||
<div class="off-services">
|
||
<div class="lbl">Dienste</div>
|
||
<div class="off-svc-row">
|
||
<span class="off-svc-label">Entra ID:</span>
|
||
<label class="off-radio"><input type="radio" name="offEntra" value="none" checked> nichts tun</label>
|
||
<label class="off-radio"><input type="radio" name="offEntra" value="disable"> deaktivieren</label>
|
||
<label class="off-radio"><input type="radio" name="offEntra" value="delete"> löschen</label>
|
||
</div>
|
||
<label class="check-chip"><input type="checkbox" id="offSvcIntune" checked><span>Aus Intune löschen</span></label>
|
||
<label class="check-chip"><input type="checkbox" id="offSvcAutopilot"><span>Aus Autopilot löschen</span></label>
|
||
</div>
|
||
|
||
<div id="offKeys" class="off-keys"></div>
|
||
|
||
<label class="check-chip off-confirm-chip"><input type="checkbox" id="offConfirmChk"><span>Ich verstehe, dass die Geräte gelöscht/entfernt werden.</span></label>
|
||
<div id="offResults"></div>
|
||
</div>
|
||
<div class="modal-foot">
|
||
<span id="offModalInfo" class="muted" style="margin-right:auto;"></span>
|
||
<button class="btn btn-secondary" data-close>Abbrechen</button>
|
||
<button class="btn btn-danger" id="offExecuteBtn" disabled>Offboarding ausführen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<style>
|
||
/* Nutzt die Theme-Variablen der App -> passt sich Light/Dark automatisch an. */
|
||
.pol-assign-row { border:1px solid var(--hairline); border-radius:8px; padding:12px 14px; margin-bottom:12px; overflow-wrap:anywhere; }
|
||
.pol-assign-head { display:flex; align-items:baseline; gap:8px; margin-bottom:10px; flex-wrap:wrap; }
|
||
.pol-assign-head .pol-name { font-weight:600; color:var(--ink); min-width:0; word-break:break-word; overflow-wrap:anywhere; }
|
||
.pol-assign-head .pol-type-badge { flex:0 0 auto; }
|
||
.pol-assign-pickers { display:flex; gap:16px; flex-wrap:wrap; }
|
||
.pol-assign-picker { flex:1; min-width:220px; }
|
||
.pol-assign-picker > .lbl { display:block; font-size:12px; margin-bottom:4px; color:var(--body); }
|
||
.pol-assign-picker[data-kind="exclude"] > .lbl { color:var(--pink); }
|
||
.pol-assign-chips { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:6px; }
|
||
.pol-assign-chip { display:inline-flex; align-items:center; gap:6px; padding:2px 8px; border-radius:12px;
|
||
background:var(--surface-strong); color:var(--ink); font-size:12px; }
|
||
.pol-assign-chip.exclude { background:rgba(236,72,153,0.18); color:var(--ink); }
|
||
.pol-assign-chip button { border:none; background:none; cursor:pointer; font-size:14px; line-height:1; padding:0; color:var(--muted); }
|
||
.pol-assign-chip button:hover { color:var(--ink); }
|
||
.pol-assign-search-wrap { position:relative; }
|
||
.pol-assign-dropdown { position:absolute; z-index:20; left:0; right:0; top:100%; margin-top:2px;
|
||
background:var(--canvas); border:1px solid var(--hairline); border-radius:6px;
|
||
max-height:200px; overflow-y:auto; box-shadow:0 6px 18px rgba(0,0,0,.25); }
|
||
.pol-assign-dropdown .opt { padding:6px 10px; cursor:pointer; font-size:13px; color:var(--body); }
|
||
.pol-assign-dropdown .opt:hover { background:var(--surface-soft); }
|
||
.pol-assign-dropdown .opt .desc { font-size:11px; color:var(--muted); }
|
||
.pol-assign-dropdown .msg { padding:6px 10px; font-size:12px; color:var(--muted); }
|
||
.pol-import-row { display:flex; align-items:center; gap:10px; padding:8px 0; border-bottom:1px solid var(--hairline); }
|
||
.pol-import-row:last-child { border-bottom:none; }
|
||
.pol-import-name { flex:1; min-width:0; }
|
||
.pol-import-row .pol-type-badge { flex:0 0 auto; }
|
||
/* Offboarding */
|
||
.off-view { padding:16px 20px; }
|
||
.off-bar { display:flex; align-items:center; gap:10px; margin-bottom:10px; flex-wrap:wrap; }
|
||
.off-note { font-size:12.5px; margin-bottom:12px; line-height:1.5; }
|
||
.off-table-wrap { overflow-x:auto; }
|
||
.off-table td, .off-table th { white-space:nowrap; }
|
||
.off-badge { display:inline-block; padding:1px 7px; border-radius:10px; font-size:11px; background:var(--surface-strong); color:var(--ink); margin-right:3px; }
|
||
.off-badge.warn { background:rgba(245,158,11,0.22); }
|
||
.off-warn { background:rgba(239,68,68,0.14); border:1px solid rgba(239,68,68,0.35); color:var(--ink);
|
||
border-radius:8px; padding:10px 12px; font-size:13px; margin-bottom:12px; }
|
||
.off-confirm-list { margin-bottom:12px; }
|
||
.off-confirm-row { font-size:13px; padding:4px 0; border-bottom:1px solid var(--hairline); overflow-wrap:anywhere; }
|
||
.off-confirm-row:last-child { border-bottom:none; }
|
||
.off-services { margin:12px 0; padding:12px; border:1px solid var(--hairline); border-radius:8px; }
|
||
.off-services .lbl { display:block; font-size:12px; color:var(--body); margin-bottom:8px; }
|
||
.off-svc-row { display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin-bottom:10px; }
|
||
.off-svc-label { font-size:13px; color:var(--ink); font-weight:600; }
|
||
.off-radio { display:inline-flex; align-items:center; gap:5px; font-size:13px; cursor:pointer; }
|
||
.off-keys { margin:12px 0; }
|
||
.off-keys .lbl { display:block; font-size:12px; color:var(--pink); margin-bottom:6px; }
|
||
.off-key-dev { border:1px solid var(--hairline); border-radius:6px; padding:8px 10px; margin-bottom:6px; font-size:12.5px; overflow-wrap:anywhere; }
|
||
.off-key { margin-top:3px; }
|
||
.off-key code { background:var(--surface-strong); color:var(--ink); padding:1px 5px; border-radius:4px; user-select:all; }
|
||
.off-confirm-chip { margin-top:8px; }
|
||
.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>
|
||
|
||
<!-- 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 & 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 & 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 und Client ID setzen. Abteilungs-Präfixe sind optional. 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="setConnMode">Verbindungsmodus</label>
|
||
<select id="setConnMode" class="input">
|
||
<option value="single">Single-Tenant (ein Mandant)</option>
|
||
<option value="multi">Multi-Tenant (mehrere Mandanten, Umschalten in der Topbar)</option>
|
||
</select>
|
||
<div class="form-hint">Im Multi-Tenant-Modus hinterlegst du mehrere Mandanten mit je eigener App-Registrierung und wechselst oben rechts zwischen ihnen.</div>
|
||
</div>
|
||
|
||
<!-- Single-Tenant Felder -->
|
||
<div id="setSingleConn">
|
||
<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>
|
||
|
||
<!-- Multi-Tenant Profile -->
|
||
<div id="setMultiConn" class="hidden">
|
||
<div class="form-group">
|
||
<label class="lbl">Tenant-Profile</label>
|
||
<div class="form-hint" style="margin-bottom:8px;">Pro Mandant eine eigene App-Registrierung. Der aktive Mandant ist markiert; umschalten geht auch über die Topbar.</div>
|
||
<div id="setTenantList" class="tenant-list"></div>
|
||
<button type="button" id="btnAddTenant" class="btn btn-secondary btn-sm" style="margin-top:8px;">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
Tenant hinzufügen
|
||
</button>
|
||
</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 GroupMember.ReadWrite.All User.Read.All 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 GroupMember.Read.All User.Read.All 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 abt-extern"></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>Policy-Backup (Git)</h4>
|
||
<div class="settings-sec-sub">Lokaler Git-Ordner für Policy-Snapshots. Der „Git-Snapshot"-Button im Policies-Tab schreibt alle Policies als JSON dorthin und committet sie (optional Push).</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="lbl" for="setPolicyGitPath">Git-Repo-Ordner (lokal)</label>
|
||
<div class="form-row" style="gap:8px;align-items:center;">
|
||
<input type="text" id="setPolicyGitPath" class="input mono" placeholder="z. B. C:\Intune-Backup" spellcheck="false" autocomplete="off" style="flex:1;">
|
||
<button type="button" class="btn btn-secondary btn-sm" id="btnPickPolicyGit">Ordner…</button>
|
||
</div>
|
||
<div class="form-hint">Wird bei Bedarf angelegt und als Git-Repo initialisiert. Leer = Funktion deaktiviert.</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="check-chip"><input type="checkbox" id="setPolicyGitPush"><span>Nach dem Commit automatisch <code>git push</code></span></label>
|
||
<div class="form-hint">Nutzt deinen vorhandenen Git-Credential-Helper (Remote muss im Repo konfiguriert sein). Kein Token in der App.</div>
|
||
</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.<ext></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 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>
|