v0.5.2-pre.006-fix-010
This commit is contained in:
@@ -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électionné</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>
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user