Release 0.0.13

This commit is contained in:
2026-04-20 22:00:37 +00:00
parent 7482685b61
commit c217761792
4 changed files with 79 additions and 62 deletions

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

View File

@@ -34,7 +34,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
async _prepareContext() { async _prepareContext() {
const findings = this.#analysis?.findings ?? []; const findings = this.#analysis?.findings ?? [];
const visibleFindings = this.#showAll ? findings : findings.filter(f => f.severity !== "info"); const visibleFindings = this.#showAll ? findings : findings.filter(f => f.severity !== "info");
const groupedFindings = groupFindings(visibleFindings); const groupedFindings = await enrichGroupedFindings(groupFindings(visibleFindings));
return { return {
loading: this.#loading, loading: this.#loading,
hasAnalysis: !!this.#analysis, hasAnalysis: !!this.#analysis,
@@ -43,7 +43,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 findings: await enrichFindings(visibleFindings)
}; };
} }
@@ -87,13 +87,6 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
_replaceHTML(result, content) { _replaceHTML(result, content) {
content.replaceChildren(result); 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() { async runAnalysis() {
@@ -384,7 +377,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>${renderSourceLink(finding.source)}</dd></div>` : ""} ${finding.source ? `<div><dt>${localize("KSA.Field.Source")}</dt><dd>${finding.source.renderedSource ?? renderPlainSourceLabel(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>
@@ -400,7 +393,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>${renderSourceLink(source)}</li>`).join(""); const rows = sources.map(source => `<li>${source.renderedSource ?? renderPlainSourceLabel(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>`;
} }
@@ -550,43 +543,79 @@ 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 trail = Array.isArray(source.sourceTrail) && source.sourceTrail.length ? source.sourceTrail : null;
if (trail) {
return `<span class="storage-audit__trail">${trail.map(renderTrailNode).join('<span class="storage-audit__trail-separator">-&gt;</span>')}</span>`;
}
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 `<a class="content-link" draggable="true" data-link data-uuid="${escapeHtml(uuid)}" data-tooltip="${escapeHtml(label)}"><i class="fa-solid fa-file-lines"></i><span>${escapeHtml(label)}</span></a>`;
}
function buildSourceKey(source) { function buildSourceKey(source) {
const trailKey = Array.isArray(source.sourceTrail) && source.sourceTrail.length const trailKey = Array.isArray(source.sourceTrail) && source.sourceTrail.length
? source.sourceTrail.map(node => node.uuid ?? node.label ?? "").join(">") ? source.sourceTrail.map(node => node.uuid ?? node.label ?? "").join(">")
: ""; : "";
return `${source.sourceUuid ?? ""}|${trailKey}|${source.sourceLabel ?? ""}`; 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);
}

View File

@@ -27,7 +27,8 @@ export async function analyzeStorage({ listFiles, listSources, onProgress, i18n
sourceScope: source.sourceScope, sourceScope: source.sourceScope,
sourceLabel: source.sourceLabel, sourceLabel: source.sourceLabel,
sourceName: source.sourceName, sourceName: source.sourceName,
sourceUuid: source.sourceUuid sourceUuid: source.sourceUuid,
resolveSourceTrail: source.resolveSourceTrail
}); });
references.push(...extracted); references.push(...extracted);
referenceCount += extracted.length; referenceCount += extracted.length;

View File

@@ -126,6 +126,7 @@
min-width: 0; min-width: 0;
width: 100%; width: 100%;
max-width: 18rem; max-width: 18rem;
justify-self: end;
} }
.storage-audit__actions .button { .storage-audit__actions .button {
@@ -279,25 +280,11 @@
overflow-wrap: anywhere; 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) { @media (max-width: 1100px) {
.storage-audit__actions { .storage-audit__actions {
width: 100%; width: 100%;
max-width: none; max-width: none;
justify-self: stretch;
} }
} }