Add orphan result filters

This commit is contained in:
2026-04-21 20:28:35 +00:00
parent 366a7fbf50
commit fa496fc81e
5 changed files with 114 additions and 11 deletions

View File

@@ -21,6 +21,11 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
#analysis = null;
#loading = false;
#progress = null;
#orphanFilters = {
modules: true,
systems: true,
corePublic: true
};
constructor(options = {}) {
super(options);
@@ -28,7 +33,8 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
}
async _prepareContext() {
const findings = this.#analysis?.findings ?? [];
const visibleAnalysis = this.#getVisibleAnalysis();
const findings = visibleAnalysis?.findings ?? [];
const groupedFindings = await enrichGroupedFindings(groupFindings(findings));
return {
loading: this.#loading,
@@ -36,8 +42,9 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
progress: this.#progress,
notices: this.#analysis?.notices ?? [],
moduleVersion: game.modules.get("kosmos-storage-audit")?.version ?? null,
summary: this.#summarize(this.#analysis),
groupedFindings
summary: this.#summarize(visibleAnalysis),
groupedFindings,
orphanFilters: this.#orphanFilters
};
}
@@ -69,13 +76,23 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
${renderProgress(context.progress, context.loading)}
${renderNotices(context.notices, context.loading)}
${renderSummary(context.summary, context.loading)}
${renderGroupedFindingList(context.groupedFindings, context.hasAnalysis, context.loading)}
${renderGroupedFindingList(context.groupedFindings, context.hasAnalysis, context.loading, context.orphanFilters)}
`;
return container;
}
_replaceHTML(result, content) {
content.replaceChildren(result);
for (const input of content.querySelectorAll("[data-orphan-filter]")) {
input.addEventListener("change", event => {
const key = event.currentTarget.dataset.orphanFilter;
this.#orphanFilters = {
...this.#orphanFilters,
[key]: event.currentTarget.checked
};
this.render({ force: true });
});
}
}
async runAnalysis() {
@@ -107,13 +124,15 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
}
exportReport() {
if (!this.#analysis) return;
const visibleAnalysis = this.#getVisibleAnalysis();
if (!visibleAnalysis) return;
const payload = {
exportedAt: new Date().toISOString(),
orphanFilters: this.#orphanFilters,
notices: this.#analysis.notices ?? [],
summary: this.#summarize(this.#analysis),
groupedFindings: groupFindings(this.#analysis.findings),
findings: this.#analysis.findings.map(serializeFinding)
summary: this.#summarize(visibleAnalysis),
groupedFindings: groupFindings(visibleAnalysis.findings),
findings: visibleAnalysis.findings.map(serializeFinding)
};
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
@@ -154,6 +173,15 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
};
}
#getVisibleAnalysis() {
if (!this.#analysis) return null;
const findings = this.#analysis.findings.filter(finding => !shouldHideOrphanFinding(finding, this.#orphanFilters));
return {
...this.#analysis,
findings
};
}
#updateProgress(update) {
this.#progress = {
...(this.#progress ?? {}),
@@ -251,7 +279,7 @@ function renderSummary(summary, loading) {
`;
}
function renderGroupedFindingList(groupedFindings, hasAnalysis, loading) {
function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, orphanFilters) {
if (loading || !hasAnalysis) return "";
const sections = [];
@@ -283,6 +311,7 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading) {
sections.push(renderGroupedSection(
localize("KSA.Section.OrphanCandidates"),
localize("KSA.Section.OrphanCandidatesDesc"),
renderOrphanFilters(orphanFilters),
renderGroupedTable(groupedFindings.orphans, {
includeOwner: false,
includeReason: true,
@@ -305,18 +334,43 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading) {
return `<section class="storage-audit__grouped">${sections.join("")}</section>`;
}
function renderGroupedSection(title, description, content) {
function renderGroupedSection(title, description, prelude = "", content = "") {
if (!content) {
content = prelude;
prelude = "";
}
return `
<section class="storage-audit__group">
<div class="storage-audit__group-header">
<h3>${title}</h3>
<p>${description}</p>
</div>
${prelude}
<div class="storage-audit__table-wrap">${content}</div>
</section>
`;
}
function renderOrphanFilters(filters) {
return `
<div class="storage-audit__orphan-filters">
<span class="storage-audit__orphan-filters-label">${localize("KSA.Filter.OrphansLabel")}</span>
${renderOrphanFilterToggle("modules", localize("KSA.Filter.OrphansModules"), filters.modules)}
${renderOrphanFilterToggle("systems", localize("KSA.Filter.OrphansSystems"), filters.systems)}
${renderOrphanFilterToggle("corePublic", localize("KSA.Filter.OrphansCorePublic"), filters.corePublic)}
</div>
`;
}
function renderOrphanFilterToggle(key, label, checked) {
return `
<label class="storage-audit__checkbox">
<input type="checkbox" data-orphan-filter="${escapeHtml(key)}" ${checked ? "checked" : ""}>
<span>${escapeHtml(label)}</span>
</label>
`;
}
function renderGroupedTable(groups, { includeOwner=false, includeReason=false, includeSources=false }={}) {
const headers = [
`<th>${localize("KSA.Table.Severity")}</th>`,
@@ -523,6 +577,21 @@ async function enrichGroupedFindings(groupedFindings) {
};
}
function shouldHideOrphanFinding(finding, orphanFilters) {
if (finding.kind !== "orphan-file") return false;
const target = finding.target?.locator ?? formatTarget(finding.target ?? {});
const [, path = ""] = String(target).split(":", 2);
if (orphanFilters.modules && path.startsWith("modules/")) return true;
if (orphanFilters.systems && path.startsWith("systems/")) return true;
if (
orphanFilters.corePublic &&
["canvas/", "cards/", "icons/", "sounds/", "toolclips/", "ui/"].some(prefix => path.startsWith(prefix))
) return true;
return false;
}
async function enrichGroupedSources(groups) {
const enriched = [];
for (const group of groups) {