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

@@ -17,6 +17,12 @@
"Running": "Analysiere...", "Running": "Analysiere...",
"Export": "Report exportieren" "Export": "Report exportieren"
}, },
"Filter": {
"OrphansLabel": "Ausblenden:",
"OrphansModules": "modules/*",
"OrphansSystems": "systems/*",
"OrphansCorePublic": "canvas/cards/icons/sounds/toolclips/ui"
},
"Progress": { "Progress": {
"Initialize": "Initialisiere Analyse", "Initialize": "Initialisiere Analyse",
"Analyzing": "Analysiere", "Analyzing": "Analysiere",

View File

@@ -17,6 +17,12 @@
"Running": "Analyzing...", "Running": "Analyzing...",
"Export": "Export Report" "Export": "Export Report"
}, },
"Filter": {
"OrphansLabel": "Hide:",
"OrphansModules": "modules/*",
"OrphansSystems": "systems/*",
"OrphansCorePublic": "canvas/cards/icons/sounds/toolclips/ui"
},
"Progress": { "Progress": {
"Initialize": "Initializing analysis", "Initialize": "Initializing analysis",
"Analyzing": "Analyzing", "Analyzing": "Analyzing",

View File

@@ -2,7 +2,7 @@
"id": "kosmos-storage-audit", "id": "kosmos-storage-audit",
"title": "Kosmos Storage Audit", "title": "Kosmos Storage Audit",
"description": "Analyzes media references and risky storage locations across Foundry data and public roots.", "description": "Analyzes media references and risky storage locations across Foundry data and public roots.",
"version": "0.0.29", "version": "0.0.30",
"compatibility": { "compatibility": {
"minimum": "13", "minimum": "13",
"verified": "13" "verified": "13"

View File

@@ -21,6 +21,11 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
#analysis = null; #analysis = null;
#loading = false; #loading = false;
#progress = null; #progress = null;
#orphanFilters = {
modules: true,
systems: true,
corePublic: true
};
constructor(options = {}) { constructor(options = {}) {
super(options); super(options);
@@ -28,7 +33,8 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
} }
async _prepareContext() { async _prepareContext() {
const findings = this.#analysis?.findings ?? []; const visibleAnalysis = this.#getVisibleAnalysis();
const findings = visibleAnalysis?.findings ?? [];
const groupedFindings = await enrichGroupedFindings(groupFindings(findings)); const groupedFindings = await enrichGroupedFindings(groupFindings(findings));
return { return {
loading: this.#loading, loading: this.#loading,
@@ -36,8 +42,9 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
progress: this.#progress, progress: this.#progress,
notices: this.#analysis?.notices ?? [], notices: this.#analysis?.notices ?? [],
moduleVersion: game.modules.get("kosmos-storage-audit")?.version ?? null, moduleVersion: game.modules.get("kosmos-storage-audit")?.version ?? null,
summary: this.#summarize(this.#analysis), summary: this.#summarize(visibleAnalysis),
groupedFindings groupedFindings,
orphanFilters: this.#orphanFilters
}; };
} }
@@ -69,13 +76,23 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
${renderProgress(context.progress, context.loading)} ${renderProgress(context.progress, context.loading)}
${renderNotices(context.notices, context.loading)} ${renderNotices(context.notices, context.loading)}
${renderSummary(context.summary, 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; return container;
} }
_replaceHTML(result, content) { _replaceHTML(result, content) {
content.replaceChildren(result); 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() { async runAnalysis() {
@@ -107,13 +124,15 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
} }
exportReport() { exportReport() {
if (!this.#analysis) return; const visibleAnalysis = this.#getVisibleAnalysis();
if (!visibleAnalysis) return;
const payload = { const payload = {
exportedAt: new Date().toISOString(), exportedAt: new Date().toISOString(),
orphanFilters: this.#orphanFilters,
notices: this.#analysis.notices ?? [], notices: this.#analysis.notices ?? [],
summary: this.#summarize(this.#analysis), summary: this.#summarize(visibleAnalysis),
groupedFindings: groupFindings(this.#analysis.findings), groupedFindings: groupFindings(visibleAnalysis.findings),
findings: this.#analysis.findings.map(serializeFinding) findings: visibleAnalysis.findings.map(serializeFinding)
}; };
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" }); const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob); 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) { #updateProgress(update) {
this.#progress = { this.#progress = {
...(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 ""; if (loading || !hasAnalysis) return "";
const sections = []; const sections = [];
@@ -283,6 +311,7 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading) {
sections.push(renderGroupedSection( sections.push(renderGroupedSection(
localize("KSA.Section.OrphanCandidates"), localize("KSA.Section.OrphanCandidates"),
localize("KSA.Section.OrphanCandidatesDesc"), localize("KSA.Section.OrphanCandidatesDesc"),
renderOrphanFilters(orphanFilters),
renderGroupedTable(groupedFindings.orphans, { renderGroupedTable(groupedFindings.orphans, {
includeOwner: false, includeOwner: false,
includeReason: true, includeReason: true,
@@ -305,18 +334,43 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading) {
return `<section class="storage-audit__grouped">${sections.join("")}</section>`; 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 ` return `
<section class="storage-audit__group"> <section class="storage-audit__group">
<div class="storage-audit__group-header"> <div class="storage-audit__group-header">
<h3>${title}</h3> <h3>${title}</h3>
<p>${description}</p> <p>${description}</p>
</div> </div>
${prelude}
<div class="storage-audit__table-wrap">${content}</div> <div class="storage-audit__table-wrap">${content}</div>
</section> </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 }={}) { function renderGroupedTable(groups, { includeOwner=false, includeReason=false, includeSources=false }={}) {
const headers = [ const headers = [
`<th>${localize("KSA.Table.Severity")}</th>`, `<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) { async function enrichGroupedSources(groups) {
const enriched = []; const enriched = [];
for (const group of groups) { for (const group of groups) {

View File

@@ -45,6 +45,28 @@
overflow-x: auto; overflow-x: auto;
} }
.storage-audit__orphan-filters {
display: flex;
flex-wrap: wrap;
gap: 0.6rem 1rem;
align-items: center;
padding: 0.35rem 1.1rem 0;
}
.storage-audit__orphan-filters-label {
font-weight: 700;
}
.storage-audit__checkbox {
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
.storage-audit__checkbox input {
margin: 0;
}
.storage-audit__progress { .storage-audit__progress {
padding: 1rem 1.1rem; padding: 1rem 1.1rem;
} }