diff --git a/module.json b/module.json index f7ba39a..55052a4 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.12", + "version": "0.0.13", "compatibility": { "minimum": "13", "verified": "13" diff --git a/scripts/apps/audit-report-app.js b/scripts/apps/audit-report-app.js index 9eda157..9a14d00 100644 --- a/scripts/apps/audit-report-app.js +++ b/scripts/apps/audit-report-app.js @@ -34,7 +34,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV async _prepareContext() { const findings = this.#analysis?.findings ?? []; const visibleFindings = this.#showAll ? findings : findings.filter(f => f.severity !== "info"); - const groupedFindings = groupFindings(visibleFindings); + const groupedFindings = await enrichGroupedFindings(groupFindings(visibleFindings)); return { loading: this.#loading, hasAnalysis: !!this.#analysis, @@ -43,7 +43,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV progress: this.#progress, summary: this.#summarize(this.#analysis), groupedFindings, - findings: visibleFindings + findings: await enrichFindings(visibleFindings) }; } @@ -87,13 +87,6 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV _replaceHTML(result, content) { content.replaceChildren(result); - for (const link of content.querySelectorAll(".content-link[data-uuid]")) { - link.addEventListener("click", event => { - event.preventDefault(); - event.stopPropagation(); - void openSourceUuid(link.dataset.uuid); - }); - } } async runAnalysis() { @@ -384,7 +377,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {
${escapeHtml(finding.reason)}
${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}${escapeHtml(finding.recommendation)}
@@ -400,7 +393,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) { function renderSampleSources(sources) { if (!sources.length) return ""; - const rows = sources.map(source => `$1");
}
-function renderSourceLink(source) {
- const trail = Array.isArray(source.sourceTrail) && source.sourceTrail.length ? source.sourceTrail : null;
- if (trail) {
- return `${trail.map(renderTrailNode).join('->')}`;
- }
-
- const label = source.sourceName ? `${source.sourceLabel} (${source.sourceName})` : source.sourceLabel;
- if (!source.sourceUuid) return escapeHtml(label);
- return renderUuidLink(source.sourceUuid, label);
-}
-
-async function openSourceUuid(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 renderTrailNode(node) {
- if (!node?.uuid) return escapeHtml(node?.label ?? "");
- return renderUuidLink(node.uuid, node.label ?? node.uuid);
-}
-
-function renderUuidLink(uuid, label) {
- return `${escapeHtml(label)}`;
-}
-
function buildSourceKey(source) {
const trailKey = Array.isArray(source.sourceTrail) && source.sourceTrail.length
? source.sourceTrail.map(node => node.uuid ?? node.label ?? "").join(">")
: "";
return `${source.sourceUuid ?? ""}|${trailKey}|${source.sourceLabel ?? ""}`;
}
+
+async function enrichGroupedFindings(groupedFindings) {
+ return {
+ brokenReferences: await enrichGroupedSources(groupedFindings.brokenReferences),
+ nonPackageToPackage: await enrichGroupedSources(groupedFindings.nonPackageToPackage),
+ orphans: groupedFindings.orphans
+ };
+}
+
+async function enrichGroupedSources(groups) {
+ const enriched = [];
+ for (const group of groups) {
+ const sources = [];
+ for (const source of group.sources) {
+ sources.push({
+ ...source,
+ renderedSource: await renderSourceHtml(source)
+ });
+ }
+ enriched.push({ ...group, sources });
+ }
+ return enriched;
+}
+
+async function enrichFindings(findings) {
+ const enriched = [];
+ for (const finding of findings) {
+ if (!finding.source) {
+ enriched.push(finding);
+ continue;
+ }
+ enriched.push({
+ ...finding,
+ source: {
+ ...finding.source,
+ renderedSource: await renderSourceHtml(finding.source)
+ }
+ });
+ }
+ return enriched;
+}
+
+async function renderSourceHtml(source) {
+ const markup = buildSourceMarkup(source);
+ if (!markup) return renderPlainSourceLabel(source);
+ return TextEditor.enrichHTML(markup);
+}
+
+function buildSourceMarkup(source) {
+ const trail = Array.isArray(source.sourceTrail) && source.sourceTrail.length ? source.sourceTrail : null;
+ if (trail?.length) {
+ return trail.map(node => renderTrailMarkup(node)).join(" -> ");
+ }
+ if (!source.sourceUuid) return null;
+ return renderTrailMarkup({
+ uuid: source.sourceUuid,
+ label: source.sourceName ? `${source.sourceLabel} (${source.sourceName})` : source.sourceLabel
+ });
+}
+
+function renderTrailMarkup(node) {
+ if (!node?.uuid) return foundry.utils.escapeHTML(node?.label ?? "");
+ const label = foundry.utils.escapeHTML(node.label ?? node.uuid);
+ return `@UUID[${node.uuid}]{${label}}`;
+}
+
+function renderPlainSourceLabel(source) {
+ const label = source.sourceName ? `${source.sourceLabel} (${source.sourceName})` : source.sourceLabel;
+ return escapeHtml(label);
+}
diff --git a/scripts/core/analyzer.js b/scripts/core/analyzer.js
index 857326d..180ba87 100644
--- a/scripts/core/analyzer.js
+++ b/scripts/core/analyzer.js
@@ -27,7 +27,8 @@ export async function analyzeStorage({ listFiles, listSources, onProgress, i18n
sourceScope: source.sourceScope,
sourceLabel: source.sourceLabel,
sourceName: source.sourceName,
- sourceUuid: source.sourceUuid
+ sourceUuid: source.sourceUuid,
+ resolveSourceTrail: source.resolveSourceTrail
});
references.push(...extracted);
referenceCount += extracted.length;
diff --git a/styles/audit.css b/styles/audit.css
index fccea1b..a74411f 100644
--- a/styles/audit.css
+++ b/styles/audit.css
@@ -126,6 +126,7 @@
min-width: 0;
width: 100%;
max-width: 18rem;
+ justify-self: end;
}
.storage-audit__actions .button {
@@ -279,25 +280,11 @@
overflow-wrap: anywhere;
}
-.storage-audit__trail {
- display: inline-flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 0.2rem;
-}
-
-.storage-audit__trail .content-link {
- white-space: nowrap;
-}
-
-.storage-audit__trail-separator {
- opacity: 0.65;
-}
-
@media (max-width: 1100px) {
.storage-audit__actions {
width: 100%;
max-width: none;
+ justify-self: stretch;
}
}