Release 0.0.4

This commit is contained in:
2026-04-20 20:48:37 +00:00
parent 8044a630e2
commit 5e1096ebae
6 changed files with 52 additions and 11 deletions

View File

@@ -34,7 +34,8 @@
}, },
"Notify": { "Notify": {
"Completed": "Kosmos Storage Audit abgeschlossen: {count} Findings.", "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": { "Summary": {
"NoAnalysis": "Noch keine Analyse ausgeführt.", "NoAnalysis": "Noch keine Analyse ausgeführt.",

View File

@@ -34,7 +34,8 @@
}, },
"Notify": { "Notify": {
"Completed": "Kosmos Storage Audit completed: {count} findings.", "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": { "Summary": {
"NoAnalysis": "No analysis has been run yet.", "NoAnalysis": "No analysis has been run yet.",

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.3", "version": "0.0.4",
"compatibility": { "compatibility": {
"minimum": "13", "minimum": "13",
"verified": "13" "verified": "13"

View File

@@ -37,6 +37,8 @@ function worldCollectionEntries() {
subtype: doc.documentName?.toLowerCase() ?? collection.documentName?.toLowerCase() ?? "document" subtype: doc.documentName?.toLowerCase() ?? collection.documentName?.toLowerCase() ?? "document"
}, },
sourceLabel: `${doc.documentName ?? "Document"} ${doc.id}`, sourceLabel: `${doc.documentName ?? "Document"} ${doc.id}`,
sourceName: doc.name ?? null,
sourceUuid: doc.uuid ?? null,
value: doc.toObject ? doc.toObject() : doc value: doc.toObject ? doc.toObject() : doc
}); });
} }
@@ -89,6 +91,8 @@ async function* packagePackEntries(onProgress = null) {
subtype: `pack:${pack.collection}` subtype: `pack:${pack.collection}`
}, },
sourceLabel: `${pack.collection} ${document.id}`, sourceLabel: `${pack.collection} ${document.id}`,
sourceName: document.name ?? null,
sourceUuid: document.uuid ?? null,
value: document.toObject ? document.toObject() : document value: document.toObject ? document.toObject() : document
}; };
} }

View File

