diff --git a/lang/de.json b/lang/de.json index 293372a..de954b8 100644 --- a/lang/de.json +++ b/lang/de.json @@ -34,7 +34,8 @@ }, "Notify": { "Completed": "Kosmos Storage Audit abgeschlossen: {count} Findings.", - "Failed": "Kosmos Storage Audit fehlgeschlagen: {message}" + "Failed": "Kosmos Storage Audit fehlgeschlagen: {message}", + "OpenSourceFailed": "Die Quelle konnte nicht geöffnet werden: {uuid}" }, "Summary": { "NoAnalysis": "Noch keine Analyse ausgeführt.", diff --git a/lang/en.json b/lang/en.json index 9060aa7..bb9cfc2 100644 --- a/lang/en.json +++ b/lang/en.json @@ -34,7 +34,8 @@ }, "Notify": { "Completed": "Kosmos Storage Audit completed: {count} findings.", - "Failed": "Kosmos Storage Audit failed: {message}" + "Failed": "Kosmos Storage Audit failed: {message}", + "OpenSourceFailed": "Could not open source: {uuid}" }, "Summary": { "NoAnalysis": "No analysis has been run yet.", diff --git a/module.json b/module.json index abda8f0..9295ccd 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.3", + "version": "0.0.4", "compatibility": { "minimum": "13", "verified": "13" diff --git a/scripts/adapters/foundry-runtime.js b/scripts/adapters/foundry-runtime.js index 886994b..0b8cb36 100644 --- a/scripts/adapters/foundry-runtime.js +++ b/scripts/adapters/foundry-runtime.js @@ -37,6 +37,8 @@ function worldCollectionEntries() { subtype: doc.documentName?.toLowerCase() ?? collection.documentName?.toLowerCase() ?? "document" }, sourceLabel: `${doc.documentName ?? "Document"} ${doc.id}`, + sourceName: doc.name ?? null, + sourceUuid: doc.uuid ?? null, value: doc.toObject ? doc.toObject() : doc }); } @@ -89,6 +91,8 @@ async function* packagePackEntries(onProgress = null) { subtype: `pack:${pack.collection}` }, sourceLabel: `${pack.collection} ${document.id}`, + sourceName: document.name ?? null, + sourceUuid: document.uuid ?? null, value: document.toObject ? document.toObject() : document }; } diff --git a/scripts/apps/audit-report-app.js b/scripts/apps/audit-report-app.js index 7bbf211..bbf4832 100644 --- a/scripts/apps/audit-report-app.js +++ b/scripts/apps/audit-report-app.js @@ -7,7 +7,8 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV runAnalysis: StorageAuditReportApp.#onRunAnalysis, toggleShowAll: StorageAuditReportApp.#onToggleShowAll, toggleRaw: StorageAuditReportApp.#onToggleRaw, - exportReport: StorageAuditReportApp.#onExportReport + exportReport: StorageAuditReportApp.#onExportReport, + openSource: StorageAuditReportApp.#onOpenSource }, window: { title: "Kosmos Storage Audit", @@ -43,7 +44,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV progress: this.#progress, summary: this.#summarize(this.#analysis), groupedFindings, - findings: visibleFindings.slice(0, 50) + findings: visibleFindings }; } @@ -197,6 +198,21 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV static #onExportReport(_event, _button) { return this.exportReport(); } + + static async #onOpenSource(_event, button) { + const uuid = button?.dataset?.uuid; + if (!uuid) return; + const document = await fromUuid(uuid); + if (document?.sheet) { + document.sheet.render(true, { focus: true }); + return; + } + if (document?.parent?.sheet) { + document.parent.sheet.render(true, { focus: true }); + return; + } + ui.notifications.warn(format("KSA.Notify.OpenSourceFailed", { uuid })); + } } function renderProgress(progress, loading) { @@ -340,7 +356,7 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll } function renderGroupedSection(title, description, groups, renderGroup) { - const items = groups.slice(0, 12).map(renderGroup).join(""); + const items = groups.map(renderGroup).join(""); return `
@@ -375,7 +391,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {

${escapeHtml(finding.reason)}

${localize("KSA.Field.Target")}
${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}
- ${finding.source ? `
${localize("KSA.Field.Source")}
${escapeHtml(finding.source.sourceLabel)}
` : ""} + ${finding.source ? `
${localize("KSA.Field.Source")}
${renderSourceLink(finding.source)}
` : ""}

${escapeHtml(finding.recommendation)}

@@ -391,7 +407,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) { function renderSampleSources(sources) { if (!sources.length) return ""; - const rows = sources.map(source => `
  • ${escapeHtml(source)}
  • `).join(""); + const rows = sources.map(source => `
  • ${renderSourceLink(source)}
  • `).join(""); return `
    ${localize("KSA.Section.Samples")}
      ${rows}
    `; } @@ -428,15 +444,22 @@ function groupByTarget(findings) { reason: finding.reason, recommendation: finding.recommendation, targetKind: finding.target.targetKind ?? "local-file", - sources: new Set() + sources: new Map() }; current.count += 1; if (finding.severity === "high") current.severity = "high"; - if (finding.source?.sourceLabel) current.sources.add(finding.source.sourceLabel); + if (finding.source?.sourceLabel) { + const key = finding.source.sourceUuid ?? finding.source.sourceLabel; + current.sources.set(key, { + sourceLabel: finding.source.sourceLabel, + sourceName: finding.source.sourceName ?? null, + sourceUuid: finding.source.sourceUuid ?? null + }); + } grouped.set(target, current); } return [...grouped.values()] - .map(group => ({ ...group, sources: [...group.sources].slice(0, 5) })) + .map(group => ({ ...group, sources: [...group.sources.values()] })) .sort((a, b) => compareSeverity(a.severity, b.severity) || (b.count - a.count) || a.target.localeCompare(b.target)); } @@ -488,6 +511,8 @@ function serializeFinding(finding) { ? { sourceType: finding.source.sourceType, sourceLabel: finding.source.sourceLabel, + sourceName: finding.source.sourceName, + sourceUuid: finding.source.sourceUuid, sourceScope: finding.source.sourceScope, rawValue: finding.source.rawValue, normalized: finding.source.normalized @@ -529,3 +554,9 @@ function renderLocalizedCodeText(key, data, codeValues) { } return escapeHtml(text).replace(/@@CODE:([^@]+)@@/g, "$1"); } + +function renderSourceLink(source) { + const label = source.sourceName ? `${source.sourceLabel} (${source.sourceName})` : source.sourceLabel; + if (!source.sourceUuid) return escapeHtml(label); + return `${escapeHtml(source.sourceUuid)}${label ? ` ${escapeHtml(label)}` : ""}`; +} diff --git a/scripts/core/reference-extractor.js b/scripts/core/reference-extractor.js index c68c11d..4edcb73 100644 --- a/scripts/core/reference-extractor.js +++ b/scripts/core/reference-extractor.js @@ -28,6 +28,8 @@ export function extractReferencesFromValue(value, source) { sourceType: source.sourceType, sourceScope: source.sourceScope, sourceLabel: source.sourceLabel, + sourceName: source.sourceName, + sourceUuid: source.sourceUuid, rawValue: candidate, normalized: { ...direct, @@ -46,6 +48,8 @@ export function extractReferencesFromValue(value, source) { sourceType: source.sourceType, sourceScope: source.sourceScope, sourceLabel: source.sourceLabel, + sourceName: source.sourceName, + sourceUuid: source.sourceUuid, rawValue: match[1], normalized: { ...nested,