422 lines
24 KiB
TypeScript
422 lines
24 KiB
TypeScript
// file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
|
||
// version: 10
|
||
|
||
import * as bootstrap from "bootstrap";
|
||
import "simplebar";
|
||
import { invoke } from "@tauri-apps/api/core";
|
||
import { listen } from "@tauri-apps/api/event";
|
||
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
|
||
import { renderJsonViewer } from "./json_viewer.ts";
|
||
import type { DemoExecutionMetadataScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataScenarioPayload.ts";
|
||
import type { DemoExecutionMetadataPreparedStepPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataPreparedStepPayload.ts";
|
||
import type { DemoExecutionMetadataRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataRequest.ts";
|
||
import type { DemoExecutionMetadataSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSummaryPayload.ts";
|
||
import type { DemoExecutionDevnetStoreReadinessPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionDevnetStoreReadinessPayload.ts";
|
||
import type { DemoExecutionSolanaCoreOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts";
|
||
import type { DemoExecutionSolanaCoreProfileOption } from "./bindings/kb_app_demo_desktop/demo_execution_solana_core/DemoExecutionSolanaCoreProfileOption.ts";
|
||
|
||
const storageKey = "kb-app-demo-desktop.metadata.selected-scenario";
|
||
const profileStorageKey = "kb-app-demo-desktop.metadata.selected-profile";
|
||
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
||
let scenarios: DemoExecutionMetadataScenarioPayload[] = [];
|
||
let selectedProfileName: string | null = null;
|
||
let executionRunning = false;
|
||
let profileStoreReady = false;
|
||
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
|
||
let preparedScenarioId: string | null = null;
|
||
let preparedStepIndex: number | null = null;
|
||
const metadataLogLines: string[] = [];
|
||
const maximumMetadataLogLines = 1200;
|
||
|
||
type MetadataProgressPayload = { timestamp: string; level: string; stage: string; message: string; signature?: string | null };
|
||
|
||
function appendMetadataLog(payload: MetadataProgressPayload): void {
|
||
const signature = payload.signature ? ` signature=${payload.signature}` : "";
|
||
metadataLogLines.push(`${payload.timestamp} ${payload.level.toUpperCase()} [${payload.stage}] ${payload.message}${signature}`);
|
||
if (metadataLogLines.length > maximumMetadataLogLines) {
|
||
metadataLogLines.splice(0, metadataLogLines.length - maximumMetadataLogLines);
|
||
}
|
||
const output = element<HTMLTextAreaElement>("#metadataExecutionLogOutput");
|
||
output.value = metadataLogLines.join("\n");
|
||
output.scrollTop = output.scrollHeight;
|
||
}
|
||
|
||
function titled(title: string, value: unknown): unknown { return { title, value }; }
|
||
|
||
function element<T extends HTMLElement>(selector: string): T {
|
||
const value = document.querySelector<T>(selector);
|
||
if (value === null) {
|
||
throw new Error(`Élément introuvable : ${selector}`);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
function activeSelect(): HTMLSelectElement {
|
||
return document.querySelector("#metaplexDevnetCollapse.show") !== null ? element<HTMLSelectElement>("#metadataDevnetScenarioSelect") : element<HTMLSelectElement>("#metadataSyntheticScenarioSelect");
|
||
}
|
||
|
||
function selectedScenario(): DemoExecutionMetadataScenarioPayload | null {
|
||
const id = activeSelect().value;
|
||
return scenarios.find(scenario => scenario.id === id) ?? null;
|
||
}
|
||
|
||
function fillSelect(selector: string, mode: string): void {
|
||
const select = element<HTMLSelectElement>(selector);
|
||
select.replaceChildren();
|
||
for (const scenario of scenarios.filter(candidate => candidate.mode === mode)) {
|
||
const option = document.createElement("option");
|
||
option.value = scenario.id;
|
||
option.textContent = `${scenario.assetFamily} — ${scenario.label}`;
|
||
select.append(option);
|
||
}
|
||
}
|
||
|
||
function operationName(operationCode: string): string {
|
||
const parts = operationCode.split(".");
|
||
return parts.length === 0 ? operationCode : parts[parts.length - 1] ?? operationCode;
|
||
}
|
||
|
||
function renderScenarioSteps(scenario: DemoExecutionMetadataScenarioPayload): void {
|
||
const select = element<HTMLSelectElement>("#metadataStepSelect");
|
||
const previousValue = Number.parseInt(select.value, 10);
|
||
select.replaceChildren();
|
||
scenario.operationCodes.forEach((operationCode, index) => {
|
||
const option = document.createElement("option");
|
||
option.value = String(index);
|
||
option.textContent = `${index + 1}. ${operationName(operationCode)}`;
|
||
select.append(option);
|
||
});
|
||
if (Number.isInteger(previousValue) && previousValue >= 0 && previousValue < scenario.operationCodes.length) {
|
||
select.value = String(previousValue);
|
||
}
|
||
const index = Number.parseInt(select.value, 10);
|
||
element<HTMLButtonElement>("#previousMetadataStepButton").disabled = !Number.isInteger(index) || index <= 0;
|
||
element<HTMLButtonElement>("#nextMetadataStepButton").disabled = !Number.isInteger(index) || index >= scenario.operationCodes.length - 1;
|
||
element<HTMLButtonElement>("#prepareMetadataScenarioStepButton").textContent = Number.isInteger(index) && scenario.operationCodes[index] !== undefined
|
||
? `Préparer l’étape ${index + 1} — ${operationName(scenario.operationCodes[index])}`
|
||
: "Préparer l’étape courante";
|
||
}
|
||
|
||
function renderSelectedScenario(): void {
|
||
const scenario = selectedScenario();
|
||
if (scenario === null) {
|
||
return;
|
||
}
|
||
window.localStorage.setItem(storageKey, scenario.id);
|
||
const networkMode = scenario.mode !== "synthetic";
|
||
renderScenarioSteps(scenario);
|
||
preparedScenarioId = null;
|
||
preparedStepIndex = null;
|
||
element<HTMLTextAreaElement>("#metadataOperationJson").value = "";
|
||
element<HTMLTextAreaElement>("#metadataPreflightReadsJson").value = "[]";
|
||
element<HTMLTextAreaElement>("#metadataPostconditionReadsJson").value = "[]";
|
||
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Parcours</dt><dd class="col-6">${scenario.label}</dd><dt class="col-6">Famille</dt><dd class="col-6"><code>${scenario.assetFamily}</code></dd><dt class="col-6">Fixture</dt><dd class="col-6"><code>${scenario.fixtureKind}</code></dd><dt class="col-6">État initial</dt><dd class="col-6"><code>${scenario.initialState}</code></dd><dt class="col-6">État attendu</dt><dd class="col-6"><code>${scenario.resultingState}</code></dd><dt class="col-6">Mode</dt><dd class="col-6"><code>${scenario.mode}</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${networkMode ? selectedProfileName ?? "non chargé" : "non applicable"}</code></dd><dt class="col-6">Collection</dt><dd class="col-6">${scenario.requiresCollection ? "requise" : "non requise"}</dd><dt class="col-6">Rule set</dt><dd class="col-6">${scenario.requiresProgrammableRules ? "requis" : "non requis"}</dd><dt class="col-6">Validation</dt><dd class="col-6"><code>${scenario.validationStatus}</code></dd>`;
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", {
|
||
scenarioId: scenario.id,
|
||
operationCodes: scenario.operationCodes,
|
||
checks: ["program_id", "owner", "pda_and_seeds", "required_accounts", "authorities", "stateful_correlations"],
|
||
networkExecutionPerformed: false,
|
||
}));
|
||
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", {
|
||
mode: scenario.mode,
|
||
policy: "simulation_first",
|
||
networkExecutionPerformed: false,
|
||
automaticSubmission: false,
|
||
operatorConfirmationRequired: true,
|
||
expectedState: scenario.validationStatus,
|
||
note: networkMode ? "Renseignez un intent typé puis lancez une simulation RPC réelle." : "Scénario synthétique déterministe.",
|
||
}));
|
||
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", {
|
||
required: scenario.requiresPostcondition,
|
||
evidenceKinds: scenario.requiredEvidence,
|
||
networkEvidenceCollected: false,
|
||
materializationTargets: scenario.materializationTargets,
|
||
rule: "Aucune validation réseau n'est déclarée sans simulation RPC réelle, signature éventuelle, slot et postconditions.",
|
||
}));
|
||
}
|
||
|
||
|
||
|
||
async function prepareScenarioStep(): Promise<void> {
|
||
const profileName = element<HTMLSelectElement>("#metadataProfileSelect").value;
|
||
const scenario = selectedScenario();
|
||
const stepIndex = Number.parseInt(element<HTMLSelectElement>("#metadataStepSelect").value, 10);
|
||
if (profileName.length === 0) { throw new Error("Sélectionnez un profil Devnet."); }
|
||
if (scenario === null || scenario.mode === "synthetic") { throw new Error("Sélectionnez un scénario Devnet."); }
|
||
if (!Number.isInteger(stepIndex) || stepIndex < 0) { throw new Error("Sélectionnez une étape valide."); }
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "fixture", message: `Préparation de ${scenario.id} étape ${stepIndex + 1}` });
|
||
const prepared = await invoke<DemoExecutionMetadataPreparedStepPayload>("demo_execution_metadata_prepare_step", {
|
||
profileName,
|
||
scenarioId: scenario.id,
|
||
stepIndex,
|
||
previousOperationJson: element<HTMLTextAreaElement>("#metadataOperationJson").value.trim().length === 0
|
||
? null
|
||
: element<HTMLTextAreaElement>("#metadataOperationJson").value,
|
||
});
|
||
element<HTMLTextAreaElement>("#metadataOperationJson").value = prepared.operationJson;
|
||
element<HTMLTextAreaElement>("#metadataPreflightReadsJson").value = prepared.preflightReadsJson;
|
||
element<HTMLTextAreaElement>("#metadataPostconditionReadsJson").value = prepared.postconditionReadsJson;
|
||
preparedScenarioId = prepared.scenarioId;
|
||
preparedStepIndex = prepared.stepIndex;
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Étape de scénario préparée", {
|
||
scenarioId: prepared.scenarioId,
|
||
stepIndex: prepared.stepIndex,
|
||
operation: prepared.operation,
|
||
fixtureState: prepared.fixtureState,
|
||
preflightReads: JSON.parse(prepared.preflightReadsJson),
|
||
postconditionReads: JSON.parse(prepared.postconditionReadsJson),
|
||
simulationReady: prepared.simulationReady,
|
||
submissionReady: prepared.submissionReady,
|
||
}));
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "fixture", message: `${prepared.label} prête (${prepared.fixtureState})` });
|
||
setExecutionRunning(executionRunning);
|
||
}
|
||
|
||
function moveScenarioStep(offset: number): void {
|
||
const select = element<HTMLSelectElement>("#metadataStepSelect");
|
||
const current = Number.parseInt(select.value, 10);
|
||
const next = current + offset;
|
||
if (Number.isInteger(next) && next >= 0 && next < select.options.length) {
|
||
select.value = String(next);
|
||
const scenario = selectedScenario();
|
||
if (scenario !== null) { renderScenarioSteps(scenario); }
|
||
}
|
||
}
|
||
|
||
function parseJsonText(value: string, label: string): unknown {
|
||
try {
|
||
return JSON.parse(value);
|
||
} catch (caught) {
|
||
const message = caught instanceof Error ? caught.message : String(caught);
|
||
throw new Error(`${label} invalide : ${message}`);
|
||
}
|
||
}
|
||
|
||
function selectedProfile(): DemoExecutionSolanaCoreProfileOption | null {
|
||
const name = element<HTMLSelectElement>("#metadataProfileSelect").value;
|
||
return profileOptions.find(profile => profile.name === name) ?? null;
|
||
}
|
||
|
||
function updateProfileHelp(): void {
|
||
const profile = selectedProfile();
|
||
const help = element<HTMLElement>("#metadataProfileHelp");
|
||
if (profile === null) {
|
||
help.textContent = "Aucun profil Devnet compatible.";
|
||
return;
|
||
}
|
||
help.textContent = `wallet=${profile.walletAlias}, spendMax=${profile.maxSpendLamports}, airdropMax=${profile.maxAirdropLamports}, send=${profile.sendEnabled ? "enabled" : "disabled"}`;
|
||
}
|
||
|
||
function setExecutionRunning(value: boolean): void {
|
||
executionRunning = value;
|
||
const profile = selectedProfile();
|
||
const missingOperation = element<HTMLTextAreaElement>("#metadataOperationJson").value.trim().length === 0;
|
||
const unavailable = value || profile === null || !profileStoreReady || missingOperation;
|
||
element<HTMLButtonElement>("#simulateMetadataButton").disabled = unavailable;
|
||
element<HTMLButtonElement>("#submitMetadataButton").disabled = unavailable || !profile?.sendEnabled;
|
||
element<HTMLButtonElement>("#prepareMetadataScenarioStepButton").disabled = value || profile === null || !profileStoreReady;
|
||
}
|
||
|
||
async function executeMetadata(submit: boolean): Promise<void> {
|
||
if (executionRunning) {
|
||
return;
|
||
}
|
||
const profileName = element<HTMLSelectElement>("#metadataProfileSelect").value;
|
||
if (profileName.length === 0 || !profileStoreReady) {
|
||
throw new Error("Sélectionnez et préparez un profil Devnet.");
|
||
}
|
||
const operationJson = element<HTMLTextAreaElement>("#metadataOperationJson").value;
|
||
const scenario = selectedScenario();
|
||
const stepIndex = Number.parseInt(element<HTMLSelectElement>("#metadataStepSelect").value, 10);
|
||
if (operationJson.trim().length === 0) {
|
||
throw new Error("Préparez l’étape courante avant l’exécution.");
|
||
}
|
||
if (scenario === null || preparedScenarioId !== scenario.id || preparedStepIndex !== stepIndex) {
|
||
throw new Error("Préparez de nouveau l’étape après toute modification du scénario ou de l’étape.");
|
||
}
|
||
const preflightReadsJson = element<HTMLTextAreaElement>("#metadataPreflightReadsJson").value;
|
||
const postconditionReadsJson = element<HTMLTextAreaElement>("#metadataPostconditionReadsJson").value;
|
||
parseJsonText(operationJson, "Intent Metaplex");
|
||
parseJsonText(preflightReadsJson, "Lectures de préflight");
|
||
parseJsonText(postconditionReadsJson, "Lectures de postcondition");
|
||
const operatorConfirmed = element<HTMLInputElement>("#metadataOperatorConfirmed").checked;
|
||
if (submit && !operatorConfirmed) {
|
||
throw new Error("La soumission exige la confirmation explicite de l’opérateur.");
|
||
}
|
||
const request: DemoExecutionMetadataRequest = {
|
||
profileName,
|
||
intentId: `desktop-metaplex-${Date.now()}`,
|
||
operationJson,
|
||
preflightReadsJson,
|
||
postconditionReadsJson,
|
||
submit,
|
||
operatorConfirmed,
|
||
materializeAfterConfirmation: element<HTMLInputElement>("#metadataMaterializeInput").checked,
|
||
};
|
||
setExecutionRunning(true);
|
||
const badge = element<HTMLElement>("#metadataStatusBadge");
|
||
badge.textContent = submit ? "Soumission" : "Simulation";
|
||
badge.className = "badge text-bg-warning";
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: submit ? "submit" : "simulation", message: submit ? "Démarrage du parcours Devnet complet." : "Démarrage de la simulation RPC exacte." });
|
||
try {
|
||
const summary = await invoke<DemoExecutionMetadataSummaryPayload>("demo_execution_metadata_execute", { request });
|
||
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", JSON.parse(summary.preflightJson)));
|
||
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", {
|
||
profileName: summary.profileName,
|
||
cluster: summary.cluster,
|
||
genesisHash: summary.genesisHash,
|
||
walletPublicKey: summary.walletPublicKey,
|
||
balanceLamports: summary.balanceLamports,
|
||
simulationSuccess: summary.simulationSuccess,
|
||
transactionSignature: summary.transactionSignature,
|
||
confirmationStatus: summary.confirmationStatus,
|
||
materializationRequested: summary.materializationRequested,
|
||
materializedSnapshotCount: summary.materializedSnapshotCount,
|
||
plan: JSON.parse(summary.planJson),
|
||
simulation: JSON.parse(summary.simulationJson),
|
||
}));
|
||
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", JSON.parse(summary.evidenceJson)));
|
||
badge.textContent = summary.transactionSignature === null ? "Simulé" : "Confirmé";
|
||
badge.className = summary.simulationSuccess ? "badge text-bg-success" : "badge text-bg-danger";
|
||
} finally {
|
||
setExecutionRunning(false);
|
||
}
|
||
}
|
||
|
||
async function loadProfiles(): Promise<void> {
|
||
const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_metadata_options");
|
||
profileOptions = options.profiles;
|
||
const select = element<HTMLSelectElement>("#metadataProfileSelect");
|
||
select.replaceChildren();
|
||
for (const profile of options.profiles) {
|
||
const option = document.createElement("option");
|
||
option.value = profile.name;
|
||
option.textContent = `${profile.name} — ${profile.walletAlias}`;
|
||
select.append(option);
|
||
}
|
||
const restored = window.localStorage.getItem(profileStorageKey);
|
||
if (restored !== null && Array.from(select.options).some(option => option.value === restored)) {
|
||
select.value = restored;
|
||
} else if (options.defaultProfileName !== null) {
|
||
select.value = options.defaultProfileName;
|
||
}
|
||
selectedProfileName = select.value.length === 0 ? null : select.value;
|
||
updateProfileHelp();
|
||
if (options.profiles.length === 0) {
|
||
const message = "Aucun profil Devnet compatible n’a été retourné par la configuration active.";
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: "error", stage: "profile_options", message });
|
||
element<HTMLElement>("#metadataProfileStatusBadge").textContent = "Aucun profil";
|
||
element<HTMLElement>("#metadataProfileStatusBadge").className = "badge text-bg-danger";
|
||
renderSelectedScenario();
|
||
return;
|
||
}
|
||
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "profile_options", message: `${options.profiles.length} profil(s) Devnet chargé(s).` });
|
||
renderSelectedScenario();
|
||
await prepareProfile();
|
||
}
|
||
|
||
async function prepareProfile(): Promise<void> {
|
||
const badge = element<HTMLElement>("#metadataProfileStatusBadge");
|
||
const profileName = element<HTMLSelectElement>("#metadataProfileSelect").value;
|
||
if (profileName.length === 0) {
|
||
badge.textContent = "Aucun profil";
|
||
badge.className = "badge text-bg-danger";
|
||
return;
|
||
}
|
||
profileStoreReady = false;
|
||
setExecutionRunning(executionRunning);
|
||
badge.textContent = "Préparation";
|
||
badge.className = "badge text-bg-warning";
|
||
try {
|
||
const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName });
|
||
selectedProfileName = profileName;
|
||
window.localStorage.setItem(profileStorageKey, profileName);
|
||
renderJsonViewer("#metadataProfileOutput", readiness);
|
||
profileStoreReady = true;
|
||
badge.textContent = "Base prête";
|
||
badge.className = "badge text-bg-success";
|
||
setExecutionRunning(executionRunning);
|
||
renderSelectedScenario();
|
||
} catch (caught) {
|
||
const message = caught instanceof Error ? caught.message : String(caught);
|
||
profileStoreReady = false;
|
||
badge.textContent = "Erreur";
|
||
badge.className = "badge text-bg-danger";
|
||
setExecutionRunning(executionRunning);
|
||
frontendError("kb-app-demo-desktop.frontend.execution_metadata", message);
|
||
}
|
||
}
|
||
|
||
async function loadScenarios(): Promise<void> {
|
||
const badge = element<HTMLElement>("#metadataStatusBadge");
|
||
badge.textContent = "Chargement";
|
||
badge.className = "badge text-bg-warning";
|
||
try {
|
||
scenarios = await invoke<DemoExecutionMetadataScenarioPayload[]>("demo_execution_metadata_scenarios");
|
||
fillSelect("#metadataSyntheticScenarioSelect", "synthetic");
|
||
fillSelect("#metadataDevnetScenarioSelect", "network_simulation");
|
||
const restored = window.localStorage.getItem(storageKey);
|
||
for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) {
|
||
const select = element<HTMLSelectElement>(selector);
|
||
if (restored !== null && Array.from(select.options).some(option => option.value === restored)) {
|
||
select.value = restored;
|
||
}
|
||
}
|
||
renderSelectedScenario();
|
||
badge.textContent = `${scenarios.length} contrats`;
|
||
badge.className = "badge text-bg-success";
|
||
} catch (caught) {
|
||
const message = caught instanceof Error ? caught.message : String(caught);
|
||
profileStoreReady = false;
|
||
badge.textContent = "Erreur";
|
||
badge.className = "badge text-bg-danger";
|
||
setExecutionRunning(executionRunning);
|
||
frontendError("kb-app-demo-desktop.frontend.execution_metadata", message);
|
||
}
|
||
}
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
installFrontendConsoleBridge("kb-app-demo-desktop.frontend.execution_metadata");
|
||
for (const selector of ["#metadataSyntheticScenarioSelect", "#metadataDevnetScenarioSelect"]) {
|
||
element<HTMLSelectElement>(selector).addEventListener("change", renderSelectedScenario);
|
||
}
|
||
for (const id of ["metaplexSyntheticCollapse", "metaplexDevnetCollapse"]) {
|
||
element<HTMLElement>(`#${id}`).addEventListener("shown.bs.collapse", renderSelectedScenario);
|
||
}
|
||
element<HTMLSelectElement>("#metadataProfileSelect").addEventListener("change", event => {
|
||
const target = event.currentTarget as HTMLSelectElement;
|
||
selectedProfileName = target.value.length === 0 ? null : target.value;
|
||
profileStoreReady = false;
|
||
updateProfileHelp();
|
||
setExecutionRunning(executionRunning);
|
||
renderSelectedScenario();
|
||
void prepareProfile();
|
||
});
|
||
element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile());
|
||
element<HTMLButtonElement>("#refreshMetadataScenariosButton").addEventListener("click", () => void loadScenarios());
|
||
element<HTMLSelectElement>("#metadataStepSelect").addEventListener("change", () => {
|
||
const scenario = selectedScenario();
|
||
if (scenario !== null) { renderScenarioSteps(scenario); }
|
||
});
|
||
element<HTMLButtonElement>("#previousMetadataStepButton").addEventListener("click", () => moveScenarioStep(-1));
|
||
element<HTMLButtonElement>("#nextMetadataStepButton").addEventListener("click", () => moveScenarioStep(1));
|
||
element<HTMLTextAreaElement>("#metadataOperationJson").addEventListener("input", () => {
|
||
preparedScenarioId = null;
|
||
preparedStepIndex = null;
|
||
setExecutionRunning(executionRunning);
|
||
});
|
||
element<HTMLButtonElement>("#prepareMetadataScenarioStepButton").addEventListener("click", () => { void prepareScenarioStep().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); });
|
||
element<HTMLTextAreaElement>("#metadataExecutionLogOutput").addEventListener("app-log-clear", () => { metadataLogLines.length = 0; });
|
||
void listen<MetadataProgressPayload>("demo-execution-solana-core-progress", event => appendMetadataLog(event.payload));
|
||
element<HTMLButtonElement>("#simulateMetadataButton").addEventListener("click", () => {
|
||
void executeMetadata(false).catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught)));
|
||
});
|
||
element<HTMLButtonElement>("#submitMetadataButton").addEventListener("click", () => {
|
||
void executeMetadata(true).catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught)));
|
||
});
|
||
setExecutionRunning(false);
|
||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", "Metadata execution window loaded");
|
||
void loadProfiles().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught)));
|
||
void loadScenarios();
|
||
});
|