From eeabb1db31ce9f18e905aeff88aa0134b43ff7ef Mon Sep 17 00:00:00 2001 From: Marco Wende Date: Wed, 17 Jun 2026 08:43:07 +0200 Subject: [PATCH] =?UTF-8?q?Install=20anzeige=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Intune/Intune-App-Manager-Web/www/app.js | 100 +++++++++++-------- Intune/Intune-App-Manager-Web/www/styles.css | 44 ++++++++ 2 files changed, 100 insertions(+), 44 deletions(-) diff --git a/Intune/Intune-App-Manager-Web/www/app.js b/Intune/Intune-App-Manager-Web/www/app.js index 93f2f0a..2f5b0bd 100644 --- a/Intune/Intune-App-Manager-Web/www/app.js +++ b/Intune/Intune-App-Manager-Web/www/app.js @@ -1261,6 +1261,12 @@ function renderAppDetailPanel(appId) { sections.push(renderAssignmentsSection(app)); } + // Installationen — direkt nach Zuweisungen, damit die operative Kennzahl + // sichtbar ist ohne scrollen zu muessen. + if (d.InstallSummary) { + sections.push(renderInstallStatsSection(d.InstallSummary)); + } + // Allgemein const general = [ row('Hersteller', d.Publisher), @@ -1330,11 +1336,6 @@ function renderAppDetailPanel(appId) { ].filter(Boolean).join(''); if (metaRows) sections.push(`
Meta
${metaRows}
`); - // Installationen — Stats aus installSummary (Erfolg/Fehler je User/Device) - if (d.InstallSummary) { - sections.push(renderInstallStatsSection(d.InstallSummary)); - } - // Dependencies — Voraussetzungen dieser App if (d.Dependencies && d.Dependencies.length) { sections.push(renderRelationshipFlow({ @@ -1404,8 +1405,6 @@ function renderAppDetailPanel(appId) { // ============================================================= function renderInstallStatsSection(s) { - // Berechnet einen kleinen Erfolgs-Quotient (Installed / (Installed + Failed)) - // pro Achse. Wenn keine Vorgaenge: zeige nur Counter ohne %-Wert. const pct = (ok, fail) => { const total = ok + fail; if (total === 0) return null; @@ -1417,52 +1416,65 @@ function renderInstallStatsSection(s) {
${escapeHtml(label)}
`; + // Stacked-Balken: Segmente in Reihenfolge ok / err / pend / not / na + const stackedBar = (ok, fail, pend, not, na) => { + const total = ok + fail + pend + not + na; + if (total === 0) return `
`; + const w = (v) => `${Math.max(0, Math.round((v / total) * 1000) / 10)}%`; + return `
+ ${ok ? `
` : ''} + ${fail ? `
` : ''} + ${pend ? `
` : ''} + ${not ? `
` : ''} + ${na ? `
` : ''} +
`; + }; + const dev = { - ok: s.InstalledDeviceCount, - fail: s.FailedDeviceCount, - pend: s.PendingInstallDeviceCount, - not: s.NotInstalledDeviceCount, - na: s.NotApplicableDeviceCount, + ok: s.InstalledDeviceCount || 0, + fail: s.FailedDeviceCount || 0, + pend: s.PendingInstallDeviceCount || 0, + not: s.NotInstalledDeviceCount || 0, + na: s.NotApplicableDeviceCount || 0, }; const usr = { - ok: s.InstalledUserCount, - fail: s.FailedUserCount, - pend: s.PendingInstallUserCount, - not: s.NotInstalledUserCount, - na: s.NotApplicableUserCount, + ok: s.InstalledUserCount || 0, + fail: s.FailedUserCount || 0, + pend: s.PendingInstallUserCount || 0, + not: s.NotInstalledUserCount || 0, + na: s.NotApplicableUserCount || 0, }; const devPct = pct(dev.ok, dev.fail); const usrPct = pct(usr.ok, usr.fail); + const axis = (title, d, ratePct) => ` +
+
+ ${escapeHtml(title)} + ${ratePct !== null ? `${ratePct}% erfolgreich` : ''} +
+ ${stackedBar(d.ok, d.fail, d.pend, d.not, d.na)} +
+ Installiert + Fehler + Ausstehend + Nicht inst. + N/A +
+
+ ${stat('Installiert', d.ok, 'ok')} + ${stat('Fehler', d.fail, 'err')} + ${stat('Ausstehend', d.pend, 'pend')} + ${stat('Nicht inst.', d.not, 'not')} + ${stat('N/A', d.na, 'na')} +
+
`; + return `
-
Installationen
+
Installationsübersicht
-
-
- Geräte - ${devPct !== null ? `${devPct}% erfolgreich` : ''} -
-
- ${stat('Installiert', dev.ok, 'ok')} - ${stat('Fehler', dev.fail, 'err')} - ${stat('Ausstehend', dev.pend, 'pend')} - ${stat('Nicht inst.', dev.not, 'not')} - ${stat('N/A', dev.na, 'na')} -
-
-
-
- Benutzer - ${usrPct !== null ? `${usrPct}% erfolgreich` : ''} -
-
- ${stat('Installiert', usr.ok, 'ok')} - ${stat('Fehler', usr.fail, 'err')} - ${stat('Ausstehend', usr.pend, 'pend')} - ${stat('Nicht inst.', usr.not, 'not')} - ${stat('N/A', usr.na, 'na')} -
-
+ ${axis('Geräte', dev, devPct)} + ${axis('Benutzer', usr, usrPct)}
`; } diff --git a/Intune/Intune-App-Manager-Web/www/styles.css b/Intune/Intune-App-Manager-Web/www/styles.css index bfef6f9..03e11e0 100644 --- a/Intune/Intune-App-Manager-Web/www/styles.css +++ b/Intune/Intune-App-Manager-Web/www/styles.css @@ -4058,6 +4058,50 @@ body.members-page { .stat-not .stat-num { color: var(--muted); } .stat-na .stat-num { color: var(--muted-soft); } +/* Stacked-Balken */ +.install-bar { + display: flex; + height: 10px; + border-radius: var(--r-pill); + overflow: hidden; + background: var(--hairline); + margin-bottom: 6px; + gap: 1px; +} +.install-bar-seg { height: 100%; min-width: 2px; transition: opacity .15s; } +.install-bar-seg:hover { opacity: .75; } +.install-bar-ok { background: var(--success); } +.install-bar-err { background: var(--error); } +.install-bar-pend { background: var(--warning); } +.install-bar-not { background: var(--muted); } +.install-bar-na { background: var(--hairline-soft); } + +/* Balken-Legende */ +.install-bar-legend { + display: flex; + flex-wrap: wrap; + gap: 4px 10px; + margin-bottom: 10px; +} +.ibl { + font-size: 10.5px; + color: var(--muted); + display: flex; + align-items: center; + gap: 4px; +} +.ibl::before { + content: ''; + display: inline-block; + width: 9px; height: 9px; + border-radius: 2px; +} +.ibl-ok::before { background: var(--success); } +.ibl-err::before { background: var(--error); } +.ibl-pend::before { background: var(--warning); } +.ibl-not::before { background: var(--muted); } +.ibl-na::before { background: var(--hairline-soft); border: 1px solid var(--hairline); } + /* ============================================================= App-Detail — Flow-Visualisierung (Dependencies + Supersedence) ============================================================= */