diff --git a/lang/de.json b/lang/de.json index 16d4720..6f2424d 100644 --- a/lang/de.json +++ b/lang/de.json @@ -17,6 +17,12 @@ "Running": "Analysiere...", "Export": "Report exportieren" }, + "Filter": { + "OrphansLabel": "Ausblenden:", + "OrphansModules": "modules/*", + "OrphansSystems": "systems/*", + "OrphansCorePublic": "canvas/cards/icons/sounds/toolclips/ui" + }, "Progress": { "Initialize": "Initialisiere Analyse", "Analyzing": "Analysiere", diff --git a/lang/en.json b/lang/en.json index f4da138..e9b16a5 100644 --- a/lang/en.json +++ b/lang/en.json @@ -17,6 +17,12 @@ "Running": "Analyzing...", "Export": "Export Report" }, + "Filter": { + "OrphansLabel": "Hide:", + "OrphansModules": "modules/*", + "OrphansSystems": "systems/*", + "OrphansCorePublic": "canvas/cards/icons/sounds/toolclips/ui" + }, "Progress": { "Initialize": "Initializing analysis", "Analyzing": "Analyzing", diff --git a/module.json b/module.json index 364af6e..468d953 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "kosmos-storage-audit", "title": "Kosmos Storage Audit", "description": "Analyzes media references and risky storage locations across Foundry data and public roots.", - "version": "0.0.29", + "version": "0.0.30", "compatibility": { "minimum": "13", "verified": "13" diff --git a/scripts/apps/audit-report-app.js b/scripts/apps/audit-report-app.js index abd4618..578307a 100644 --- a/scripts/apps/audit-report-app.js +++ b/scripts/apps/audit-report-app.js @@ -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 `
${sections.join("")}
`; } -function renderGroupedSection(title, description, content) { +function renderGroupedSection(title, description, prelude = "", content = "") { + if (!content) { + content = prelude; + prelude = ""; + } return `

${title}

${description}

+ ${prelude}
${content}
`; } +function renderOrphanFilters(filters) { + return ` +
+ ${localize("KSA.Filter.OrphansLabel")} + ${renderOrphanFilterToggle("modules", localize("KSA.Filter.OrphansModules"), filters.modules)} + ${renderOrphanFilterToggle("systems", localize("KSA.Filter.OrphansSystems"), filters.systems)} + ${renderOrphanFilterToggle("corePublic", localize("KSA.Filter.OrphansCorePublic"), filters.corePublic)} +
+ `; +} + +function renderOrphanFilterToggle(key, label, checked) { + return ` + + `; +} + function renderGroupedTable(groups, { includeOwner=false, includeReason=false, includeSources=false }={}) { const headers = [ `${localize("KSA.Table.Severity")}`, @@ -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) { diff --git a/styles/audit.css b/styles/audit.css index 0ad194b..c82e2db 100644 --- a/styles/audit.css +++ b/styles/audit.css @@ -45,6 +45,28 @@ 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 { padding: 1rem 1.1rem; }