v0.5.2-pre.006-fix-010

This commit is contained in:
2026-08-11 12:03:35 +02:00
parent 1ee1d0297d
commit 56572cec40
67 changed files with 2484 additions and 539 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/frontend/demo_wallet.html -->
<!-- version: 4 -->
<!-- version: 5 -->
<!DOCTYPE html>
<html lang="fr">
@@ -49,6 +49,33 @@
<dt class="col-sm-3">Wallets trouvés</dt>
<dd class="col-sm-9"><code id="walletInventoryCount">0</code></dd>
</dl>
<div class="border rounded p-3 mb-3">
<h2 class="h6 mb-2">Wallet d'exécution des démos Devnet</h2>
<p class="small text-body-secondary mb-3">La sélection est propre à cette session desktop. Elle ne modifie pas <code>wallet.config.json</code> et remplace <code>wallet_alias</code> uniquement pour les démos du profil Devnet choisi.</p>
<div class="row g-3 align-items-end">
<div class="col-12 col-lg-4">
<label for="walletExecutionProfileSelect" class="form-label">Profil Devnet</label>
<select id="walletExecutionProfileSelect" class="form-select"></select>
</div>
<div class="col-12 col-lg-5">
<label for="walletExecutionAliasSelect" class="form-label">Wallet d'exécution</label>
<select id="walletExecutionAliasSelect" class="form-select"></select>
</div>
<div class="col-12 col-lg-3">
<button id="applyWalletExecutionSelectionButton" class="btn btn-outline-primary w-100" type="button">Appliquer</button>
</div>
</div>
<dl class="row small mb-0 mt-3">
<dt class="col-sm-4">Configuration</dt>
<dd class="col-sm-8"><code id="walletExecutionConfiguredAlias"></code></dd>
<dt class="col-sm-4">Override session</dt>
<dd class="col-sm-8"><code id="walletExecutionRuntimeAlias"></code></dd>
<dt class="col-sm-4">Effectif</dt>
<dd class="col-sm-8"><code id="walletExecutionEffectiveAlias">temporaire</code></dd>
<dt class="col-sm-4">Statut</dt>
<dd class="col-sm-8"><code id="walletExecutionSelectionStatus">En attente</code></dd>
</dl>
</div>
<div class="table-responsive app-table-full-width">
<table class="table table-sm align-middle mb-0" id="walletInventoryTable">
<thead>
@@ -56,7 +83,7 @@
<th>Alias</th>
<th>Pubkey déclarée</th>
<th>Format</th>
<th>Sélection</th>
<th>Exécution</th>
<th>Action</th>
</tr>
</thead>
@@ -128,13 +155,16 @@
<div class="card shadow-sm border-0 h-100">
<div class="card-body">
<h2 class="h5 card-title mb-2">Exporter un wallet natif</h2>
<p class="text-body-secondary">L'export exige le même secret backend que la création et refuse d'écraser un fichier existant.</p>
<p class="text-body-secondary">L'export exige le même secret backend que la création, refuse d'écraser un fichier existant et écrit uniquement dans <code>./data/wallets/</code>.</p>
<label for="walletExportAliasSelect" class="form-label">Wallet</label>
<select id="walletExportAliasSelect" class="form-select"></select>
<label for="walletExportFormatSelect" class="form-label mt-3">Format d'export</label>
<select id="walletExportFormatSelect" class="form-select"></select>
<label for="walletExportDestinationPathInput" class="form-label mt-3">Chemin destination</label>
<input id="walletExportDestinationPathInput" class="form-control font-monospace" type="text" autocomplete="off" placeholder="/chemin/vers/export.json">
<label for="walletExportFileNameInput" class="form-label mt-3">Nom du fichier</label>
<div class="input-group">
<span id="walletExportDirectory" class="input-group-text font-monospace">data/wallets/</span>
<input id="walletExportFileNameInput" class="form-control font-monospace" type="text" autocomplete="off" maxlength="128" placeholder="operator.json">
</div>
<div class="d-flex flex-wrap gap-2 mt-3">
<button id="exportWalletTransferButton" class="btn btn-primary" type="button">Exporter</button>
</div>
@@ -298,4 +328,4 @@
<script type="module" src="ts/demo_wallet.ts" defer></script>
</body>
</html>
</html>

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/frontend/ts/demo_wallet.ts
// version: 4
// version: 5
import * as bootstrap from "bootstrap";
import "simplebar";
@@ -10,6 +10,8 @@ import { renderJsonViewer } from "./json_viewer.ts";
import type { DemoWalletBalancePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletBalancePayload.ts";
import type { DemoWalletCreateRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletCreateRequest.ts";
import type { DemoWalletExportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExportRequest.ts";
import type { DemoWalletExecutionProfilePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionProfilePayload.ts";
import type { DemoWalletExecutionSelectionRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletExecutionSelectionRequest.ts";
import type { DemoWalletIdentityPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletIdentityPayload.ts";
import type { DemoWalletImportRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletImportRequest.ts";
import type { DemoWalletInventoryPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletInventoryPayload.ts";
@@ -96,6 +98,102 @@ function renderOnchainProfiles(profiles: ReadonlyArray<DemoWalletOnchainProfileP
updateOnchainClusterLabel();
}
function selectedExecutionProfilePayload(): DemoWalletExecutionProfilePayload | null {
const profile = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.value.trim() ?? "";
if (!walletInventory || profile.length === 0) {
return null;
}
return walletInventory.execution_profiles.find(candidate => candidate.name === profile) ?? null;
}
function renderExecutionProfiles(profiles: ReadonlyArray<DemoWalletExecutionProfilePayload>): void {
const select = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect");
if (!select) {
return;
}
const previous = select.value;
select.replaceChildren();
for (const profile of profiles) {
const option = document.createElement("option");
option.value = profile.name;
option.textContent = profile.name;
select.appendChild(option);
}
if (profiles.some(profile => profile.name === previous)) {
select.value = previous;
} else if (profiles.length > 0) {
select.value = profiles[0].name;
}
}
function renderExecutionWalletAliases(wallets: ReadonlyArray<DemoWalletIdentityPayload>): void {
const select = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect");
if (!select) {
return;
}
select.replaceChildren();
const configuredOption = document.createElement("option");
configuredOption.value = "";
configuredOption.textContent = "Utiliser la configuration du profil";
select.appendChild(configuredOption);
for (const wallet of wallets) {
const option = document.createElement("option");
option.value = wallet.alias;
option.textContent = `${wallet.alias}${wallet.public_key}`;
select.appendChild(option);
}
}
function updateExecutionSelectionDisplay(): void {
const profile = selectedExecutionProfilePayload();
const aliasSelect = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect");
if (!profile) {
setText("#walletExecutionConfiguredAlias", "—");
setText("#walletExecutionRuntimeAlias", "—");
setText("#walletExecutionEffectiveAlias", "temporaire");
if (aliasSelect) {
aliasSelect.value = "";
}
return;
}
setText("#walletExecutionConfiguredAlias", profile.configured_alias ?? "—");
setText("#walletExecutionRuntimeAlias", profile.runtime_alias ?? "—");
setText("#walletExecutionEffectiveAlias", profile.effective_alias ?? "temporaire");
if (aliasSelect) {
const requested = profile.runtime_alias ?? "";
if (Array.from(aliasSelect.options).some(option => option.value === requested)) {
aliasSelect.value = requested;
} else {
aliasSelect.value = "";
}
}
}
async function applyExecutionWalletSelection(): Promise<void> {
const profile = document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.value.trim() ?? "";
const aliasText = document.querySelector<HTMLSelectElement>("#walletExecutionAliasSelect")?.value.trim() ?? "";
if (profile.length === 0) {
setText("#walletExecutionSelectionStatus", "Profil Devnet requis");
return;
}
const request: DemoWalletExecutionSelectionRequest = {
profile,
alias: aliasText.length === 0 ? null : aliasText,
};
setText("#walletExecutionSelectionStatus", "Validation...");
try {
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_select_execution_wallet", { request });
applyWalletInventoryPayload(payload);
const effective = payload.execution_profiles.find(candidate => candidate.name === profile)?.effective_alias ?? null;
setText("#walletExecutionSelectionStatus", effective ? `Persistant : ${effective}` : "Configuration du profil");
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `execution wallet selection updated for ${profile}`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
setText("#walletExecutionSelectionStatus", `Erreur : ${message}`);
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Execution wallet selection failed: ${message}`);
}
}
async function executeWalletRpc(profile: string, method: string, params: unknown[]): Promise<DemoWalletRpcExecutionPayload> {
const request: DemoWalletRpcRequest = {
profile,
@@ -159,6 +257,28 @@ function selectedTransferFormat(selector: string): string {
return document.querySelector<HTMLSelectElement>(selector)?.value.trim() ?? "";
}
function selectedExportFormatPayload(): DemoWalletTransferFormatPayload | null {
const code = selectedTransferFormat("#walletExportFormatSelect");
if (!walletInventory || code.length === 0) {
return null;
}
return walletInventory.transfer_formats.find(format => format.code === code) ?? null;
}
function updateExportFileName(force: boolean): void {
const input = document.querySelector<HTMLInputElement>("#walletExportFileNameInput");
const alias = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.value.trim() ?? "";
const format = selectedExportFormatPayload();
if (!input || alias.length === 0 || !format) {
return;
}
const generated = `${alias}.${format.default_extension}`;
if (force || input.value.trim().length === 0 || input.dataset.autoGenerated === "true") {
input.value = generated;
input.dataset.autoGenerated = "true";
}
}
function setTransferPublicKey(publicKey: string): void {
inspectedTransferPublicKey = publicKey;
setText("#walletTransferPublicKey", publicKey.length > 0 ? publicKey : "—");
@@ -196,7 +316,8 @@ function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): vo
publicKeyCell.textContent = wallet.public_key;
publicKeyCell.className = "font-monospace text-break";
formatCell.textContent = `v${wallet.format_version}`;
selectedCell.textContent = wallet.selected ? "Oui" : "Non";
const executionSelected = selectedExecutionProfilePayload()?.effective_alias === wallet.alias;
selectedCell.textContent = executionSelected ? "Oui" : "Non";
exploreButton.type = "button";
exploreButton.className = "btn btn-sm btn-outline-primary";
exploreButton.textContent = "Explorer";
@@ -208,7 +329,7 @@ function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): vo
document.querySelector<HTMLElement>("#walletAddressInput")?.scrollIntoView({ behavior: "smooth", block: "center" });
});
actionCell.appendChild(exploreButton);
if (wallet.selected) {
if (executionSelected) {
row.classList.add("table-primary");
}
row.append(aliasCell, publicKeyCell, formatCell, selectedCell, actionCell);
@@ -216,21 +337,30 @@ function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): vo
}
}
function applyWalletInventoryPayload(payload: DemoWalletInventoryPayload): void {
walletInventory = payload;
setText("#walletProfile", payload.profile);
setText("#walletCluster", payload.cluster);
renderOnchainProfiles(payload.onchain_profiles, payload.profile);
renderExecutionProfiles(payload.execution_profiles);
renderTransferFormats(payload.transfer_formats);
renderExportWalletAliases(payload.wallets);
renderExecutionWalletAliases(payload.wallets);
setText("#walletCreateSecretStatus", payload.create_password_configured ? "configuré" : "absent");
setText("#walletSelectedAlias", payload.selected_alias ?? "—");
setText("#walletInventoryCount", String(payload.wallets.length));
setText("#walletExportDirectory", `${payload.export_directory}/`);
setText("#walletInventoryStatus", "OK");
renderWalletRows(payload.wallets);
updateExecutionSelectionDisplay();
updateExportFileName(false);
}
async function refreshWalletInventory(): Promise<void> {
setText("#walletInventoryStatus", "Chargement...");
try {
const payload = await invoke<DemoWalletInventoryPayload>("demo_wallet_inventory");
walletInventory = payload;
setText("#walletProfile", payload.profile);
setText("#walletCluster", payload.cluster);
renderOnchainProfiles(payload.onchain_profiles, payload.profile);
renderTransferFormats(payload.transfer_formats);
renderExportWalletAliases(payload.wallets);
setText("#walletCreateSecretStatus", payload.create_password_configured ? "configuré" : "absent");
setText("#walletSelectedAlias", payload.selected_alias ?? "—");
setText("#walletInventoryCount", String(payload.wallets.length));
setText("#walletInventoryStatus", "OK");
renderWalletRows(payload.wallets);
applyWalletInventoryPayload(payload);
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", "wallet inventory refreshed");
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
@@ -238,6 +368,7 @@ async function refreshWalletInventory(): Promise<void> {
setText("#walletInventoryCount", "0");
renderWalletRows([]);
renderExportWalletAliases([]);
renderExecutionWalletAliases([]);
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Wallet inventory loading failed: ${message}`);
}
}
@@ -324,16 +455,16 @@ async function importTransferFile(): Promise<void> {
async function exportWallet(): Promise<void> {
const alias = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.value.trim() ?? "";
const format = selectedTransferFormat("#walletExportFormatSelect");
const destinationPath = inputValue("#walletExportDestinationPathInput");
if (alias.length === 0 || format.length === 0 || destinationPath.length === 0) {
setText("#walletExportStatus", "Wallet, format et destination requis");
const fileName = inputValue("#walletExportFileNameInput");
if (alias.length === 0 || format.length === 0 || fileName.length === 0) {
setText("#walletExportStatus", "Wallet, format et nom de fichier requis");
return;
}
const request: DemoWalletExportRequest = { alias, destination_path: destinationPath, format };
const request: DemoWalletExportRequest = { alias, file_name: fileName, format };
setText("#walletExportStatus", "Export...");
try {
await invoke<void>("demo_wallet_export_file", { request });
setText("#walletExportStatus", `Exporté : ${alias}`);
const exportedPath = await invoke<string>("demo_wallet_export_file", { request });
setText("#walletExportStatus", `Exporté : ${exportedPath}`);
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `native wallet exported: ${alias}`);
} catch (caughtError) {
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
@@ -647,6 +778,16 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector<HTMLButtonElement>("#refreshWalletInventoryButton")?.addEventListener("click", () => {
void refreshWalletInventory();
});
document.querySelector<HTMLButtonElement>("#applyWalletExecutionSelectionButton")?.addEventListener("click", () => {
void applyExecutionWalletSelection();
});
document.querySelector<HTMLSelectElement>("#walletExecutionProfileSelect")?.addEventListener("change", () => {
updateExecutionSelectionDisplay();
if (walletInventory) {
renderWalletRows(walletInventory.wallets);
}
setText("#walletExecutionSelectionStatus", "En attente");
});
document.querySelector<HTMLButtonElement>("#createWalletButton")?.addEventListener("click", () => {
void createWallet();
});
@@ -668,6 +809,16 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector<HTMLButtonElement>("#exportWalletTransferButton")?.addEventListener("click", () => {
void exportWallet();
});
document.querySelector<HTMLSelectElement>("#walletExportAliasSelect")?.addEventListener("change", () => {
updateExportFileName(false);
});
document.querySelector<HTMLSelectElement>("#walletExportFormatSelect")?.addEventListener("change", () => {
updateExportFileName(false);
});
document.querySelector<HTMLInputElement>("#walletExportFileNameInput")?.addEventListener("input", event => {
const input = event.currentTarget as HTMLInputElement;
input.dataset.autoGenerated = "false";
});
document.querySelector<HTMLInputElement>("#walletImportSourcePathInput")?.addEventListener("input", () => {
setTransferPublicKey("");
setText("#walletTransferStatus", "En attente");