Release 0.0.2
This commit is contained in:
@@ -5,7 +5,9 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
tag: "div",
|
||||
actions: {
|
||||
runAnalysis: StorageAuditReportApp.#onRunAnalysis,
|
||||
toggleShowAll: StorageAuditReportApp.#onToggleShowAll
|
||||
toggleShowAll: StorageAuditReportApp.#onToggleShowAll,
|
||||
toggleRaw: StorageAuditReportApp.#onToggleRaw,
|
||||
exportReport: StorageAuditReportApp.#onExportReport
|
||||
},
|
||||
window: {
|
||||
title: "Kosmos Storage Audit",
|
||||
@@ -22,6 +24,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
#loading = false;
|
||||
#showAll = false;
|
||||
#progress = null;
|
||||
#showRaw = false;
|
||||
|
||||
async _prepareContext() {
|
||||
const findings = this.#analysis?.findings ?? [];
|
||||
@@ -31,10 +34,11 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
loading: this.#loading,
|
||||
hasAnalysis: !!this.#analysis,
|
||||
showAll: this.#showAll,
|
||||
showRaw: this.#showRaw,
|
||||
progress: this.#progress,
|
||||
summary: this.#summarize(this.#analysis),
|
||||
groupedFindings,
|
||||
findings: visibleFindings.slice(0, 100)
|
||||
findings: visibleFindings.slice(0, 50)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,16 +58,24 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
<span>${context.loading ? "Analysiere..." : "Analyse Starten"}</span>
|
||||
</button>
|
||||
<button type="button" class="button" data-action="exportReport" ${context.hasAnalysis ? "" : "disabled"}>
|
||||
<i class="fa-solid fa-file-export"></i>
|
||||
<span>Report Export</span>
|
||||
</button>
|
||||
<button type="button" class="button" data-action="toggleShowAll" ${context.hasAnalysis ? "" : "disabled"}>
|
||||
<i class="fa-solid fa-filter"></i>
|
||||
<span>${context.showAll ? "Nur Warnungen" : "Alle Findings"}</span>
|
||||
</button>
|
||||
<button type="button" class="button" data-action="toggleRaw" ${context.hasAnalysis ? "" : "disabled"}>
|
||||
<i class="fa-solid fa-list"></i>
|
||||
<span>${context.showRaw ? "Einzelfaelle Ausblenden" : "Einzelfaelle Anzeigen"}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
${renderProgress(context.progress, context.loading)}
|
||||
${renderSummary(context.summary)}
|
||||
${renderGroupedFindingList(context.groupedFindings, context.hasAnalysis, context.loading, context.showAll)}
|
||||
${renderFindingList(context.findings, context.hasAnalysis, context.loading, context.showAll)}
|
||||
${renderFindingList(context.findings, context.hasAnalysis, context.loading, context.showAll, context.showRaw)}
|
||||
`;
|
||||
return container;
|
||||
}
|
||||
@@ -105,6 +117,28 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
return this.render({ force: true });
|
||||
}
|
||||
|
||||
toggleRaw() {
|
||||
this.#showRaw = !this.#showRaw;
|
||||
return this.render({ force: true });
|
||||
}
|
||||
|
||||
exportReport() {
|
||||
if (!this.#analysis) return;
|
||||
const payload = {
|
||||
exportedAt: new Date().toISOString(),
|
||||
summary: this.#summarize(this.#analysis),
|
||||
groupedFindings: groupFindings(this.#analysis.findings),
|
||||
findings: this.#analysis.findings.map(serializeFinding)
|
||||
};
|
||||
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `kosmos-storage-audit-${Date.now()}.json`;
|
||||
link.click();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 0);
|
||||
}
|
||||
|
||||
#summarize(analysis) {
|
||||
if (!analysis) return null;
|
||||
const bySeverity = { high: 0, warning: 0, info: 0 };
|
||||
@@ -150,6 +184,14 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
static #onToggleShowAll(_event, _button) {
|
||||
return this.toggleShowAll();
|
||||
}
|
||||
|
||||
static #onToggleRaw(_event, _button) {
|
||||
return this.toggleRaw();
|
||||
}
|
||||
|
||||
static #onExportReport(_event, _button) {
|
||||
return this.exportReport();
|
||||
}
|
||||
}
|
||||
|
||||
function renderProgress(progress, loading) {
|
||||
@@ -182,7 +224,7 @@ function renderSummary(summary) {
|
||||
|
||||
const kindLines = Object.entries(summary.byKind)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([kind, count]) => `<li><code>${kind}</code><span>${count}</span></li>`)
|
||||
.map(([kind, count]) => `<li><span>${humanizeKind(kind)}</span><span>${count}</span></li>`)
|
||||
.join("");
|
||||
|
||||
return `
|
||||
@@ -223,11 +265,10 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll
|
||||
<article class="storage-audit__finding severity-${group.severity}">
|
||||
<header>
|
||||
<span class="storage-audit__severity">${group.severity}</span>
|
||||
<code>${group.kind}</code>
|
||||
<strong>${group.count} Referenzen</strong>
|
||||
</header>
|
||||
<p><code>${escapeHtml(group.target)}</code></p>
|
||||
<p>${escapeHtml(group.reason)}</p>
|
||||
<p>${escapeHtml(group.shortReason)}</p>
|
||||
<dl>
|
||||
<div><dt>Owner-Paket</dt><dd><code>${escapeHtml(group.ownerLabel)}</code></dd></div>
|
||||
<div><dt>Bewertung</dt><dd>${escapeHtml(group.explanation)}</dd></div>
|
||||
@@ -248,11 +289,11 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll
|
||||
<article class="storage-audit__finding severity-${group.severity}">
|
||||
<header>
|
||||
<span class="storage-audit__severity">${group.severity}</span>
|
||||
<code>${group.kind}</code>
|
||||
<strong>${group.count} Referenzen</strong>
|
||||
</header>
|
||||
<p><code>${escapeHtml(group.target)}</code></p>
|
||||
<p>${escapeHtml(group.reason)}</p>
|
||||
<p>${escapeHtml(group.shortReason)}</p>
|
||||
${group.targetKind === "wildcard" ? `<p>Musterreferenz: Wildcard wird von Foundry unterstuetzt, derzeit aber ohne passenden Treffer im Dateisystem.</p>` : ""}
|
||||
<p class="storage-audit__recommendation">${escapeHtml(group.recommendation)}</p>
|
||||
${renderSampleSources(group.sources)}
|
||||
</article>
|
||||
@@ -306,13 +347,16 @@ function renderGroupedSection(title, description, groups, renderGroup) {
|
||||
`;
|
||||
}
|
||||
|
||||
function renderFindingList(findings, hasAnalysis, loading, showAll) {
|
||||
function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {
|
||||
if (loading) {
|
||||
return `<section class="storage-audit__list"><p>Analyse laeuft...</p></section>`;
|
||||
}
|
||||
if (!hasAnalysis) {
|
||||
return `<section class="storage-audit__list"><p>Die Analyse kann direkt aus dieser Ansicht gestartet werden.</p></section>`;
|
||||
}
|
||||
if (!showRaw) {
|
||||
return "";
|
||||
}
|
||||
if (!findings.length) {
|
||||
return `<section class="storage-audit__list"><p>Keine ${showAll ? "" : "warnenden "}Findings gefunden.</p></section>`;
|
||||
}
|
||||
@@ -321,7 +365,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll) {
|
||||
<article class="storage-audit__finding severity-${finding.severity}">
|
||||
<header>
|
||||
<span class="storage-audit__severity">${finding.severity}</span>
|
||||
<code>${finding.kind}</code>
|
||||
<span>${humanizeKind(finding.kind)}</span>
|
||||
</header>
|
||||
<p>${escapeHtml(finding.reason)}</p>
|
||||
<dl>
|
||||
@@ -375,8 +419,10 @@ function groupByTarget(findings) {
|
||||
count: 0,
|
||||
ownerLabel,
|
||||
explanation: "Weltdaten zeigen auf ein Paketasset, fuer das in Manifesten und Paket-Packs keine sichtbare Eigenreferenz gefunden wurde.",
|
||||
shortReason: shortenReason(finding.reason),
|
||||
reason: finding.reason,
|
||||
recommendation: finding.recommendation,
|
||||
targetKind: finding.target.targetKind ?? "local-file",
|
||||
sources: new Set()
|
||||
};
|
||||
current.count += 1;
|
||||
@@ -389,6 +435,13 @@ function groupByTarget(findings) {
|
||||
.sort((a, b) => compareSeverity(a.severity, b.severity) || (b.count - a.count) || a.target.localeCompare(b.target));
|
||||
}
|
||||
|
||||
function shortenReason(reason) {
|
||||
return String(reason ?? "")
|
||||
.replace(/^world:[^ ]+ references /, "")
|
||||
.replace(/^Referenced file /, "")
|
||||
.replace(/^Wildcard reference /, "");
|
||||
}
|
||||
|
||||
function deriveOwnerLabel(target) {
|
||||
const [, path] = String(target).split(":", 2);
|
||||
const parts = (path ?? "").split("/");
|
||||
@@ -412,6 +465,35 @@ function formatTarget(target) {
|
||||
return target.locator ?? `${target.storage}:${target.path}`;
|
||||
}
|
||||
|
||||
function humanizeKind(kind) {
|
||||
return {
|
||||
"non-package-to-package-reference": "Unverankerte Paketziele",
|
||||
"broken-reference": "Kaputte Ziele",
|
||||
"orphan-file": "Orphan-Kandidaten",
|
||||
"risky-public-reference": "Riskante Public-Ziele"
|
||||
}[kind] ?? kind;
|
||||
}
|
||||
|
||||
function serializeFinding(finding) {
|
||||
return {
|
||||
kind: finding.kind,
|
||||
severity: finding.severity,
|
||||
reason: finding.reason,
|
||||
recommendation: finding.recommendation,
|
||||
confidence: finding.confidence,
|
||||
target: finding.target,
|
||||
source: finding.source
|
||||
? {
|
||||
sourceType: finding.source.sourceType,
|
||||
sourceLabel: finding.source.sourceLabel,
|
||||
sourceScope: finding.source.sourceScope,
|
||||
rawValue: finding.source.rawValue,
|
||||
normalized: finding.source.normalized
|
||||
}
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/&/g, "&")
|
||||
|
||||
Reference in New Issue
Block a user