v0.1.0-pre.052

This commit is contained in:
2026-07-25 21:57:01 +02:00
parent 184fe6da88
commit 930056d422
19 changed files with 2782 additions and 121 deletions

View File

@@ -0,0 +1,340 @@
// file: kb-app-demo-desktop/frontend/ts/demo_decode_replay.ts
// version: 5
import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
import type { DemoDecodeDiagnosticsPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeDiagnosticsPayload.ts";
import type { DemoDecodeReplayOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeReplayOptionsPayload.ts";
import type { DemoDecodeReplayProgressPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeReplayProgressPayload.ts";
import type { DemoDecodeReplayRequest } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeReplayRequest.ts";
import type { DemoDecodeReplaySummaryPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeReplaySummaryPayload.ts";
import type { DemoTransactionAnnotationRequest } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoTransactionAnnotationRequest.ts";
import type { DemoTransactionAnnotationRow } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoTransactionAnnotationRow.ts";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
const logLines: string[] = [];
let running = false;
let materializationAvailable = false;
function element<T extends HTMLElement>(selector: string): T {
const value = document.querySelector<T>(selector);
if (!value) {
throw new Error(`Missing UI element: ${selector}`);
}
return value;
}
function inputValue(selector: string): string {
return element<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(selector).value.trim();
}
function integerValue(selector: string): number {
const value = Number.parseInt(inputValue(selector), 10);
if (!Number.isFinite(value) || value < 1) {
throw new Error(`Valeur numérique invalide pour ${selector}`);
}
return value;
}
function appendLog(payload: DemoDecodeReplayProgressPayload | { timestamp: string; level: string; message: string }): void {
const progress = "completed" in payload ? ` [${payload.completed}/${payload.total}]` : "";
const campaign = "campaignId" in payload ? ` [${payload.campaignId}]` : "";
logLines.push(`${payload.timestamp} ${payload.level.toUpperCase()}${campaign}${progress} ${payload.message}`);
while (logLines.length > 1000) {
logLines.shift();
}
const output = element<HTMLTextAreaElement>("#decodeReplayLogOutput");
output.value = logLines.join("\n");
output.scrollTop = output.scrollHeight;
}
function explicitSignatureCount(): number {
return element<HTMLTextAreaElement>("#decodeReplaySignaturesTextarea")
.value
.split(/\r?\n/)
.filter(value => value.trim().length > 0)
.length;
}
function updateReplayScopeControls(): void {
const signaturesPresent = explicitSignatureCount() > 0;
const incompleteSignatures = inputValue("#decodeReplayStateSelect") === "incomplete_signatures";
const allMatchingInput = element<HTMLInputElement>("#decodeReplayAllSignaturesInput");
const forceInput = element<HTMLInputElement>("#decodeReplayForceInput");
const materializeInput = element<HTMLInputElement>("#decodeReplayMaterializeInput");
if (!running && (signaturesPresent || incompleteSignatures) && allMatchingInput.checked) {
allMatchingInput.checked = false;
}
allMatchingInput.disabled = running || signaturesPresent || incompleteSignatures;
const forceScopeMissing = !signaturesPresent && !allMatchingInput.checked;
forceInput.disabled = running || forceScopeMissing || incompleteSignatures;
if (!running && (forceScopeMissing || incompleteSignatures)) {
forceInput.checked = false;
}
materializeInput.disabled = running || !materializationAvailable;
if (!running && !materializationAvailable) {
materializeInput.checked = false;
}
}
function setRunning(value: boolean): void {
running = value;
element<HTMLButtonElement>("#startDecodeReplayButton").disabled = value;
element<HTMLButtonElement>("#cancelDecodeReplayButton").disabled = !value;
const badge = element<HTMLElement>("#decodeReplayStatusBadge");
badge.textContent = value ? "Replay en cours" : "Prêt";
badge.className = value ? "badge text-bg-warning" : "badge text-bg-success";
updateReplayScopeControls();
}
function selectedDecoderNames(): string[] {
return Array.from(document.querySelectorAll<HTMLInputElement>(".decode-replay-decoder-input:checked"))
.map(input => input.value);
}
function buildRequest(): DemoDecodeReplayRequest {
return {
signaturesText: element<HTMLTextAreaElement>("#decodeReplaySignaturesTextarea").value || null,
programId: inputValue("#decodeReplayProgramIdInput") || null,
instructionState: inputValue("#decodeReplayStateSelect"),
instructionPathsText: element<HTMLTextAreaElement>("#decodeReplayPathsTextarea").value || null,
decoderNames: selectedDecoderNames(),
limit: integerValue("#decodeReplayLimitInput"),
maxConcurrentInputs: integerValue("#decodeReplayConcurrencyInput"),
allCompatible: element<HTMLInputElement>("#decodeReplayAllCompatibleInput").checked,
forceReplay: element<HTMLInputElement>("#decodeReplayForceInput").checked,
forceReplayAllMatching: element<HTMLInputElement>("#decodeReplayAllSignaturesInput").checked,
materializeAfterDecode: element<HTMLInputElement>("#decodeReplayMaterializeInput").checked,
};
}
function validationMessage(request: DemoDecodeReplayRequest): string | null {
const signatureCount = request.signaturesText
? request.signaturesText.split(/\r?\n/).filter(value => value.trim().length > 0).length
: 0;
if (request.decoderNames.length === 0) {
return "Sélectionner au moins un décodeur.";
}
if (request.instructionState === "incomplete_signatures" && request.forceReplay) {
return "Le mode signatures incomplètes rejoue son périmètre sans Force replay global.";
}
if (request.forceReplay && signatureCount === 0 && !request.forceReplayAllMatching) {
return "Le force replay nécessite des signatures explicites ou lautorisation Toutes les signatures.";
}
if (request.forceReplayAllMatching && signatureCount > 0) {
return "Le mode « Toutes les signatures » ne peut pas être combiné avec une liste de signatures explicites.";
}
if (request.forceReplayAllMatching && !request.forceReplay) {
return "Le mode « Toutes les signatures » est uniquement disponible avec Force replay.";
}
if (request.materializeAfterDecode && !materializationAvailable) {
return "Aucun matérialiseur nest actuellement enregistré dans cette application.";
}
return null;
}
function reportValidationWarning(message: string): void {
element<HTMLTextAreaElement>("#decodeReplaySummaryOutput").value = JSON.stringify({ validation: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "warn", message });
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Decode replay request rejected locally: ${message}`);
}
async function executeDecodeReplay(): Promise<void> {
if (running) {
return;
}
try {
const request = buildRequest();
const invalid = validationMessage(request);
if (invalid) {
reportValidationWarning(invalid);
return;
}
setRunning(true);
element<HTMLTextAreaElement>("#decodeReplaySummaryOutput").value = "Exécution en cours...";
const signatureCount = request.signaturesText
? request.signaturesText.split(/\r?\n/).filter(value => value.trim().length > 0).length
: 0;
const requestSummary = `signatures=${signatureCount}, programId=${request.programId ?? "auto"}, state=${request.instructionState}, decoders=${request.decoderNames.join(",")}, limit=${request.limit}, concurrency=${request.maxConcurrentInputs}, allCompatible=${request.allCompatible}, forceReplay=${request.forceReplay}, allSignatures=${request.forceReplayAllMatching}, materialize=${request.materializeAfterDecode}`;
appendLog({ timestamp: new Date().toISOString(), level: "info", message: `Démarrage du replay de décodage contextualisé : ${requestSummary}` });
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_execute ${requestSummary}`);
const summary = await invoke<DemoDecodeReplaySummaryPayload>("demo_decode_replay_execute", { request });
element<HTMLTextAreaElement>("#decodeReplaySummaryOutput").value = JSON.stringify(summary, null, 2);
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_execute completed summary=${JSON.stringify(summary)}`);
appendLog({
timestamp: new Date().toISOString(),
level: summary.cancelled || summary.failedInputs > 0 ? "warn" : "info",
message: `Campagne ${summary.campaignId} terminée : completed=${summary.completed}, unmatched=${summary.unmatched}, failed=${summary.failedInputs}, notStarted=${summary.notStarted}`,
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#decodeReplaySummaryOutput").value = JSON.stringify({ error: message }, null, 2);
appendLog({ timestamp: new Date().toISOString(), level: "error", message });
frontendError("kb_app_demo_desktop.frontend.demo_decode_replay", `Decode replay failed: ${message}`);
} finally {
setRunning(false);
}
}
async function cancelDecodeReplay(): Promise<void> {
try {
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", "Invoke demo_decode_replay_cancel");
const accepted = await invoke<boolean>("demo_decode_replay_cancel");
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_cancel completed accepted=${accepted}`);
appendLog({
timestamp: new Date().toISOString(),
level: accepted ? "warn" : "info",
message: accepted ? "Demande d'arrêt envoyée." : "Aucune campagne active.",
});
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
frontendError("kb_app_demo_desktop.frontend.demo_decode_replay", `Cancellation failed: ${message}`);
}
}
async function loadDiagnostics(): Promise<void> {
try {
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", "Invoke demo_decode_replay_diagnostics");
const diagnostics = await invoke<DemoDecodeDiagnosticsPayload>("demo_decode_replay_diagnostics");
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_diagnostics completed tables=${diagnostics.tables.length} coverage=${diagnostics.coverage.length}`);
element<HTMLTextAreaElement>("#decodeReplayDiagnosticsOutput").value = JSON.stringify(diagnostics, null, 2);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLTextAreaElement>("#decodeReplayDiagnosticsOutput").value = JSON.stringify({ error: message }, null, 2);
frontendError("kb_app_demo_desktop.frontend.demo_decode_replay", `Diagnostics loading failed: ${message}`);
}
}
function tableCell(value: string, title?: string): HTMLTableCellElement {
const cell = document.createElement("td");
cell.textContent = value;
if (title) {
cell.title = title;
}
return cell;
}
function shortened(value: string, visible: number): string {
return value.length > visible ? `${value.slice(0, visible)}` : value;
}
function renderAnnotations(rows: DemoTransactionAnnotationRow[]): void {
const body = element<HTMLTableSectionElement>("#annotationJournalBody");
body.replaceChildren();
for (const row of rows) {
const tableRow = document.createElement("tr");
tableRow.append(
tableCell(row.slot),
tableCell(shortened(row.signature, 18), row.signature),
tableCell(row.instructionPath),
tableCell(row.generation, row.programId),
tableCell(row.text),
tableCell(String(row.payloadLengthBytes)),
tableCell(row.verifiedSigners.join(", ") || "—"),
tableCell(`${shortened(row.payloadSha256, 12)} · ${row.decoder}`, `${row.payloadSha256}\n${row.idempotenceKey}\ntransaction_annotations@${row.processorVersion}`),
);
body.append(tableRow);
}
element<HTMLElement>("#annotationJournalStatus").textContent = `${rows.length} annotation(s) committed chargée(s).`;
}
async function loadAnnotations(): Promise<void> {
const request: DemoTransactionAnnotationRequest = {
signatureContains: inputValue("#annotationSignatureInput") || null,
limit: integerValue("#annotationLimitInput"),
};
try {
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_annotations limit=${request.limit}`);
const rows = await invoke<DemoTransactionAnnotationRow[]>("demo_decode_replay_annotations", { request });
renderAnnotations(rows);
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_annotations completed rows=${rows.length}`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
element<HTMLElement>("#annotationJournalStatus").textContent = `Erreur : ${message}`;
frontendError("kb_app_demo_desktop.frontend.demo_decode_replay", `Annotation journal loading failed: ${message}`);
}
}
async function openWindow(command: "open_demo_sql_pg_core_window" | "open_demo_sql_replay_candidates_window"): Promise<void> {
try {
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke ${command}`);
await invoke(command);
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke ${command} completed`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
frontendError("kb_app_demo_desktop.frontend.demo_decode_replay", `Window opening failed: ${message}`);
}
}
async function loadOptions(): Promise<void> {
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", "Invoke demo_decode_replay_options");
const options = await invoke<DemoDecodeReplayOptionsPayload>("demo_decode_replay_options");
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_options completed options=${JSON.stringify(options)}`);
element<HTMLElement>("#decodeReplayVersionBadge").textContent = `Pipeline ${options.pipelineVersion}`;
element<HTMLInputElement>("#decodeReplayLimitInput").value = String(options.defaultLimit);
element<HTMLInputElement>("#decodeReplayConcurrencyInput").value = String(options.defaultMaxConcurrentInputs);
const container = element<HTMLElement>("#decodeReplayDecoderList");
container.innerHTML = "";
for (const decoder of options.decoders) {
const wrapper = document.createElement("div");
wrapper.className = "form-check border rounded p-3 ps-5 mb-2";
const input = document.createElement("input");
input.className = "form-check-input decode-replay-decoder-input";
input.type = "checkbox";
input.value = decoder.name;
input.id = `decode-${decoder.name}`;
input.checked = true;
const label = document.createElement("label");
label.className = "form-check-label";
label.htmlFor = input.id;
label.textContent = `${decoder.name} v${decoder.version}${decoder.programIds.length} programmes`;
wrapper.append(input, label);
container.append(wrapper);
}
materializationAvailable = options.materializerNames.length > 0;
const materializeHelp = element<HTMLElement>("#decodeReplayMaterializeHelp");
materializeHelp.textContent = materializationAvailable
? `Matérialiseurs disponibles : ${options.materializerNames.join(", ")}`
: "Aucun matérialiseur enregistré : option désactivée.";
setRunning(options.running);
}
document.addEventListener("DOMContentLoaded", () => {
installFrontendConsoleBridge("kb_app_demo_desktop.frontend.demo_decode_replay");
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", "decode replay demo window loaded");
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(item => new bootstrap.Tooltip(item));
element<HTMLTextAreaElement>("#decodeReplaySignaturesTextarea").addEventListener("input", () => updateReplayScopeControls());
element<HTMLSelectElement>("#decodeReplayStateSelect").addEventListener("change", () => updateReplayScopeControls());
element<HTMLInputElement>("#decodeReplayAllSignaturesInput").addEventListener("change", event => {
const input = event.currentTarget as HTMLInputElement;
if (input.checked) {
element<HTMLInputElement>("#decodeReplayForceInput").checked = true;
}
updateReplayScopeControls();
});
element<HTMLInputElement>("#decodeReplayForceInput").addEventListener("change", event => {
const input = event.currentTarget as HTMLInputElement;
if (!input.checked) {
element<HTMLInputElement>("#decodeReplayAllSignaturesInput").checked = false;
}
updateReplayScopeControls();
});
element<HTMLButtonElement>("#startDecodeReplayButton").addEventListener("click", () => void executeDecodeReplay());
element<HTMLButtonElement>("#cancelDecodeReplayButton").addEventListener("click", () => void cancelDecodeReplay());
element<HTMLButtonElement>("#loadDecodeDiagnosticsButton").addEventListener("click", () => void loadDiagnostics());
element<HTMLButtonElement>("#loadAnnotationsButton").addEventListener("click", () => void loadAnnotations());
element<HTMLButtonElement>("#openReplayCandidatesFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_sql_replay_candidates_window"));
element<HTMLButtonElement>("#openCoreDiagnosticsFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_sql_pg_core_window"));
void listen<DemoDecodeReplayProgressPayload>("demo-decode-replay-progress", event => appendLog(event.payload));
void loadOptions().catch(caughtError => {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
appendLog({ timestamp: new Date().toISOString(), level: "error", message });
frontendError("kb_app_demo_desktop.frontend.demo_decode_replay", `Options loading failed: ${message}`);
});
});