Release 0.0.3
This commit is contained in:
@@ -27,7 +27,7 @@ Dieses Verzeichnis enthaelt einen ersten Modul-Prototypen fuer die Analyse von M
|
||||
- `analyzer.js`
|
||||
- Orchestrierung von Dateien, Quellen und Findings
|
||||
- `audit-report-app.js`
|
||||
- Foundry-UI mit Fortschrittsanzeige, gruppierter Arbeitsansicht und JSON-Export
|
||||
- Foundry-UI mit Fortschrittsanzeige, gruppierter Arbeitsansicht, JSON-Export und lokalisierter Oberfläche
|
||||
|
||||
## Wichtige Heuristik
|
||||
|
||||
@@ -41,6 +41,7 @@ Dieses Verzeichnis enthaelt einen ersten Modul-Prototypen fuer die Analyse von M
|
||||
- Wildcard-Pfade wie `Ork*_Token.webp` werden nicht als kaputt markiert, solange mindestens eine passende Datei im gescannten Root existiert.
|
||||
- Dieselben Wildcard-Referenzen zaehlen auch als eingehende Referenz fuer passende Dateien und verhindern damit false-positive `orphan-file`-Treffer.
|
||||
- URL-encodierte oder unterschiedlich normalisierte Umlaute sollen auf dieselbe kanonische Pfadform zusammengefuehrt werden.
|
||||
- Die UI unterstuetzt konsistent Deutsch und Englisch ueber Foundry-Sprachdateien.
|
||||
|
||||
## Noch offen
|
||||
|
||||
|
||||
100
lang/de.json
Normal file
100
lang/de.json
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"KSA": {
|
||||
"AppTitle": "Kosmos Storage Audit",
|
||||
"Menu": {
|
||||
"Name": "Kosmos Storage Audit",
|
||||
"Label": "Report öffnen",
|
||||
"Hint": "Die Storage-Analyse ausführen und priorisierte Findings prüfen."
|
||||
},
|
||||
"Hero": {
|
||||
"Title": "Kosmos Storage Audit",
|
||||
"Intro1": "Prüft Medienreferenzen und markiert primär benutzerrelevante Risiken in den Foundry-Roots {dataRoot} und {publicRoot}.",
|
||||
"Intro2": "Orphans werden bewusst nicht global für den gesamten {dataRoot}-Root behauptet, sondern nur in klar weltlokalen oder explizit riskanten Bereichen.",
|
||||
"Intro3": "Weltverweise auf Modul- oder Systemassets gelten nicht pauschal als Problem. Relevant sind vor allem Ziele, die im owning Paket selbst nicht sichtbar referenziert werden."
|
||||
},
|
||||
"Action": {
|
||||
"Run": "Analyse starten",
|
||||
"Running": "Analysiere...",
|
||||
"Export": "Report exportieren",
|
||||
"ShowAll": "Alle Findings",
|
||||
"ShowWarnings": "Nur Warnungen",
|
||||
"ShowRaw": "Einzelfälle anzeigen",
|
||||
"HideRaw": "Einzelfälle ausblenden"
|
||||
},
|
||||
"Progress": {
|
||||
"Initialize": "Initialisiere Analyse",
|
||||
"Analyzing": "Analysiere",
|
||||
"Current": "Aktuell: {source}",
|
||||
"Files": "Dateien: {count}",
|
||||
"Sources": "Quellen: {count}",
|
||||
"References": "Referenzen: {count}",
|
||||
"Findings": "Findings: {count}",
|
||||
"BrowseStorage": "Durchsuche {storage}:{path}",
|
||||
"ReadPack": "Lese Paket-Pack {pack}"
|
||||
},
|
||||
"Notify": {
|
||||
"Completed": "Kosmos Storage Audit abgeschlossen: {count} Findings.",
|
||||
"Failed": "Kosmos Storage Audit fehlgeschlagen: {message}"
|
||||
},
|
||||
"Summary": {
|
||||
"NoAnalysis": "Noch keine Analyse ausgeführt.",
|
||||
"Files": "Dateien",
|
||||
"References": "Referenzen",
|
||||
"Findings": "Findings",
|
||||
"High": "High",
|
||||
"Warning": "Warning",
|
||||
"ByType": "Findings nach Typ",
|
||||
"NoFindings": "Keine Findings",
|
||||
"WorkBlocks": "Arbeitsblöcke",
|
||||
"MissingTargets": "Deduplizierte fehlende Ziele",
|
||||
"UnanchoredPackageTargets": "Unverankerte Paketziele",
|
||||
"OrphanCandidates": "Orphan-Kandidaten"
|
||||
},
|
||||
"Section": {
|
||||
"WorkView": "Arbeitsansicht",
|
||||
"NoGrouped": "Keine gruppierten {scope}Findings vorhanden.",
|
||||
"NoPrompt": "Die Analyse kann direkt aus dieser Ansicht gestartet werden.",
|
||||
"Running": "Analyse läuft...",
|
||||
"NoRaw": "Keine {scope}Findings gefunden.",
|
||||
"RawEntries": "Einzelfälle",
|
||||
"Samples": "Beispiele",
|
||||
"UnanchoredPackageTargets": "Unverankerte Paketziele",
|
||||
"UnanchoredPackageTargetsDesc": "Diese Ziele liegen in Modul- oder Systemordnern, werden aus Weltdaten referenziert, sind im owning Paket selbst aber derzeit nicht als Referenz sichtbar.",
|
||||
"BrokenTargets": "Kaputte Ziele",
|
||||
"BrokenTargetsDesc": "Diese Dateien fehlen, werden aber weiterhin referenziert.",
|
||||
"OrphanCandidates": "Orphan-Kandidaten",
|
||||
"OrphanCandidatesDesc": "Diese Dateien haben im aktuellen Analysekontext keine eingehende Referenz."
|
||||
},
|
||||
"Field": {
|
||||
"OwnerPackage": "Owner-Paket",
|
||||
"Assessment": "Bewertung",
|
||||
"Target": "Ziel",
|
||||
"Source": "Quelle"
|
||||
},
|
||||
"FindingKind": {
|
||||
"non-package-to-package-reference": "Unverankerte Paketziele",
|
||||
"broken-reference": "Kaputte Ziele",
|
||||
"orphan-file": "Orphan-Kandidaten",
|
||||
"risky-public-reference": "Riskante Public-Ziele"
|
||||
},
|
||||
"Finding": {
|
||||
"PackageExplanation": "Weltdaten zeigen auf ein Paketasset, für das in Manifesten und Paket-Packs keine sichtbare Eigenreferenz gefunden wurde.",
|
||||
"WildcardNoMatch": "Musterreferenz: Wildcard wird von Foundry unterstützt, derzeit aber ohne passenden Treffer im Dateisystem."
|
||||
},
|
||||
"Severity": {
|
||||
"high": "High",
|
||||
"warning": "Warning",
|
||||
"info": "Info"
|
||||
},
|
||||
"Owner": {
|
||||
"Unknown": "unbekannt"
|
||||
},
|
||||
"Export": {
|
||||
"Filename": "kosmos-storage-audit-{timestamp}.json"
|
||||
},
|
||||
"Scope": {
|
||||
"Warning": "warnenden ",
|
||||
"Empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
100
lang/en.json
Normal file
100
lang/en.json
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"KSA": {
|
||||
"AppTitle": "Kosmos Storage Audit",
|
||||
"Menu": {
|
||||
"Name": "Kosmos Storage Audit",
|
||||
"Label": "Open Report",
|
||||
"Hint": "Run the storage audit and inspect prioritized findings."
|
||||
},
|
||||
"Hero": {
|
||||
"Title": "Kosmos Storage Audit",
|
||||
"Intro1": "Audits media references and highlights primarily user-relevant risks in the Foundry roots {dataRoot} and {publicRoot}.",
|
||||
"Intro2": "Orphans are intentionally not claimed globally for the entire {dataRoot} root, but only in clearly world-local or explicitly risky areas.",
|
||||
"Intro3": "World references to module or system assets are not treated as a problem by default. The main focus is on targets that are not visibly referenced by their owning package."
|
||||
},
|
||||
"Action": {
|
||||
"Run": "Start Analysis",
|
||||
"Running": "Analyzing...",
|
||||
"Export": "Export Report",
|
||||
"ShowAll": "All Findings",
|
||||
"ShowWarnings": "Warnings Only",
|
||||
"ShowRaw": "Show Raw Entries",
|
||||
"HideRaw": "Hide Raw Entries"
|
||||
},
|
||||
"Progress": {
|
||||
"Initialize": "Initializing analysis",
|
||||
"Analyzing": "Analyzing",
|
||||
"Current": "Current: {source}",
|
||||
"Files": "Files: {count}",
|
||||
"Sources": "Sources: {count}",
|
||||
"References": "References: {count}",
|
||||
"Findings": "Findings: {count}",
|
||||
"BrowseStorage": "Browsing {storage}:{path}",
|
||||
"ReadPack": "Reading package pack {pack}"
|
||||
},
|
||||
"Notify": {
|
||||
"Completed": "Kosmos Storage Audit completed: {count} findings.",
|
||||
"Failed": "Kosmos Storage Audit failed: {message}"
|
||||
},
|
||||
"Summary": {
|
||||
"NoAnalysis": "No analysis has been run yet.",
|
||||
"Files": "Files",
|
||||
"References": "References",
|
||||
"Findings": "Findings",
|
||||
"High": "High",
|
||||
"Warning": "Warning",
|
||||
"ByType": "Findings by Type",
|
||||
"NoFindings": "No findings",
|
||||
"WorkBlocks": "Work Blocks",
|
||||
"MissingTargets": "Deduplicated missing targets",
|
||||
"UnanchoredPackageTargets": "Unanchored package targets",
|
||||
"OrphanCandidates": "Orphan candidates"
|
||||
},
|
||||
"Section": {
|
||||
"WorkView": "Work View",
|
||||
"NoGrouped": "No grouped {scope}findings available.",
|
||||
"NoPrompt": "The analysis can be started directly from this view.",
|
||||
"Running": "Analysis in progress...",
|
||||
"NoRaw": "No {scope}findings found.",
|
||||
"RawEntries": "Raw Entries",
|
||||
"Samples": "Examples",
|
||||
"UnanchoredPackageTargets": "Unanchored Package Targets",
|
||||
"UnanchoredPackageTargetsDesc": "These targets live in module or system folders, are referenced from world data, but are currently not visibly referenced by their owning package.",
|
||||
"BrokenTargets": "Broken Targets",
|
||||
"BrokenTargetsDesc": "These files are missing but are still referenced.",
|
||||
"OrphanCandidates": "Orphan Candidates",
|
||||
"OrphanCandidatesDesc": "These files currently have no incoming reference in the active analysis context."
|
||||
},
|
||||
"Field": {
|
||||
"OwnerPackage": "Owning Package",
|
||||
"Assessment": "Assessment",
|
||||
"Target": "Target",
|
||||
"Source": "Source"
|
||||
},
|
||||
"FindingKind": {
|
||||
"non-package-to-package-reference": "Unanchored package targets",
|
||||
"broken-reference": "Broken targets",
|
||||
"orphan-file": "Orphan candidates",
|
||||
"risky-public-reference": "Risky public targets"
|
||||
},
|
||||
"Finding": {
|
||||
"PackageExplanation": "World data points at a package asset for which no visible self-reference was found in manifests or package packs.",
|
||||
"WildcardNoMatch": "Pattern reference: wildcard is supported by Foundry, but currently has no matching file in the scanned storage."
|
||||
},
|
||||
"Severity": {
|
||||
"high": "High",
|
||||
"warning": "Warning",
|
||||
"info": "Info"
|
||||
},
|
||||
"Owner": {
|
||||
"Unknown": "unknown"
|
||||
},
|
||||
"Export": {
|
||||
"Filename": "kosmos-storage-audit-{timestamp}.json"
|
||||
},
|
||||
"Scope": {
|
||||
"Warning": "warning ",
|
||||
"Empty": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
15
module.json
15
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.2",
|
||||
"version": "0.0.3",
|
||||
"compatibility": {
|
||||
"minimum": "13",
|
||||
"verified": "13"
|
||||
@@ -18,7 +18,18 @@
|
||||
"styles": [
|
||||
"styles/audit.css"
|
||||
],
|
||||
"languages": [],
|
||||
"languages": [
|
||||
{
|
||||
"lang": "de",
|
||||
"name": "Deutsch",
|
||||
"path": "lang/de.json"
|
||||
},
|
||||
{
|
||||
"lang": "en",
|
||||
"name": "English",
|
||||
"path": "lang/en.json"
|
||||
}
|
||||
],
|
||||
"relationships": {},
|
||||
"url": "https://gitea.kosmos.ac/kosmos/kosmos-storage-audit",
|
||||
"manifest": "https://gitea.kosmos.ac/kosmos/kosmos-storage-audit/raw/branch/main/module.json",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { analyzeStorage } from "../core/analyzer.js";
|
||||
import { isMediaPath, normalizePath } from "../core/path-utils.js";
|
||||
|
||||
async function* walkFilePicker(storage, target = "", onProgress = null) {
|
||||
onProgress?.({ phase: "files", label: `Durchsuche ${storage}:${target || "/"}` });
|
||||
onProgress?.({ phase: "files", label: format("KSA.Progress.BrowseStorage", { storage, path: target || "/" }) });
|
||||
const result = await FilePicker.browse(storage, target);
|
||||
for (const file of result.files ?? []) {
|
||||
const path = normalizePath(file);
|
||||
@@ -78,7 +78,7 @@ async function* packagePackEntries(onProgress = null) {
|
||||
const ownerType = pack.metadata.packageType;
|
||||
const ownerId = pack.metadata.packageName;
|
||||
if (!["module", "system"].includes(ownerType) || !ownerId) continue;
|
||||
onProgress?.({ phase: "sources", label: `Lese Paket-Pack ${pack.collection}`, currentSource: pack.collection });
|
||||
onProgress?.({ phase: "sources", label: format("KSA.Progress.ReadPack", { pack: pack.collection }), currentSource: pack.collection });
|
||||
const documents = await pack.getDocuments();
|
||||
for (const document of documents) {
|
||||
yield {
|
||||
@@ -108,3 +108,7 @@ export async function runRuntimeAnalysis({ onProgress }={}) {
|
||||
onProgress
|
||||
});
|
||||
}
|
||||
|
||||
function format(key, data) {
|
||||
return game.i18n?.format(key, data) ?? key;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
#progress = null;
|
||||
#showRaw = false;
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.options.window.title = localize("KSA.AppTitle");
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const findings = this.#analysis?.findings ?? [];
|
||||
const visibleFindings = this.#showAll ? findings : findings.filter(f => f.severity !== "info");
|
||||
@@ -48,27 +53,27 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
container.innerHTML = `
|
||||
<section class="storage-audit__hero">
|
||||
<div>
|
||||
<h2>Kosmos Storage Audit</h2>
|
||||
<p>Prueft Medienreferenzen und markiert primaer benutzerrelevante Risiken in den Foundry-Roots <code>data</code> und <code>public</code>.</p>
|
||||
<p>Orphans werden bewusst nicht global fuer den gesamten <code>data</code>-Root behauptet, sondern nur in klar weltlokalen oder explizit riskanten Bereichen.</p>
|
||||
<p>Weltverweise auf Modul- oder Systemassets gelten nicht pauschal als Problem. Relevant sind vor allem Ziele, die im owning Paket selbst nicht sichtbar referenziert werden.</p>
|
||||
<h2>${localize("KSA.Hero.Title")}</h2>
|
||||
<p>${renderLocalizedCodeText("KSA.Hero.Intro1", { dataRoot: "data", publicRoot: "public" }, ["data", "public"])}</p>
|
||||
<p>${renderLocalizedCodeText("KSA.Hero.Intro2", { dataRoot: "data" }, ["data"])}</p>
|
||||
<p>${escapeHtml(localize("KSA.Hero.Intro3"))}</p>
|
||||
</div>
|
||||
<div class="storage-audit__actions">
|
||||
<button type="button" class="button" data-action="runAnalysis" ${context.loading ? "disabled" : ""}>
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
<span>${context.loading ? "Analysiere..." : "Analyse Starten"}</span>
|
||||
<span>${context.loading ? localize("KSA.Action.Running") : localize("KSA.Action.Run")}</span>
|
||||
</button>
|
||||
<button type="button" class="button" data-action="exportReport" ${context.hasAnalysis ? "" : "disabled"}>
|
||||
<i class="fa-solid fa-file-export"></i>
|
||||
<span>Report Export</span>
|
||||
<span>${localize("KSA.Action.Export")}</span>
|
||||
</button>
|
||||
<button type="button" class="button" data-action="toggleShowAll" ${context.hasAnalysis ? "" : "disabled"}>
|
||||
<i class="fa-solid fa-filter"></i>
|
||||
<span>${context.showAll ? "Nur Warnungen" : "Alle Findings"}</span>
|
||||
<span>${context.showAll ? localize("KSA.Action.ShowWarnings") : localize("KSA.Action.ShowAll")}</span>
|
||||
</button>
|
||||
<button type="button" class="button" data-action="toggleRaw" ${context.hasAnalysis ? "" : "disabled"}>
|
||||
<i class="fa-solid fa-list"></i>
|
||||
<span>${context.showRaw ? "Einzelfaelle Ausblenden" : "Einzelfaelle Anzeigen"}</span>
|
||||
<span>${context.showRaw ? localize("KSA.Action.HideRaw") : localize("KSA.Action.ShowRaw")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
@@ -88,7 +93,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
this.#loading = true;
|
||||
this.#progress = {
|
||||
phase: "start",
|
||||
label: "Initialisiere Analyse",
|
||||
label: localize("KSA.Progress.Initialize"),
|
||||
files: 0,
|
||||
sources: 0,
|
||||
references: 0,
|
||||
@@ -102,10 +107,10 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
onProgress: update => this.#updateProgress(update)
|
||||
});
|
||||
const count = this.#analysis.findings.length;
|
||||
ui.notifications.info(`Kosmos Storage Audit abgeschlossen: ${count} Findings.`);
|
||||
ui.notifications.info(format("KSA.Notify.Completed", { count }));
|
||||
} catch (error) {
|
||||
console.error("kosmos-storage-audit | analysis failed", error);
|
||||
ui.notifications.error(`Kosmos Storage Audit fehlgeschlagen: ${error.message}`);
|
||||
ui.notifications.error(format("KSA.Notify.Failed", { message: error.message }));
|
||||
} finally {
|
||||
this.#loading = false;
|
||||
await this.render({ force: true });
|
||||
@@ -134,7 +139,7 @@ export class StorageAuditReportApp extends foundry.applications.api.ApplicationV
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `kosmos-storage-audit-${Date.now()}.json`;
|
||||
link.download = format("KSA.Export.Filename", { timestamp: Date.now() });
|
||||
link.click();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 0);
|
||||
}
|
||||
@@ -202,13 +207,13 @@ function renderProgress(progress, loading) {
|
||||
<div class="storage-audit__progress-fill phase-${escapeHtml(progress.phase ?? "start")}"></div>
|
||||
</div>
|
||||
<div class="storage-audit__progress-meta">
|
||||
<strong>${escapeHtml(progress.label ?? "Analysiere")}</strong>
|
||||
<span>Dateien: ${progress.files ?? 0}</span>
|
||||
<span>Quellen: ${progress.sources ?? 0}</span>
|
||||
<span>Referenzen: ${progress.references ?? 0}</span>
|
||||
${progress.findings != null ? `<span>Findings: ${progress.findings}</span>` : ""}
|
||||
<strong>${escapeHtml(progress.label ?? localize("KSA.Progress.Analyzing"))}</strong>
|
||||
<span>${escapeHtml(format("KSA.Progress.Files", { count: progress.files ?? 0 }))}</span>
|
||||
<span>${escapeHtml(format("KSA.Progress.Sources", { count: progress.sources ?? 0 }))}</span>
|
||||
<span>${escapeHtml(format("KSA.Progress.References", { count: progress.references ?? 0 }))}</span>
|
||||
${progress.findings != null ? `<span>${escapeHtml(format("KSA.Progress.Findings", { count: progress.findings }))}</span>` : ""}
|
||||
</div>
|
||||
${progress.currentSource ? `<p class="storage-audit__progress-source">Aktuell: ${escapeHtml(progress.currentSource)}</p>` : ""}
|
||||
${progress.currentSource ? `<p class="storage-audit__progress-source">${escapeHtml(format("KSA.Progress.Current", { source: progress.currentSource }))}</p>` : ""}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
@@ -217,7 +222,7 @@ function renderSummary(summary) {
|
||||
if (!summary) {
|
||||
return `
|
||||
<section class="storage-audit__summary">
|
||||
<p>Noch keine Analyse ausgefuehrt.</p>
|
||||
<p>${localize("KSA.Summary.NoAnalysis")}</p>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
@@ -230,22 +235,22 @@ function renderSummary(summary) {
|
||||
return `
|
||||
<section class="storage-audit__summary">
|
||||
<div class="storage-audit__stats">
|
||||
<article><span>Dateien</span><strong>${summary.files}</strong></article>
|
||||
<article><span>Referenzen</span><strong>${summary.references}</strong></article>
|
||||
<article><span>Findings</span><strong>${summary.findings}</strong></article>
|
||||
<article><span>High</span><strong>${summary.bySeverity.high}</strong></article>
|
||||
<article><span>Warning</span><strong>${summary.bySeverity.warning}</strong></article>
|
||||
<article><span>${localize("KSA.Summary.Files")}</span><strong>${summary.files}</strong></article>
|
||||
<article><span>${localize("KSA.Summary.References")}</span><strong>${summary.references}</strong></article>
|
||||
<article><span>${localize("KSA.Summary.Findings")}</span><strong>${summary.findings}</strong></article>
|
||||
<article><span>${localize("KSA.Summary.High")}</span><strong>${summary.bySeverity.high}</strong></article>
|
||||
<article><span>${localize("KSA.Summary.Warning")}</span><strong>${summary.bySeverity.warning}</strong></article>
|
||||
</div>
|
||||
<div class="storage-audit__kinds">
|
||||
<h3>Findings nach Typ</h3>
|
||||
<ul>${kindLines || "<li><span>Keine Findings</span><span>0</span></li>"}</ul>
|
||||
<h3>${localize("KSA.Summary.ByType")}</h3>
|
||||
<ul>${kindLines || `<li><span>${localize("KSA.Summary.NoFindings")}</span><span>0</span></li>`}</ul>
|
||||
</div>
|
||||
<div class="storage-audit__kinds">
|
||||
<h3>Arbeitsbloecke</h3>
|
||||
<h3>${localize("KSA.Summary.WorkBlocks")}</h3>
|
||||
<ul>
|
||||
<li><span>Deduplizierte fehlende Ziele</span><span>${summary.grouped?.brokenReferences ?? 0}</span></li>
|
||||
<li><span>Unverankerte Paketziele</span><span>${summary.grouped?.packageReferences ?? 0}</span></li>
|
||||
<li><span>Orphan-Kandidaten</span><span>${summary.grouped?.orphans ?? 0}</span></li>
|
||||
<li><span>${localize("KSA.Summary.MissingTargets")}</span><span>${summary.grouped?.brokenReferences ?? 0}</span></li>
|
||||
<li><span>${localize("KSA.Summary.UnanchoredPackageTargets")}</span><span>${summary.grouped?.packageReferences ?? 0}</span></li>
|
||||
<li><span>${localize("KSA.Summary.OrphanCandidates")}</span><span>${summary.grouped?.orphans ?? 0}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
@@ -258,20 +263,20 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll
|
||||
const sections = [];
|
||||
if (groupedFindings.nonPackageToPackage.length) {
|
||||
sections.push(renderGroupedSection(
|
||||
"Unverankerte Paketziele",
|
||||
"Diese Ziele liegen in Modul- oder Systemordnern, werden aus Weltdaten referenziert, sind im owning Paket selbst aber derzeit nicht als Referenz sichtbar.",
|
||||
localize("KSA.Section.UnanchoredPackageTargets"),
|
||||
localize("KSA.Section.UnanchoredPackageTargetsDesc"),
|
||||
groupedFindings.nonPackageToPackage,
|
||||
group => `
|
||||
<article class="storage-audit__finding severity-${group.severity}">
|
||||
<header>
|
||||
<span class="storage-audit__severity">${group.severity}</span>
|
||||
<strong>${group.count} Referenzen</strong>
|
||||
<span class="storage-audit__severity">${severityLabel(group.severity)}</span>
|
||||
<strong>${formatCount(group.count, "KSA.Summary.References")}</strong>
|
||||
</header>
|
||||
<p><code>${escapeHtml(group.target)}</code></p>
|
||||
<p>${escapeHtml(group.shortReason)}</p>
|
||||
<dl>
|
||||
<div><dt>Owner-Paket</dt><dd><code>${escapeHtml(group.ownerLabel)}</code></dd></div>
|
||||
<div><dt>Bewertung</dt><dd>${escapeHtml(group.explanation)}</dd></div>
|
||||
<div><dt>${localize("KSA.Field.OwnerPackage")}</dt><dd><code>${escapeHtml(group.ownerLabel)}</code></dd></div>
|
||||
<div><dt>${localize("KSA.Field.Assessment")}</dt><dd>${escapeHtml(group.explanation)}</dd></div>
|
||||
</dl>
|
||||
<p class="storage-audit__recommendation">${escapeHtml(group.recommendation)}</p>
|
||||
${renderSampleSources(group.sources)}
|
||||
@@ -282,18 +287,18 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll
|
||||
|
||||
if (groupedFindings.brokenReferences.length) {
|
||||
sections.push(renderGroupedSection(
|
||||
"Kaputte Ziele",
|
||||
"Diese Dateien fehlen, werden aber weiterhin referenziert.",
|
||||
localize("KSA.Section.BrokenTargets"),
|
||||
localize("KSA.Section.BrokenTargetsDesc"),
|
||||
groupedFindings.brokenReferences,
|
||||
group => `
|
||||
<article class="storage-audit__finding severity-${group.severity}">
|
||||
<header>
|
||||
<span class="storage-audit__severity">${group.severity}</span>
|
||||
<strong>${group.count} Referenzen</strong>
|
||||
<span class="storage-audit__severity">${severityLabel(group.severity)}</span>
|
||||
<strong>${formatCount(group.count, "KSA.Summary.References")}</strong>
|
||||
</header>
|
||||
<p><code>${escapeHtml(group.target)}</code></p>
|
||||
<p>${escapeHtml(group.shortReason)}</p>
|
||||
${group.targetKind === "wildcard" ? `<p>Musterreferenz: Wildcard wird von Foundry unterstuetzt, derzeit aber ohne passenden Treffer im Dateisystem.</p>` : ""}
|
||||
${group.targetKind === "wildcard" ? `<p>${localize("KSA.Finding.WildcardNoMatch")}</p>` : ""}
|
||||
<p class="storage-audit__recommendation">${escapeHtml(group.recommendation)}</p>
|
||||
${renderSampleSources(group.sources)}
|
||||
</article>
|
||||
@@ -303,14 +308,14 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll
|
||||
|
||||
if (showAll && groupedFindings.orphans.length) {
|
||||
sections.push(renderGroupedSection(
|
||||
"Orphan-Kandidaten",
|
||||
"Diese Dateien haben im aktuellen Analysekontext keine eingehende Referenz.",
|
||||
localize("KSA.Section.OrphanCandidates"),
|
||||
localize("KSA.Section.OrphanCandidatesDesc"),
|
||||
groupedFindings.orphans,
|
||||
group => `
|
||||
<article class="storage-audit__finding severity-${group.severity}">
|
||||
<header>
|
||||
<span class="storage-audit__severity">${group.severity}</span>
|
||||
<code>${group.kind}</code>
|
||||
<span class="storage-audit__severity">${severityLabel(group.severity)}</span>
|
||||
<code>${humanizeKind(group.kind)}</code>
|
||||
</header>
|
||||
<p><code>${escapeHtml(group.target)}</code></p>
|
||||
<p>${escapeHtml(group.reason)}</p>
|
||||
@@ -324,8 +329,8 @@ function renderGroupedFindingList(groupedFindings, hasAnalysis, loading, showAll
|
||||
return `
|
||||
<section class="storage-audit__grouped">
|
||||
<div class="storage-audit__group-header">
|
||||
<h3>Arbeitsansicht</h3>
|
||||
<p>Keine gruppierten ${showAll ? "" : "warnenden "}Findings vorhanden.</p>
|
||||
<h3>${localize("KSA.Section.WorkView")}</h3>
|
||||
<p>${format("KSA.Section.NoGrouped", { scope: localize(showAll ? "KSA.Scope.Empty" : "KSA.Scope.Warning") })}</p>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
@@ -349,28 +354,28 @@ function renderGroupedSection(title, description, groups, renderGroup) {
|
||||
|
||||
function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {
|
||||
if (loading) {
|
||||
return `<section class="storage-audit__list"><p>Analyse laeuft...</p></section>`;
|
||||
return `<section class="storage-audit__list"><p>${localize("KSA.Section.Running")}</p></section>`;
|
||||
}
|
||||
if (!hasAnalysis) {
|
||||
return `<section class="storage-audit__list"><p>Die Analyse kann direkt aus dieser Ansicht gestartet werden.</p></section>`;
|
||||
return `<section class="storage-audit__list"><p>${localize("KSA.Section.NoPrompt")}</p></section>`;
|
||||
}
|
||||
if (!showRaw) {
|
||||
return "";
|
||||
}
|
||||
if (!findings.length) {
|
||||
return `<section class="storage-audit__list"><p>Keine ${showAll ? "" : "warnenden "}Findings gefunden.</p></section>`;
|
||||
return `<section class="storage-audit__list"><p>${format("KSA.Section.NoRaw", { scope: localize(showAll ? "KSA.Scope.Empty" : "KSA.Scope.Warning") })}</p></section>`;
|
||||
}
|
||||
|
||||
const items = findings.map(finding => `
|
||||
<article class="storage-audit__finding severity-${finding.severity}">
|
||||
<header>
|
||||
<span class="storage-audit__severity">${finding.severity}</span>
|
||||
<span class="storage-audit__severity">${severityLabel(finding.severity)}</span>
|
||||
<span>${humanizeKind(finding.kind)}</span>
|
||||
</header>
|
||||
<p>${escapeHtml(finding.reason)}</p>
|
||||
<dl>
|
||||
<div><dt>Ziel</dt><dd><code>${escapeHtml(finding.target.locator ?? `${finding.target.storage}:${finding.target.path}`)}</code></dd></div>
|
||||
${finding.source ? `<div><dt>Quelle</dt><dd>${escapeHtml(finding.source.sourceLabel)}</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>` : ""}
|
||||
</dl>
|
||||
<p class="storage-audit__recommendation">${escapeHtml(finding.recommendation)}</p>
|
||||
</article>
|
||||
@@ -378,7 +383,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {
|
||||
|
||||
return `
|
||||
<section class="storage-audit__list storage-audit__list--raw">
|
||||
<h3>Einzelfaelle</h3>
|
||||
<h3>${localize("KSA.Section.RawEntries")}</h3>
|
||||
${items}
|
||||
</section>
|
||||
`;
|
||||
@@ -387,7 +392,7 @@ function renderFindingList(findings, hasAnalysis, loading, showAll, showRaw) {
|
||||
function renderSampleSources(sources) {
|
||||
if (!sources.length) return "";
|
||||
const rows = sources.map(source => `<li>${escapeHtml(source)}</li>`).join("");
|
||||
return `<div class="storage-audit__samples"><span>Beispiele</span><ul>${rows}</ul></div>`;
|
||||
return `<div class="storage-audit__samples"><span>${localize("KSA.Section.Samples")}</span><ul>${rows}</ul></div>`;
|
||||
}
|
||||
|
||||
function groupFindings(findings) {
|
||||
@@ -418,7 +423,7 @@ function groupByTarget(findings) {
|
||||
target,
|
||||
count: 0,
|
||||
ownerLabel,
|
||||
explanation: "Weltdaten zeigen auf ein Paketasset, fuer das in Manifesten und Paket-Packs keine sichtbare Eigenreferenz gefunden wurde.",
|
||||
explanation: localize("KSA.Finding.PackageExplanation"),
|
||||
shortReason: shortenReason(finding.reason),
|
||||
reason: finding.reason,
|
||||
recommendation: finding.recommendation,
|
||||
@@ -448,7 +453,7 @@ function deriveOwnerLabel(target) {
|
||||
if ((parts[0] === "modules") || (parts[0] === "systems")) {
|
||||
return `${parts[0]}:${parts[1] ?? "unknown"}`;
|
||||
}
|
||||
return "unbekannt";
|
||||
return localize("KSA.Owner.Unknown");
|
||||
}
|
||||
|
||||
function compareFindings(a, b) {
|
||||
@@ -466,12 +471,9 @@ function formatTarget(target) {
|
||||
}
|
||||
|
||||
function humanizeKind(kind) {
|
||||
return {
|
||||
"non-package-to-package-reference": "Unverankerte Paketziele",
|
||||
"broken-reference": "Kaputte Ziele",
|
||||
"orphan-file": "Orphan-Kandidaten",
|
||||
"risky-public-reference": "Riskante Public-Ziele"
|
||||
}[kind] ?? kind;
|
||||
const key = `KSA.FindingKind.${kind}`;
|
||||
const localized = localize(key);
|
||||
return localized === key ? kind : localized;
|
||||
}
|
||||
|
||||
function serializeFinding(finding) {
|
||||
@@ -501,3 +503,29 @@ function escapeHtml(value) {
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\"/g, """);
|
||||
}
|
||||
|
||||
function localize(key) {
|
||||
return game.i18n?.localize(key) ?? key;
|
||||
}
|
||||
|
||||
function format(key, data) {
|
||||
return game.i18n?.format(key, data) ?? key;
|
||||
}
|
||||
|
||||
function severityLabel(severity) {
|
||||
const key = `KSA.Severity.${severity}`;
|
||||
const localized = localize(key);
|
||||
return localized === key ? severity : localized;
|
||||
}
|
||||
|
||||
function formatCount(count, nounKey) {
|
||||
return `${count} ${localize(nounKey)}`;
|
||||
}
|
||||
|
||||
function renderLocalizedCodeText(key, data, codeValues) {
|
||||
let text = format(key, data);
|
||||
for (const value of codeValues) {
|
||||
text = text.replaceAll(value, `@@CODE:${value}@@`);
|
||||
}
|
||||
return escapeHtml(text).replace(/@@CODE:([^@]+)@@/g, "<code>$1</code>");
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { StorageAuditReportApp } from "./apps/audit-report-app.js";
|
||||
Hooks.once("init", () => {
|
||||
console.log("kosmos-storage-audit | init");
|
||||
game.settings.registerMenu("kosmos-storage-audit", "report", {
|
||||
name: "Kosmos Storage Audit",
|
||||
label: "Open Report",
|
||||
hint: "Run the storage audit and inspect prioritized findings.",
|
||||
name: localize("KSA.Menu.Name"),
|
||||
label: localize("KSA.Menu.Label"),
|
||||
hint: localize("KSA.Menu.Hint"),
|
||||
icon: "fa-solid fa-broom-ball",
|
||||
type: StorageAuditReportApp,
|
||||
restricted: true
|
||||
@@ -20,3 +20,7 @@ Hooks.once("init", () => {
|
||||
Hooks.once("ready", () => {
|
||||
console.log("kosmos-storage-audit | ready");
|
||||
});
|
||||
|
||||
function localize(key) {
|
||||
return game.i18n?.localize(key) ?? key;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user