v0.5.2-pre.006-fix-004
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-app-demo-desktop/frontend/demo_wallet.html -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -90,6 +90,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-7">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 card-title mb-2">Importer ou inspecter une keypair externe</h2>
|
||||
<p class="text-body-secondary">Le fichier source reste inchangé. L'inspection valide la keypair et ne retourne que sa pubkey ; l'import crée ensuite un nouveau <code>.kswallet</code> dans le store du profil actif.</p>
|
||||
<label for="walletImportSourcePathInput" class="form-label">Chemin source</label>
|
||||
<input id="walletImportSourcePathInput" class="form-control font-monospace" type="text" autocomplete="off" placeholder="/chemin/vers/local-devnet-operator.json">
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="walletImportFormatSelect" class="form-label">Format source</label>
|
||||
<select id="walletImportFormatSelect" class="form-select"></select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="walletImportAliasInput" class="form-label">Alias destination</label>
|
||||
<input id="walletImportAliasInput" class="form-control font-monospace" type="text" autocomplete="off" maxlength="64" placeholder="local-devnet-operator">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<button id="inspectWalletTransferButton" class="btn btn-outline-primary" type="button">Inspecter la keypair</button>
|
||||
<button id="importWalletTransferButton" class="btn btn-primary" type="button">Importer en .kswallet</button>
|
||||
<button id="exploreWalletTransferButton" class="btn btn-outline-secondary" type="button" disabled>Explorer la pubkey</button>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<span class="fw-semibold">Pubkey détectée :</span>
|
||||
<code id="walletTransferPublicKey" class="ms-1 text-break">—</code>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<span class="fw-semibold">Statut :</span>
|
||||
<code id="walletTransferStatus" class="ms-1">En attente</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-5">
|
||||
<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>
|
||||
<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">
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<button id="exportWalletTransferButton" class="btn btn-primary" type="button">Exporter</button>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<span class="fw-semibold">Statut :</span>
|
||||
<code id="walletExportStatus" class="ms-1">En attente</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
@@ -104,7 +160,6 @@
|
||||
<div class="col-12 col-lg-3">
|
||||
<label for="walletOnchainProfileSelect" class="form-label">Profil RPC</label>
|
||||
<select id="walletOnchainProfileSelect" class="form-select"></select>
|
||||
<div class="small text-body-secondary mt-1">Cluster : <code id="walletOnchainCluster">—</code></div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-7">
|
||||
<label for="walletAddressInput" class="form-label">Adresse publique</label>
|
||||
@@ -115,6 +170,7 @@
|
||||
<input id="walletSignatureLimitInput" class="form-control" type="number" min="1" max="100" value="20">
|
||||
</div>
|
||||
</div>
|
||||
<div class="small text-body-secondary mt-2">Profil sélectionné : <code id="walletOnchainSelectedProfile">—</code> · Cluster : <code id="walletOnchainCluster">—</code></div>
|
||||
<div class="row g-3 mt-2">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="border rounded p-3 h-100">
|
||||
@@ -242,4 +298,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: 3
|
||||
// version: 4
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -9,11 +9,16 @@ import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./fr
|
||||
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 { 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";
|
||||
import type { DemoWalletOnchainProfilePayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletOnchainProfilePayload.ts";
|
||||
import type { DemoWalletRpcExecutionPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletRpcExecutionPayload.ts";
|
||||
import type { DemoWalletRpcRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletRpcRequest.ts";
|
||||
import type { DemoWalletTransferFormatPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletTransferFormatPayload.ts";
|
||||
import type { DemoWalletTransferInspectionPayload } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletTransferInspectionPayload.ts";
|
||||
import type { DemoWalletTransferInspectionRequest } from "./bindings/kb_app_demo_desktop/demo_wallet/DemoWalletTransferInspectionRequest.ts";
|
||||
|
||||
(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
|
||||
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;
|
||||
@@ -37,6 +42,7 @@ interface ParsedSignatureInfo {
|
||||
}
|
||||
|
||||
let walletInventory: DemoWalletInventoryPayload | null = null;
|
||||
let inspectedTransferPublicKey = "";
|
||||
|
||||
function setText(selector: string, value: string): void {
|
||||
const element = document.querySelector<HTMLElement>(selector);
|
||||
@@ -63,6 +69,7 @@ function selectedOnchainProfilePayload(): DemoWalletOnchainProfilePayload | null
|
||||
|
||||
function updateOnchainClusterLabel(): void {
|
||||
const selected = selectedOnchainProfilePayload();
|
||||
setText("#walletOnchainSelectedProfile", selected?.name ?? "—");
|
||||
setText("#walletOnchainCluster", selected?.cluster ?? "—");
|
||||
}
|
||||
|
||||
@@ -102,6 +109,65 @@ function parseJsonResponse(response: DemoWalletRpcExecutionPayload): unknown {
|
||||
return JSON.parse(response.response_json) as unknown;
|
||||
}
|
||||
|
||||
function renderTransferFormats(formats: ReadonlyArray<DemoWalletTransferFormatPayload>): void {
|
||||
for (const selector of ["#walletImportFormatSelect", "#walletExportFormatSelect"]) {
|
||||
const select = document.querySelector<HTMLSelectElement>(selector);
|
||||
if (!select) {
|
||||
continue;
|
||||
}
|
||||
const previous = select.value;
|
||||
select.replaceChildren();
|
||||
for (const format of formats) {
|
||||
const option = document.createElement("option");
|
||||
option.value = format.code;
|
||||
option.textContent = `${format.label} (.${format.default_extension})`;
|
||||
option.dataset.defaultExtension = format.default_extension;
|
||||
select.appendChild(option);
|
||||
}
|
||||
if (formats.some(format => format.code === previous)) {
|
||||
select.value = previous;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderExportWalletAliases(wallets: ReadonlyArray<DemoWalletIdentityPayload>): void {
|
||||
const select = document.querySelector<HTMLSelectElement>("#walletExportAliasSelect");
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
const previous = select.value;
|
||||
select.replaceChildren();
|
||||
if (wallets.length === 0) {
|
||||
const option = document.createElement("option");
|
||||
option.value = "";
|
||||
option.textContent = "Aucun wallet natif";
|
||||
select.appendChild(option);
|
||||
return;
|
||||
}
|
||||
for (const wallet of wallets) {
|
||||
const option = document.createElement("option");
|
||||
option.value = wallet.alias;
|
||||
option.textContent = `${wallet.alias} — ${wallet.public_key}`;
|
||||
select.appendChild(option);
|
||||
}
|
||||
if (wallets.some(wallet => wallet.alias === previous)) {
|
||||
select.value = previous;
|
||||
}
|
||||
}
|
||||
|
||||
function selectedTransferFormat(selector: string): string {
|
||||
return document.querySelector<HTMLSelectElement>(selector)?.value.trim() ?? "";
|
||||
}
|
||||
|
||||
function setTransferPublicKey(publicKey: string): void {
|
||||
inspectedTransferPublicKey = publicKey;
|
||||
setText("#walletTransferPublicKey", publicKey.length > 0 ? publicKey : "—");
|
||||
const exploreButton = document.querySelector<HTMLButtonElement>("#exploreWalletTransferButton");
|
||||
if (exploreButton) {
|
||||
exploreButton.disabled = publicKey.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
function renderWalletRows(wallets: ReadonlyArray<DemoWalletIdentityPayload>): void {
|
||||
const body = document.querySelector<HTMLTableSectionElement>("#walletInventoryTableBody");
|
||||
if (!body) {
|
||||
@@ -158,6 +224,8 @@ async function refreshWalletInventory(): Promise<void> {
|
||||
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));
|
||||
@@ -169,6 +237,7 @@ async function refreshWalletInventory(): Promise<void> {
|
||||
setText("#walletInventoryStatus", `Erreur : ${message}`);
|
||||
setText("#walletInventoryCount", "0");
|
||||
renderWalletRows([]);
|
||||
renderExportWalletAliases([]);
|
||||
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Wallet inventory loading failed: ${message}`);
|
||||
}
|
||||
}
|
||||
@@ -201,6 +270,89 @@ async function createWallet(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function inspectTransferFile(): Promise<void> {
|
||||
const sourcePath = inputValue("#walletImportSourcePathInput");
|
||||
const format = selectedTransferFormat("#walletImportFormatSelect");
|
||||
if (sourcePath.length === 0 || format.length === 0) {
|
||||
setText("#walletTransferStatus", "Chemin source et format requis");
|
||||
setTransferPublicKey("");
|
||||
return;
|
||||
}
|
||||
const request: DemoWalletTransferInspectionRequest = { source_path: sourcePath, format };
|
||||
setText("#walletTransferStatus", "Inspection...");
|
||||
setTransferPublicKey("");
|
||||
try {
|
||||
const payload = await invoke<DemoWalletTransferInspectionPayload>("demo_wallet_inspect_transfer_file", { request });
|
||||
setTransferPublicKey(payload.public_key);
|
||||
setText("#walletTransferStatus", `Keypair valide — ${payload.format}`);
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", "external transfer keypair inspected");
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
setText("#walletTransferStatus", `Erreur : ${message}`);
|
||||
setTransferPublicKey("");
|
||||
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `External transfer inspection failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function importTransferFile(): Promise<void> {
|
||||
const sourcePath = inputValue("#walletImportSourcePathInput");
|
||||
const format = selectedTransferFormat("#walletImportFormatSelect");
|
||||
const alias = inputValue("#walletImportAliasInput");
|
||||
if (sourcePath.length === 0 || format.length === 0 || alias.length === 0) {
|
||||
setText("#walletTransferStatus", "Chemin source, format et alias requis");
|
||||
return;
|
||||
}
|
||||
const request: DemoWalletImportRequest = { alias, source_path: sourcePath, format };
|
||||
setText("#walletTransferStatus", "Import...");
|
||||
try {
|
||||
const payload = await invoke<DemoWalletIdentityPayload>("demo_wallet_import_file", { request });
|
||||
setTransferPublicKey(payload.public_key);
|
||||
setText("#walletTransferStatus", `Importé : ${payload.alias}.kswallet`);
|
||||
const addressInput = document.querySelector<HTMLInputElement>("#walletAddressInput");
|
||||
if (addressInput) {
|
||||
addressInput.value = payload.public_key;
|
||||
}
|
||||
await refreshWalletInventory();
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `external keypair imported: ${payload.alias}`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
setText("#walletTransferStatus", `Erreur : ${message}`);
|
||||
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `External keypair import failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
const request: DemoWalletExportRequest = { alias, destination_path: destinationPath, format };
|
||||
setText("#walletExportStatus", "Export...");
|
||||
try {
|
||||
await invoke<void>("demo_wallet_export_file", { request });
|
||||
setText("#walletExportStatus", `Exporté : ${alias}`);
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_wallet", `native wallet exported: ${alias}`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
setText("#walletExportStatus", `Erreur : ${message}`);
|
||||
frontendError("kb-app-demo-desktop.frontend.demo_wallet", `Native wallet export failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function exploreInspectedTransfer(): void {
|
||||
if (inspectedTransferPublicKey.length === 0) {
|
||||
return;
|
||||
}
|
||||
const addressInput = document.querySelector<HTMLInputElement>("#walletAddressInput");
|
||||
if (addressInput) {
|
||||
addressInput.value = inspectedTransferPublicKey;
|
||||
}
|
||||
document.querySelector<HTMLElement>("#walletAddressInput")?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
|
||||
function parseTokenAccounts(value: unknown, program: string): ParsedTokenAccount[] {
|
||||
if (typeof value !== "object" || value === null || !("value" in value)) {
|
||||
return [];
|
||||
@@ -504,6 +656,26 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
void createWallet();
|
||||
}
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#inspectWalletTransferButton")?.addEventListener("click", () => {
|
||||
void inspectTransferFile();
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#importWalletTransferButton")?.addEventListener("click", () => {
|
||||
void importTransferFile();
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#exploreWalletTransferButton")?.addEventListener("click", () => {
|
||||
exploreInspectedTransfer();
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#exportWalletTransferButton")?.addEventListener("click", () => {
|
||||
void exportWallet();
|
||||
});
|
||||
document.querySelector<HTMLInputElement>("#walletImportSourcePathInput")?.addEventListener("input", () => {
|
||||
setTransferPublicKey("");
|
||||
setText("#walletTransferStatus", "En attente");
|
||||
});
|
||||
document.querySelector<HTMLSelectElement>("#walletImportFormatSelect")?.addEventListener("change", () => {
|
||||
setTransferPublicKey("");
|
||||
setText("#walletTransferStatus", "En attente");
|
||||
});
|
||||
document.querySelector<HTMLSelectElement>("#walletOnchainProfileSelect")?.addEventListener("change", () => {
|
||||
updateOnchainClusterLabel();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user