Policies: Bulk-Zuweisung von Gruppen inkl. Alle Geräte/Alle Benutzer
- 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>
This commit is contained in:
+168
@@ -6409,6 +6409,12 @@ function polUpdateSelCount() {
|
||||
const n = keys.length;
|
||||
document.getElementById('polSelCount').textContent = n;
|
||||
document.getElementById('btnPolExport').disabled = n === 0;
|
||||
const btnA = document.getElementById('btnPolAssignGroups');
|
||||
if (btnA) {
|
||||
btnA.disabled = n === 0;
|
||||
const c = document.getElementById('polAssignSelCount');
|
||||
if (c) c.textContent = n;
|
||||
}
|
||||
// Konsolidieren nur bei >=2 Policies UND alle vom Typ Settings Catalog.
|
||||
const btnC = document.getElementById('btnPolConsolidate');
|
||||
if (btnC) {
|
||||
@@ -6843,6 +6849,168 @@ document.getElementById('polAssignApply')?.addEventListener('click', async () =>
|
||||
}
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
// Bulk: mehreren ausgewählten Policies dieselben Gruppen zuweisen
|
||||
// =============================================================
|
||||
const POL_TYPE_TO_EXPORT = {
|
||||
settingscatalog: 'SettingsCatalog',
|
||||
compliance: 'CompliancePolicy',
|
||||
configuration: 'ConfigurationProfile',
|
||||
administrativetemplate: 'AdministrativeTemplate',
|
||||
};
|
||||
const PolBulkAssign = { policies: [], include: [], exclude: [] };
|
||||
let _polBulkSearchTimer = null;
|
||||
|
||||
function openPolBulkAssign() {
|
||||
const sel = PolState.items.filter(p => PolState.selected.has(polKey(p)));
|
||||
if (!sel.length) return;
|
||||
PolBulkAssign.policies = sel.map(p => ({
|
||||
id: p.id,
|
||||
name: p.name || '(ohne Name)',
|
||||
exportType: POL_TYPE_TO_EXPORT[p.type] || '',
|
||||
}));
|
||||
PolBulkAssign.include = [];
|
||||
PolBulkAssign.exclude = [];
|
||||
document.getElementById('polBulkCount').textContent = sel.length;
|
||||
document.getElementById('polBulkNames').innerHTML = PolBulkAssign.policies.map(p =>
|
||||
`<span class="pol-bulk-name">${escapeHtml(p.name)} <span class="pol-type-badge">${escapeHtml(POL_TYPE_LABELS[p.exportType] || 'Policy')}</span></span>`
|
||||
).join('');
|
||||
const addRadio = document.querySelector('input[name="polBulkMode"][value="add"]');
|
||||
if (addRadio) addRadio.checked = true;
|
||||
const ad = document.getElementById('polBulkAllDevices'); if (ad) ad.checked = false;
|
||||
const au = document.getElementById('polBulkAllUsers'); if (au) au.checked = false;
|
||||
renderPolBulkChips('include');
|
||||
renderPolBulkChips('exclude');
|
||||
updatePolBulkApply();
|
||||
openModal('modalPolBulkAssign');
|
||||
}
|
||||
|
||||
function renderPolBulkChips(kind) {
|
||||
const box = document.querySelector(`.pol-assign-chips[data-scope="bulk"][data-kind="${kind}"]`);
|
||||
if (!box) return;
|
||||
box.innerHTML = PolBulkAssign[kind].map((g, j) => `
|
||||
<span class="pol-assign-chip ${kind === 'exclude' ? 'exclude' : ''}">
|
||||
${escapeHtml(g.name)}
|
||||
<button type="button" data-kind="${kind}" data-j="${j}" aria-label="Entfernen">×</button>
|
||||
</span>`).join('');
|
||||
updatePolBulkApply();
|
||||
}
|
||||
|
||||
function updatePolBulkApply() {
|
||||
const inc = PolBulkAssign.include.length, exc = PolBulkAssign.exclude.length;
|
||||
const allDev = document.getElementById('polBulkAllDevices')?.checked || false;
|
||||
const allUsr = document.getElementById('polBulkAllUsers')?.checked || false;
|
||||
const has = inc + exc > 0 || allDev || allUsr;
|
||||
const btn = document.getElementById('polBulkApply');
|
||||
if (btn) btn.disabled = !has;
|
||||
const info = document.getElementById('polBulkInfo');
|
||||
if (info) {
|
||||
if (!has) { info.textContent = 'Mindestens eine Gruppe oder ein integriertes Ziel wählen.'; }
|
||||
else {
|
||||
const parts = [];
|
||||
if (allDev) parts.push('Alle Geräte');
|
||||
if (allUsr) parts.push('Alle Benutzer');
|
||||
if (inc) parts.push(`${inc} Include`);
|
||||
if (exc) parts.push(`${exc} Exclude`);
|
||||
info.textContent = parts.join(' · ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function polBulkHideDropdowns() {
|
||||
document.querySelectorAll('.pol-bulk-dropdown').forEach(d => { d.classList.add('hidden'); d.innerHTML = ''; });
|
||||
}
|
||||
|
||||
// Gruppensuche (debounced) im Bulk-Modal
|
||||
document.getElementById('modalPolBulkAssign')?.addEventListener('input', e => {
|
||||
const input = e.target.closest('.pol-bulk-search');
|
||||
if (!input) return;
|
||||
const kind = input.dataset.kind;
|
||||
const dd = document.querySelector(`.pol-bulk-dropdown[data-kind="${kind}"]`);
|
||||
const q = input.value.trim();
|
||||
clearTimeout(_polBulkSearchTimer);
|
||||
if (q.length < 2) { if (dd) { dd.classList.add('hidden'); dd.innerHTML = ''; } return; }
|
||||
if (dd) { dd.classList.remove('hidden'); dd.innerHTML = '<div class="msg">Suche…</div>'; }
|
||||
_polBulkSearchTimer = setTimeout(async () => {
|
||||
try {
|
||||
const data = await api(`/api/groups/search?q=${encodeURIComponent(q)}`);
|
||||
const items = (data.items || []).slice(0, 25);
|
||||
if (!dd) return;
|
||||
if (!items.length) { dd.innerHTML = '<div class="msg">Keine Gruppen gefunden.</div>'; return; }
|
||||
dd.innerHTML = items.map(g => `
|
||||
<div class="opt" data-id="${escapeHtml(g.Id)}" data-name="${escapeHtml(g.DisplayName)}">
|
||||
<div>${escapeHtml(g.DisplayName)}</div>
|
||||
${g.Description ? `<div class="desc">${escapeHtml(g.Description)}</div>` : ''}
|
||||
</div>`).join('');
|
||||
} catch (err) {
|
||||
if (dd) dd.innerHTML = `<div class="msg">Suche fehlgeschlagen: ${escapeHtml(err.message)}</div>`;
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Option wählen / Chip entfernen im Bulk-Modal
|
||||
document.getElementById('modalPolBulkAssign')?.addEventListener('click', e => {
|
||||
const opt = e.target.closest('.pol-bulk-dropdown .opt');
|
||||
if (opt) {
|
||||
const dd = opt.closest('.pol-bulk-dropdown');
|
||||
const kind = dd.dataset.kind;
|
||||
const id = opt.dataset.id, name = opt.dataset.name;
|
||||
const other = kind === 'include' ? 'exclude' : 'include';
|
||||
if (!PolBulkAssign[kind].some(g => g.id === id) && !PolBulkAssign[other].some(g => g.id === id)) {
|
||||
PolBulkAssign[kind].push({ id, name });
|
||||
renderPolBulkChips(kind);
|
||||
}
|
||||
const input = document.querySelector(`.pol-bulk-search[data-kind="${kind}"]`);
|
||||
if (input) input.value = '';
|
||||
dd.classList.add('hidden'); dd.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
const rm = e.target.closest('.pol-assign-chip button');
|
||||
if (rm) {
|
||||
const kind = rm.dataset.kind, j = +rm.dataset.j;
|
||||
PolBulkAssign[kind].splice(j, 1);
|
||||
renderPolBulkChips(kind);
|
||||
}
|
||||
});
|
||||
document.addEventListener('click', e => {
|
||||
if (!e.target.closest('#modalPolBulkAssign .pol-assign-search-wrap')) polBulkHideDropdowns();
|
||||
});
|
||||
|
||||
document.getElementById('polBulkAllDevices')?.addEventListener('change', updatePolBulkApply);
|
||||
document.getElementById('polBulkAllUsers')?.addEventListener('change', updatePolBulkApply);
|
||||
|
||||
document.getElementById('polBulkApply')?.addEventListener('click', async () => {
|
||||
const inc = PolBulkAssign.include.map(g => g.id);
|
||||
const exc = PolBulkAssign.exclude.map(g => g.id);
|
||||
const allDevices = document.getElementById('polBulkAllDevices')?.checked || false;
|
||||
const allUsers = document.getElementById('polBulkAllUsers')?.checked || false;
|
||||
if (!inc.length && !exc.length && !allDevices && !allUsers) return;
|
||||
const mode = (document.querySelector('input[name="polBulkMode"]:checked')?.value) === 'replace' ? 'replace' : 'add';
|
||||
const items = PolBulkAssign.policies.map(p => ({
|
||||
exportType: p.exportType, id: p.id, policyName: p.name,
|
||||
include: inc, exclude: exc, allDevices, allUsers, mode,
|
||||
}));
|
||||
setLoading('Weise Gruppen zu…');
|
||||
try {
|
||||
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');
|
||||
} catch (e) {
|
||||
toast('Zuweisung fehlgeschlagen: ' + e.message, 'err');
|
||||
} finally {
|
||||
clearLoading();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btnPolAssignGroups')?.addEventListener('click', openPolBulkAssign);
|
||||
|
||||
// Alle Policies als Snapshot in den konfigurierten Git-Ordner schreiben + committen.
|
||||
async function polGitSnapshot() {
|
||||
setLoading('Erstelle Policy-Snapshot & committe…');
|
||||
|
||||
Reference in New Issue
Block a user