App Report via Export Jobs, Umlaut-Suche, Geräte-Export, Mitglieder entfernen
- App Report: Zahlen per AppInstallStatusAggregate Export Job (ZIP/CSV) - Geräte-Export: getDeviceInstallStatusReport statt deviceStatuses - Benutzersuche: ConsistencyLevel+$count für Umlaut-Kompatibilität - Gruppen: Mitglieder entfernen in modalMembers und Group Explorer (gex) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+100
-4
@@ -3290,8 +3290,9 @@ function renderMembersList() {
|
||||
if (e.target.checked) mmContext.selectedIds.add(id);
|
||||
else mmContext.selectedIds.delete(id);
|
||||
row.classList.toggle('selected', e.target.checked);
|
||||
document.getElementById('mmInfo').textContent =
|
||||
mmContext.selectedIds.size === 0 ? 'Keine ausgewählt' : `${mmContext.selectedIds.size} ausgewählt`;
|
||||
const n = mmContext.selectedIds.size;
|
||||
document.getElementById('mmInfo').textContent = n === 0 ? 'Keine ausgewählt' : `${n} ausgewählt`;
|
||||
document.getElementById('mmRemove').disabled = n === 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3314,6 +3315,43 @@ document.getElementById('mmClear')?.addEventListener('click', () => {
|
||||
mmContext.selectedIds.clear();
|
||||
renderMembersList();
|
||||
document.getElementById('mmInfo').textContent = 'Keine ausgewählt';
|
||||
document.getElementById('mmRemove').disabled = true;
|
||||
});
|
||||
|
||||
document.getElementById('mmRemove')?.addEventListener('click', async () => {
|
||||
const n = mmContext.selectedIds.size;
|
||||
if (n === 0) return;
|
||||
const names = mmContext.members
|
||||
.filter(m => mmContext.selectedIds.has(m.Id))
|
||||
.map(m => m.DisplayName || m.UserPrincipalName)
|
||||
.slice(0, 5).join('\n');
|
||||
const more = n > 5 ? `\n… und ${n - 5} weitere` : '';
|
||||
if (!confirm(`${n} Mitglied${n > 1 ? 'er' : ''} aus "${mmContext.groupName}" entfernen?\n\n${names}${more}`)) return;
|
||||
|
||||
const btn = document.getElementById('mmRemove');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Entferne…';
|
||||
|
||||
const ids = [...mmContext.selectedIds];
|
||||
let ok = 0, fail = 0;
|
||||
for (const userId of ids) {
|
||||
try {
|
||||
await api(`/api/groups/${encodeURIComponent(mmContext.groupId)}/members/${encodeURIComponent(userId)}`, { method: 'DELETE' });
|
||||
mmContext.members = mmContext.members.filter(m => m.Id !== userId);
|
||||
mmContext.selectedIds.delete(userId);
|
||||
ok++;
|
||||
} catch (e) {
|
||||
fail++;
|
||||
console.error('Remove member failed:', userId, e);
|
||||
}
|
||||
}
|
||||
|
||||
btn.textContent = 'Aus Gruppe entfernen';
|
||||
document.getElementById('mmInfo').textContent = 'Keine ausgewählt';
|
||||
renderMembersList();
|
||||
|
||||
if (fail === 0) toast(`${ok} Mitglied${ok > 1 ? 'er' : ''} entfernt`, 'ok');
|
||||
else toast(`${ok} entfernt, ${fail} fehlgeschlagen`, 'warn');
|
||||
});
|
||||
|
||||
document.getElementById('mmApply')?.addEventListener('click', () => {
|
||||
@@ -4488,25 +4526,83 @@ function _gexRenderMembers() {
|
||||
if (m.duplicates > 0) html += ` (${m.duplicates} Duplikate aus verschachtelten Gruppen zusammengeführt)`;
|
||||
html += '</div>';
|
||||
|
||||
const directGroupName = m.groupName || '';
|
||||
for (const [path, users] of byPath.entries()) {
|
||||
const isDirect = path === directGroupName;
|
||||
html += `<div class="gex-path-group">
|
||||
<div class="gex-path-head">${escapeHtml(path)} <span class="count-badge">${users.length}</span></div>
|
||||
<div class="gex-user-list">`;
|
||||
for (const u of users) {
|
||||
const upn = u.UserPrincipalName || u.Mail || u.Id;
|
||||
const searchKey = `${(u.DisplayName || '').toLowerCase()} ${upn.toLowerCase()}`;
|
||||
html += `<div class="gex-user-item" data-search="${escapeHtml(searchKey)}">
|
||||
const cbx = isDirect
|
||||
? `<input type="checkbox" class="gex-member-chk" data-uid="${escapeHtml(u.Id)}" data-name="${escapeHtml(u.DisplayName || upn)}">`
|
||||
: `<span style="width:14px;flex-shrink:0;"></span>`;
|
||||
html += `<label class="gex-user-item" data-search="${escapeHtml(searchKey)}">
|
||||
${cbx}
|
||||
<span class="gex-user-name">${escapeHtml(u.DisplayName || '—')}</span>
|
||||
<span class="gex-user-upn">${escapeHtml(upn)}</span>
|
||||
</div>`;
|
||||
</label>`;
|
||||
}
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
area.innerHTML = html;
|
||||
if (GexState.memberFilter) _gexApplyFilter();
|
||||
|
||||
// Checkboxen verdrahten
|
||||
const removeBtn = document.getElementById('gexRemoveBtn');
|
||||
if (removeBtn) removeBtn.style.display = '';
|
||||
area.querySelectorAll('.gex-member-chk').forEach(chk => {
|
||||
chk.addEventListener('change', () => {
|
||||
const n = area.querySelectorAll('.gex-member-chk:checked').length;
|
||||
if (removeBtn) {
|
||||
removeBtn.disabled = n === 0;
|
||||
removeBtn.textContent = n > 0 ? `${n} Mitglied${n > 1 ? 'er' : ''} entfernen` : 'Aus Gruppe entfernen';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Mitglieder entfernen ----
|
||||
document.getElementById('gexRemoveBtn')?.addEventListener('click', async () => {
|
||||
const area = document.getElementById('gexMembersArea');
|
||||
const checked = [...(area?.querySelectorAll('.gex-member-chk:checked') || [])];
|
||||
if (!checked.length || !GexState.selectedGroup) return;
|
||||
|
||||
const names = checked.slice(0, 5).map(c => c.dataset.name).join('\n');
|
||||
const more = checked.length > 5 ? `\n… und ${checked.length - 5} weitere` : '';
|
||||
if (!confirm(`${checked.length} Mitglied${checked.length > 1 ? 'er' : ''} aus "${GexState.selectedGroup.DisplayName}" entfernen?\n\n${names}${more}`)) return;
|
||||
|
||||
const btn = document.getElementById('gexRemoveBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Entferne…';
|
||||
const groupId = GexState.selectedGroup.Id;
|
||||
|
||||
let ok = 0, fail = 0;
|
||||
for (const chk of checked) {
|
||||
try {
|
||||
await api(`/api/groups/${encodeURIComponent(groupId)}/members/${encodeURIComponent(chk.dataset.uid)}`, { method: 'DELETE' });
|
||||
chk.closest('label').remove();
|
||||
ok++;
|
||||
} catch (e) {
|
||||
fail++;
|
||||
console.error('Remove failed:', chk.dataset.uid, e);
|
||||
}
|
||||
}
|
||||
|
||||
btn.textContent = 'Aus Gruppe entfernen';
|
||||
btn.disabled = true;
|
||||
if (fail === 0) toast(`${ok} Mitglied${ok > 1 ? 'er' : ''} entfernt`, 'ok');
|
||||
else toast(`${ok} entfernt, ${fail} fehlgeschlagen`, 'warn');
|
||||
|
||||
// Mitgliederzahl aktualisieren
|
||||
if (GexState.members) {
|
||||
GexState.members.count = Math.max(0, GexState.members.count - ok);
|
||||
document.getElementById('gexStats').textContent = `${GexState.members.count} Benutzer`;
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Benutzer-Suche im Add-Tab ----
|
||||
|
||||
let _gexUserSearchTimer = null;
|
||||
|
||||
Reference in New Issue
Block a user