diff --git a/.DS_Store b/.DS_Store index c9f1432..f234f68 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Intune/.DS_Store b/Intune/.DS_Store index a56c8ef..c797a86 100644 Binary files a/Intune/.DS_Store and b/Intune/.DS_Store differ diff --git a/Intune/Intune-App-Manager-Web/www/app.js b/Intune/Intune-App-Manager-Web/www/app.js index dbb1662..0bd0828 100644 --- a/Intune/Intune-App-Manager-Web/www/app.js +++ b/Intune/Intune-App-Manager-Web/www/app.js @@ -1094,6 +1094,14 @@ function renderAppRow(app) { + `; + const removableReq = app.RequiredGroups.filter(g => !g.IsNative); + const removableAvail = app.AvailableGroups.filter(g => !g.IsNative); + const mkRemoveBtn = (groups, type, label) => groups.length > 0 + ? `` + : ``; + const removeReqBtn = mkRemoveBtn(removableReq, 'required', 'R'); + const removeAvailBtn = mkRemoveBtn(removableAvail, 'available', 'A'); + const hasNoAssignments = app.AvailableGroups.length === 0 && app.RequiredGroups.length === 0; // Direkt-Link in das Intune Admin Center fuer diese App (dezentes Icon, NICHT der Name selbst) const intuneIcon = intuneAppLink(app.AppId, app.AppName); @@ -1137,6 +1145,8 @@ function renderAppRow(app) {
${availBtn || '—'}
${reqBtn || '—'}
${createBtn}
+
${removeReqBtn}
+
${removeAvailBtn}
${deleteBtn}
${detailHtml} @@ -1688,6 +1698,26 @@ function attachAppRowEvents(wrap) { openCreateGroupModal(btn.dataset.appId, btn.dataset.appName); }); }); + wrap.querySelectorAll('[data-remove-group]').forEach(btn => { + btn.addEventListener('click', e => { + e.stopPropagation(); + const app = State.apps.find(a => a.AppId === btn.dataset.appId); + if (!app) return; + const type = btn.dataset.removeType; // 'required' | 'available' + const src = type === 'required' ? app.RequiredGroups : app.AvailableGroups; + const groups = src.filter(g => !g.IsNative).map(g => ({ ...g, _type: type })); + if (groups.length === 0) return; + if (groups.length === 1) { + removeAssignmentWithConfirm({ + appId: app.AppId, appName: app.AppName, + groupId: groups[0].GroupId, groupName: groups[0].GroupName, + type, + }); + return; + } + showRemoveGroupPicker(btn, app, groups); + }); + }); wrap.querySelectorAll('[data-delete-app]').forEach(btn => { btn.addEventListener('click', e => { e.preventDefault(); @@ -1893,6 +1923,62 @@ function showGroupPicker(anchorEl, app, groups, type) { }, 0); } +function showRemoveGroupPicker(anchorEl, app, groups) { + closeGroupPicker(); + + pickerEl = document.createElement('div'); + pickerEl.className = 'group-picker picker-remove'; + pickerEl.innerHTML = ` +
Gruppe entfernen — ${escapeHtml(app.AppName)}
+
+ ${groups.map(g => { + const badge = g._type === 'required' + ? `Req` + : `Avail`; + return ` +
+ +
`; + }).join('')} +
+ `; + document.body.appendChild(pickerEl); + + const r = anchorEl.getBoundingClientRect(); + const pw = pickerEl.offsetWidth; + const ph = pickerEl.offsetHeight; + let left = r.left; + let top = r.bottom + 4; + if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8; + if (top + ph > window.innerHeight - 8) top = r.top - ph - 4; + pickerEl.style.left = left + 'px'; + pickerEl.style.top = top + 'px'; + + pickerEl.querySelectorAll('.picker-remove-item').forEach(item => { + item.addEventListener('click', e => { + e.stopPropagation(); + const ctx = { + appId: app.AppId, appName: app.AppName, + groupId: item.dataset.groupId, groupName: item.dataset.groupName, + type: item.dataset.type, + }; + closeGroupPicker(); + removeAssignmentWithConfirm(ctx); + }); + }); + + setTimeout(() => { + document.addEventListener('click', onPickerOutsideClick, true); + document.addEventListener('keydown', onPickerEscape); + }, 0); +} + // ============================================================= // Membership // ============================================================= diff --git a/Intune/Intune-App-Manager-Web/www/styles.css b/Intune/Intune-App-Manager-Web/www/styles.css index 57bf071..afe42d3 100644 --- a/Intune/Intune-App-Manager-Web/www/styles.css +++ b/Intune/Intune-App-Manager-Web/www/styles.css @@ -1330,6 +1330,8 @@ html[data-theme="dark"] .entra-link.teams-link:hover { 98px /* available */ 98px /* required */ 26px /* + */ + 32px /* −R */ + 32px /* −A */ 26px; /* delete */ column-gap: 8px; padding: 6px 12px; @@ -1610,7 +1612,9 @@ html[data-theme="dark"] .entra-link.teams-link:hover { .col-version { justify-content: flex-start; } .col-avail { justify-content: stretch; } .col-req { justify-content: stretch; } -.col-create { justify-content: stretch; } +.col-create { justify-content: stretch; } +.col-remove-req { justify-content: stretch; } +.col-remove-avail { justify-content: stretch; } /* Buttons fuellen ihre Spalte komplett — saubere vertikale Linien. Available/Required: Label klebt links, Count-Badge rechts, damit die @@ -1621,7 +1625,11 @@ html[data-theme="dark"] .entra-link.teams-link:hover { justify-content: flex-start; padding: 0 10px; } -.col-create .type-btn { +.col-create .type-btn, +.col-remove-req .type-btn, +.col-remove-req .type-remove, +.col-remove-avail .type-btn, +.col-remove-avail .type-remove { width: 100%; justify-content: center; padding: 0; @@ -1891,6 +1899,26 @@ a.app-name:hover { border-color: var(--ink); border-style: solid; } +.type-remove { + width: 100%; + padding: 0; + font-size: 11px; + font-weight: 600; + color: var(--muted); + border-style: dashed; + justify-content: center; + letter-spacing: -0.5px; +} +.type-remove:hover:not(:disabled) { + color: var(--danger, #e55); + border-color: var(--danger, #e55); + border-style: solid; +} +.type-remove--empty { + visibility: hidden; + pointer-events: none; + border: none; +} /* Anzahl-Hinweis (z.B. "Available 3" wenn 3 Available-Gruppen existieren) */ .type-btn-count {