v0.4.7-pre.009
This commit is contained in:
166
kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
Normal file
166
kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
// 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<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.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<HTMLElement>("#metadataRequirements").innerHTML = `<dt class="col-6">Famille</dt><dd class="col-6"><code>${scenario.assetFamily}</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", {
|
||||
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<void> {
|
||||
const options = await invoke<DemoExecutionSolanaCoreOptionsPayload>("demo_execution_solana_core_options");
|
||||
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;
|
||||
renderSelectedScenario();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
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<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);
|
||||
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<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;
|
||||
renderSelectedScenario();
|
||||
});
|
||||
element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile());
|
||||
element<HTMLButtonElement>("#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();
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/main.ts
|
||||
// version: 4
|
||||
// version: 6
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import MarkdownIt from "markdown-it";
|
||||
@@ -80,6 +80,7 @@ const pipelineDemoLinks: ReadonlyArray<readonly [string, string]> = [
|
||||
["openDemoDecodeReplayLink", "open_demo_decode_replay_window"],
|
||||
["openDemoExecutionSolanaCoreLink", "open_demo_execution_solana_core_window"],
|
||||
["openDemoExecutionSplLink", "open_demo_execution_spl_window"],
|
||||
["openDemoExecutionMetadataLink", "open_demo_execution_metadata_window"],
|
||||
];
|
||||
for (const [elementId, command] of pipelineDemoLinks) {
|
||||
const link = document.querySelector<HTMLAnchorElement>(`#${elementId}`);
|
||||
|
||||
Reference in New Issue
Block a user