Add structured JSON evidence export workflow
Dieser Commit ist enthalten in:
@@ -22,6 +22,7 @@
|
|||||||
"src/modules/vg-observe/module.js",
|
"src/modules/vg-observe/module.js",
|
||||||
"src/background/db/db-constants.js",
|
"src/background/db/db-constants.js",
|
||||||
"src/background/db/db-core.js",
|
"src/background/db/db-core.js",
|
||||||
|
"src/core/evidence-export-json.js",
|
||||||
"src/background/gvl/gvl-vendor-normalizer.js",
|
"src/background/gvl/gvl-vendor-normalizer.js",
|
||||||
"src/background/gvl/gvl-vendor-relationship-normalizer.js",
|
"src/background/gvl/gvl-vendor-relationship-normalizer.js",
|
||||||
"src/background/gvl/gvl-catalog-normalizer.js",
|
"src/background/gvl/gvl-catalog-normalizer.js",
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ async function handleVendorGetMessage(message, sender) {
|
|||||||
return handleFetchOfficialGvlMessage();
|
return handleFetchOfficialGvlMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.type === "export_evidence_json") {
|
||||||
|
return handleExportEvidenceJsonMessage();
|
||||||
|
}
|
||||||
|
|
||||||
if (message.type === "start_evidence_maintenance_session") {
|
if (message.type === "start_evidence_maintenance_session") {
|
||||||
return startEvidenceMaintenanceSession(message?.payload?.source);
|
return startEvidenceMaintenanceSession(message?.payload?.source);
|
||||||
}
|
}
|
||||||
@@ -169,6 +173,13 @@ async function handleGetEvidenceRetentionStatusMessage() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleExportEvidenceJsonMessage() {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
export: await exportVendorGetEvidenceJson()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function handleGetLatestGvlUpdateStatusMessage() {
|
async function handleGetLatestGvlUpdateStatusMessage() {
|
||||||
const db = await openVendorGetDb();
|
const db = await openVendorGetDb();
|
||||||
const latestSnapshot = await getLatestGvlSnapshotByVendorListVersion(db);
|
const latestSnapshot = await getLatestGvlSnapshotByVendorListVersion(db);
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
async function exportVendorGetEvidenceJson() {
|
||||||
|
const db = await openVendorGetDb();
|
||||||
|
const storeNames = [...VENDORGET_EVIDENCE_STORE_NAMES];
|
||||||
|
const stores = await exportVendorGetEvidenceStores(db, storeNames);
|
||||||
|
const summary = buildVendorGetEvidenceExportSummary(stores);
|
||||||
|
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
exportVersion: 1,
|
||||||
|
exportedAt: new Date().toISOString(),
|
||||||
|
project: "VG-Environment",
|
||||||
|
extension: "VendorGet",
|
||||||
|
dbName: db.name,
|
||||||
|
dbVersion: db.version,
|
||||||
|
stores: storeNames,
|
||||||
|
summary
|
||||||
|
},
|
||||||
|
stores
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVendorGetEvidenceExportSummary(stores) {
|
||||||
|
const storeRecordCounts = {};
|
||||||
|
let totalRecordCount = 0;
|
||||||
|
|
||||||
|
for (const [storeName, storeExport] of Object.entries(stores)) {
|
||||||
|
const recordCount = storeExport.recordCount;
|
||||||
|
|
||||||
|
storeRecordCounts[storeName] = recordCount;
|
||||||
|
totalRecordCount += recordCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalRecordCount,
|
||||||
|
storeRecordCounts
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportVendorGetEvidenceStores(db, storeNames) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const stores = {};
|
||||||
|
const tx = db.transaction(storeNames, "readonly");
|
||||||
|
|
||||||
|
tx.onerror = () => reject(tx.error);
|
||||||
|
tx.onabort = () => reject(tx.error);
|
||||||
|
tx.oncomplete = () => resolve(stores);
|
||||||
|
|
||||||
|
for (const storeName of storeNames) {
|
||||||
|
const objectStore = tx.objectStore(storeName);
|
||||||
|
const storeExport = {
|
||||||
|
keyPath: objectStore.keyPath,
|
||||||
|
autoIncrement: objectStore.autoIncrement,
|
||||||
|
recordCount: 0,
|
||||||
|
records: []
|
||||||
|
};
|
||||||
|
|
||||||
|
stores[storeName] = storeExport;
|
||||||
|
|
||||||
|
const cursorRequest = objectStore.openCursor();
|
||||||
|
|
||||||
|
cursorRequest.onerror = () => reject(cursorRequest.error);
|
||||||
|
cursorRequest.onsuccess = () => {
|
||||||
|
const cursor = cursorRequest.result;
|
||||||
|
|
||||||
|
if (!cursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
storeExport.records.push({
|
||||||
|
key: cursor.key,
|
||||||
|
value: cursor.value
|
||||||
|
});
|
||||||
|
storeExport.recordCount += 1;
|
||||||
|
cursor.continue();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -63,6 +63,10 @@
|
|||||||
<button id="evidence-dashboard-button" type="button">
|
<button id="evidence-dashboard-button" type="button">
|
||||||
Evidence Dashboard öffnen
|
Evidence Dashboard öffnen
|
||||||
</button>
|
</button>
|
||||||
|
<button id="evidence-export-json-button" type="button">
|
||||||
|
Export Evidence JSON
|
||||||
|
</button>
|
||||||
|
<div id="evidence-export-json-status" class="retention-status" aria-live="polite"></div>
|
||||||
<div id="evidence-retention-status" class="retention-status" aria-live="polite">
|
<div id="evidence-retention-status" class="retention-status" aria-live="polite">
|
||||||
Status wird geladen
|
Status wird geladen
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ const evidenceLockedCount = document.getElementById("evidence-locked-count");
|
|||||||
const evidenceDashboardButton = document.getElementById(
|
const evidenceDashboardButton = document.getElementById(
|
||||||
"evidence-dashboard-button"
|
"evidence-dashboard-button"
|
||||||
);
|
);
|
||||||
|
const evidenceExportJsonButton = document.getElementById(
|
||||||
|
"evidence-export-json-button"
|
||||||
|
);
|
||||||
|
const evidenceExportJsonStatus = document.getElementById(
|
||||||
|
"evidence-export-json-status"
|
||||||
|
);
|
||||||
const evidenceRetentionStatus = document.getElementById(
|
const evidenceRetentionStatus = document.getElementById(
|
||||||
"evidence-retention-status"
|
"evidence-retention-status"
|
||||||
);
|
);
|
||||||
@@ -61,6 +67,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
url: browser.runtime.getURL("src/dashboard/dashboard.html")
|
url: browser.runtime.getURL("src/dashboard/dashboard.html")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
evidenceExportJsonButton.addEventListener("click", exportEvidenceJsonFile);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function renderSettings() {
|
async function renderSettings() {
|
||||||
@@ -134,3 +142,70 @@ function formatStatusValue(value) {
|
|||||||
function renderEvidenceRetentionMessage(message) {
|
function renderEvidenceRetentionMessage(message) {
|
||||||
evidenceRetentionStatus.textContent = message;
|
evidenceRetentionStatus.textContent = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function exportEvidenceJsonFile() {
|
||||||
|
evidenceExportJsonButton.disabled = true;
|
||||||
|
renderEvidenceExportJsonMessage("Export läuft…");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await browser.runtime.sendMessage({
|
||||||
|
type: "export_evidence_json"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result?.success || !result.export) {
|
||||||
|
throw new Error(result?.error ?? "export_evidence_json_failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadJsonExport(result.export);
|
||||||
|
renderEvidenceExportJsonMessage(buildExportSuccessMessage(result.export));
|
||||||
|
} catch (error) {
|
||||||
|
renderEvidenceExportJsonMessage("Export fehlgeschlagen");
|
||||||
|
console.warn("VendorGet-IV evidence JSON export failed", error);
|
||||||
|
} finally {
|
||||||
|
evidenceExportJsonButton.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadJsonExport(exportContainer) {
|
||||||
|
const json = JSON.stringify(exportContainer, null, 2);
|
||||||
|
const blob = new Blob([json], { type: "application/json" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const downloadLink = document.createElement("a");
|
||||||
|
|
||||||
|
downloadLink.href = url;
|
||||||
|
downloadLink.download = `vendorget-evidence-export-${formatExportTimestamp(
|
||||||
|
new Date()
|
||||||
|
)}.json`;
|
||||||
|
document.body.append(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
downloadLink.remove();
|
||||||
|
|
||||||
|
setTimeout(() => URL.revokeObjectURL(url), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildExportSuccessMessage(exportContainer) {
|
||||||
|
const storeCount = exportContainer?.metadata?.stores?.length ?? 0;
|
||||||
|
const totalRecordCount =
|
||||||
|
exportContainer?.metadata?.summary?.totalRecordCount ?? 0;
|
||||||
|
|
||||||
|
return `Export gespeichert: ${storeCount} Stores, ${totalRecordCount} Records`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatExportTimestamp(date) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = padDatePart(date.getMonth() + 1);
|
||||||
|
const day = padDatePart(date.getDate());
|
||||||
|
const hours = padDatePart(date.getHours());
|
||||||
|
const minutes = padDatePart(date.getMinutes());
|
||||||
|
const seconds = padDatePart(date.getSeconds());
|
||||||
|
|
||||||
|
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function padDatePart(value) {
|
||||||
|
return String(value).padStart(2, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEvidenceExportJsonMessage(message) {
|
||||||
|
evidenceExportJsonStatus.textContent = message;
|
||||||
|
}
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren