v0.2.6-pre.008

This commit is contained in:
2026-08-21 11:36:28 +02:00
parent 787df2861f
commit 1a9edd14e1
19 changed files with 902 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/frontend/ts/main.ts
// version: 7
// version: 8
import "bootstrap";
import DataTable from "datatables.net-bs5";
@@ -9,6 +9,10 @@ import "simplebar";
import { getCurrentWindow } from "@tauri-apps/api/window";
import type { WalletBalanceDto } from "./bindings/ksp_app_wallet_desk/wallet_balance/WalletBalanceDto.ts";
import type { RuntimeStatusDto } from "./bindings/ksp_app_wallet_desk/dto_common/RuntimeStatusDto.ts";
import type { WalletImportRequestDto } from "./bindings/ksp_app_wallet_desk/wallet_import/WalletImportRequestDto.ts";
import type { WalletImportSourceRequestDto } from "./bindings/ksp_app_wallet_desk/wallet_import/WalletImportSourceRequestDto.ts";
import type { WalletTransferFormatDto } from "./bindings/ksp_app_wallet_desk/wallet_import/WalletTransferFormatDto.ts";
import type { WalletTransferInspectionDto } from "./bindings/ksp_app_wallet_desk/wallet_import/WalletTransferInspectionDto.ts";
import type { LockedWalletDto } from "./bindings/ksp_app_wallet_desk/wallet_inventory/LockedWalletDto.ts";
import type { WalletInventoryEntryDto } from "./bindings/ksp_app_wallet_desk/wallet_inventory/WalletInventoryEntryDto.ts";
import type { WalletSelectionRequestDto } from "./bindings/ksp_app_wallet_desk/wallet_inventory/WalletSelectionRequestDto.ts";
@@ -40,6 +44,7 @@ let activeWalletId: string | null = null;
let activeSessionState: ActiveSessionState = "no_selection";
let activeViewEnabled = false;
let configuredSecretCandidateCount = 0;
let stagedImportSource: WalletTransferInspectionDto | null = null;
function isViewId(value: string): value is ViewId {
return value in viewTitles;
@@ -170,7 +175,7 @@ function initializeWalletTable(): void {
zeroRecords: "Aucun wallet correspondant.",
},
});
frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.007-wallet-balance" });
frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.008-wallet-import" });
}
function renderWalletInventory(entries: WalletInventoryEntryDto[]): void {
@@ -230,6 +235,44 @@ function updateUnlockActions(): void {
setText("#unlockSecretCandidateCount", configuredSecretCandidateCount.toString());
}
function importFormatLabel(format: WalletTransferFormatDto): string {
return format === "solana_cli_json" ? "Solana CLI JSON" : "Keypair Base58 complet";
}
function updateImportActions(): void {
const submit = document.querySelector<HTMLButtonElement>("#importWalletSubmit");
if (submit) {
submit.disabled = stagedImportSource === null;
}
}
function clearImportSourceProjection(): void {
stagedImportSource = null;
setText("#importSourceName", "—");
setText("#importSourceFormat", "—");
setText("#importSourcePubkey", "—");
updateImportActions();
}
function clearImportSensitiveInputs(): void {
const ownerPassword = document.querySelector<HTMLInputElement>("#importWalletOwnerPassword");
const viewPassword = document.querySelector<HTMLInputElement>("#importWalletViewPassword");
const alias = document.querySelector<HTMLInputElement>("#importWalletAlias");
const note = document.querySelector<HTMLTextAreaElement>("#importWalletInitialNote");
if (ownerPassword) {
ownerPassword.value = "";
}
if (viewPassword) {
viewPassword.value = "";
}
if (alias) {
alias.value = "";
}
if (note) {
note.value = "";
}
}
function clearCreateFormSensitiveInputs(): void {
const ownerPassword = document.querySelector<HTMLInputElement>("#createWalletOwnerPassword");
const viewPassword = document.querySelector<HTMLInputElement>("#createWalletViewPassword");
@@ -483,6 +526,129 @@ function bindCreateWalletForm(): void {
frontendTrace("main", "Create Wallet form handlers installed");
}
function importRequestFromForm(): WalletImportRequestDto | null {
const filename = document.querySelector<HTMLInputElement>("#importWalletFilename");
const ownerPassword = document.querySelector<HTMLInputElement>("#importWalletOwnerPassword");
const enableView = document.querySelector<HTMLInputElement>("#importWalletEnableView");
const viewPassword = document.querySelector<HTMLInputElement>("#importWalletViewPassword");
const alias = document.querySelector<HTMLInputElement>("#importWalletAlias");
const note = document.querySelector<HTMLTextAreaElement>("#importWalletInitialNote");
if (!stagedImportSource || !filename || !ownerPassword || !enableView || !viewPassword || !alias || !note) {
return null;
}
const normalizedFilename = filename.value.trim();
if (!normalizedFilename.endsWith(".kspwallet") || normalizedFilename.length <= ".kspwallet".length || ownerPassword.value.length === 0) {
return null;
}
if (enableView.checked && viewPassword.value.length === 0) {
return null;
}
return {
alias: alias.value.length === 0 ? null : alias.value,
filename: normalizedFilename,
initialNote: note.value.length === 0 ? null : note.value,
ownerPassword: ownerPassword.value,
viewPassword: enableView.checked ? viewPassword.value : null,
};
}
async function inspectImportSource(): Promise<void> {
const formatControl = document.querySelector<HTMLSelectElement>("#importTransferFormat");
if (!formatControl) {
return;
}
const format = formatControl.value as WalletTransferFormatDto;
const request: WalletImportSourceRequestDto = { format };
clearImportSourceProjection();
setText("#importWalletStatus", "Sélection native de la source…");
frontendDebug("main", "Wallet import native picker requested", { format });
try {
const inspection = await invokeKsp<WalletTransferInspectionDto | null>("main", "inspect_import_source", { request });
if (!inspection) {
setText("#importWalletStatus", "Sélection annulée.");
frontendDebug("main", "Wallet import native picker cancelled", { format });
return;
}
stagedImportSource = inspection;
setText("#importSourceName", inspection.sourceName);
setText("#importSourceFormat", importFormatLabel(inspection.format));
setText("#importSourcePubkey", inspection.pubkey);
setText("#importWalletStatus", "Source validée. Saisir les nouveaux credentials KSP.");
updateImportActions();
frontendInfo("main", "Wallet transfer source inspected", { format: inspection.format });
} catch {
clearImportSourceProjection();
setText("#importWalletStatus", "Source invalide ou illisible. Consulter le diagnostic et les logs.");
frontendWarn("main", "Wallet transfer source inspection failed", { format });
}
}
function bindImportWalletForm(): void {
const form = document.querySelector<HTMLFormElement>("#importWalletForm");
const format = document.querySelector<HTMLSelectElement>("#importTransferFormat");
const pick = document.querySelector<HTMLButtonElement>("#inspectImportSource");
const enableView = document.querySelector<HTMLInputElement>("#importWalletEnableView");
const viewPassword = document.querySelector<HTMLInputElement>("#importWalletViewPassword");
if (format) {
format.addEventListener("change", () => {
clearImportSourceProjection();
setText("#importWalletStatus", "Format modifié ; choisir une nouvelle source.");
void invokeKsp<void>("main", "clear_import_source").catch(() => {
frontendWarn("main", "Backend staged Wallet import source clear failed");
});
frontendTrace("main", "Wallet import format changed", { format: format.value });
});
}
if (pick) {
pick.addEventListener("click", () => {
void inspectImportSource();
});
}
if (enableView && viewPassword) {
enableView.addEventListener("change", () => {
viewPassword.disabled = !enableView.checked;
if (!enableView.checked) {
viewPassword.value = "";
}
frontendTrace("main", "Import Wallet VIEW option changed", { enabled: enableView.checked });
});
}
if (!form) {
return;
}
form.addEventListener("submit", event => {
event.preventDefault();
const request = importRequestFromForm();
if (!request || !stagedImportSource) {
setText("#importWalletStatus", "Source/filename/passwords invalides ou incomplets.");
frontendWarn("main", "Import Wallet form validation rejected request");
return;
}
const requestedWalletId = request.filename;
const transferFormat = stagedImportSource.format;
const requestedViewEnabled = request.viewPassword !== null;
setText("#importWalletStatus", "Import en cours…");
frontendDebug("main", "Import Wallet request submitted", { transferFormat, viewEnabled: requestedViewEnabled, walletId: requestedWalletId });
clearSelectedWallet();
void invokeKsp<WalletAuthorizedDto>("main", "import_wallet", { request })
.then(async wallet => {
clearImportSensitiveInputs();
clearImportSourceProjection();
renderAuthorizedWallet(wallet);
setText("#importWalletStatus", "Wallet importé ; session OWNER ouverte.");
await loadWalletInventory("list_wallets", false);
activateView("dashboard", "user");
})
.catch(() => {
clearImportSensitiveInputs();
clearImportSourceProjection();
setText("#importWalletStatus", "Import impossible ; choisir à nouveau la source. Consulter le diagnostic et les logs.");
frontendWarn("main", "Import Wallet operation failed", { transferFormat, walletId: requestedWalletId });
});
});
frontendTrace("main", "Import Wallet form handlers installed");
}
function beginFrontendUnlock(capability: UnlockCapability, provider: "manual" | "configured"): string | null {
if (activeSessionState !== "locked" || !activeWalletId) {
return null;
@@ -658,7 +824,7 @@ function renderRuntimeStatus(status: RuntimeStatusDto): void {
setText("#runtimeEffectiveWalletsDirectory", status.effectiveWalletsDirectory);
setText("#runtimeWalletDirectoryCreated", status.effectiveWalletsDirectoryCreatedOnStartup ? "oui" : "non, déjà présent");
setText("#runtimeShellPhase", status.shellPhase);
setText("#shellStatus", "Config/Transport résolus ; VIEW/OWNER et getBalance prêts.");
setText("#shellStatus", "Config/Transport résolus ; import natif, VIEW/OWNER et getBalance prêts.");
frontendTrace("main", "Wallet Desk runtime status rendered", {
compositeProfile: status.activeCompositeProfile,
fallbackLoggingActive: status.fallbackLoggingActive,
@@ -706,6 +872,7 @@ async function initializeMain(): Promise<void> {
bindNavigation();
bindWalletTableSelection();
bindCreateWalletForm();
bindImportWalletForm();
bindUnlockActions();
bindBalanceActions();
bindShellActions();