Files
khadhroony-bot3/kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
2026-08-06 17:26:14 +02:00

572 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
// version: 12
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 { DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload.ts";
import type { DemoExecutionMetadataProgressPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataProgressPayload.ts";
import type { DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload.ts";
import type { DemoExecutionMetadataMetaplexTokenMetadataRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataRequest.ts";
import type { DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload.ts";
import type { DemoExecutionMetadataSolanaProgramCampaignRequest } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramCampaignRequest.ts";
import type { DemoExecutionMetadataSolanaProgramCampaignSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramCampaignSummaryPayload.ts";
import type { DemoExecutionMetadataSolanaProgramScenarioPayload } from "./bindings/kb_app_demo_desktop/demo_execution_metadata/DemoExecutionMetadataSolanaProgramScenarioPayload.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 solanaProgramMetadataStorageKey = "kb-app-demo-desktop.metadata.solana-program.selected-scenario";
const profileStorageKey = "kb-app-demo-desktop.metadata.selected-profile";
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
let scenarios: DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload[] = [];
let solanaProgramMetadataScenarios: DemoExecutionMetadataSolanaProgramScenarioPayload[] = [];
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 MetadataProgressInput = Omit<DemoExecutionMetadataProgressPayload, "signature"> & { signature?: string | null };
function appendMetadataLog(payload: MetadataProgressInput): 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;
}
const profileSelectSelectors = ["#metadataProfileSelect", "#solanaProgramMetadataProfileSelect"] as const;
const profileHelpSelectors = ["#metadataProfileHelp", "#solanaProgramMetadataProfileHelp"] as const;
const profileStatusSelectors = ["#metadataProfileStatusBadge", "#solanaProgramMetadataProfileStatusBadge"] as const;
const profileOutputSelectors = ["#metadataProfileOutput", "#solanaProgramMetadataProfileOutput"] as const;
function synchronizeProfileSelections(profileName: string): void {
for (const selector of profileSelectSelectors) {
const select = element<HTMLSelectElement>(selector);
if (Array.from(select.options).some(option => option.value === profileName)) {
select.value = profileName;
}
}
selectedProfileName = profileName.length === 0 ? null : profileName;
}
function updateProfileStatus(text: string, className: string): void {
for (const selector of profileStatusSelectors) {
const badge = element<HTMLElement>(selector);
badge.textContent = text;
badge.className = className;
}
}
function activeSelect(): HTMLSelectElement {
return document.querySelector("#metaplexDevnetCollapse.show") !== null ? element<HTMLSelectElement>("#metadataDevnetScenarioSelect") : element<HTMLSelectElement>("#metadataSyntheticScenarioSelect");
}
function selectedScenario(): DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload | 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: DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload): 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<DemoExecutionMetadataMetaplexTokenMetadataPreparedStepPayload>("demo_execution_metadata_metaplex_token_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 {
if (selectedProfileName === null) {
return null;
}
return profileOptions.find(profile => profile.name === selectedProfileName) ?? null;
}
function updateProfileHelp(): void {
const profile = selectedProfile();
const text = profile === null
? "Aucun profil Devnet compatible."
: `wallet=${profile.walletAlias}, spendMax=${profile.maxSpendLamports}, airdropMax=${profile.maxAirdropLamports}, send=${profile.sendEnabled ? "enabled" : "disabled"}`;
for (const selector of profileHelpSelectors) {
element<HTMLElement>(selector).textContent = text;
}
}
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;
const solanaProgramConfirmed = element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").checked;
element<HTMLButtonElement>("#executeSolanaProgramMetadataCampaignButton").disabled = value || profile === null || !profileStoreReady || !profile.sendEnabled || !solanaProgramConfirmed;
}
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 lexé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 lopérateur.");
}
const request: DemoExecutionMetadataMetaplexTokenMetadataRequest = {
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<DemoExecutionMetadataMetaplexTokenMetadataSummaryPayload>("demo_execution_metadata_metaplex_token_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);
}
}
function selectedSolanaProgramMetadataScenario(): DemoExecutionMetadataSolanaProgramScenarioPayload | null {
const id = element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect").value;
return solanaProgramMetadataScenarios.find(scenario => scenario.id === id) ?? null;
}
function renderActiveMetadataSelection(): void {
if (document.querySelector("#solanaProgramMetadataCollapse.show") !== null) {
renderSolanaProgramMetadataScenario();
return;
}
renderSelectedScenario();
}
function renderSolanaProgramMetadataScenario(): void {
const scenario = selectedSolanaProgramMetadataScenario();
if (scenario === null) {
return;
}
window.localStorage.setItem(solanaProgramMetadataStorageKey, scenario.id);
renderJsonViewer("#solanaProgramMetadataScenarioOutput", titled("Parcours Solana Program Metadata", {
scenarioId: scenario.id,
label: scenario.label,
fixtureAccount: scenario.fixtureAccount,
steps: scenario.steps,
}));
const allSteps = solanaProgramMetadataScenarios.flatMap(candidate => candidate.steps);
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.solana_program_metadata</code></dd><dt class="col-6">Program ID</dt><dd class="col-6"><code>ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S</code></dd><dt class="col-6">Parcours affiché</dt><dd class="col-6">${scenario.label}</dd><dt class="col-6">Étapes totales</dt><dd class="col-6">${allSteps.length}</dd><dt class="col-6">Transactions</dt><dd class="col-6">11 minimum</dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${selectedProfileName ?? "non chargé"}</code></dd>`;
renderJsonViewer("#metadataPreflightOutput", titled("Préflight et opérations", {
family: "metadata.solana_program_metadata",
journeys: solanaProgramMetadataScenarios,
operationCount: allSteps.length,
fixtureAccounts: ["buffer", "metadata"],
prefundingTransactionCount: 2,
}));
renderJsonViewer("#metadataSimulationOutput", titled("Simulation et confirmation", {
policy: "simulation_first",
submission: "required",
operatorConfirmationRequired: true,
stopOnFirstFailure: true,
networkExecutionPerformed: false,
}));
renderJsonViewer("#metadataPostconditionsOutput", titled("Postconditions et preuves", {
requiredEvidence: ["stateful_preflight", "rpc_simulation", "confirmed_signature", "stateful_postcondition", "materialized_snapshot_or_account_absence"],
currentStatus: "not_run",
networkEvidenceCollected: false,
}));
}
async function executeSolanaProgramMetadataCampaign(): Promise<void> {
if (executionRunning) {
return;
}
const profile = selectedProfile();
if (profile === null || selectedProfileName === null || !profileStoreReady) {
throw new Error("Sélectionnez et préparez un profil Devnet.");
}
if (!profile.sendEnabled) {
throw new Error("Le profil sélectionné nautorise pas les soumissions Devnet.");
}
const operatorConfirmed = element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").checked;
if (!operatorConfirmed) {
throw new Error("La campagne exige la confirmation explicite du préfinancement et des neuf mutations.");
}
const request: DemoExecutionMetadataSolanaProgramCampaignRequest = {
profileName: selectedProfileName,
operatorConfirmed,
};
setExecutionRunning(true);
const badge = element<HTMLElement>("#metadataStatusBadge");
badge.textContent = "Campagne ProgM6";
badge.className = "badge text-bg-warning";
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "spm_campaign", message: "Démarrage de la campagne complète : 2 préfinancements et 9 opérations." });
try {
const summary = await invoke<DemoExecutionMetadataSolanaProgramCampaignSummaryPayload>("demo_execution_metadata_solana_program_execute_campaign", { request });
renderJsonViewer("#metadataPreflightOutput", titled("Fixture et opérations préparées", JSON.parse(summary.fixtureJson)));
renderJsonViewer("#metadataSimulationOutput", titled("Synthèse de campagne", {
profileName: summary.profileName,
authority: summary.authority,
describedProgram: summary.describedProgram,
buffer: summary.buffer,
bufferPrefundSignature: summary.bufferPrefundSignature,
metadata: summary.metadata,
metadataPrefundSignature: summary.metadataPrefundSignature,
stepCount: summary.stepCount,
confirmedStepCount: summary.confirmedStepCount,
materializedSnapshotCount: summary.materializedSnapshotCount,
completed: summary.completed,
}));
renderJsonViewer("#metadataPostconditionsOutput", titled("Preuves ordonnées des neuf opérations", JSON.parse(summary.stepsJson)));
element<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>metadata.solana_program_metadata</code></dd><dt class="col-6">Profil</dt><dd class="col-6"><code>${summary.profileName}</code></dd><dt class="col-6">Buffer</dt><dd class="col-6"><code>${summary.buffer}</code></dd><dt class="col-6">Metadata</dt><dd class="col-6"><code>${summary.metadata}</code></dd><dt class="col-6">Confirmées</dt><dd class="col-6">${summary.confirmedStepCount}/${summary.stepCount}</dd><dt class="col-6">Snapshots</dt><dd class="col-6">${summary.materializedSnapshotCount}</dd>`;
badge.textContent = summary.completed ? "Campagne confirmée" : "Campagne incomplète";
badge.className = summary.completed ? "badge text-bg-success" : "badge text-bg-danger";
appendMetadataLog({ timestamp: new Date().toISOString(), level: summary.completed ? "info" : "error", stage: "spm_campaign", message: `Campagne terminée : confirmed=${summary.confirmedStepCount}/${summary.stepCount}, snapshots=${summary.materializedSnapshotCount}` });
} finally {
setExecutionRunning(false);
}
}
async function loadProfiles(): Promise<void> {
const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_metadata_options");
profileOptions = options.profiles;
for (const selector of profileSelectSelectors) {
const select = element<HTMLSelectElement>(selector);
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);
const availableNames = options.profiles.map(profile => profile.name);
const selected = restored !== null && availableNames.includes(restored)
? restored
: options.defaultProfileName ?? availableNames[0] ?? "";
synchronizeProfileSelections(selected);
updateProfileHelp();
if (options.profiles.length === 0) {
const message = "Aucun profil Devnet compatible na été retourné par la configuration active.";
appendMetadataLog({ timestamp: new Date().toISOString(), level: "error", stage: "profile_options", message });
updateProfileStatus("Aucun profil", "badge text-bg-danger");
renderActiveMetadataSelection();
return;
}
appendMetadataLog({ timestamp: new Date().toISOString(), level: "info", stage: "profile_options", message: `${options.profiles.length} profil(s) Devnet chargé(s).` });
renderActiveMetadataSelection();
await prepareProfile();
}
async function prepareProfile(): Promise<void> {
const profileName = selectedProfileName ?? "";
if (profileName.length === 0) {
updateProfileStatus("Aucun profil", "badge text-bg-danger");
return;
}
profileStoreReady = false;
setExecutionRunning(executionRunning);
updateProfileStatus("Préparation", "badge text-bg-warning");
try {
const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName });
synchronizeProfileSelections(profileName);
window.localStorage.setItem(profileStorageKey, profileName);
for (const selector of profileOutputSelectors) {
renderJsonViewer(selector, readiness);
}
profileStoreReady = true;
updateProfileStatus("Base prête", "badge text-bg-success");
setExecutionRunning(executionRunning);
renderActiveMetadataSelection();
} catch (caught) {
const message = caught instanceof Error ? caught.message : String(caught);
profileStoreReady = false;
updateProfileStatus("Erreur", "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<DemoExecutionMetadataMetaplexTokenMetadataScenarioPayload[]>("demo_execution_metadata_metaplex_token_metadata_scenarios");
solanaProgramMetadataScenarios = await invoke<DemoExecutionMetadataSolanaProgramScenarioPayload[]>("demo_execution_metadata_solana_program_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;
}
}
const solanaSelect = element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect");
solanaSelect.replaceChildren();
for (const scenario of solanaProgramMetadataScenarios) {
const option = document.createElement("option");
option.value = scenario.id;
option.textContent = `${scenario.fixtureAccount}${scenario.label}`;
solanaSelect.append(option);
}
const restoredSolana = window.localStorage.getItem(solanaProgramMetadataStorageKey);
if (restoredSolana !== null && Array.from(solanaSelect.options).some(option => option.value === restoredSolana)) {
solanaSelect.value = restoredSolana;
}
renderActiveMetadataSelection();
badge.textContent = `${scenarios.length + solanaProgramMetadataScenarios.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<HTMLElement>("#solanaProgramMetadataCollapse").addEventListener("shown.bs.collapse", renderSolanaProgramMetadataScenario);
element<HTMLSelectElement>("#solanaProgramMetadataScenarioSelect").addEventListener("change", renderSolanaProgramMetadataScenario);
for (const selector of profileSelectSelectors) {
element<HTMLSelectElement>(selector).addEventListener("change", event => {
const target = event.currentTarget as HTMLSelectElement;
synchronizeProfileSelections(target.value);
profileStoreReady = false;
updateProfileHelp();
setExecutionRunning(executionRunning);
renderActiveMetadataSelection();
void prepareProfile();
});
}
element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile());
element<HTMLButtonElement>("#prepareSolanaProgramMetadataProfileButton").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<DemoExecutionMetadataProgressPayload>("demo-execution-metadata-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)));
});
element<HTMLInputElement>("#solanaProgramMetadataOperatorConfirmed").addEventListener("change", () => setExecutionRunning(executionRunning));
element<HTMLButtonElement>("#executeSolanaProgramMetadataCampaignButton").addEventListener("click", () => {
void executeSolanaProgramMetadataCampaign().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();
});