// file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts // version: 3 import "bootstrap"; import "simplebar"; import { invoke } from "@tauri-apps/api/core"; import { frontendError } from "./frontend_log.ts"; import { 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 { 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"; const storageKey = "kb-app-demo-desktop.metadata.selected-scenario"; const profileStorageKey = "kb-app-demo-desktop.metadata.selected-profile"; let scenarios: DemoExecutionMetadataScenarioPayload[] = []; let selectedProfileName: string | null = null; function element(selector: string): T { const value = document.querySelector(selector); if (value === null) { throw new Error(`Élément introuvable : ${selector}`); } return value; } function activeSelect(): HTMLSelectElement { return document.querySelector("#metaplexDevnetCollapse.show") !== null ? element("#metadataDevnetScenarioSelect") : element("#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(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.id}`; select.append(option); } } function renderSelectedScenario(): void { const scenario = selectedScenario(); if (scenario === null) { return; } window.localStorage.setItem(storageKey, scenario.id); const networkMode = scenario.mode !== "synthetic"; element("#metadataRequirements").innerHTML = `
Famille
${scenario.assetFamily}
Mode
${scenario.mode}
Profil
${networkMode ? selectedProfileName ?? "non chargé" : "non applicable"}
Collection
${scenario.requiresCollection ? "requise" : "non requise"}
Rule set
${scenario.requiresProgrammableRules ? "requis" : "non requis"}
Validation
${scenario.validationStatus}
`; renderJsonViewer("#metadataPreflightOutput", { scenarioId: scenario.id, operationCodes: scenario.operationCodes, checks: ["program_id", "owner", "pda_and_seeds", "required_accounts", "authorities", "stateful_correlations"], networkExecutionPerformed: false, }); renderJsonViewer("#metadataSimulationOutput", { mode: scenario.mode, policy: "simulation_first", networkExecutionPerformed: false, automaticSubmission: false, operatorConfirmationRequired: true, expectedState: scenario.validationStatus, note: networkMode ? "Le contrat Devnet est sélectionné, mais aucune transaction Metaplex n'est simulée par cette commande de chargement." : "Scénario synthétique déterministe.", }); renderJsonViewer("#metadataPostconditionsOutput", { required: scenario.requiresPostcondition, evidenceKinds: scenario.requiredEvidence, networkEvidenceCollected: false, rule: "Aucune validation réseau n'est déclarée sans simulation RPC réelle, signature éventuelle, slot et postconditions.", }); } async function loadProfiles(): Promise { const options = await invoke("demo_execution_solana_core_options"); const select = element("#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; renderSelectedScenario(); } async function prepareProfile(): Promise { const badge = element("#metadataProfileStatusBadge"); const profileName = element("#metadataProfileSelect").value; if (profileName.length === 0) { badge.textContent = "Aucun profil"; badge.className = "badge text-bg-danger"; return; } badge.textContent = "Préparation"; badge.className = "badge text-bg-warning"; try { const readiness = await invoke("demo_execution_devnet_prepare_profile", { profileName }); selectedProfileName = profileName; window.localStorage.setItem(profileStorageKey, profileName); renderJsonViewer("#metadataProfileOutput", readiness); badge.textContent = "Base prête"; badge.className = "badge text-bg-success"; renderSelectedScenario(); } catch (caught) { const message = caught instanceof Error ? caught.message : String(caught); badge.textContent = "Erreur"; badge.className = "badge text-bg-danger"; frontendError("kb-app-demo-desktop.frontend.execution_metadata", message); } } async function loadScenarios(): Promise { const badge = element("#metadataStatusBadge"); badge.textContent = "Chargement"; badge.className = "badge text-bg-warning"; try { scenarios = await invoke("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(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); badge.textContent = "Erreur"; badge.className = "badge text-bg-danger"; 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(selector).addEventListener("change", renderSelectedScenario); } for (const id of ["metaplexSyntheticCollapse", "metaplexDevnetCollapse"]) { element(`#${id}`).addEventListener("shown.bs.collapse", renderSelectedScenario); } element("#metadataProfileSelect").addEventListener("change", event => { const target = event.currentTarget as HTMLSelectElement; selectedProfileName = target.value.length === 0 ? null : target.value; renderSelectedScenario(); }); element("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile()); element("#refreshMetadataScenariosButton").addEventListener("click", () => void loadScenarios()); void loadProfiles().catch(caught => frontendError("kb-app-demo-desktop.frontend.execution_metadata", caught instanceof Error ? caught.message : String(caught))); void loadScenarios(); });