@@ -7,7 +7,8 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
runAnalysis: StorageAuditReportApp.#onRunAnalysis, runAnalysis: StorageAuditReportApp.#onRunAnalysis,
toggleShowAll: StorageAuditReportApp.#onToggleShowAll, toggleShowAll: StorageAuditReportApp.#onToggleShowAll,
toggleRaw: StorageAuditReportApp.#onToggleRaw, toggleRaw: StorageAuditReportApp.#onToggleRaw,
exportReport: StorageAuditReportApp.#onExportReport exportReport: StorageAuditReportApp.#onExportReport,
openSource: StorageAuditReportApp.#onOpenSource
}, },
window: { window: {
title: "Kosmos Storage Audit", title: "Kosmos Storage Audit",
@@ -43,7 +44,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
progress: this.#progress, progress: this.#progress,
summary: this.#summarize(this.#analysis), summary: this.#summarize(this.#analysis),
groupedFindings, groupedFindings,
findings: visibleFindings.slice(0, 50) findings: visibleFindings
}; };
} }
@@ -197,6 +198,21 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
static #onExportReport(_event, _button) { static #onExportReport(_event, _button) {
return this.exportReport(); 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) { function renderProgress(progress, loading) {
@@ -340,7 +356,7 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll
} }
function renderGroupedSection(title, description, groups, renderGroup) { function renderGroupedSection(title, description, groups, renderGroup) {
const items = groups.slice(0, 12).map(renderGroup).join(""); const items = groups.map(renderGroup).join("");
return ` return `
<section class="storage-audit__group"> <section class="storage-audit__group">
<div class="storage-audit__group-header"> <div class="storage-audit__group-header">
@@ -375,7 +391,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {
<p>${escapeHtml(finding.reason)}</p> <p>${escapeHtml(finding.reason)}</p>
<dl> <dl>
<div><dt>${localize("KSA.Field.Target")}</dt><dd><code>${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}</code></dd></div> <div><dt>${localize("KSA.Field.Target")}</dt><dd><code>${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}</code></dd></div>
${finding.source ? `<div><dt>${localize("KSA.Field.Source")}</dt><dd>${escapeHtml(finding.source.sourceLabel)}</dd></div>` : ""} ${finding.source ? `<div><dt>${localize("KSA.Field.Source")}</dt><dd>${renderSourceLink(finding.source)}</dd></div>` : ""}
</dl> </dl>
<p class="storage-audit__recommendation">${escapeHtml(finding.recommendation)}</p> <p class="storage-audit__recommendation">${escapeHtml(finding.recommendation)}</p>
</article> </article>
@@ -391,7 +407,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {
function renderSampleSources(sources) { function renderSampleSources(sources) {
if (!sources.length) return ""; if (!sources.length) return "";
const rows = sources.map(source => `<li>${escapeHtml(source)}</li>`).join(""); const rows = sources.map(source => `<li>${renderSourceLink(source)}</li>`).join("");
return `<div class="storage-audit__samples"><span>${localize("KSA.Section.Samples")}</span><ul>${rows}</ul></div>`; return `<div class="storage-audit__samples"><span>${localize("KSA.Section.Samples")}</span><ul>${rows}</ul></div>`;
} }
@@ -428,15 +444,22 @@ function groupByTarget(findings) {
reason: finding.reason, reason: finding.reason,
recommendation: finding.recommendation, recommendation: finding.recommendation,
targetKind: finding.target.targetKind ?? "local-file", targetKind: finding.target.targetKind ?? "local-file",
sources: new Set() sources: new Map()
}; };
current.count += 1; current.count += 1;
if (finding.severity === "high") current.severity = "high"; 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); grouped.set(target, current);
} }
return [...grouped.values()] 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)); .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, sourceType: finding.source.sourceType,
sourceLabel: finding.source.sourceLabel, sourceLabel: finding.source.sourceLabel,
sourceName: finding.source.sourceName,
sourceUuid: finding.source.sourceUuid,
sourceScope: finding.source.sourceScope, sourceScope: finding.source.sourceScope,
rawValue: finding.source.rawValue, rawValue: finding.source.rawValue,
normalized: finding.source.normalized normalized: finding.source.normalized
@@ -529,3 +554,9 @@ function renderLocalizedCodeText(key, data, codeValues) {
} }
return escapeHtml(text).replace(/@@CODE:([^@]+)@@/g, "<code>$1</code>"); return escapeHtml(text).replace(/@@CODE:([^@]+)@@/g, "<code>$1</code>");
} }
function renderSourceLink(source) {
const label = source.sourceName ? `${source.sourceLabel} (${source.sourceName})` : source.sourceLabel;
if (!source.sourceUuid) return escapeHtml(label);
return `<a href="#" data-action="openSource" data-uuid="${escapeHtml(source.sourceUuid)}"><code>${escapeHtml(source.sourceUuid)}</code></a>${label ? ` <span>${escapeHtml(label)}</span>` : ""}`;
}

View File

@@ -28,6 +28,8 @@ export function extractReferencesFromValue(value, source) {
sourceType: source.sourceType, sourceType: source.sourceType,
sourceScope: source.sourceScope, sourceScope: source.sourceScope,
sourceLabel: source.sourceLabel, sourceLabel: source.sourceLabel,
sourceName: source.sourceName,
sourceUuid: source.sourceUuid,
rawValue: candidate, rawValue: candidate,
normalized: { normalized: {
...direct, ...direct,
@@ -46,6 +48,8 @@ export function extractReferencesFromValue(value, source) {
sourceType: source.sourceType, sourceType: source.sourceType,
sourceScope: source.sourceScope, sourceScope: source.sourceScope,
sourceLabel: source.sourceLabel, sourceLabel: source.sourceLabel,
sourceName: source.sourceName,
sourceUuid: source.sourceUuid,
rawValue: match[1], rawValue: match[1],
normalized: { normalized: {
...nested, ...nested,