|
|
|
|
@@ -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) {
|
|
|
|
|
<p>${escapeHtml(finding.reason)}</p>
|
|
|
|
|
<dl>
|
|
|
|
|
<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>
|
|
|
|
|
<p class="storage-audit__recommendation">${escapeHtml(finding.recommendation)}</p>
|
|
|
|
|
</article>
|
|
|
|
|
@@ -400,7 +393,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {
|
|
|
|
|
|
|
|
|
|
function renderSampleSources(sources) {
|
|
|
|
|
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>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -550,43 +543,79 @@ function renderLocalizedCodeText(key, data, codeValues) {
|
|
|
|
|
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">-></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) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|