v0.2.6-pre.008
This commit is contained in:
@@ -42,7 +42,7 @@
|
||||
<h1 id="viewTitle" class="h3 mb-1">Dashboard</h1>
|
||||
<p class="text-body-secondary mb-0">Identité autorisée et balance SOL via le Transport HTTP composite sont actives.</p>
|
||||
</div>
|
||||
<span class="badge text-bg-secondary">0.2.6-pre.007</span>
|
||||
<span class="badge text-bg-secondary">0.2.6-pre.008</span>
|
||||
</div>
|
||||
<section data-view-panel="dashboard">
|
||||
<div class="row g-3">
|
||||
@@ -163,6 +163,71 @@
|
||||
Les passwords sont envoyés uniquement du frontend vers Rust pour l'opération courante. Ils ne sont jamais renvoyés par Rust ni journalisés.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header fw-semibold">Importer un keypair Solana vers un wallet KSP</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-5">
|
||||
<label class="form-label" for="importTransferFormat">Format source</label>
|
||||
<select id="importTransferFormat" class="form-select">
|
||||
<option value="solana_cli_json">Solana CLI JSON</option>
|
||||
<option value="solana_keypair_base58">Keypair Base58 complet</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-7 d-flex align-items-end">
|
||||
<button id="inspectImportSource" class="btn btn-outline-primary" type="button">
|
||||
<i class="fa-solid fa-folder-open me-2" aria-hidden="true"></i>Choisir et inspecter la source
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="alert alert-secondary mb-0">
|
||||
<div><strong>Source :</strong> <span id="importSourceName">—</span></div>
|
||||
<div><strong>Format :</strong> <span id="importSourceFormat">—</span></div>
|
||||
<div><strong>Pubkey inspectée :</strong> <span id="importSourcePubkey" class="text-break">—</span></div>
|
||||
<div class="small text-body-secondary mt-2">Le chemin et le contenu secret restent exclusivement côté Rust. Le frontend ne reçoit que cette projection publique.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form id="importWalletForm" autocomplete="off">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="importWalletFilename">Destination .kspwallet</label>
|
||||
<input id="importWalletFilename" class="form-control" type="text" value="imported-01.kspwallet" required spellcheck="false">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="importWalletAlias">Alias interne optionnel</label>
|
||||
<input id="importWalletAlias" class="form-control" type="text" autocomplete="off">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="importWalletOwnerPassword">Nouveau OWNER password</label>
|
||||
<input id="importWalletOwnerPassword" class="form-control" type="password" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-end">
|
||||
<div class="form-check mb-2">
|
||||
<input id="importWalletEnableView" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label" for="importWalletEnableView">Créer aussi une capability VIEW</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="importWalletViewPassword">Nouveau VIEW password</label>
|
||||
<input id="importWalletViewPassword" class="form-control" type="password" autocomplete="new-password" disabled>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label" for="importWalletInitialNote">Note initiale optionnelle</label>
|
||||
<textarea id="importWalletInitialNote" class="form-control" rows="3" autocomplete="off"></textarea>
|
||||
</div>
|
||||
<div class="col-12 d-flex align-items-center gap-3">
|
||||
<button id="importWalletSubmit" class="btn btn-primary" type="submit" disabled>
|
||||
<i class="fa-solid fa-file-import me-2" aria-hidden="true"></i>Import wallet
|
||||
</button>
|
||||
<span id="importWalletStatus" class="text-body-secondary" role="status">Choisir une source.</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section data-view-panel="details" hidden>
|
||||
@@ -246,7 +311,7 @@
|
||||
</div>
|
||||
</main>
|
||||
<footer class="app-footer bg-dark text-white-50 d-flex align-items-center px-4">
|
||||
<span>KSP Wallet Desk · authorized identity + getBalance pre.007</span>
|
||||
<span>KSP Wallet Desk · native transfer import pre.008</span>
|
||||
</footer>
|
||||
<script type="module" src="./ts/main.ts"></script>
|
||||
</body>
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user