- #1 Spalte "Zuweisungen" in der Policy-Liste (Anzahl); Klick öffnet eine Detailansicht mit den konkreten Include-/Exclude-Gruppen und Alle Geräte/ Benutzer. Get-GraphPolicyList lädt jetzt $expand=assignments und löst Gruppennamen in einem Bulk-Lookup (Resolve-GroupNamesBulk) auf. - #4 Bulk-Zuweisung zeigt statt nur eines Toasts einen Ergebnis-Report pro Policy (zugewiesen / übersprungen / Fehler) und lädt die Liste neu. - #5 Post-Import-Zuweisung erhält Alle Geräte/Alle Benutzer (Parität zur Bulk-Zuweisung) und nutzt dieselbe Ergebnisansicht. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+42
-2
@@ -154,14 +154,35 @@ function Get-GraphPolicyList {
|
|||||||
param([Parameter(Mandatory=$true)][string]$Type)
|
param([Parameter(Mandatory=$true)][string]$Type)
|
||||||
$cfg = Get-PolicyTypeConfig $Type
|
$cfg = Get-PolicyTypeConfig $Type
|
||||||
if (-not $cfg) { throw "Unbekannter Policy-Typ: $Type" }
|
if (-not $cfg) { throw "Unbekannter Policy-Typ: $Type" }
|
||||||
$raw = Get-GraphPaged -Uri "https://graph.microsoft.com/beta/deviceManagement/$($cfg.Collection)"
|
# Zuweisungen gleich mitladen ($expand=assignments), damit die Liste je Policy
|
||||||
$items = @()
|
# Anzahl + aufgeloeste Ziele zeigen kann.
|
||||||
|
$raw = Get-GraphPaged -Uri "https://graph.microsoft.com/beta/deviceManagement/$($cfg.Collection)?`$expand=assignments"
|
||||||
|
$items = @()
|
||||||
|
$needGroups = @{} # eindeutige Gruppen-Ids ueber alle Policies (fuer 1 Bulk-Lookup)
|
||||||
foreach ($r in $raw) {
|
foreach ($r in $raw) {
|
||||||
$id = Get-PolicyProp $r 'id'
|
$id = Get-PolicyProp $r 'id'
|
||||||
if (-not $id) { continue }
|
if (-not $id) { continue }
|
||||||
$name = Get-PolicyProp $r $cfg.NameField
|
$name = Get-PolicyProp $r $cfg.NameField
|
||||||
if (-not $name) { $name = Get-PolicyProp $r 'displayName' }
|
if (-not $name) { $name = Get-PolicyProp $r 'displayName' }
|
||||||
if (-not $name) { $name = Get-PolicyProp $r 'name' }
|
if (-not $name) { $name = Get-PolicyProp $r 'name' }
|
||||||
|
|
||||||
|
$asg = @()
|
||||||
|
foreach ($a in @(Get-PolicyProp $r 'assignments')) {
|
||||||
|
$t = Get-PolicyProp $a 'target'
|
||||||
|
if (-not $t) { continue }
|
||||||
|
$ot = [string](Get-PolicyProp $t '@odata.type')
|
||||||
|
$gid = [string](Get-PolicyProp $t 'groupId')
|
||||||
|
$entry = $null
|
||||||
|
if ($ot -like '*exclusionGroupAssignmentTarget') { $entry = [ordered]@{ mode = 'exclude'; kind = 'group'; groupId = $gid } }
|
||||||
|
elseif ($ot -like '*groupAssignmentTarget') { $entry = [ordered]@{ mode = 'include'; kind = 'group'; groupId = $gid } }
|
||||||
|
elseif ($ot -like '*allDevicesAssignmentTarget') { $entry = [ordered]@{ mode = 'include'; kind = 'allDevices'; groupId = '' } }
|
||||||
|
elseif ($ot -like '*allLicensedUsersAssignmentTarget') { $entry = [ordered]@{ mode = 'include'; kind = 'allUsers'; groupId = '' } }
|
||||||
|
if ($entry) {
|
||||||
|
if ($gid) { $needGroups[$gid] = $true }
|
||||||
|
$asg += $entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$items += [ordered]@{
|
$items += [ordered]@{
|
||||||
id = [string]$id
|
id = [string]$id
|
||||||
name = [string]$name
|
name = [string]$name
|
||||||
@@ -170,6 +191,25 @@ function Get-GraphPolicyList {
|
|||||||
platform = Get-PolicyPlatformLabel -Raw $r -Type $cfg.Key
|
platform = Get-PolicyPlatformLabel -Raw $r -Type $cfg.Key
|
||||||
odataType = [string](Get-PolicyProp $r '@odata.type')
|
odataType = [string](Get-PolicyProp $r '@odata.type')
|
||||||
lastModifiedDateTime = Get-PolicyProp $r 'lastModifiedDateTime'
|
lastModifiedDateTime = Get-PolicyProp $r 'lastModifiedDateTime'
|
||||||
|
assignments = $asg
|
||||||
|
assignmentCount = @($asg).Count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gruppennamen in EINEM Bulk-Lookup aufloesen (bereits bekannte aus dem Cache).
|
||||||
|
$lookup = @{}
|
||||||
|
try {
|
||||||
|
foreach ($g in @($script:State.Groups)) { if ($g.Id) { $lookup[[string]$g.Id] = [string]$g.DisplayName } }
|
||||||
|
foreach ($g in @($script:State.RpaGroups)) { if ($g.Id) { $lookup[[string]$g.Id] = [string]$g.DisplayName } }
|
||||||
|
} catch {}
|
||||||
|
$unknown = [string[]]@($needGroups.Keys | ForEach-Object { [string]$_ } | Where-Object { $_ -and -not $lookup.ContainsKey($_) })
|
||||||
|
if ($unknown.Count -gt 0) { try { Resolve-GroupNamesBulk -Ids $unknown -Lookup $lookup } catch {} }
|
||||||
|
foreach ($it in $items) {
|
||||||
|
foreach ($a in @($it.assignments)) {
|
||||||
|
if ($a.kind -eq 'group') {
|
||||||
|
$gid = [string]$a.groupId
|
||||||
|
$a.groupName = if ($lookup.ContainsKey($gid)) { $lookup[$gid] } else { $gid }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $items
|
return $items
|
||||||
|
|||||||
+81
-20
@@ -6321,7 +6321,7 @@ function polFmtDate(iso) {
|
|||||||
|
|
||||||
async function loadPolicies() {
|
async function loadPolicies() {
|
||||||
const body = document.getElementById('polBody');
|
const body = document.getElementById('polBody');
|
||||||
body.innerHTML = `<tr><td colspan="5" class="pol-hint"><span class="spinner" style="width:14px;height:14px;border-width:2px;"></span> Lade Policies…</td></tr>`;
|
body.innerHTML = `<tr><td colspan="6" class="pol-hint"><span class="spinner" style="width:14px;height:14px;border-width:2px;"></span> Lade Policies…</td></tr>`;
|
||||||
// Drei Typen parallel laden; ein fehlschlagender Typ (z.B. fehlender Scope)
|
// Drei Typen parallel laden; ein fehlschlagender Typ (z.B. fehlender Scope)
|
||||||
// soll die anderen nicht blockieren.
|
// soll die anderen nicht blockieren.
|
||||||
const endpoints = [
|
const endpoints = [
|
||||||
@@ -6368,7 +6368,7 @@ function renderPolicies() {
|
|||||||
const list = polFiltered();
|
const list = polFiltered();
|
||||||
document.getElementById('polCount').textContent = list.length;
|
document.getElementById('polCount').textContent = list.length;
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
body.innerHTML = `<tr><td colspan="5" class="pol-empty">${PolState.items.length ? 'Keine Policy passt zum Filter.' : 'Keine Policies gefunden.'}</td></tr>`;
|
body.innerHTML = `<tr><td colspan="6" class="pol-empty">${PolState.items.length ? 'Keine Policy passt zum Filter.' : 'Keine Policies gefunden.'}</td></tr>`;
|
||||||
polUpdateSelCount();
|
polUpdateSelCount();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -6380,9 +6380,19 @@ function renderPolicies() {
|
|||||||
<td class="pol-name" title="${escapeHtml(p.name || '')}">${escapeHtml(p.name || '—')}</td>
|
<td class="pol-name" title="${escapeHtml(p.name || '')}">${escapeHtml(p.name || '—')}</td>
|
||||||
<td><span class="pol-type-badge">${escapeHtml(p.typeLabel || p.type)}</span></td>
|
<td><span class="pol-type-badge">${escapeHtml(p.typeLabel || p.type)}</span></td>
|
||||||
<td>${escapeHtml(p.platform || '—')}</td>
|
<td>${escapeHtml(p.platform || '—')}</td>
|
||||||
|
<td class="pol-col-asg">${p.assignmentCount > 0
|
||||||
|
? `<button type="button" class="pol-asg-badge" data-key="${escapeHtml(key)}" title="Zuweisungen anzeigen">${p.assignmentCount}</button>`
|
||||||
|
: '<span class="muted">—</span>'}</td>
|
||||||
<td>${polFmtDate(p.lastModifiedDateTime)}</td>
|
<td>${polFmtDate(p.lastModifiedDateTime)}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
body.querySelectorAll('.pol-asg-badge').forEach(btn => {
|
||||||
|
btn.addEventListener('click', e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const p = PolState.items.find(x => polKey(x) === btn.dataset.key);
|
||||||
|
if (p) openPolAssignView(p);
|
||||||
|
});
|
||||||
|
});
|
||||||
body.querySelectorAll('.pol-row-check').forEach(cb => {
|
body.querySelectorAll('.pol-row-check').forEach(cb => {
|
||||||
cb.addEventListener('change', () => {
|
cb.addEventListener('change', () => {
|
||||||
if (cb.checked) PolState.selected.add(cb.dataset.key);
|
if (cb.checked) PolState.selected.add(cb.dataset.key);
|
||||||
@@ -6702,6 +6712,8 @@ function openPolAssignModal(assignable) {
|
|||||||
exportType: r.exportType || '',
|
exportType: r.exportType || '',
|
||||||
include: [],
|
include: [],
|
||||||
exclude: [],
|
exclude: [],
|
||||||
|
allDevices: false,
|
||||||
|
allUsers: false,
|
||||||
}));
|
}));
|
||||||
renderPolAssignList();
|
renderPolAssignList();
|
||||||
openModal('modalPolAssign');
|
openModal('modalPolAssign');
|
||||||
@@ -6728,6 +6740,10 @@ function renderPolAssignList() {
|
|||||||
</div>
|
</div>
|
||||||
`).join('')}
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pol-assign-builtin">
|
||||||
|
<label class="pol-bulk-check"><input type="checkbox" class="pol-assign-alldev" data-idx="${i}" ${p.allDevices ? 'checked' : ''}> Alle Geräte</label>
|
||||||
|
<label class="pol-bulk-check"><input type="checkbox" class="pol-assign-allusr" data-idx="${i}" ${p.allUsers ? 'checked' : ''}> Alle Benutzer</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
PolAssignState.policies.forEach((_, i) => {
|
PolAssignState.policies.forEach((_, i) => {
|
||||||
@@ -6753,10 +6769,18 @@ function renderPolAssignChips(idx, kind) {
|
|||||||
function updatePolAssignInfo() {
|
function updatePolAssignInfo() {
|
||||||
const info = document.getElementById('polAssignInfo');
|
const info = document.getElementById('polAssignInfo');
|
||||||
if (!info) return;
|
if (!info) return;
|
||||||
const n = PolAssignState.policies.filter(p => p.include.length || p.exclude.length).length;
|
const n = PolAssignState.policies.filter(p => p.include.length || p.exclude.length || p.allDevices || p.allUsers).length;
|
||||||
info.textContent = n ? `${n} Policy(s) mit Zuweisung` : 'Keine Zuweisung gewählt';
|
info.textContent = n ? `${n} Policy(s) mit Zuweisung` : 'Keine Zuweisung gewählt';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Integrierte Ziele (Alle Geräte/Benutzer) pro Policy im Post-Import-Modal.
|
||||||
|
document.getElementById('polAssignList')?.addEventListener('change', e => {
|
||||||
|
const dev = e.target.closest('.pol-assign-alldev');
|
||||||
|
const usr = e.target.closest('.pol-assign-allusr');
|
||||||
|
if (dev) { PolAssignState.policies[+dev.dataset.idx].allDevices = dev.checked; updatePolAssignInfo(); }
|
||||||
|
else if (usr) { PolAssignState.policies[+usr.dataset.idx].allUsers = usr.checked; updatePolAssignInfo(); }
|
||||||
|
});
|
||||||
|
|
||||||
let _polAssignSearchTimer = null;
|
let _polAssignSearchTimer = null;
|
||||||
function polAssignHideDropdowns() {
|
function polAssignHideDropdowns() {
|
||||||
document.querySelectorAll('.pol-assign-dropdown').forEach(d => { d.classList.add('hidden'); d.innerHTML = ''; });
|
document.querySelectorAll('.pol-assign-dropdown').forEach(d => { d.classList.add('hidden'); d.innerHTML = ''; });
|
||||||
@@ -6821,27 +6845,24 @@ document.addEventListener('click', e => {
|
|||||||
|
|
||||||
document.getElementById('polAssignApply')?.addEventListener('click', async () => {
|
document.getElementById('polAssignApply')?.addEventListener('click', async () => {
|
||||||
const items = PolAssignState.policies
|
const items = PolAssignState.policies
|
||||||
.filter(p => p.include.length || p.exclude.length)
|
.filter(p => p.include.length || p.exclude.length || p.allDevices || p.allUsers)
|
||||||
.map(p => ({
|
.map(p => ({
|
||||||
exportType: p.exportType,
|
exportType: p.exportType,
|
||||||
id: p.id,
|
id: p.id,
|
||||||
policyName: p.name,
|
policyName: p.name,
|
||||||
include: p.include.map(g => g.id),
|
include: p.include.map(g => g.id),
|
||||||
exclude: p.exclude.map(g => g.id),
|
exclude: p.exclude.map(g => g.id),
|
||||||
|
allDevices: !!p.allDevices,
|
||||||
|
allUsers: !!p.allUsers,
|
||||||
|
mode: 'replace', // frisch importierte Policy hat noch keine Zuweisungen
|
||||||
}));
|
}));
|
||||||
if (!items.length) { closeModal('modalPolAssign'); return; }
|
if (!items.length) { closeModal('modalPolAssign'); return; }
|
||||||
setLoading('Weise Gruppen zu…');
|
setLoading('Weise Gruppen zu…');
|
||||||
try {
|
try {
|
||||||
const res = await api('/api/policies/assign', { method: 'POST', body: { items }, timeoutMs: 120000 });
|
const res = await api('/api/policies/assign', { method: 'POST', body: { items }, timeoutMs: 120000 });
|
||||||
const results = res.results || [];
|
|
||||||
const fail = results.filter(r => !r.success);
|
|
||||||
if (fail.length) {
|
|
||||||
const first = fail[0];
|
|
||||||
toast(`${res.assignedCount || 0} zugewiesen, ${fail.length} fehlgeschlagen. z.B. "${first.policyName || '?'}": ${first.error || 'Fehler'}`, 'err', 'Zuweisung');
|
|
||||||
} else {
|
|
||||||
toast(`${res.assignedCount || 0} Policy(s) zugewiesen.`, 'ok', 'Zuweisung');
|
|
||||||
}
|
|
||||||
closeModal('modalPolAssign');
|
closeModal('modalPolAssign');
|
||||||
|
showPolAssignResult(res.results || [], 'replace');
|
||||||
|
await loadPolicies();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast('Zuweisung fehlgeschlagen: ' + e.message, 'err');
|
toast('Zuweisung fehlgeschlagen: ' + e.message, 'err');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -6861,6 +6882,30 @@ const POL_TYPE_TO_EXPORT = {
|
|||||||
const PolBulkAssign = { policies: [], include: [], exclude: [] };
|
const PolBulkAssign = { policies: [], include: [], exclude: [] };
|
||||||
let _polBulkSearchTimer = null;
|
let _polBulkSearchTimer = null;
|
||||||
|
|
||||||
|
// Zuweisungen einer einzelnen Policy ansehen (aus der Listen-Spalte).
|
||||||
|
function openPolAssignView(p) {
|
||||||
|
document.getElementById('polAssignViewName').innerHTML =
|
||||||
|
`<b>${escapeHtml(p.name || '—')}</b> <span class="pol-type-badge">${escapeHtml(p.typeLabel || p.type)}</span>`;
|
||||||
|
const body = document.getElementById('polAssignViewBody');
|
||||||
|
const asg = p.assignments || [];
|
||||||
|
if (!asg.length) {
|
||||||
|
body.innerHTML = '<div class="muted">Keine Zuweisungen.</div>';
|
||||||
|
} else {
|
||||||
|
const label = a => a.kind === 'allDevices' ? 'Alle Geräte'
|
||||||
|
: a.kind === 'allUsers' ? 'Alle Benutzer'
|
||||||
|
: (a.groupName || a.groupId);
|
||||||
|
const inc = asg.filter(a => a.mode === 'include');
|
||||||
|
const exc = asg.filter(a => a.mode === 'exclude');
|
||||||
|
const chip = (a, cls) => `<span class="pol-assign-chip ${cls}">${escapeHtml(label(a))}</span>`;
|
||||||
|
body.innerHTML =
|
||||||
|
`<div class="pol-av-group"><label class="lbl">Include (${inc.length})</label>`
|
||||||
|
+ `<div class="pol-assign-chips">${inc.length ? inc.map(a => chip(a, '')).join('') : '<span class="muted">—</span>'}</div></div>`
|
||||||
|
+ `<div class="pol-av-group" style="margin-top:12px;"><label class="lbl">Exclude (${exc.length})</label>`
|
||||||
|
+ `<div class="pol-assign-chips">${exc.length ? exc.map(a => chip(a, 'exclude')).join('') : '<span class="muted">—</span>'}</div></div>`;
|
||||||
|
}
|
||||||
|
openModal('modalPolAssignView');
|
||||||
|
}
|
||||||
|
|
||||||
function openPolBulkAssign() {
|
function openPolBulkAssign() {
|
||||||
const sel = PolState.items.filter(p => PolState.selected.has(polKey(p)));
|
const sel = PolState.items.filter(p => PolState.selected.has(polKey(p)));
|
||||||
if (!sel.length) return;
|
if (!sel.length) return;
|
||||||
@@ -6993,15 +7038,9 @@ document.getElementById('polBulkApply')?.addEventListener('click', async () => {
|
|||||||
setLoading('Weise Gruppen zu…');
|
setLoading('Weise Gruppen zu…');
|
||||||
try {
|
try {
|
||||||
const res = await api('/api/policies/assign', { method: 'POST', body: { items }, timeoutMs: 180000 });
|
const res = await api('/api/policies/assign', { method: 'POST', body: { items }, timeoutMs: 180000 });
|
||||||
const results = res.results || [];
|
|
||||||
const fail = results.filter(r => !r.success);
|
|
||||||
if (fail.length) {
|
|
||||||
const first = fail[0];
|
|
||||||
toast(`${res.assignedCount || 0} zugewiesen, ${fail.length} fehlgeschlagen. z.B. "${first.policyName || '?'}": ${first.error || 'Fehler'}`, 'err', 'Zuweisung');
|
|
||||||
} else {
|
|
||||||
toast(`Gruppen ${mode === 'add' ? 'hinzugefügt' : 'ersetzt'}: ${res.assignedCount || 0} Policy(s).`, 'ok', 'Zuweisung');
|
|
||||||
}
|
|
||||||
closeModal('modalPolBulkAssign');
|
closeModal('modalPolBulkAssign');
|
||||||
|
showPolAssignResult(res.results || [], mode);
|
||||||
|
await loadPolicies(); // Zuweisungs-Zähler in der Liste aktualisieren
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast('Zuweisung fehlgeschlagen: ' + e.message, 'err');
|
toast('Zuweisung fehlgeschlagen: ' + e.message, 'err');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -7009,6 +7048,28 @@ document.getElementById('polBulkApply')?.addEventListener('click', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Ergebnis einer (Bulk-)Zuweisung pro Policy anzeigen.
|
||||||
|
function showPolAssignResult(results, mode) {
|
||||||
|
const ok = results.filter(r => r.success && !r.skipped).length;
|
||||||
|
const skip = results.filter(r => r.success && r.skipped).length;
|
||||||
|
const fail = results.filter(r => !r.success).length;
|
||||||
|
document.getElementById('polBulkResultSummary').innerHTML =
|
||||||
|
`Modus: <b>${mode === 'add' ? 'Hinzufügen' : 'Ersetzen'}</b> — `
|
||||||
|
+ `<span class="pol-res-ok">${ok} zugewiesen</span>`
|
||||||
|
+ (skip ? ` · <span class="muted">${skip} übersprungen</span>` : '')
|
||||||
|
+ (fail ? ` · <span class="pol-res-err">${fail} fehlgeschlagen</span>` : '');
|
||||||
|
document.getElementById('polBulkResultBody').innerHTML = results.map(r => {
|
||||||
|
let badge, detail = '';
|
||||||
|
if (!r.success) { badge = '<span class="pol-res-badge err">Fehler</span>'; detail = `<span class="pol-res-msg">${escapeHtml(r.error || 'Unbekannter Fehler')}</span>`; }
|
||||||
|
else if (r.skipped) { badge = '<span class="pol-res-badge skip">Übersprungen</span>'; }
|
||||||
|
else { badge = '<span class="pol-res-badge ok">Zugewiesen</span>'; }
|
||||||
|
return `<div class="pol-res-row">${badge}<span class="pol-res-name">${escapeHtml(r.policyName || r.id || '—')}</span>${detail}</div>`;
|
||||||
|
}).join('');
|
||||||
|
openModal('modalPolBulkResult');
|
||||||
|
const anyFail = fail > 0;
|
||||||
|
toast(anyFail ? `${ok} zugewiesen, ${fail} fehlgeschlagen.` : `${ok} Policy(s) zugewiesen.`, anyFail ? 'warn' : 'ok', 'Zuweisung');
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('btnPolAssignGroups')?.addEventListener('click', openPolBulkAssign);
|
document.getElementById('btnPolAssignGroups')?.addEventListener('click', openPolBulkAssign);
|
||||||
|
|
||||||
// Alle Policies als Snapshot in den konfigurierten Git-Ordner schreiben + committen.
|
// Alle Policies als Snapshot in den konfigurierten Git-Ordner schreiben + committen.
|
||||||
|
|||||||
+38
-1
@@ -640,11 +640,12 @@
|
|||||||
<th class="sortable" data-col="name">Name <span class="sort-ico"></span></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="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="platform">Plattform <span class="sort-ico"></span></th>
|
||||||
|
<th class="sortable" data-col="assignmentCount">Zuweisungen <span class="sort-ico"></span></th>
|
||||||
<th class="sortable" data-col="lastModifiedDateTime">Geändert <span class="sort-ico"></span></th>
|
<th class="sortable" data-col="lastModifiedDateTime">Geändert <span class="sort-ico"></span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="polBody">
|
<tbody id="polBody">
|
||||||
<tr><td colspan="5" class="pol-hint">Klicke auf „Neu laden" um Policies zu laden.</td></tr>
|
<tr><td colspan="6" class="pol-hint">Klicke auf „Neu laden" um Policies zu laden.</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -1040,6 +1041,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Zuweisungen einer Policy ansehen -->
|
||||||
|
<div id="modalPolAssignView" class="modal hidden">
|
||||||
|
<div class="modal-backdrop" data-close></div>
|
||||||
|
<div class="modal-box">
|
||||||
|
<div class="modal-head">
|
||||||
|
<h3>Zuweisungen</h3>
|
||||||
|
<button class="modal-close" data-close aria-label="Schließen">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="polAssignViewName" class="form-hint" style="margin-bottom:12px;"></div>
|
||||||
|
<div id="polAssignViewBody"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-foot">
|
||||||
|
<button class="btn btn-secondary" data-close>Schließen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ergebnis der Bulk-Zuweisung -->
|
||||||
|
<div id="modalPolBulkResult" class="modal hidden">
|
||||||
|
<div class="modal-backdrop" data-close></div>
|
||||||
|
<div class="modal-box modal-lg">
|
||||||
|
<div class="modal-head">
|
||||||
|
<h3>Ergebnis der Zuweisung</h3>
|
||||||
|
<button class="modal-close" data-close aria-label="Schließen">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="polBulkResultSummary" class="form-hint" style="margin-bottom:12px;"></div>
|
||||||
|
<div id="polBulkResultBody" class="pol-result-list"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-foot">
|
||||||
|
<button class="btn btn-primary" data-close>Schließen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- MODAL: Geräte-Offboarding -->
|
<!-- MODAL: Geräte-Offboarding -->
|
||||||
<div id="modalOffboard" class="modal hidden">
|
<div id="modalOffboard" class="modal hidden">
|
||||||
<div class="modal-backdrop" data-close></div>
|
<div class="modal-backdrop" data-close></div>
|
||||||
|
|||||||
@@ -5579,6 +5579,7 @@ body.read-only .app-row {
|
|||||||
.pol-assign-dropdown .opt:hover { background: var(--hover, rgba(255,255,255,.06)); }
|
.pol-assign-dropdown .opt:hover { background: var(--hover, rgba(255,255,255,.06)); }
|
||||||
.pol-assign-dropdown .opt .desc { font-size: 11px; color: var(--text-dim, #888); margin-top: 1px; }
|
.pol-assign-dropdown .opt .desc { font-size: 11px; color: var(--text-dim, #888); margin-top: 1px; }
|
||||||
.pol-assign-dropdown .msg { padding: 8px 10px; font-size: 12px; color: var(--text-dim, #888); }
|
.pol-assign-dropdown .msg { padding: 8px 10px; font-size: 12px; color: var(--text-dim, #888); }
|
||||||
|
.pol-assign-builtin { margin-top: 10px; }
|
||||||
|
|
||||||
/* Bulk-Zuweisung: Policy-Liste + Modus */
|
/* Bulk-Zuweisung: Policy-Liste + Modus */
|
||||||
.pol-bulk-names { display: flex; flex-wrap: wrap; gap: 6px; max-height: 120px; overflow-y: auto; }
|
.pol-bulk-names { display: flex; flex-wrap: wrap; gap: 6px; max-height: 120px; overflow-y: auto; }
|
||||||
@@ -5592,6 +5593,26 @@ body.read-only .app-row {
|
|||||||
.pol-bulk-radio input { margin-top: 2px; }
|
.pol-bulk-radio input { margin-top: 2px; }
|
||||||
.pol-bulk-builtin .lbl { display: block; font-size: 12px; font-weight: 600; margin-bottom: 6px; color: var(--text-dim, #888); }
|
.pol-bulk-builtin .lbl { display: block; font-size: 12px; font-weight: 600; margin-bottom: 6px; color: var(--text-dim, #888); }
|
||||||
.pol-bulk-check { display: inline-flex; align-items: center; gap: 6px; margin-right: 18px; font-size: 13px; cursor: pointer; }
|
.pol-bulk-check { display: inline-flex; align-items: center; gap: 6px; margin-right: 18px; font-size: 13px; cursor: pointer; }
|
||||||
|
/* Zuweisungs-Spalte + Detailansicht */
|
||||||
|
.pol-asg-badge {
|
||||||
|
min-width: 24px; padding: 1px 8px; border: 1px solid var(--hairline, #333);
|
||||||
|
border-radius: var(--r-pill, 999px); background: var(--bg3, var(--hover));
|
||||||
|
color: var(--ink, inherit); font-size: 12px; font-weight: 600; cursor: pointer;
|
||||||
|
}
|
||||||
|
.pol-asg-badge:hover { border-color: var(--accent, #60a5fa); color: var(--accent, #60a5fa); }
|
||||||
|
.pol-av-group .lbl { display: block; font-size: 12px; font-weight: 600; margin-bottom: 6px; color: var(--text-dim, #888); }
|
||||||
|
/* Ergebnis-Report der Zuweisung */
|
||||||
|
.pol-result-list { max-height: 50vh; overflow-y: auto; }
|
||||||
|
.pol-res-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--hairline-soft, rgba(255,255,255,.06)); font-size: 13px; }
|
||||||
|
.pol-res-row:last-child { border-bottom: 0; }
|
||||||
|
.pol-res-name { font-weight: 500; }
|
||||||
|
.pol-res-msg { color: #f87171; font-size: 12px; margin-left: auto; text-align: right; }
|
||||||
|
.pol-res-badge { flex: none; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: var(--r-pill, 999px); }
|
||||||
|
.pol-res-badge.ok { background: rgba(74,222,128,.15); color: #4ade80; }
|
||||||
|
.pol-res-badge.err { background: rgba(248,113,113,.15); color: #f87171; }
|
||||||
|
.pol-res-badge.skip { background: var(--hairline-soft); color: var(--muted, #999); }
|
||||||
|
.pol-res-ok { color: #4ade80; }
|
||||||
|
.pol-res-err { color: #f87171; }
|
||||||
|
|
||||||
/* =============================================================
|
/* =============================================================
|
||||||
Multi-Tenant: Topbar-Umschalter + Profil-Editor
|
Multi-Tenant: Topbar-Umschalter + Profil-Editor
|
||||||
|
|||||||
Reference in New Issue
Block a user