Install anzeige hinzugefügt

This commit is contained in:
2026-06-17 08:43:07 +02:00
parent e98660134a
commit eeabb1db31
2 changed files with 100 additions and 44 deletions
+56 -44
View File
@@ -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(`<div class="app-detail-section"><div class="app-detail-section-title">Meta</div>${metaRows}</div>`);
// 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) {
<div class="stat-lbl">${escapeHtml(label)}</div>
</div>`;
// 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 `<div class="install-bar"><div class="install-bar-seg install-bar-na" style="width:100%" title="Keine Daten"></div></div>`;
const w = (v) => `${Math.max(0, Math.round((v / total) * 1000) / 10)}%`;
return `<div class="install-bar" title="${ok} inst. / ${fail} Fehler / ${pend} ausst. / ${not} nicht inst. / ${na} N/A (gesamt ${total})">
${ok ? `<div class="install-bar-seg install-bar-ok" style="width:${w(ok)}" title="${ok} installiert"></div>` : ''}
${fail ? `<div class="install-bar-seg install-bar-err" style="width:${w(fail)}" title="${fail} Fehler"></div>` : ''}
${pend ? `<div class="install-bar-seg install-bar-pend" style="width:${w(pend)}" title="${pend} ausstehend"></div>` : ''}
${not ? `<div class="install-bar-seg install-bar-not" style="width:${w(not)}" title="${not} nicht inst."></div>` : ''}
${na ? `<div class="install-bar-seg install-bar-na" style="width:${w(na)}" title="${na} N/A"></div>` : ''}
</div>`;
};
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) => `
<div class="install-axis">
<div class="install-axis-head">
<span class="install-axis-title">${escapeHtml(title)}</span>
${ratePct !== null ? `<span class="install-axis-rate ${ratePct >= 95 ? 'good' : ratePct >= 75 ? 'mid' : 'bad'}">${ratePct}% erfolgreich</span>` : ''}
</div>
${stackedBar(d.ok, d.fail, d.pend, d.not, d.na)}
<div class="install-bar-legend">
<span class="ibl ibl-ok">Installiert</span>
<span class="ibl ibl-err">Fehler</span>
<span class="ibl ibl-pend">Ausstehend</span>
<span class="ibl ibl-not">Nicht inst.</span>
<span class="ibl ibl-na">N/A</span>
</div>
<div class="install-stats-grid">
${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')}
</div>
</div>`;
return `<div class="app-detail-section app-detail-section--wide">
<div class="app-detail-section-title">Installationen</div>
<div class="app-detail-section-title">Installationsübersicht</div>
<div class="install-stats">
<div class="install-axis">
<div class="install-axis-head">
<span class="install-axis-title">Geräte</span>
${devPct !== null ? `<span class="install-axis-rate ${devPct >= 95 ? 'good' : devPct >= 75 ? 'mid' : 'bad'}">${devPct}% erfolgreich</span>` : ''}
</div>
<div class="install-stats-grid">
${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')}
</div>
</div>
<div class="install-axis">
<div class="install-axis-head">
<span class="install-axis-title">Benutzer</span>
${usrPct !== null ? `<span class="install-axis-rate ${usrPct >= 95 ? 'good' : usrPct >= 75 ? 'mid' : 'bad'}">${usrPct}% erfolgreich</span>` : ''}
</div>
<div class="install-stats-grid">
${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')}
</div>
</div>
${axis('Geräte', dev, devPct)}
${axis('Benutzer', usr, usrPct)}
</div>
</div>`;
}
@@ -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)
============================================================= */