Install anzeige hinzugefügt
This commit is contained in:
@@ -1261,6 +1261,12 @@ function renderAppDetailPanel(appId) {
|
|||||||
sections.push(renderAssignmentsSection(app));
|
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
|
// Allgemein
|
||||||
const general = [
|
const general = [
|
||||||
row('Hersteller', d.Publisher),
|
row('Hersteller', d.Publisher),
|
||||||
@@ -1330,11 +1336,6 @@ function renderAppDetailPanel(appId) {
|
|||||||
].filter(Boolean).join('');
|
].filter(Boolean).join('');
|
||||||
if (metaRows) sections.push(`<div class="app-detail-section"><div class="app-detail-section-title">Meta</div>${metaRows}</div>`);
|
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
|
// Dependencies — Voraussetzungen dieser App
|
||||||
if (d.Dependencies && d.Dependencies.length) {
|
if (d.Dependencies && d.Dependencies.length) {
|
||||||
sections.push(renderRelationshipFlow({
|
sections.push(renderRelationshipFlow({
|
||||||
@@ -1404,8 +1405,6 @@ function renderAppDetailPanel(appId) {
|
|||||||
// =============================================================
|
// =============================================================
|
||||||
|
|
||||||
function renderInstallStatsSection(s) {
|
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 pct = (ok, fail) => {
|
||||||
const total = ok + fail;
|
const total = ok + fail;
|
||||||
if (total === 0) return null;
|
if (total === 0) return null;
|
||||||
@@ -1417,52 +1416,65 @@ function renderInstallStatsSection(s) {
|
|||||||
<div class="stat-lbl">${escapeHtml(label)}</div>
|
<div class="stat-lbl">${escapeHtml(label)}</div>
|
||||||
</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 = {
|
const dev = {
|
||||||
ok: s.InstalledDeviceCount,
|
ok: s.InstalledDeviceCount || 0,
|
||||||
fail: s.FailedDeviceCount,
|
fail: s.FailedDeviceCount || 0,
|
||||||
pend: s.PendingInstallDeviceCount,
|
pend: s.PendingInstallDeviceCount || 0,
|
||||||
not: s.NotInstalledDeviceCount,
|
not: s.NotInstalledDeviceCount || 0,
|
||||||
na: s.NotApplicableDeviceCount,
|
na: s.NotApplicableDeviceCount || 0,
|
||||||
};
|
};
|
||||||
const usr = {
|
const usr = {
|
||||||
ok: s.InstalledUserCount,
|
ok: s.InstalledUserCount || 0,
|
||||||
fail: s.FailedUserCount,
|
fail: s.FailedUserCount || 0,
|
||||||
pend: s.PendingInstallUserCount,
|
pend: s.PendingInstallUserCount || 0,
|
||||||
not: s.NotInstalledUserCount,
|
not: s.NotInstalledUserCount || 0,
|
||||||
na: s.NotApplicableUserCount,
|
na: s.NotApplicableUserCount || 0,
|
||||||
};
|
};
|
||||||
const devPct = pct(dev.ok, dev.fail);
|
const devPct = pct(dev.ok, dev.fail);
|
||||||
const usrPct = pct(usr.ok, usr.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">
|
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-stats">
|
||||||
<div class="install-axis">
|
${axis('Geräte', dev, devPct)}
|
||||||
<div class="install-axis-head">
|
${axis('Benutzer', usr, usrPct)}
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4058,6 +4058,50 @@ body.members-page {
|
|||||||
.stat-not .stat-num { color: var(--muted); }
|
.stat-not .stat-num { color: var(--muted); }
|
||||||
.stat-na .stat-num { color: var(--muted-soft); }
|
.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)
|
App-Detail — Flow-Visualisierung (Dependencies + Supersedence)
|
||||||
============================================================= */
|
============================================================= */
|
||||||
|
|||||||
Reference in New Issue
Block a user