Compare commits
126 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c9c1d1349 | |||
| 79fee574d9 | |||
| fca9abb11a | |||
| ba8f42f9b1 | |||
| 362123f357 | |||
| 5c0be421da | |||
| 738b340cc7 | |||
| 92a2c4fff3 | |||
| 946d88322b | |||
| d370027c02 | |||
| bd561cad47 | |||
| 6907e4bbb8 | |||
| c07db925b1 | |||
| 263464ddd7 | |||
| 4b350b4161 | |||
| ecbd6b746d | |||
| 8ba554306a | |||
| 6dcd278459 | |||
| 4dcdbfaa55 | |||
| 42326e7f02 | |||
| 2b7afab663 | |||
| 7d03825ec6 | |||
| 4db4087b8a | |||
| 1a9edd14e1 | |||
| 787df2861f | |||
| 60b346c552 | |||
| f11f0921b0 | |||
| 4c9086e2f0 | |||
| a207cf7161 | |||
| 63e711629f | |||
| 7aa38b0daf | |||
| 2132dfd884 | |||
| 76bfce17c1 | |||
| 6eb4d71043 | |||
| 7eb0381932 | |||
| 3fa4ab251a | |||
| 0e72914237 | |||
| f9f9ac79b6 | |||
| 1a57cdeef0 | |||
| 927b9e4709 | |||
| b7d9e7fd1f | |||
| 136ea6d76e | |||
| 8c65096f4c | |||
| cb142b1cb8 | |||
| 08c8046262 | |||
| c0b131bf6f | |||
| 5bf9651038 | |||
| e91bf36e9e | |||
| 1125ade4c1 | |||
| 518cc9257f | |||
| 46afe10423 | |||
| 25edcc231e | |||
| 6094d33120 | |||
| dcd23abb92 | |||
| 36b98e0abc | |||
| 3c069347b7 | |||
| a0a5595186 | |||
| 6c1172efc3 | |||
| d5536b0778 | |||
| c6794af05b | |||
| bcda6db11f | |||
| 51b5114ae2 | |||
| c97a8739ab | |||
| c2ccef3849 | |||
| 58134c81d1 | |||
| 5b3967327a | |||
| 804b92b957 | |||
| e6772c109d | |||
| 7e84522787 | |||
| 37461ec248 | |||
| 4252a21140 | |||
| 7a889dd466 | |||
| d3cc7c93f0 | |||
| 561b6678ed | |||
| e500024c3a | |||
| 033800f897 | |||
| cf8d79567d | |||
| cb19742cf7 | |||
| c03a3eaf5d | |||
| f7acac3fc8 | |||
| 039b93cfb3 | |||
| c9a0f7fd37 | |||
| 704110d509 | |||
| fb32ed03f8 | |||
| 4439589f0d | |||
| 1e1bc0d421 | |||
| bd26d4f636 | |||
| 2a7f3e8f40 | |||
| 1d37f04d63 | |||
| 1ccf11bcc1 | |||
| d06284fca2 | |||
| fff69ead9c | |||
| c93a833bac | |||
| 05e0e96083 | |||
| b57f796187 | |||
| ecfb9500eb | |||
| e14ce1c36f | |||
| 83cb861e54 | |||
| 1d1bc6a4d6 | |||
| c4636ac8b9 | |||
| f15448ff6e | |||
| bec1f2ec08 | |||
| f625ee5979 | |||
| e68f073505 | |||
| bffb4f9a31 | |||
| 9059a2dc45 | |||
| 6c3ecf1f18 | |||
| 79b67f8eae | |||
| e0a7ac0bf8 | |||
| 598474438b | |||
| f5d98c4e69 | |||
| 14bcbf2cfb | |||
| ac1b1033c4 | |||
| bc71fba289 | |||
| 9c1568ee1c | |||
| c1cea6e813 | |||
| ed978179d8 | |||
| babe7d9f2b | |||
| d3fc0c6d69 | |||
| 0cff0406ab | |||
| d98d152f08 | |||
| 624202c363 | |||
| 24cf2c5a11 | |||
| e721464a7c | |||
| 259bff6707 | |||
| 7d40de3249 |
32
.env.example
32
.env.example
@@ -1,12 +1,36 @@
|
|||||||
# file: .env.example
|
# file: .env.example
|
||||||
# version: 2
|
# version: 4
|
||||||
|
|
||||||
# KSP Logging root directory. Used by config/std.logging.json for relative log output paths.
|
# KSP Logging root directory. Used by config/std.logging.json for relative log output paths.
|
||||||
# The current Config document fallback is "logs" when neither the process environment nor .env defines this variable.
|
# The current Config document fallback is "logs" when neither the process environment nor .env defines this variable.
|
||||||
KSP_LOGS_DIRECTORY=logs
|
KSP_LOGS_DIRECTORY=logs
|
||||||
|
|
||||||
# Minimum time in milliseconds that a KSP desk splash remains visible after its frontend is ready.
|
# KSP Wallet root directory. Used by config/std.wallet.json before an optional profile subdirectory is appended.
|
||||||
|
# The committed Wallet document falls back to "wallets" when neither the process environment nor .env defines this variable.
|
||||||
|
KSP_WALLETS_DIRECTORY=wallets
|
||||||
|
|
||||||
|
# Optional Wallet Desk password candidates are introduced by the later unlock tranche.
|
||||||
|
# Additional KSP_SECRET_WALLET_PASS_<LABEL> variables may be defined locally; never commit real password values.
|
||||||
|
# KSP_SECRET_WALLET_PASS_WALLET_01='test'
|
||||||
|
# KSP_SECRET_WALLET_PASS_01='test'
|
||||||
|
|
||||||
|
# Optional public Solana Devnet HTTP endpoint override used by config/std.transport.json.
|
||||||
|
# The committed Transport document falls back to https://api.devnet.solana.com when this variable is absent.
|
||||||
|
KSP_PUBLIC_SOLANA_DEVNET_HTTP_URL=https://api.devnet.solana.com
|
||||||
|
|
||||||
|
# Optional public Solana Mainnet HTTP endpoint override used by config/std.transport.json and its example.
|
||||||
|
# The committed Transport document falls back to https://api.mainnet-beta.solana.com when this variable is absent.
|
||||||
|
KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL=https://api.mainnet-beta.solana.com
|
||||||
|
|
||||||
|
# Optional complete private-provider HTTP endpoint URL used only by the Transport example when explicitly selected.
|
||||||
|
# Keep provider credentials in a KSP_SECRET_* variable; do not copy a real credential-bearing URL into committed JSON.
|
||||||
|
# KSP_SECRET_SOLANA_HTTP_URL=https://provider.example/?api-key=replace-me
|
||||||
|
|
||||||
|
# Fade-in duration in milliseconds used by the common KSP desk splash lifecycle.
|
||||||
|
KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||||
|
|
||||||
|
# Minimum fully-visible time in milliseconds used by the common KSP desk splash lifecycle.
|
||||||
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||||
|
|
||||||
# Fade-in/fade-out duration in milliseconds used by the common KSP desk splash lifecycle.
|
# Fade-out duration in milliseconds used by the common KSP desk splash lifecycle.
|
||||||
KSP_DESK_SPLASH_FADE_MS=300
|
KSP_DESK_SPLASH_FADE_OUT_MS=300
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -36,6 +36,7 @@ yarn.lock
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*~
|
*~
|
||||||
|
.pydevproject
|
||||||
|
|
||||||
# Operating system files
|
# Operating system files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,10 +1,42 @@
|
|||||||
<!-- file: CHANGELOG.md -->
|
<!-- file: CHANGELOG.md -->
|
||||||
<!-- version: 3 -->
|
<!-- version: 10 -->
|
||||||
|
|
||||||
# Changelog KSP
|
# Changelog KSP
|
||||||
|
|
||||||
Ce changelog résume uniquement les releases KSP considérées comme stables, dans l'ordre chronologique décroissant. Les détails de chaque livraison restent dans `deltas/`.
|
Ce changelog résume uniquement les releases KSP considérées comme stables, dans l'ordre chronologique décroissant. Les détails de chaque livraison restent dans `deltas/`.
|
||||||
|
|
||||||
|
## 0.2.6 — Wallet Desk + `.kspwallet` V2 — 2026-08-22
|
||||||
|
|
||||||
|
`0.2.6` stabilise `ksp-app-wallet-desk` comme seconde application Tauri KSP spécialisée et étend `ksp-wallet-lib` avec le wire binaire `.kspwallet` V2. Wallet Desk compose Config, Wallet, Transport HTTP et Logging sans déplacer leurs responsabilités : inventory root-scoped et symlink-safe, création/import V2 par défaut, ouverture VIEW/OWNER V1/V2, candidats secrets résolus exclusivement par Config, `getBalance` Devnet via Transport, administration alias/notes, rotations OWNER/VIEW, disable/recreate VIEW fort et export Solana CLI JSON/Base58. Les secrets, keypairs, handles, chemins complets import/export et credentials Config restent côté Rust ; le frontend ne reçoit que des projections sûres et utilise des modals Bootstrap pour les opérations privilégiées.
|
||||||
|
|
||||||
|
Le format V1 JSON reste stable et lisible ; V2 ajoute un framing binaire KSP canonique, des IDs numériques, longueurs big-endian et domains/transcripts distincts tout en conservant Argon2id, XChaCha20-Poly1305 et l'autorité Ed25519 OWNER. Les APIs génériques créent/importent explicitement en V2 et auto-détectent V1/V2 à la lecture, tandis que les variantes `_v1`/`_v2` imposent un format exact et que `DEFAULT_WALLET_FORMAT` reste indépendant de `LATEST_SUPPORTED_WALLET_FORMAT`. La migration V1 -> V2 est explicite, OWNER-authentifiée, no-clobber ou atomique in-place avec protection stale-state ; aucune ouverture ne migre silencieusement. La release ferme également le runtime Tauri distribué : les documents Config/schemas sont embarqués comme resources, `ksp-config-lib` prépare une racine KSP user-writable commune sans écraser les Config utilisateur ni embarquer `.env`, et les bundles Linux `.deb`, `.rpm` et `.AppImage` ont été produits avec succès après le gate final `pre.018-fix.001`. Le prompt `prompts/012-V0_2_7_START_PROMPT.md` ouvre `0.2.7 — WebSocket Solana standard` par lectures, audit officiel, threat-model et sizing avant implémentation lourde.
|
||||||
|
|
||||||
|
## 0.2.5 — Wallet foundation — 2026-08-20
|
||||||
|
|
||||||
|
`0.2.5` publie `ksp-wallet-lib` et le format natif interopérable `.kspwallet` V1. La release stabilise les capacités cryptographiquement indépendantes VIEW/OWNER, le masquage de la Pubkey/alias/notes à l’état verrouillé, Argon2id v19 avec paramètres sérialisés et profil de création `65 536 KiB / 3 / 1`, XChaCha20-Poly1305, une autorité Ed25519 OWNER distincte de la keypair Solana, les compartiments `owner_control`/`metadata`/`secret`, la création et l’ouverture mémoire/fichier, la persistence no-clobber, la signature Solana OWNER sans getter secret, l’administration alias/notes, les rotations OWNER/VIEW, la révocation forte VIEW avec rekey metadata, la détection de handles stale et les adapters Solana CLI JSON/Base58 pour inspection/import/export. Le wire V1 est documenté indépendamment de Rust dans `docs/formats/KSPWALLET_V1.md` et couvert par des vecteurs publics ainsi que des canaris adversariaux et une reproduction externe. La release conserve Wallet indépendant de Config/Transport/ExecutionPolicy/Store/Tauri et expose la Pubkey via `ksp-core-lib` tandis que `solana-keypair` reste encapsulée dans Wallet.
|
||||||
|
|
||||||
|
La clôture technique `pre.010-fix.001` à `fix.003` met la dépendance directe KSP à `ed25519-dalek 3.0.0`, accepte la génération `2.2.0` transitive encore imposée par `solana-keypair 3.1.2`, normalise les sources Rust du workspace et introduit `python3 scripts/audit_rust_workspace_rules.py` comme gate structurel complémentaire à rustfmt/Clippy. Le checkpoint final `pre.010-fix.003` est validé avec audit Python clean, `cargo check`, Clippy, tests Wallet et workspace complet verts. Le prompt `prompts/011-V0_2_6_START_PROMPT.md` ouvre ensuite `0.2.6 — Wallet Desk` par audit/sizing avant implémentation.
|
||||||
|
|
||||||
|
## 0.2.4 — HTTP Blocks + Economics + compliance HTTP finale — 2026-08-18
|
||||||
|
|
||||||
|
`0.2.4` achève la surface HTTP Solana standard de `ksp-onchain-transport-lib` : les 10 wrappers Blocks et 5 wrappers Economics portent la couverture typée à **52/52 méthodes HTTP courantes**, tandis que les **14/14 méthodes historiques Deprecated/runtime Removed** restent conservées pour compliance. La release stabilise notamment `getBlock` moderne avec sa forme bare encoding legacy dépréciée, les quatre variantes `transactionDetails`, les versions transaction numériques génériques, `numRewardPartitions` (SIMD-0118), `commissionBps` (SIMD-0291), les overloads de ranges/performance, `getBlockProduction`, les valeurs Economics fournies par le runtime et `getInflationReward` positionnel sans plafond d'adresses inventé. Le réaudit final confirme l'égalité exacte entre l'inventaire HTTP officiel courant et le registre KSP, applique `KSP-TRANSPORT-007` aux **52/52 wrappers**, et ne matérialise aucune extension spéculative issue de la watchlist SIMD. Les canaries de frontières, le workspace complet et les deux smokes Devnet — Transport pur couvrant Accounts/Tokens/Cluster/Transactions/Blocks/Economics et composition Config -> Transport transitoire — sont validés avant publication. Le prompt `prompts/010-V0_2_5_START_PROMPT.md`, enrichi par `pre.009-fix.001`, ouvre `0.2.5 — Wallet foundation` avec threat model, interopérabilité `.kspwallet` et capacités indépendantes VIEW/OWNER.
|
||||||
|
|
||||||
|
## 0.2.3 — HTTP Transactions — 2026-08-18
|
||||||
|
|
||||||
|
`0.2.3` complète la famille HTTP Transactions de `ksp-onchain-transport-lib` avec 11 wrappers typés, portant la surface HTTP typée courante à 37 méthodes : 4 foundation, 22 Accounts/Tokens/Cluster et 11 Transactions. La release stabilise la classification exacte `8 Read / RetrySafe`, `2 WriteSubmission / NeverAfterDispatch` (`requestAirdrop`, `sendTransaction`) et `1 Simulation / RetrySafe` (`simulateTransaction`), avec preuves end-to-end qu'une write submission n'est jamais resoumise après dispatch ambigu mais peut retenter lorsqu'un échec est prouvé `NotDispatched`. `getTransaction` couvre la config moderne complète et la forme bare encoding legacy dépréciée ; les variantes de transaction, metadata, version et `transactionIndex` restent préservées losslessly. `simulateTransaction` couvre sa config complète, ses invariants déterministes et son résultat riche sans introduire de client RPC Solana haut niveau ni de décodage transactionnel dans Transport. La release formalise `KSP-TRANSPORT-007` et réaudite rétroactivement les 37 wrappers courants sans remédiation fonctionnelle. Les canaries conservent 52 méthodes HTTP courantes, 14 historiques Deprecated/Removed et 15 Blocks/Economics réservées à `0.2.4`. Les graphes Cargo et les deux smokes Devnet — Transport pur read-only et composition Config -> Transport transitoire — ont été validés avant publication. Le prompt `prompts/009-V0_2_4_START_PROMPT.md` ouvre `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale`.
|
||||||
|
|
||||||
|
## 0.2.2 — HTTP Accounts + Tokens + Cluster — 2026-08-18
|
||||||
|
|
||||||
|
`0.2.2` complète la surface HTTP typée de `ksp-onchain-transport-lib` avec 22 wrappers supplémentaires : 5 Accounts, 5 Tokens et 12 Cluster, portant la couverture typée totale à 26 méthodes avec les quatre canaris foundation de `0.2.1`. La release stabilise les DTOs/configs wire communs, préserve les formes Account encodées/`jsonParsed`, les `null` et champs optionnels, les selectors Token exclusifs, les contextes RPC, les structures Cluster/leader/vote et les limites locales auditées (`getMultipleAccounts <= 100`, `getProgramAccounts <= 4` filtres, `memcmp` raw <= 128 octets, `getSlotLeaders` entre 1 et 5000). Les 22 descriptors restent `Read / RetrySafe` et passent exclusivement par le flux central `descriptor -> execute_standard_rpc -> pool/admission -> executor HTTP`. La release n'ajoute aucune dépendance externe, ne crée aucun client HTTP parallèle et conserve le firewall Transport -> Config/Store/Program. Elle ajoute un smoke Devnet pur à Transport avec settings programmatiques couvrant Accounts/Tokens/Cluster, conserve séparément le smoke historique Config -> Transport comme exception transitoire, et valide les deux explicitement avant publication. Les canaries figent toujours 52 méthodes HTTP courantes, 14 historiques Deprecated/Removed et la partition typed `4 / 22 / 11 / 15`. Le prompt `prompts/008-V0_2_3_START_PROMPT.md` ouvre `0.2.3 — HTTP Transactions`.
|
||||||
|
|
||||||
|
## 0.2.1 — HTTP Solana foundation — 2026-08-17
|
||||||
|
|
||||||
|
`0.2.1` stabilise `ksp-onchain-transport-lib` comme foundation HTTP JSON-RPC Solana provider-neutral : settings publics, endpoints/pool/rôles, priorités et fairness, RPS/burst/concurrence/cooldown, deadline commune, retry/backoff, classification no-resend après dispatch ambigu, snapshots sûrs et exécution HTTP réelle via `reqwest`/rustls. La release fige un registre audité de 52 méthodes HTTP courantes et 14 méthodes historiques Deprecated/Removed, avec quatre wrappers typés canari (`getBalance`, `getGenesisHash`, `getHealth`, `getVersion`) et une partition explicite des 48 méthodes restantes sur `0.2.2`–`0.2.4`. Elle ajoute `std.transport`, son schema/exemple et l'adapter `ksp-config-lib -> ksp-onchain-transport-lib`, sans dépendance inverse, ainsi que la redaction des URLs/provider credentials, la neutralisation des URLs contenues dans les `reqwest::Error`, un sink Logging Transport dédié à `info`, des fixtures HTTP déterministes, des canaries de complétude et un smoke Devnet opt-in validant la composition Config -> Transport. Le smoke cross-crates reste temporairement hébergé dans Config et doit migrer vers une future surface d'intégration/orchestration ; ce placement n'est pas un modèle pour les futurs smokes `Config + autre crate`. Le prompt `prompts/007-V0_2_2_START_PROMPT.md` ouvre `0.2.2 — HTTP Accounts + Tokens + Cluster`.
|
||||||
|
|
||||||
|
## 0.2.0 — Audit bot3 et planification de la série `0.2.x` — 2026-08-17
|
||||||
|
|
||||||
|
`0.2.0` stabilise le cadrage de la prochaine phase fonctionnelle de KSP après audit de `khadhroony-bot3`. La release fixe l'ordre `0.2.1+` autour du transport HTTP Solana, du Wallet `.kspwallet`, de Wallet Desk, des transports WebSocket/LaserStream/Yellowstone, du transport off-chain de prix, de `ksp-interface-lib` et de `ksp-program-api`; elle impose la couverture exhaustive des surfaces Transport documentées avec warnings KSP pour les opérations deprecated/obsolete encore fonctionnelles et unstable/experimental. Elle stabilise également la progression durable `RAW -> CORE -> DECODE -> SPECIALIZED`, RAW/CORE sans décodage Program, puis des vertical slices complets par groupe à partir de DECODE, avec priorité Solana Core, SPL token/trading, metadata token, Anchor, Meteora/Raydium/Pump/Orca, routing et Market Desk progressive. Le prompt `prompts/006-V0_2_1_START_PROMPT.md` ouvre `0.2.1 — ksp-onchain-transport-lib / HTTP Solana foundation` avec un gate de sizing imposant qu'une release concrète reste clôturable dans une seule session.
|
||||||
|
|
||||||
## 0.1.4 — Config Desk — 2026-08-17
|
## 0.1.4 — Config Desk — 2026-08-17
|
||||||
|
|
||||||
`0.1.4` stabilise `ksp-app-config-desk` comme première application desktop/Tauri spécialisée et modèle de référence des futures applications KSP. La release valide de bout en bout les contrats de `ksp-config-lib` et le lifecycle de `ksp-logging-lib` : shell splash/main, inventaire et diagnostics des documents Config, profils et provenance sûre, management `.env` avec shadowing et reveal Secret privilégié, éditeur Logging typé multi-profils/multi-sinks, persistence atomique, hot reload transactionnel, rollback, sélection runtime explicite, génération observable, fichiers de logs distincts par lancement, bridge frontend vers la façade KSP et panneau Test Logging pour démontrer le routing niveau/target/domain. Elle ajoute également les audits desktop/ownership/sécurité, un registre extensible `file_id -> éditeur spécialisé`, une baseline Logging de release `info`/`warn`, et prépare `0.2.0` comme release intermédiaire d’audit de `khadhroony-bot3` et de planification du reste de `0.2.x`.
|
`0.1.4` stabilise `ksp-app-config-desk` comme première application desktop/Tauri spécialisée et modèle de référence des futures applications KSP. La release valide de bout en bout les contrats de `ksp-config-lib` et le lifecycle de `ksp-logging-lib` : shell splash/main, inventaire et diagnostics des documents Config, profils et provenance sûre, management `.env` avec shadowing et reveal Secret privilégié, éditeur Logging typé multi-profils/multi-sinks, persistence atomique, hot reload transactionnel, rollback, sélection runtime explicite, génération observable, fichiers de logs distincts par lancement, bridge frontend vers la façade KSP et panneau Test Logging pour démontrer le routing niveau/target/domain. Elle ajoute également les audits desktop/ownership/sécurité, un registre extensible `file_id -> éditeur spécialisé`, une baseline Logging de release `info`/`warn`, et prépare `0.2.0` comme release intermédiaire d’audit de `khadhroony-bot3` et de planification du reste de `0.2.x`.
|
||||||
|
|||||||
29
Cargo.toml
29
Cargo.toml
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 92
|
# version: 191
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.4"
|
version = "0.2.6"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
@@ -14,20 +14,31 @@ authors = ["SinuS von SifriduS <sinus@sasedev.net>"]
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
argon2 = { version = "^0.5", default-features = false }
|
||||||
|
chacha20poly1305 = { version = "^0.11", default-features = false }
|
||||||
|
directories = { version = "^6.0" }
|
||||||
|
ed25519-dalek = { version = "^3.0", default-features = false }
|
||||||
|
getrandom = { version = "^0.4", default-features = false }
|
||||||
|
base64 = { version = "^0.23" }
|
||||||
fs2 = { version = "^0.4" }
|
fs2 = { version = "^0.4" }
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0" }
|
||||||
serde_json = { version = "^1.0" }
|
serde_json = { version = "^1.0" }
|
||||||
jsonschema = { version = "^0.49", default-features = false }
|
jsonschema = { version = "^0.50", default-features = false }
|
||||||
|
reqwest = { version = "^0.13", default-features = false }
|
||||||
|
solana-keypair = { version = "^3.1", default-features = false }
|
||||||
solana-pubkey = { version = "^4.3", default-features = false }
|
solana-pubkey = { version = "^4.3", default-features = false }
|
||||||
tracing = { version = "^0.1", default-features = false, features = ["std"] }
|
tracing = { version = "^0.1", default-features = false }
|
||||||
tracing-subscriber = { version = "^0.3", default-features = false, features = ["fmt", "json", "ansi"] }
|
tracing-subscriber = { version = "^0.3", default-features = false }
|
||||||
tracing-appender = { version = "^0.2", default-features = false }
|
tracing-appender = { version = "^0.2", default-features = false }
|
||||||
tokio = { version = "^1.53", default-features = false, features = ["rt", "rt-multi-thread", "macros", "time"] }
|
tokio = { version = "^1.53", default-features = false }
|
||||||
chrono = { version = "^0.4", default-features = false, features = ["std", "now"] }
|
tempfile = { version = "^3.27" }
|
||||||
|
chrono = { version = "^0.4", default-features = false }
|
||||||
tauri = { version = "^2.11" }
|
tauri = { version = "^2.11" }
|
||||||
tauri-build = { version = "^2.6" }
|
tauri-build = { version = "^2.6" }
|
||||||
|
tauri-plugin-dialog = { version = "^2.7" }
|
||||||
tauri-plugin-tracing = { version = "^0.3" }
|
tauri-plugin-tracing = { version = "^0.3" }
|
||||||
ts-rs = { version = "^12.0" }
|
ts-rs = { version = "^12.0" }
|
||||||
|
zeroize = { version = "^1.9" }
|
||||||
|
|
||||||
[workspace.lints.rust]
|
[workspace.lints.rust]
|
||||||
missing_docs = "warn"
|
missing_docs = "warn"
|
||||||
|
|||||||
188
ROADMAP.md
188
ROADMAP.md
@@ -1,11 +1,9 @@
|
|||||||
<!-- file: ROADMAP.md -->
|
<!-- file: ROADMAP.md -->
|
||||||
<!-- version: 20 -->
|
<!-- version: 80 -->
|
||||||
|
|
||||||
# Roadmap KSP
|
# Roadmap KSP
|
||||||
|
|
||||||
Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues pour y parvenir. Une série `X.Y.x` regroupe une famille fonctionnelle de travaux ; elle peut contenir plusieurs releases concrètes et plusieurs sessions.
|
Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. Une série `X.Y.x` regroupe une famille fonctionnelle ; chaque release concrète reste une unité de développement/session distincte.
|
||||||
|
|
||||||
Les décisions architecturales négatives ou de prudence n'apparaissent pas comme des tâches à cocher. Elles sont conservées dans les règles et documents d'architecture.
|
|
||||||
|
|
||||||
## Légende
|
## Légende
|
||||||
|
|
||||||
@@ -15,6 +13,14 @@ Les décisions architecturales négatives ou de prudence n'apparaissent pas comm
|
|||||||
- `[C]` — annulé ;
|
- `[C]` — annulé ;
|
||||||
- `[R]` — reporté.
|
- `[R]` — reporté.
|
||||||
|
|
||||||
|
## Discipline de livraison
|
||||||
|
|
||||||
|
- Une prerelease vise environ **15 à 20 minutes de travail effectif**.
|
||||||
|
- Une release concrète doit être dimensionnée pour pouvoir être ouverte et clôturée dans **une seule session de chat**.
|
||||||
|
- Cette règle fixe une durée maximale par release, pas une obligation de changer de session après chaque release : si une release est clôturée plus vite que prévu, la même session peut ouvrir puis clôturer la release suivante si son propre sizing reste positif et si toutes les frontières version/delta/validation sont conservées.
|
||||||
|
- Si `pre.001` révèle qu'une release est trop grosse, elle est scindée avant implémentation fonctionnelle lourde.
|
||||||
|
- À partir des couches Program/Decode, KSP progresse verticalement groupe par groupe plutôt que par grandes vagues horizontales de decoders/materializers/executors séparés.
|
||||||
|
|
||||||
## 0.0.x — Fondation
|
## 0.0.x — Fondation
|
||||||
|
|
||||||
- [X] `0.0.1` — Initialiser le dépôt.
|
- [X] `0.0.1` — Initialiser le dépôt.
|
||||||
@@ -25,97 +31,127 @@ Les décisions architecturales négatives ou de prudence n'apparaissent pas comm
|
|||||||
|
|
||||||
## 0.1.x — Fondations N1
|
## 0.1.x — Fondations N1
|
||||||
|
|
||||||
### Objectifs
|
- [X] `0.1.1` — `ksp-core-lib` : Error/Result, Program IDs fondamentaux et primitives N1.
|
||||||
|
- [X] `0.1.2` — `ksp-logging-lib` : façade KSP de tracing.
|
||||||
|
- [X] `0.1.3` — `ksp-config-lib` : documents, profils, environnement, persistence et adapters.
|
||||||
|
- [X] `0.1.4` — `ksp-app-config-desk` : première application Tauri de référence.
|
||||||
|
|
||||||
Regrouper les releases consacrées aux fondations N1. Chaque release concrète est une unité de développement/session distincte et commence par son propre `pre.001` de brainstorming/audit/planification.
|
## 0.2.x — Accès Solana, Wallet et contrats d'extension initiaux
|
||||||
|
|
||||||
### Releases concrètes
|
### Cadrage
|
||||||
|
|
||||||
- [X] `0.1.1` — Stabiliser `ksp-core-lib` : `Error`/`Result`, Program IDs fondamentaux et primitives réellement N1.
|
- [X] `0.2.0` — Audit bot3, ordre fonctionnel de `0.2.x`, architecture durable, discipline de sizing et pipeline RAW/CORE/DECODE/SPECIALIZED stabilisés.
|
||||||
- [X] `0.1.2` — Introduire `ksp-logging-lib` comme façade KSP de `tracing`, `tracing-appender` et `tracing-subscriber`.
|
- [X] `0.2.1` — HTTP foundation stable : matrice 52+14, runtime/routing/résilience/exécution HTTP, 4 canaris typés, `std.transport`, adapter Config -> Transport, canaries de clôture, smoke Devnet opt-in et documentation durable validés ; les 48 wrappers typés restants sont reportés à `0.2.2`–`0.2.4`.
|
||||||
- [X] `0.1.3` — Stabiliser `ksp-config-lib` : documents, profils, résolution, validation, environnement KSP/KSPB, management/persistence et adapter Logging.
|
|
||||||
- [X] `0.1.4` — Introduire `ksp-app-config-desk` pour valider réellement Config et la frontière Tauri.
|
|
||||||
|
|
||||||
`0.1.1`, `0.1.2`, `0.1.3` et `0.1.4` sont désormais stables. `0.1.4` publie `ksp-app-config-desk` comme première validation desktop/Tauri de Config et modèle de référence des futures applications Tauri KSP. La prochaine session est `0.2.0`, release intermédiaire d’audit de `khadhroony-bot3` et de planification du reste de `0.2.x`.
|
### Releases fonctionnelles décidées/pressenties
|
||||||
|
|
||||||
Les contrats publics supplémentaires ne sont introduits que lorsqu'une release concrète en démontre le besoin.
|
- [X] `0.2.1` — **HTTP transport foundation réduite par le gate `pre.001`** : crate/settings/JSON-RPC/registry 52 current + 14 deprecated historiques, pool/rôles/limites/retry, Config adapter, documentation et 4 méthodes typées canari (`getBalance`, `getGenesisHash`, `getHealth`, `getVersion`) publiés stables.
|
||||||
|
- [X] `0.2.2` — HTTP Accounts + Tokens + Cluster : 22 wrappers typés (5 Accounts + 5 Tokens + 12 Cluster), canaries de complétude 52+14, smoke Devnet Transport pur et smoke historique Config -> Transport validés, documentation durable et prompt `0.2.3` publiés stables.
|
||||||
|
- [X] `0.2.3` — HTTP Transactions stable : 11/11 wrappers typés publiés, classification `8 Read / 2 WriteSubmission / 1 Simulation`, no-resend ambigu prouvé pour les write submissions, `KSP-TRANSPORT-007` réaudité conforme sur les 37 wrappers HTTP courants, graphes Cargo et deux smokes Devnet validés ; `0.2.4` reprend les 15 Blocks/Economics restants.
|
||||||
|
- [X] `0.2.4` — HTTP Blocks + Economics stable : 15/15 wrappers `V0_2_4` publiés, surface typed complète à 52/52 méthodes courantes, 14/14 historiques conservées, réaudit SIMD/inventaire final et `KSP-TRANSPORT-007` global validés ; deux smokes Devnet passés avant publication.
|
||||||
|
- [X] `0.2.5` — Wallet foundation stable : `.kspwallet` V1, VIEW/OWNER indépendants, Argon2id/XChaCha20-Poly1305, autorité Ed25519 OWNER, persistence no-clobber, signature, administration/rotations/révocation VIEW forte, import/export Solana CLI JSON + Base58, canaris adversariaux, interop externe et documentation durable publiés. La clôture `pre.010-fix.001`–`fix.003` ajoute `ed25519-dalek 3.0.0` direct, normalise le Rust workspace et installe l’audit structurel Python complémentaire à rustfmt/Clippy. `Pubkey` reste via `ksp-core-lib`, la keypair reste encapsulée dans Wallet et Config/Transport/ExecutionPolicy/Store/Tauri restent hors Wallet.
|
||||||
|
- [X] `0.2.6` — `ksp-app-wallet-desk` + `.kspwallet` V2 stables : composition Config/Wallet/HTTP/Logging, lifecycle VIEW/OWNER, balance, administration/import/export, wire binaire V2, APIs multi-version, migration V1 -> V2 explicite et runtime Tauri packagé user-writable validés ; bundles Linux `.deb`/`.rpm`/`.AppImage` produits avant publication. Plan clôturé : `docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md`.
|
||||||
|
- [ ] `0.2.7` — Étendre `ksp-onchain-transport-lib` au WebSocket Solana standard complet ; permettre plusieurs sessions sur une même URL sans imposer encore un pool automatique complexe.
|
||||||
|
- [ ] `0.2.8` — Ajouter Helius LaserStream WebSocket comme extension du moteur WebSocket standard, sans duplication de client.
|
||||||
|
- [ ] `0.2.9` — Ajouter une première fondation Yellowstone gRPC standard/provider-neutral ; dimensionner la surface exacte à `pre.001` selon la documentation normative actuelle.
|
||||||
|
- [ ] `0.2.10` — Introduire `ksp-offchain-transport-lib` avec un premier lecteur de prix, au minimum SOL/USD et SOL/EUR.
|
||||||
|
- [ ] `0.2.11` — Introduire une petite application desk de visualisation/validation des prix offchain, puis intégrer cette capacité dans `ksp-app-wallet-desk` sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé.
|
||||||
|
- [ ] `0.2.12` — Introduire la première surface de `ksp-interface-lib`, comprenant une API wire publique utilisable par les implémentations officielles et externes.
|
||||||
|
- [ ] `0.2.13` — Introduire `ksp-program-api` comme premier contrat Program extensible, sans imposer encore `ksp-program-lib` complet.
|
||||||
|
|
||||||
## 0.2.x — Accès Solana et fondation programmes
|
### Règles Transport pour toute la série
|
||||||
|
|
||||||
### Release de cadrage `0.2.0`
|
- [ ] Couvrir toutes les méthodes/opérations documentées pour la surface normative ciblée par chaque release.
|
||||||
|
- [ ] Conserver les méthodes deprecated/obsolete encore fonctionnelles et émettre un `warn` KSP lors de leur utilisation.
|
||||||
|
- [ ] Implémenter les méthodes unstable/experimental ciblées et émettre un `warn` KSP lors de leur utilisation.
|
||||||
|
- [ ] Centraliser la metadata de statut des méthodes plutôt que disperser des warnings ad hoc.
|
||||||
|
- [ ] Garder `ksp-onchain-transport-lib` indépendant de `ksp-config-lib`, du Store et des modèles Program/métier.
|
||||||
|
|
||||||
- [ ] `0.2.0` — Auditer les fonctionnalités pertinentes de `khadhroony-bot3`, décider ce qui doit être repris, refondu, abandonné ou ajouté dans KSP, puis découper et ordonner les releases fonctionnelles restantes de `0.2.x`.
|
## Architecture de données — progression canonique
|
||||||
|
|
||||||
`0.2.0` est une release intermédiaire de transition et de planification de série. Elle ne doit pas démarrer par l'implémentation arbitraire d'un composant N2 : elle établit d'abord la cartographie fonctionnelle, les écarts avec KSP, les dépendances, les contrats à préserver ou redéfinir et le découpage concret de `0.2.1`, `0.2.2`, etc.
|
La chaîne durable cible est :
|
||||||
|
|
||||||
### Capacités à répartir dans les releases fonctionnelles suivantes
|
```text
|
||||||
|
RAW -> CORE -> DECODE -> SPECIALIZED
|
||||||
|
```
|
||||||
|
|
||||||
- [ ] Introduire `ksp-onchain-transport-lib` avec des modèles de transport homogènes indépendants du store.
|
- **RAW** et **CORE** ne nécessitent aucun décodage Program.
|
||||||
- [ ] Introduire `ksp-wallet-lib` et `ksp-app-wallet-desk`.
|
- À la fin de chaque couche horizontale RAW/CORE, ajouter les jobs/workers/apps nécessaires pour la rendre réellement exploitable avant d'ouvrir la couche suivante.
|
||||||
- [ ] Développer la première surface utile de `ksp-interface-lib`.
|
- À partir de **DECODE**, avancer verticalement groupe par groupe : wire -> decode -> matérialisation -> projection spécialisée si utile -> préparation d'exécution -> policy -> execution -> scénarios Devnet.
|
||||||
- [ ] Introduire `ksp-program-api` puis `ksp-program-lib`.
|
|
||||||
- [ ] Définir `ksp-execution-policy-api` comme contrat de policy commun à plusieurs contextes.
|
|
||||||
- [ ] Introduire `ksp-execution-lib` lorsque le premier cycle d'exécution réel justifie l'orchestration programme/policy/wallet/transport.
|
|
||||||
- [ ] Introduire `ksp-offchain-transport-lib` seulement au premier besoin réel.
|
|
||||||
|
|
||||||
## 0.3.x — Données, stockage et acquisition raw
|
## 0.3.x — RAW / acquisition persistée
|
||||||
|
|
||||||
- [ ] Introduire `ksp-materializer-api` / `ksp-materializer-lib`.
|
- [ ] `0.3.1` — Introduire `ksp-store-api` + `ksp-store-lib` avec PostgreSQL de référence et **modèles/persistence RAW uniquement**.
|
||||||
- [ ] Introduire `ksp-store-api` / `ksp-store-lib` avec PostgreSQL de référence.
|
- [ ] `0.3.2` — Étendre `ksp-interface-lib` avec les wires génériques nécessaires aux acquisitions et à la future normalisation CORE.
|
||||||
- [ ] Établir les niveaux durables D1 Raw, D2 Core, D3 journal de matérialisation générique et D4 projections de domaine.
|
- [ ] `0.3.3` — Introduire `ksp-job-api` et un job de backfill historique concret.
|
||||||
- [ ] Garantir des replays indépendants D1 -> D2, D2 -> D3 et D3 -> D4.
|
- [ ] `0.3.4` — Introduire une application spécialisée de backfill/inspection RAW.
|
||||||
- [ ] Introduire `ksp-app-store-desk`.
|
- [ ] Compléter ensuite la couche RAW avec le worker/service live, son contrôle et les outils d'exploitation réellement nécessaires avant de passer à CORE.
|
||||||
- [ ] Introduire `ksp-worker-api` et `ksp-worker-raw-retriever`.
|
|
||||||
- [ ] Introduire `ksp-worker-control-lib` lorsque le manager W1 crée le premier besoin concret.
|
|
||||||
- [ ] Introduire `ksp-job-api` et `ksp-job-backfill`.
|
|
||||||
- [ ] Introduire les pipelines spécialisés raw ingestion, Core processing, generic materialization et domain projection lorsque leurs premières frontières fonctionnelles sont développées.
|
|
||||||
- [ ] Introduire les jobs de replay indépendants D1 -> D2, D2 -> D3 et D3 -> D4.
|
|
||||||
- [ ] Normaliser les notifications de données persistées indépendamment de leur producteur et conserver le Store comme source de vérité du backlog.
|
|
||||||
- [ ] Mettre en place claim/lease, outcomes durables et reprise après crash pour les traitements concurrents.
|
|
||||||
|
|
||||||
## 0.4.x — Baseline Solana, SPL et metadata
|
## Série CORE suivante
|
||||||
|
|
||||||
- [ ] Ajouter progressivement les decoders et `ProgramExecutionPreparer` Core/SPL nécessaires.
|
- [ ] Définir la persistence CORE canonique Solana générique.
|
||||||
- [ ] Ajouter Token, Token-2022, ATA et metadata utiles.
|
- [ ] Implémenter `RAW -> CORE` sans decoder Program : blocs, slots, signatures, transactions/messages, comptes, instructions/CPI brutes, logs/meta et relations structurelles.
|
||||||
- [ ] Introduire `ksp-offchain-transport-lib` au plus tard au premier besoin externe.
|
- [ ] Ajouter replay/backfill RAW -> CORE.
|
||||||
- [ ] Ajouter materializers et jobs ponctuels nécessaires.
|
- [ ] Ajouter worker/service CORE.
|
||||||
- [ ] Ajouter les crates `ksp-scenario-<domain>-lib` spécialisées.
|
- [ ] Ajouter l'application de contrôle/inspection CORE utile.
|
||||||
- [ ] Ajouter les demos `ksp-app-scenario-<domain>-<environment>-desk-demo` correspondantes.
|
|
||||||
- [ ] Garder Memo, Token, ATA, Token-2022 séparés ; regrouper uniquement Metaplex Token Metadata + Token-2022 Metadata dans la famille metadata ; garder SPM séparé.
|
|
||||||
|
|
||||||
## 0.5.x — Anchor et protocoles trading
|
## Séries DECODE/SPECIALIZED/EXECUTION — progression verticale
|
||||||
|
|
||||||
- [ ] Introduire Anchor.
|
### Priorité 1 — Solana Core Programs
|
||||||
- [ ] Étendre Meteora par surfaces bornées.
|
|
||||||
- [ ] Étendre Raydium par surfaces bornées.
|
|
||||||
- [ ] Ajouter progressivement Pump, Orca, Jupiter, OKX et autres intégrations utiles.
|
|
||||||
- [ ] Ajouter interfaces, program implementations, materializers, jobs/scénarios/demos nécessaires pour chaque surface.
|
|
||||||
|
|
||||||
## 0.6.x — Processing autonome et orchestration
|
- [ ] Wire/decoding des programmes Core nécessaires transversalement.
|
||||||
|
- [ ] Matérialisation et projections utiles.
|
||||||
|
- [ ] Préparation d'exécution, policy et scénarios Devnet pour les opérations retenues.
|
||||||
|
|
||||||
- [ ] Introduire `ksp-worker-core-processor` pour D1 Raw -> D2 Core canonique.
|
### Priorité 2 — SPL token/trading
|
||||||
- [ ] Introduire `ksp-worker-generic-materializer` pour D2 Core -> D3 journal de matérialisation générique.
|
|
||||||
- [ ] Introduire `ksp-worker-domain-projector` pour D3 -> D4 projections spécialisées ; nom révisable.
|
|
||||||
- [ ] Exploiter les mêmes pipelines spécialisés pour les workers live et les jobs de replay afin d'éviter la duplication des frontières de processing.
|
|
||||||
- [ ] Étendre `ksp-worker-control-lib` à la gouvernance de plusieurs workers autonomes.
|
|
||||||
- [ ] Fournir pour chaque worker un mode service autonome, avec logique réutilisable séparée du binaire d'enveloppe.
|
|
||||||
- [ ] Construire d'abord les applications spécialisées nécessaires au développement, test et exploitation de chaque capacité.
|
|
||||||
- [ ] Garder jobs et workers sous des lifecycle APIs séparées.
|
|
||||||
|
|
||||||
## 0.7.x — Trading Intelligence
|
- [ ] SPL Token.
|
||||||
|
- [ ] Associated Token Account.
|
||||||
|
- [ ] Token-2022 et extensions pertinentes.
|
||||||
|
- [ ] Pour chaque famille : decode -> materialize -> specialized -> prepare -> policy -> execute -> scenarios.
|
||||||
|
|
||||||
- [ ] Statistiques et métriques.
|
### Priorité 3 — Metadata token
|
||||||
- [ ] Features et datasets historiques.
|
|
||||||
- [ ] Signaux et risque.
|
- [ ] Metaplex Token Metadata.
|
||||||
|
- [ ] Token-2022 Metadata.
|
||||||
|
- [R] Solana Program Metadata (SPM) — redéveloppement plus tard avec le décodage généraliste.
|
||||||
|
|
||||||
|
### Priorité 4 — Anchor
|
||||||
|
|
||||||
|
- [ ] Introduire les contrats et mécanismes Anchor nécessaires aux protocoles trading suivants.
|
||||||
|
|
||||||
|
### Priorité 5 — DEX à fort intérêt
|
||||||
|
|
||||||
|
- [ ] Meteora, y compris vaults/fees/positions/états auxiliaires nécessaires à son groupe.
|
||||||
|
- [ ] Raydium, y compris programmes satellites nécessaires.
|
||||||
|
- [ ] Pump, y compris fee program et composants launch/bonding/pool nécessaires.
|
||||||
|
- [ ] Orca, y compris programmes satellites nécessaires.
|
||||||
|
- [ ] Chaque groupe est terminé verticalement avant de devenir secondaire au profit du suivant.
|
||||||
|
|
||||||
|
### Market Desk V1
|
||||||
|
|
||||||
|
- [ ] Après les premiers groupes Meteora/Raydium/Pump/Orca, introduire une petite `ksp-app-market-desk` spécialisée.
|
||||||
|
- [ ] Visualiser tokens, pools/markets, liquidité, swaps/trades, prix, volumes, OHLC/candles et activité live/récente lorsque disponible.
|
||||||
|
- [ ] Lire les projections SPECIALIZED KSP ; ne pas reconstruire la logique protocolaire dans l'UI.
|
||||||
|
|
||||||
|
### Routing
|
||||||
|
|
||||||
|
- [ ] Jupiter.
|
||||||
|
- [ ] OKX et autres routeurs selon besoin réel.
|
||||||
|
- [ ] Enrichir Market Desk avec routes, legs, DEX impliqués, fees/slippage et comparaison quote/execution lorsque disponible.
|
||||||
|
|
||||||
|
### Trading-adjacent puis décodage généraliste
|
||||||
|
|
||||||
|
- [ ] Ajouter ensuite les programmes indépendants utiles au trading : oracles, locks/vesting indépendants, lifecycle token, risk/signaux, etc.
|
||||||
|
- [ ] Ne jamais y repousser un satellite appartenant à un groupe DEX déjà ciblé.
|
||||||
|
- [ ] Étendre enfin le décodage au reste de Solana selon valeur fonctionnelle.
|
||||||
|
|
||||||
|
## Trading Intelligence et produits ultérieurs
|
||||||
|
|
||||||
|
- [ ] Statistiques/features/datasets historiques.
|
||||||
|
- [ ] Signaux, risque, anomalies et patterns.
|
||||||
- [ ] Replay analytique/backtests.
|
- [ ] Replay analytique/backtests.
|
||||||
- [ ] Patterns/anomalies.
|
- [ ] XGBoost puis autres modèles lorsque les données et contrats sont stables.
|
||||||
- [ ] XGBoost puis autres modèles lorsque les contrats sont stables.
|
- [ ] Construire ensuite les produits de trading opérationnel au-dessus de ces couches.
|
||||||
|
- [ ] Faire évoluer Market Desk vers davantage d'analyse sans la confondre avec l'application globale ou l'orchestrateur.
|
||||||
## 0.8.x et suivantes — Trading opérationnel et expansion produits
|
- [ ] Étudier plus tard les autres applications Wallet : mobile, extensions navigateur et web.
|
||||||
|
|
||||||
- [ ] Construire les couches puis l'application de trading monoposte au-dessus de Trading Intelligence.
|
|
||||||
- [ ] Étendre l'automatisation de trading.
|
|
||||||
- [ ] Étendre continuellement Program IDs, decoders, execution preparers et materializers.
|
|
||||||
- [ ] Construire progressivement l'explorer Solana.
|
|
||||||
- [ ] Construire progressivement l'explorer/analyse DEX.
|
|
||||||
- [ ] Étudier plus tard d'autres applications utilisant `ksp-wallet-lib`, notamment mobile, extensions navigateur et web.
|
|
||||||
|
|||||||
66
config/composite.ksp-app-wallet-desk.json
Normal file
66
config/composite.ksp-app-wallet-desk.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"default_profile": "devnet",
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": "devnet",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"component_id": "logging",
|
||||||
|
"file_id": "cfg.std.logging",
|
||||||
|
"profile_id": "supertrace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "transport",
|
||||||
|
"file_id": "cfg.std.transport",
|
||||||
|
"profile_id": "devnet_public"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "wallet",
|
||||||
|
"file_id": "cfg.std.wallet",
|
||||||
|
"profile_id": "default"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "temporary",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"component_id": "logging",
|
||||||
|
"file_id": "cfg.std.logging",
|
||||||
|
"profile_id": "supertrace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "transport",
|
||||||
|
"file_id": "cfg.std.transport",
|
||||||
|
"profile_id": "devnet_public"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "wallet",
|
||||||
|
"file_id": "cfg.std.wallet",
|
||||||
|
"profile_id": "temporary"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "tests",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"component_id": "logging",
|
||||||
|
"file_id": "cfg.std.logging",
|
||||||
|
"profile_id": "supertrace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "transport",
|
||||||
|
"file_id": "cfg.std.transport",
|
||||||
|
"profile_id": "devnet_public"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "wallet",
|
||||||
|
"file_id": "cfg.std.wallet",
|
||||||
|
"profile_id": "tests"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
66
config/examples/composite.ksp-app-wallet-desk.example.json
Normal file
66
config/examples/composite.ksp-app-wallet-desk.example.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"default_profile": "devnet",
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": "devnet",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"component_id": "logging",
|
||||||
|
"file_id": "cfg.std.logging",
|
||||||
|
"profile_id": "supertrace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "transport",
|
||||||
|
"file_id": "cfg.std.transport",
|
||||||
|
"profile_id": "devnet_public"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "wallet",
|
||||||
|
"file_id": "cfg.std.wallet",
|
||||||
|
"profile_id": "default"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "temporary",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"component_id": "logging",
|
||||||
|
"file_id": "cfg.std.logging",
|
||||||
|
"profile_id": "supertrace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "transport",
|
||||||
|
"file_id": "cfg.std.transport",
|
||||||
|
"profile_id": "devnet_public"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "wallet",
|
||||||
|
"file_id": "cfg.std.wallet",
|
||||||
|
"profile_id": "temporary"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "tests",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"component_id": "logging",
|
||||||
|
"file_id": "cfg.std.logging",
|
||||||
|
"profile_id": "supertrace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "transport",
|
||||||
|
"file_id": "cfg.std.transport",
|
||||||
|
"profile_id": "devnet_public"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "wallet",
|
||||||
|
"file_id": "cfg.std.wallet",
|
||||||
|
"profile_id": "tests"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -61,4 +61,4 @@
|
|||||||
"target_filters": []
|
"target_filters": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
64
config/examples/std.transport.example.json
Normal file
64
config/examples/std.transport.example.json
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"retry": {
|
||||||
|
"max_retries": 3,
|
||||||
|
"initial_backoff_ms": 150,
|
||||||
|
"max_backoff_ms": 3000
|
||||||
|
},
|
||||||
|
"default_profile": "mainnet_mixed",
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": "mainnet_mixed",
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"name": "mainnet_public",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "solana-public",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}",
|
||||||
|
"connect_timeout_ms": 5000,
|
||||||
|
"request_timeout_ms": 15000,
|
||||||
|
"max_idle_connections_per_host": 8,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"request_kinds": ["*"],
|
||||||
|
"priority": 200,
|
||||||
|
"limits": {
|
||||||
|
"requests_per_second": 5,
|
||||||
|
"burst_capacity": 10,
|
||||||
|
"max_concurrent_requests": 8,
|
||||||
|
"pause_after_rate_limit_ms": 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mainnet_private",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "private-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"url": "${KSP_SECRET_SOLANA_HTTP_URL:-https://example.invalid}",
|
||||||
|
"connect_timeout_ms": 5000,
|
||||||
|
"request_timeout_ms": 10000,
|
||||||
|
"max_idle_connections_per_host": 16,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"request_kinds": ["*"],
|
||||||
|
"priority": 100,
|
||||||
|
"limits": {
|
||||||
|
"requests_per_second": 20,
|
||||||
|
"burst_capacity": 40,
|
||||||
|
"max_concurrent_requests": 16,
|
||||||
|
"pause_after_rate_limit_ms": 750
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
config/examples/std.wallet.example.json
Normal file
18
config/examples/std.wallet.example.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"wallets_directory": "${KSP_WALLETS_DIRECTORY:-wallets}",
|
||||||
|
"default_profile": "tests",
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "scenarios",
|
||||||
|
"wallets_subdirectory": "scenarios/ephemeral"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "tests",
|
||||||
|
"wallets_subdirectory": "tests"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -263,4 +263,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
config/schemas/std.transport.schema.json
Normal file
124
config/schemas/std.transport.schema.json
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "urn:ksp:schema:std.transport:v1",
|
||||||
|
"title": "KSP standard HTTP Transport configuration",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["format_version", "retry", "default_profile", "profiles"],
|
||||||
|
"properties": {
|
||||||
|
"format_version": {"const": 1},
|
||||||
|
"retry": {"$ref": "#/$defs/retry"},
|
||||||
|
"default_profile": {"$ref": "#/$defs/profileId"},
|
||||||
|
"profiles": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {"$ref": "#/$defs/profile"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$defs": {
|
||||||
|
"profileId": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
||||||
|
},
|
||||||
|
"descriptor": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"pattern": "^\\S(?:.*\\S)?$"
|
||||||
|
},
|
||||||
|
"positiveMs": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 4294967295
|
||||||
|
},
|
||||||
|
"positiveU32": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 4294967295
|
||||||
|
},
|
||||||
|
"retry": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["max_retries", "initial_backoff_ms", "max_backoff_ms"],
|
||||||
|
"properties": {
|
||||||
|
"max_retries": {"type": "integer", "minimum": 0, "maximum": 100},
|
||||||
|
"initial_backoff_ms": {"$ref": "#/$defs/positiveMs"},
|
||||||
|
"max_backoff_ms": {"$ref": "#/$defs/positiveMs"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"limits": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"requests_per_second": {"$ref": "#/$defs/positiveU32"},
|
||||||
|
"burst_capacity": {"$ref": "#/$defs/positiveU32"},
|
||||||
|
"max_concurrent_requests": {"$ref": "#/$defs/positiveU32"},
|
||||||
|
"pause_after_rate_limit_ms": {"$ref": "#/$defs/positiveMs"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["role", "enabled", "request_kinds", "priority", "limits"],
|
||||||
|
"properties": {
|
||||||
|
"role": {"$ref": "#/$defs/descriptor"},
|
||||||
|
"enabled": {"type": "boolean"},
|
||||||
|
"request_kinds": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": {"$ref": "#/$defs/descriptor"},
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"if": {"contains": {"const": "*"}},
|
||||||
|
"then": {"maxItems": 1}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"priority": {"type": "integer", "minimum": 0, "maximum": 4294967295},
|
||||||
|
"limits": {"$ref": "#/$defs/limits"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"enabled",
|
||||||
|
"provider",
|
||||||
|
"cluster",
|
||||||
|
"url",
|
||||||
|
"connect_timeout_ms",
|
||||||
|
"request_timeout_ms",
|
||||||
|
"roles"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {"$ref": "#/$defs/descriptor"},
|
||||||
|
"enabled": {"type": "boolean"},
|
||||||
|
"provider": {"$ref": "#/$defs/descriptor"},
|
||||||
|
"cluster": {"$ref": "#/$defs/descriptor"},
|
||||||
|
"url": {"type": "string", "minLength": 1},
|
||||||
|
"connect_timeout_ms": {"$ref": "#/$defs/positiveMs"},
|
||||||
|
"request_timeout_ms": {"$ref": "#/$defs/positiveMs"},
|
||||||
|
"max_idle_connections_per_host": {"type": "integer", "minimum": 1},
|
||||||
|
"roles": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {"$ref": "#/$defs/role"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["profile_id", "endpoints"],
|
||||||
|
"properties": {
|
||||||
|
"profile_id": {"$ref": "#/$defs/profileId"},
|
||||||
|
"endpoints": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {"$ref": "#/$defs/endpoint"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
config/schemas/std.wallet.schema.json
Normal file
54
config/schemas/std.wallet.schema.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "urn:ksp:schema:std.wallet:v1",
|
||||||
|
"title": "KSP standard Wallet configuration",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"format_version",
|
||||||
|
"wallets_directory",
|
||||||
|
"default_profile",
|
||||||
|
"profiles"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"format_version": {
|
||||||
|
"const": 1
|
||||||
|
},
|
||||||
|
"wallets_directory": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"default_profile": {
|
||||||
|
"$ref": "#/$defs/profileId"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/profile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$defs": {
|
||||||
|
"profileId": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-z0-9][a-z0-9._-]*$"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"profile_id"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"profile_id": {
|
||||||
|
"$ref": "#/$defs/profileId"
|
||||||
|
},
|
||||||
|
"wallets_subdirectory": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,156 @@
|
|||||||
"logs_directory": "${KSP_LOGS_DIRECTORY:-logs}",
|
"logs_directory": "${KSP_LOGS_DIRECTORY:-logs}",
|
||||||
"default_profile": "local_dev",
|
"default_profile": "local_dev",
|
||||||
"profiles": [
|
"profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": "console_error",
|
||||||
|
"default_filter": "error",
|
||||||
|
"span_events": "new_and_close",
|
||||||
|
"console": {
|
||||||
|
"enabled": true,
|
||||||
|
"output": "stderr",
|
||||||
|
"ansi": true,
|
||||||
|
"format": "compact",
|
||||||
|
"filter": {
|
||||||
|
"level": "error",
|
||||||
|
"targets": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"target_filters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "console_warn",
|
||||||
|
"default_filter": "warn",
|
||||||
|
"span_events": "new_and_close",
|
||||||
|
"console": {
|
||||||
|
"enabled": true,
|
||||||
|
"output": "stderr",
|
||||||
|
"ansi": true,
|
||||||
|
"format": "compact",
|
||||||
|
"filter": {
|
||||||
|
"level": "warn",
|
||||||
|
"targets": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"target_filters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "console_info",
|
||||||
|
"default_filter": "info",
|
||||||
|
"span_events": "new_and_close",
|
||||||
|
"console": {
|
||||||
|
"enabled": true,
|
||||||
|
"output": "stderr",
|
||||||
|
"ansi": true,
|
||||||
|
"format": "compact",
|
||||||
|
"filter": {
|
||||||
|
"level": "info",
|
||||||
|
"targets": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"target_filters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "console_debug",
|
||||||
|
"default_filter": "debug",
|
||||||
|
"span_events": "new_and_close",
|
||||||
|
"console": {
|
||||||
|
"enabled": true,
|
||||||
|
"output": "stderr",
|
||||||
|
"ansi": true,
|
||||||
|
"format": "compact",
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"target_filters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "console_trace",
|
||||||
|
"default_filter": "trace",
|
||||||
|
"span_events": "new_and_close",
|
||||||
|
"console": {
|
||||||
|
"enabled": true,
|
||||||
|
"output": "stderr",
|
||||||
|
"ansi": true,
|
||||||
|
"format": "compact",
|
||||||
|
"filter": {
|
||||||
|
"level": "trace",
|
||||||
|
"targets": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"target_filters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "file_info",
|
||||||
|
"default_filter": "info",
|
||||||
|
"span_events": "new_and_close",
|
||||||
|
"console": {
|
||||||
|
"enabled": false,
|
||||||
|
"output": "stderr",
|
||||||
|
"ansi": false,
|
||||||
|
"format": "compact",
|
||||||
|
"filter": {
|
||||||
|
"level": "off",
|
||||||
|
"targets": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"output_id": "file.all.info",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "info/ksp-info.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "info",
|
||||||
|
"targets": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"target_filters": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"profile_id": "local_dev",
|
"profile_id": "local_dev",
|
||||||
"default_filter": "warn",
|
"default_filter": "warn",
|
||||||
@@ -40,6 +190,23 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.onchain_transport.info",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "transport/onchain/ksp-onchain-transport.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "info",
|
||||||
|
"targets": [
|
||||||
|
"ksp-onchain-transport-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"output_id": "file.config.error",
|
"output_id": "file.config.error",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
@@ -70,8 +237,296 @@
|
|||||||
{
|
{
|
||||||
"target_prefix": "ksp-app-config-desk",
|
"target_prefix": "ksp-app-config-desk",
|
||||||
"level": "info"
|
"level": "info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target_prefix": "ksp-onchain-transport-lib",
|
||||||
|
"level": "info"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "superdev",
|
||||||
|
"default_filter": "debug",
|
||||||
|
"span_events": "new_and_close",
|
||||||
|
"console": {
|
||||||
|
"enabled": true,
|
||||||
|
"output": "stderr",
|
||||||
|
"ansi": true,
|
||||||
|
"format": "compact",
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_app_config_desk.debug",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/debug/ksp-app-config-desk.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"ksp-app-config-desk"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_app_wallet_desk.debug",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/debug/ksp-app-wallet-desk.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"ksp-app-wallet-desk"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_config_lib.debug",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/debug/ksp-config-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"ksp-config-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_core_lib.debug",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/debug/ksp-core-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"ksp-core-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_logging_lib.debug",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/debug/ksp-logging-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"ksp-logging-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_onchain_transport_lib.debug",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/debug/ksp-onchain-transport-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"ksp-onchain-transport-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_wallet_lib.debug",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/debug/ksp-wallet-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"ksp-wallet-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"target_filters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "supertrace",
|
||||||
|
"default_filter": "trace",
|
||||||
|
"span_events": "new_and_close",
|
||||||
|
"console": {
|
||||||
|
"enabled": true,
|
||||||
|
"output": "stderr",
|
||||||
|
"ansi": true,
|
||||||
|
"format": "compact",
|
||||||
|
"filter": {
|
||||||
|
"level": "debug",
|
||||||
|
"targets": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_app_config_desk.trace",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/trace/ksp-app-config-desk.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "trace",
|
||||||
|
"targets": [
|
||||||
|
"ksp-app-config-desk"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_app_wallet_desk.trace",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/trace/ksp-app-wallet-desk.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "trace",
|
||||||
|
"targets": [
|
||||||
|
"ksp-app-wallet-desk"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_config_lib.trace",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/trace/ksp-config-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "trace",
|
||||||
|
"targets": [
|
||||||
|
"ksp-config-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_core_lib.trace",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/trace/ksp-core-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "trace",
|
||||||
|
"targets": [
|
||||||
|
"ksp-core-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_logging_lib.trace",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/trace/ksp-logging-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "trace",
|
||||||
|
"targets": [
|
||||||
|
"ksp-logging-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_onchain_transport_lib.trace",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/trace/ksp-onchain-transport-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "trace",
|
||||||
|
"targets": [
|
||||||
|
"ksp-onchain-transport-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"output_id": "file.ksp_wallet_lib.trace",
|
||||||
|
"enabled": true,
|
||||||
|
"path": "targets/trace/ksp-wallet-lib.log",
|
||||||
|
"rotation": "daily",
|
||||||
|
"format": "human",
|
||||||
|
"ansi": false,
|
||||||
|
"filter": {
|
||||||
|
"level": "trace",
|
||||||
|
"targets": [
|
||||||
|
"ksp-wallet-lib"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"target_filters": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
69
config/std.transport.json
Normal file
69
config/std.transport.json
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"retry": {
|
||||||
|
"max_retries": 2,
|
||||||
|
"initial_backoff_ms": 100,
|
||||||
|
"max_backoff_ms": 2000
|
||||||
|
},
|
||||||
|
"default_profile": "devnet_public",
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": "devnet_public",
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"name": "solana_devnet_public",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "solana-public",
|
||||||
|
"cluster": "devnet",
|
||||||
|
"url": "${KSP_PUBLIC_SOLANA_DEVNET_HTTP_URL:-https://api.devnet.solana.com}",
|
||||||
|
"connect_timeout_ms": 5000,
|
||||||
|
"request_timeout_ms": 15000,
|
||||||
|
"max_idle_connections_per_host": 8,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"request_kinds": ["*"],
|
||||||
|
"priority": 100,
|
||||||
|
"limits": {
|
||||||
|
"requests_per_second": 5,
|
||||||
|
"burst_capacity": 10,
|
||||||
|
"max_concurrent_requests": 8,
|
||||||
|
"pause_after_rate_limit_ms": 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "mainnet_public",
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"name": "solana_mainnet_public",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "solana-public",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"url": "${KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL:-https://api.mainnet-beta.solana.com}",
|
||||||
|
"connect_timeout_ms": 5000,
|
||||||
|
"request_timeout_ms": 15000,
|
||||||
|
"max_idle_connections_per_host": 8,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"request_kinds": ["*"],
|
||||||
|
"priority": 100,
|
||||||
|
"limits": {
|
||||||
|
"requests_per_second": 5,
|
||||||
|
"burst_capacity": 10,
|
||||||
|
"max_concurrent_requests": 8,
|
||||||
|
"pause_after_rate_limit_ms": 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
config/std.wallet.json
Normal file
18
config/std.wallet.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"wallets_directory": "${KSP_WALLETS_DIRECTORY:-wallets}",
|
||||||
|
"default_profile": "default",
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "temporary",
|
||||||
|
"wallets_subdirectory": "temporary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profile_id": "tests",
|
||||||
|
"wallets_subdirectory": "tests"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# file: crates/ksp-app-config-desk/Cargo.toml
|
# file: crates/ksp-app-config-desk/Cargo.toml
|
||||||
# version: 7
|
# version: 8
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "ksp-app-config-desk"
|
name = "ksp-app-config-desk"
|
||||||
@@ -26,12 +26,12 @@ fs2.workspace = true
|
|||||||
ksp-config-lib = { path = "../ksp-config-lib" }
|
ksp-config-lib = { path = "../ksp-config-lib" }
|
||||||
ksp-core-lib = { path = "../ksp-core-lib" }
|
ksp-core-lib = { path = "../ksp-core-lib" }
|
||||||
ksp-logging-lib = { path = "../ksp-logging-lib" }
|
ksp-logging-lib = { path = "../ksp-logging-lib" }
|
||||||
serde.workspace = true
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tauri.workspace = true
|
tauri.workspace = true
|
||||||
tauri-plugin-tracing.workspace = true
|
tauri-plugin-tracing.workspace = true
|
||||||
chrono.workspace = true
|
chrono = { workspace = true, features = ["std", "now"] }
|
||||||
tokio.workspace = true
|
tokio = { workspace = true, features = ["time"] }
|
||||||
ts-rs.workspace = true
|
ts-rs.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/README.md -->
|
<!-- file: crates/ksp-app-config-desk/README.md -->
|
||||||
<!-- version: 23 -->
|
<!-- version: 27 -->
|
||||||
|
|
||||||
# `ksp-app-config-desk`
|
# `ksp-app-config-desk`
|
||||||
|
|
||||||
@@ -82,14 +82,14 @@ Les DTO Rust applicatifs restent la source de vérité et les bindings généré
|
|||||||
|
|
||||||
Le frontend possède désormais `shell_registry.ts`. Le registre décrit les vues du shell et les adapters d'éditeurs spécialisés par `file_id`. Le panneau Documents reste générique ; lorsqu'un document possède un adapter enregistré, **Ouvrir l'éditeur spécialisé** déclenche la navigation par événement de registre. `cfg.std.logging -> Logging` est le premier adapter. Ajouter un futur éditeur ne demande donc pas de réécrire le moteur Documents ni la logique générale d'activation des panneaux.
|
Le frontend possède désormais `shell_registry.ts`. Le registre décrit les vues du shell et les adapters d'éditeurs spécialisés par `file_id`. Le panneau Documents reste générique ; lorsqu'un document possède un adapter enregistré, **Ouvrir l'éditeur spécialisé** déclenche la navigation par événement de registre. `cfg.std.logging -> Logging` est le premier adapter. Ajouter un futur éditeur ne demande donc pas de réécrire le moteur Documents ni la logique générale d'activation des panneaux.
|
||||||
|
|
||||||
Deux audits d'intégration applicatifs complètent les audits Config/Logging existants : contrat Tauri/Vite/package scripts, centralisation des commandes Tauri, absence de `tauri-plugin-log`, interdiction des dialogues navigateur natifs et absence de stockage persistant pour le reveal Secret. Le script frontend `npm run check` exécute uniquement `tsc --noEmit`. Le build Vite de production n’est pas lancé séparément : il appartient au `beforeBuildCommand` de `cargo tauri build`, qui reste la dernière validation après tous les contrôles Rust, le type-check frontend et le parcours fonctionnel `tauri dev`.
|
Deux audits d'intégration applicatifs complètent les audits Config/Logging existants : contrat Tauri/Vite/package scripts, centralisation des commandes Tauri, absence de `tauri-plugin-log`, interdiction des dialogues navigateur natifs et absence de stockage persistant pour le reveal Secret. Aucun script frontend standalone n’est lancé par l’opérateur : Tauri possède le cycle dev/build et déclenche les hooks npm de la crate. Le build Vite/TypeScript de production appartient au `beforeBuildCommand` du `cargo tauri build` crate-local, qui reste la dernière validation après tous les contrôles Rust et le parcours fonctionnel `cargo tauri dev`.
|
||||||
|
|
||||||
## Artefacts frontend
|
## Artefacts frontend
|
||||||
|
|
||||||
Les artefacts frontend construits ne sont pas versionnés. `tauri.conf.json` fixe :
|
Les artefacts frontend construits ne sont pas versionnés. `tauri.conf.json` fixe :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
../../builds/khadhroony-solana-project/ksp-app-config-desk/dist
|
../../../builds/khadhroony-solana-project/ksp-app-config-desk/dist
|
||||||
```
|
```
|
||||||
|
|
||||||
`vite.config.ts` résout cette même destination depuis la racine de la crate, ce qui maintient `dist` hors du workspace source et l'aligne avec la stratégie `.cargo/config.toml` pour les artefacts Rust.
|
`vite.config.ts` résout cette même destination depuis la racine de la crate, ce qui maintient `dist` hors du workspace source et l'aligne avec la stratégie `.cargo/config.toml` pour les artefacts Rust.
|
||||||
@@ -109,8 +109,7 @@ Le port HTTP est strict. Les futures applications desk utilisent les couples sui
|
|||||||
|
|
||||||
Les bibliothèques consommées par le bundle applicatif sont déclarées sous `dependencies` : `@fltsci/tauri-plugin-tracing`, `@fortawesome/fontawesome-free`, `@tauri-apps/api`, `bootstrap`, `simplebar` et `resize-observer-polyfill`.
|
Les bibliothèques consommées par le bundle applicatif sont déclarées sous `dependencies` : `@fltsci/tauri-plugin-tracing`, `@fortawesome/fontawesome-free`, `@tauri-apps/api`, `bootstrap`, `simplebar` et `resize-observer-polyfill`.
|
||||||
|
|
||||||
Les outils de compilation/développement et déclarations de types appartiennent à `devDependencies`, notamment `@tauri-apps/cli`, `@types/bootstrap`, `@types/node`, `sass-embedded`, `typescript` et `vite`. Les commandes npm directes sont réservées à l'installation ou à la mise à jour de ces dépendances ; le cycle normal de développement et de build est lancé depuis la racine du workspace par Tauri avec la configuration explicite de l'application.
|
Les outils de compilation/développement et déclarations de types appartiennent à `devDependencies`, notamment `@tauri-apps/cli`, `@types/bootstrap`, `@types/node`, `sass-embedded`, `typescript` et `vite`. Les commandes npm directes sont réservées à l'installation ou à la mise à jour de ces dépendances. Le cycle normal de développement et de build est **crate-local** : l'opérateur peut rester positionné à la racine du workspace, mais la commande exécute d'abord `cd crates/ksp-app-config-desk` puis `cargo tauri dev`/`build`. `-c/--config` n'est pas utilisé comme sélecteur de crate.
|
||||||
|
|
||||||
|
|
||||||
## Logging frontend KSP
|
## Logging frontend KSP
|
||||||
|
|
||||||
@@ -134,26 +133,32 @@ Rust valide le niveau et le `targetId`, choisit un callsite statique puis émet
|
|||||||
|
|
||||||
## Bootstrap Config et Logging
|
## Bootstrap Config et Logging
|
||||||
|
|
||||||
Au démarrage, l'application construit `ConfigBootstrapOptions`, `ConfigFileRegistry`, `ConfigDocumentEngine` et `ConfigManagement` à partir des arguments complets du processus. En développement, le launcher normalise d'abord le current working directory Rust vers la racine du workspace : `cargo tauri dev -c ...` peut sinon lancer le binaire depuis la crate Tauri, ce qui ferait manquer les chemins Config relatifs `config/`, `config/schemas/` et le `.env` racine. Cette normalisation ne lit ni ne parse aucun fichier Config elle-même ; l'ownership reste intégralement dans `ksp-config-lib`. Elle tente ensuite de résoudre le profil Logging par défaut avec un snapshot `ConfigEnvironment` frais.
|
Au démarrage, l'application construit `ConfigBootstrapOptions`, `ConfigFileRegistry`, `ConfigDocumentEngine` et `ConfigManagement` à partir des arguments complets du processus. En développement, le launcher normalise d'abord le current working directory Rust vers la racine du workspace : `cargo tauri dev` est volontairement lancé depuis la crate Tauri, ce qui ferait manquer les chemins Config relatifs `config/`, `config/schemas/` et le `.env` racine. Cette normalisation ne lit ni ne parse aucun fichier Config elle-même ; l'ownership reste intégralement dans `ksp-config-lib`. Elle tente ensuite de résoudre le profil Logging par défaut avec un snapshot `ConfigEnvironment` frais.
|
||||||
|
|
||||||
Si le document Logging, sa résolution environnementale ou l'initialisation du runtime configuré échoue avant installation du subscriber, Config Desk installe un fallback **transitoire** en mémoire : console stderr, niveau `Info`, aucun fichier, aucun span lifecycle. Ce fallback n'est jamais persisté et son diagnostic frontend est limité à `domain`, `code` et `message`.
|
Si le document Logging, sa résolution environnementale ou l'initialisation du runtime configuré échoue avant installation du subscriber, Config Desk installe un fallback **transitoire** en mémoire : console stderr, niveau `Info`, aucun fichier, aucun span lifecycle. Ce fallback n'est jamais persisté et son diagnostic frontend est limité à `domain`, `code` et `message`.
|
||||||
|
|
||||||
Le `LoggingGuard` est conservé dans `AppState` et sert désormais au hot reload transactionnel du runtime Logging après sauvegarde.
|
Le `LoggingGuard` est conservé dans `AppState` et sert désormais au hot reload transactionnel du runtime Logging après sauvegarde.
|
||||||
|
|
||||||
|
## Runtime packagé
|
||||||
|
|
||||||
|
Le parcours release ne dépend pas du checkout source ni du répertoire depuis lequel le binaire est lancé. Les documents Config et schemas enregistrés sont embarqués comme resources Tauri. Avant `AppState::initialize`, `tauri.rs` résout le répertoire de resources puis délègue à `ksp-config-lib::prepare_packaged_runtime`. Config résout un répertoire de données utilisateur KSP writable via `ProjectDirs`, y seed les documents Config absents et y synchronise les schemas possédés par le package courant. Le process adopte ensuite ce répertoire writable comme current working directory.
|
||||||
|
|
||||||
|
Les documents Config déjà présents restent user-managed et ne sont jamais écrasés silencieusement lors d'une mise à jour. Les schemas sont resynchronisés à chaque lancement packagé. Le `.env` n'est jamais embarqué ni seedé ; les chemins relatifs `logs/` et `wallets/` restent ancrés dans ce runtime partagé sauf override Config. Config Desk et Wallet Desk utilisent le même identifiant de projet KSP afin de partager cette racine runtime sans dupliquer l'ownership des ressources.
|
||||||
|
|
||||||
## Lifecycle des fenêtres
|
## Lifecycle des fenêtres
|
||||||
|
|
||||||
`tw_splash.rs` valide l'origine de `splash_frontend_ready`, ignore les readiness dupliquées et orchestre le passage à `main`. Les timings sont résolus par `ksp-config-lib` depuis :
|
`tw_splash.rs` valide l'origine de `splash_frontend_ready`, ignore les readiness dupliquées et orchestre le passage à `main`. Les timings sont résolus par `ksp-config-lib` depuis :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||||
KSP_DESK_SPLASH_FADE_MS=300
|
KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||||
|
KSP_DESK_SPLASH_FADE_OUT_MS=300
|
||||||
```
|
```
|
||||||
|
|
||||||
Après la readiness frontend, Rust émet `fade_in`, attend la durée minimale, émet `fade_out`, attend la durée de fade puis `tw_main.rs` affiche/focalise `main` avant destruction du splash. Le backend journalise en `debug` les valeurs résolues, leur provenance (`process`, `dotenv` ou `fallback`), chaque attente réellement observée et la durée totale. Avec `12000/3000`, la durée backend attendue entre readiness et activation de `main` est donc d'environ `15000 ms`. Une valeur splash invalide ne bloque pas le manager : des timings de fallback sûrs restent en mémoire afin que la future surface `.env` puisse permettre la réparation.
|
Après la readiness frontend, Rust émet `fade_in`, attend la durée minimale, émet `fade_out`, attend la durée de fade puis `tw_main.rs` affiche/focalise `main` avant destruction du splash. Le backend journalise en `debug` les valeurs résolues, leur provenance (`process`, `dotenv` ou `fallback`), chaque attente réellement observée et la durée totale. Avec `12000/3000`, la durée backend attendue entre readiness et activation de `main` est donc d'environ `15000 ms`. Une valeur splash invalide ne bloque pas le manager : des timings de fallback sûrs restent en mémoire afin que la future surface `.env` puisse permettre la réparation.
|
||||||
|
|
||||||
Le shell principal expose les cinq routes de référence `Vue d'ensemble`, `Documents`, `Profils`, `Environnement / .env` et `Logging`. Le logo porte déjà l'identité KSP ; le texte du header suit donc la forme `Config Desk — <vue active>` au lieu de répéter `KSP`. Les quelques commandes principales restent des pills/tabs à droite ; un dropdown sera préféré lorsqu'une application possède trop de commandes pour conserver ce format lisible. La route Logging expose maintenant un brouillon typé éditable, sa persistence atomique et l’application runtime immédiate par hot reload.
|
Le shell principal expose les cinq routes de référence `Vue d'ensemble`, `Documents`, `Profils`, `Environnement / .env` et `Logging`. Le logo porte déjà l'identité KSP ; le texte du header suit donc la forme `Config Desk — <vue active>` au lieu de répéter `KSP`. Les quelques commandes principales restent des pills/tabs à droite ; un dropdown sera préféré lorsqu'une application possède trop de commandes pour conserver ce format lisible. La route Logging expose maintenant un brouillon typé éditable, sa persistence atomique et l’application runtime immédiate par hot reload.
|
||||||
|
|
||||||
|
|
||||||
## Panneau Profils
|
## Panneau Profils
|
||||||
|
|
||||||
La vue **Profils** inspecte les documents validés qui exposent le contrat standard `default_profile` / `profiles`. Elle ne revalide ni ne fusionne les valeurs dans le frontend : le backend utilise `ConfigDocumentEngine::load_resolved_profile()` puis `ResolvedConfigProfile::resolve_effective_environment_detailed()`.
|
La vue **Profils** inspecte les documents validés qui exposent le contrat standard `default_profile` / `profiles`. Elle ne revalide ni ne fusionne les valeurs dans le frontend : le backend utilise `ConfigDocumentEngine::load_resolved_profile()` puis `ResolvedConfigProfile::resolve_effective_environment_detailed()`.
|
||||||
@@ -182,7 +187,6 @@ Les actions utilisateur significatives et changements d'état sont journalisés
|
|||||||
|
|
||||||
Le splash utilise `DOS_Amazigh.ttf` (`font-family: Dos Amazigh`) pour son titre, via le `@font-face` déclaré dans `frontend/sass/splash.scss`. Le répertoire `frontend/fonts/` documente la provenance et l'empreinte de l'asset de référence. Le lifecycle du splash est désormais actif et utilise cette police pendant la transition vers la fenêtre principale.
|
Le splash utilise `DOS_Amazigh.ttf` (`font-family: Dos Amazigh`) pour son titre, via le `@font-face` déclaré dans `frontend/sass/splash.scss`. Le répertoire `frontend/fonts/` documente la provenance et l'empreinte de l'asset de référence. Le lifecycle du splash est désormais actif et utilise cette police pendant la transition vers la fenêtre principale.
|
||||||
|
|
||||||
|
|
||||||
## Panneau Environnement / `.env`
|
## Panneau Environnement / `.env`
|
||||||
|
|
||||||
La vue **Environnement / `.env`** expose la projection sûre de `ConfigManagement::environment_report()` puis une zone de management qui appelle exclusivement `ConfigManagement::set_dotenv_value()` et `ConfigManagement::remove_dotenv_value()`. Elle ne lit jamais directement le process environment ni le fichier `.env`. Pour chaque variable KSP/KSPB actuellement présente dans le process ou `.env`, le frontend reçoit uniquement : nom, namespace, sensibilité, valeur desired sûre, valeur effective sûre, source effective et indicateur de shadowing.
|
La vue **Environnement / `.env`** expose la projection sûre de `ConfigManagement::environment_report()` puis une zone de management qui appelle exclusivement `ConfigManagement::set_dotenv_value()` et `ConfigManagement::remove_dotenv_value()`. Elle ne lit jamais directement le process environment ni le fichier `.env`. Pour chaque variable KSP/KSPB actuellement présente dans le process ou `.env`, le frontend reçoit uniquement : nom, namespace, sensibilité, valeur desired sûre, valeur effective sûre, source effective et indicateur de shadowing.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
||||||
<!-- version: 23 -->
|
<!-- version: 27 -->
|
||||||
|
|
||||||
# Utilisation de `ksp-app-config-desk`
|
# Utilisation de `ksp-app-config-desk`
|
||||||
|
|
||||||
@@ -24,14 +24,14 @@ npm i -D
|
|||||||
cd ../../
|
cd ../../
|
||||||
```
|
```
|
||||||
|
|
||||||
`npm i -D` sans nom de package installe les dépendances déclarées du package ; la classification `dependencies` / `devDependencies` reste celle de `package.json`. Les lockfiles frontend restent ignorés et ne sont pas versionnés. Après cette installation, les commandes Cargo/Tauri sont exécutées depuis la racine du workspace.
|
`npm i -D` sans nom de package installe les dépendances déclarées du package ; la classification `dependencies` / `devDependencies` reste celle de `package.json`. Les lockfiles frontend restent ignorés et ne sont pas versionnés. Après cette installation, l'opérateur peut revenir à la racine du workspace, mais chaque commande Tauri entre explicitement dans la crate ciblée avant d'exécuter le CLI.
|
||||||
|
|
||||||
## Développement normal
|
## Développement normal
|
||||||
|
|
||||||
Le frontend n'est pas lancé directement avec npm. KSP est un workspace Rust multi-app : depuis la racine du workspace, la configuration Tauri de l'application doit être sélectionnée explicitement :
|
Le frontend n'est pas lancé directement avec npm. KSP est un workspace Rust multi-app : `-c/--config` ne sélectionne pas une crate Tauri. Depuis la racine du workspace, la crate applicative est donc sélectionnée en changeant explicitement de répertoire :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
|
(cd crates/ksp-app-config-desk && cargo tauri dev)
|
||||||
```
|
```
|
||||||
|
|
||||||
Tauri exécute alors le hook :
|
Tauri exécute alors le hook :
|
||||||
@@ -54,7 +54,7 @@ Si le port HTTP est déjà occupé, le démarrage doit échouer au lieu de séle
|
|||||||
Le build de l'application passe également par Tauri. Depuis la racine du workspace :
|
Le build de l'application passe également par Tauri. Depuis la racine du workspace :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo tauri build -c crates/ksp-app-config-desk/tauri.conf.json
|
(cd crates/ksp-app-config-desk && cargo tauri build)
|
||||||
```
|
```
|
||||||
|
|
||||||
`beforeBuildCommand` déclenche :
|
`beforeBuildCommand` déclenche :
|
||||||
@@ -66,7 +66,7 @@ npm run build
|
|||||||
Vite construit les pages `main.html` et `splash.html` vers :
|
Vite construit les pages `main.html` et `splash.html` vers :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
../../builds/khadhroony-solana-project/ksp-app-config-desk/dist
|
../../../builds/khadhroony-solana-project/ksp-app-config-desk/dist
|
||||||
```
|
```
|
||||||
|
|
||||||
Cette destination est résolue depuis la racine de la crate dans `vite.config.ts` et correspond au `frontendDist` de `tauri.conf.json`.
|
Cette destination est résolue depuis la racine de la crate dans `vite.config.ts` et correspond au `frontendDist` de `tauri.conf.json`.
|
||||||
@@ -97,11 +97,16 @@ frontend/ts/bindings/ksp_app_config_desk/...
|
|||||||
|
|
||||||
Les bindings sont générés au premier DTO Tauri réel ; aucune structure factice n'est ajoutée uniquement pour créer le répertoire.
|
Les bindings sont générés au premier DTO Tauri réel ; aucune structure factice n'est ajoutée uniquement pour créer le répertoire.
|
||||||
|
|
||||||
|
|
||||||
## Bootstrap backend
|
## Bootstrap backend
|
||||||
|
|
||||||
Les arguments `--cfgpath`, `--schemapath` et `--filemap=...` sont transmis tels quels à `ksp-config-lib`. En build debug, le launcher replace le current working directory Rust à la racine du workspace avant ce bootstrap afin que les defaults relatifs `config/`, `config/schemas/` et `.env` désignent les ressources racine même lorsque Tauri lance `cargo run` depuis la crate de l'application. Le launcher ne lit pas ces ressources lui-même. Le profil Logging initial est le `default_profile` de `std.logging.json`.
|
Les arguments `--cfgpath`, `--schemapath` et `--filemap=...` sont transmis tels quels à `ksp-config-lib`. En build debug, le launcher replace le current working directory Rust à la racine du workspace avant ce bootstrap afin que les defaults relatifs `config/`, `config/schemas/` et `.env` désignent les ressources racine même lorsque Tauri lance `cargo run` depuis la crate de l'application. Le launcher ne lit pas ces ressources lui-même. Le profil Logging initial est le `default_profile` de `std.logging.json`.
|
||||||
|
|
||||||
|
### Runtime packagé
|
||||||
|
|
||||||
|
En release, le CWD de développement n'est pas réutilisé. Les resources Tauri embarquent les documents Config et schemas enregistrés ; `ksp-config-lib::prepare_packaged_runtime` prépare une racine de données utilisateur KSP writable résolue par `ProjectDirs`, seed les Config uniquement lorsqu'elles sont absentes et synchronise les schemas depuis le package courant. `tauri.rs` active ensuite cette racine comme current working directory avant le bootstrap Config.
|
||||||
|
|
||||||
|
Le `.env` n'est pas inclus dans le bundle. Les Config existantes restent intactes lors d'une mise à jour et les chemins relatifs de logs/wallets sont résolus depuis la racine runtime writable, sauf configuration explicite contraire. Le chemin physique dépend de la plateforme et ne doit pas être codé en dur par l'application ou l'utilisateur.
|
||||||
|
|
||||||
Si cette configuration ne peut pas être utilisée, l'application doit rester démarrable pour permettre sa réparation : elle utilise alors un fallback Logging console/stderr en mémoire. Le fallback n'écrit aucun fichier de configuration et n'écrase aucune valeur utilisateur.
|
Si cette configuration ne peut pas être utilisée, l'application doit rester démarrable pour permettre sa réparation : elle utilise alors un fallback Logging console/stderr en mémoire. Le fallback n'écrit aucun fichier de configuration et n'écrase aucune valeur utilisateur.
|
||||||
|
|
||||||
La commande Tauri `get_app_snapshot` expose un état sûr du bootstrap. Ses types TypeScript sont générés par `cargo test -p ksp-app-config-desk` sous `frontend/ts/bindings/`.
|
La commande Tauri `get_app_snapshot` expose un état sûr du bootstrap. Ses types TypeScript sont générés par `cargo test -p ksp-app-config-desk` sous `frontend/ts/bindings/`.
|
||||||
@@ -112,12 +117,14 @@ Les variables communes sont :
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
KSP_DESK_SPLASH_MINIMUM_MS=1200
|
||||||
KSP_DESK_SPLASH_FADE_MS=300
|
KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||||
|
KSP_DESK_SPLASH_FADE_OUT_MS=300
|
||||||
```
|
```
|
||||||
|
|
||||||
La priorité est celle de Config : process > `.env` > fallback. Les valeurs sont lues uniquement via `ksp-config-lib`. Une valeur non numérique ou hors borne déclenche un fallback runtime en mémoire plutôt qu'un refus de démarrer Config Desk.
|
La priorité est celle de Config : process > `.env` > fallback. Les valeurs sont lues uniquement via `ksp-config-lib`. Une valeur non numérique ou hors borne déclenche un fallback runtime en mémoire plutôt qu'un refus de démarrer Config Desk.
|
||||||
|
|
||||||
Le frontend `splash.ts` installe son listener puis appelle `splash_frontend_ready`. Le backend vérifie que l'appel provient réellement de la WebView `splash`; une readiness dupliquée (par exemple après reload Vite) est ignorée. La durée minimale commence à cette readiness : Rust émet le fade-in, attend `minimum`, émet le fade-out, attend `fade`, puis active `main`. Ainsi `KSP_DESK_SPLASH_MINIMUM_MS=12000` et `KSP_DESK_SPLASH_FADE_MS=3000` donnent environ `15000 ms` de lifecycle backend. Des logs `debug` indiquent source des deux valeurs, attentes configurées/réelles et durée totale afin de vérifier ce contrat.
|
Le frontend `splash.ts` installe son listener puis appelle `splash_frontend_ready`. Le backend vérifie que l'appel provient réellement de la WebView `splash`; une readiness dupliquée (par exemple après reload Vite) est ignorée. La durée minimale commence à cette readiness : Rust émet le fade-in, attend `fade_in`, maintient ensuite le splash pleinement visible pendant `minimum`, émet le fade-out, attend `fade_out`, puis active `main`. Ainsi `KSP_DESK_SPLASH_MINIMUM_MS=12000` et `KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||||
|
KSP_DESK_SPLASH_FADE_OUT_MS=3000` donnent environ `15000 ms` de lifecycle backend. Des logs `debug` indiquent source des trois valeurs, attentes configurées/réelles et durée totale afin de vérifier ce contrat.
|
||||||
|
|
||||||
## Navigation principale
|
## Navigation principale
|
||||||
|
|
||||||
@@ -154,12 +161,10 @@ La sélection charge le source brut via `ConfigManagement::read_source()`, y com
|
|||||||
|
|
||||||
Les clics, sélections, chargements, remplacements DOM et sauvegardes sont tracés via le bridge frontend KSP sans journaliser le contenu du source.
|
Les clics, sélections, chargements, remplacements DOM et sauvegardes sont tracés via le bridge frontend KSP sans journaliser le contenu du source.
|
||||||
|
|
||||||
|
|
||||||
### Ouvrir un éditeur spécialisé depuis Documents
|
### Ouvrir un éditeur spécialisé depuis Documents
|
||||||
|
|
||||||
Le panneau Documents demeure générique. Lorsqu'un `file_id` possède un adapter déclaré dans `shell_registry.ts`, le détail affiche **Ouvrir l'éditeur spécialisé**. Pour `cfg.std.logging`, ce bouton active la vue Logging sans dupliquer la lecture, la validation ou la persistence Config. Un document sans adapter spécialisé reste entièrement inspectable/réparable dans le panneau Documents et n'affiche pas ce bouton.
|
Le panneau Documents demeure générique. Lorsqu'un `file_id` possède un adapter déclaré dans `shell_registry.ts`, le détail affiche **Ouvrir l'éditeur spécialisé**. Pour `cfg.std.logging`, ce bouton active la vue Logging sans dupliquer la lecture, la validation ou la persistence Config. Un document sans adapter spécialisé reste entièrement inspectable/réparable dans le panneau Documents et n'affiche pas ce bouton.
|
||||||
|
|
||||||
|
|
||||||
## Panneau Profils
|
## Panneau Profils
|
||||||
|
|
||||||
La vue **Profils** charge l'inventaire des documents Config validés qui possèdent `default_profile` et `profiles`. Pour le document sélectionné :
|
La vue **Profils** charge l'inventaire des documents Config validés qui possèdent `default_profile` et `profiles`. Pour le document sélectionné :
|
||||||
@@ -173,7 +178,6 @@ La vue **Profils** charge l'inventaire des documents Config validés qui possèd
|
|||||||
|
|
||||||
Avec le `std.logging.json` actuel et `KSP_LOGS_DIRECTORY` défini dans `.env`, la provenance attendue pour `/logs_directory` est `KSP_LOGS_DIRECTORY`, source `dotenv`, sensibilité `Internal`. Le contenu des vues JSON n'est pas recopié dans les logs ; seuls les identifiants de document/profil et métadonnées de résolution sont tracés.
|
Avec le `std.logging.json` actuel et `KSP_LOGS_DIRECTORY` défini dans `.env`, la provenance attendue pour `/logs_directory` est `KSP_LOGS_DIRECTORY`, source `dotenv`, sensibilité `Internal`. Le contenu des vues JSON n'est pas recopié dans les logs ; seuls les identifiants de document/profil et métadonnées de résolution sont tracés.
|
||||||
|
|
||||||
|
|
||||||
## Panneau Environnement / `.env`
|
## Panneau Environnement / `.env`
|
||||||
|
|
||||||
La vue **Environnement / `.env`** charge `get_environment_report`, qui projette exclusivement `ConfigManagement::environment_report()`. La table affiche :
|
La vue **Environnement / `.env`** charge `get_environment_report`, qui projette exclusivement `ConfigManagement::environment_report()`. La table affiche :
|
||||||
@@ -217,8 +221,6 @@ La vue **Logging** appelle `get_logging_document`. Le backend charge le document
|
|||||||
|
|
||||||
Le sélecteur **Profil édité** travaille sur un brouillon local. `logs_directory`, `default_profile`, profils, console, file sinks, filtres et target overrides sont modifiables. **Créer**, **Cloner**, **Renommer** et **Supprimer** agissent d'abord sur le brouillon ; la suppression de profil est confirmée par modal Bootstrap. **Sauvegarder et appliquer** envoie un candidat typé à `save_logging_document`, qui reconstruit les contrats Config, persiste atomiquement après validation, recharge un `ConfigEnvironment` frais, résout le `default_profile` et hot-reload le `LoggingGuard`. Si le runtime ne peut pas être préparé/rechargé, l'ancien runtime reste actif et la source précédente est restaurée. **Recharger le document** resynchronise uniquement le brouillon depuis la source persistée et demande confirmation si des changements non sauvegardés existent. Les messages Cargo/Tauri/Vite affichés par `cargo tauri dev` sont externes au runtime Logging KSP et ne dépendent pas de `console.enabled`.
|
Le sélecteur **Profil édité** travaille sur un brouillon local. `logs_directory`, `default_profile`, profils, console, file sinks, filtres et target overrides sont modifiables. **Créer**, **Cloner**, **Renommer** et **Supprimer** agissent d'abord sur le brouillon ; la suppression de profil est confirmée par modal Bootstrap. **Sauvegarder et appliquer** envoie un candidat typé à `save_logging_document`, qui reconstruit les contrats Config, persiste atomiquement après validation, recharge un `ConfigEnvironment` frais, résout le `default_profile` et hot-reload le `LoggingGuard`. Si le runtime ne peut pas être préparé/rechargé, l'ancien runtime reste actif et la source précédente est restaurée. **Recharger le document** resynchronise uniquement le brouillon depuis la source persistée et demande confirmation si des changements non sauvegardés existent. Les messages Cargo/Tauri/Vite affichés par `cargo tauri dev` sont externes au runtime Logging KSP et ne dépendent pas de `console.enabled`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Test Logging — routing backend et bridge frontend
|
## Test Logging — routing backend et bridge frontend
|
||||||
|
|
||||||
La section **Test Logging — routing contrôlé** utilise un message explicitement destiné aux logs. Ne jamais y copier une valeur `KSP_SECRET_*` / `KSPB_SECRET_*`.
|
La section **Test Logging — routing contrôlé** utilise un message explicitement destiné aux logs. Ne jamais y copier une valeur `KSP_SECRET_*` / `KSPB_SECRET_*`.
|
||||||
@@ -311,24 +313,18 @@ cargo test -p ksp-config-lib
|
|||||||
cargo test -p ksp-logging-lib
|
cargo test -p ksp-logging-lib
|
||||||
```
|
```
|
||||||
|
|
||||||
Puis effectuer uniquement le type-check frontend depuis `crates/ksp-app-config-desk` :
|
Aucun script npm de contrôle/dev/build n’est lancé directement par l’opérateur. Tauri possède le cycle frontend et déclenche les hooks npm de la crate.
|
||||||
|
|
||||||
|
Effectuer ensuite le contrôle fonctionnel depuis la racine en lançant Tauri dans la crate ciblée :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run check
|
(cd crates/ksp-app-config-desk && cargo tauri dev)
|
||||||
```
|
|
||||||
|
|
||||||
`npm run check` exécute seulement `tsc --noEmit`. Il ne lance pas `vite build` : le build frontend de production est déjà la responsabilité de Tauri via `beforeBuildCommand = npm run build`.
|
|
||||||
|
|
||||||
Revenir ensuite à la racine et effectuer le contrôle fonctionnel :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Seulement lorsque tous les contrôles précédents sont validés**, exécuter en toute dernière opération :
|
**Seulement lorsque tous les contrôles précédents sont validés**, exécuter en toute dernière opération :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo tauri build -c crates/ksp-app-config-desk/tauri.conf.json
|
(cd crates/ksp-app-config-desk && cargo tauri build)
|
||||||
```
|
```
|
||||||
|
|
||||||
Cette dernière commande déclenche elle-même `npm run build`, donc `tsc && vite build`, avant le build/bundling Tauri. Aucun build Vite séparé n’est nécessaire dans le cycle de validation.
|
Cette dernière commande déclenche elle-même `npm run build`, donc `tsc && vite build`, avant le build/bundling Tauri. Aucun build Vite séparé n’est nécessaire dans le cycle de validation.
|
||||||
|
|||||||
@@ -10,4 +10,4 @@
|
|||||||
"core:default",
|
"core:default",
|
||||||
"tracing:default"
|
"tracing:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
||||||
<!-- version: 16 -->
|
<!-- version: 18 -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
|
|
||||||
@@ -21,20 +21,25 @@
|
|||||||
<span class="mx-2 fs-5 text-body-secondary" aria-hidden="true">—</span>
|
<span class="mx-2 fs-5 text-body-secondary" aria-hidden="true">—</span>
|
||||||
<span id="headerViewTitle" class="fs-5 text-body text-nowrap">Vue d'ensemble</span>
|
<span id="headerViewTitle" class="fs-5 text-body text-nowrap">Vue d'ensemble</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="app-nav nav nav-pills flex-nowrap gap-1 ms-auto" aria-label="Navigation Config Desk">
|
|
||||||
<button class="nav-link app-nav-button active" type="button" data-view="overview">Vue d'ensemble</button>
|
|
||||||
<button class="nav-link app-nav-button" type="button" data-view="documents">Documents</button>
|
|
||||||
<button class="nav-link app-nav-button" type="button" data-view="profiles">Profils</button>
|
|
||||||
<button class="nav-link app-nav-button" type="button" data-view="environment">Environnement / .env</button>
|
|
||||||
<button class="nav-link app-nav-button" type="button" data-view="logging">Logging</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="app-main">
|
<main class="app-main container-fluid p-0">
|
||||||
<div class="app-scrollable" data-simplebar>
|
<div class="row g-0 h-100 flex-nowrap">
|
||||||
<div class="container-fluid px-4 py-4">
|
<aside class="col-auto app-sidebar border-end bg-body-tertiary">
|
||||||
|
<div class="app-sidebar-scroll h-100" data-simplebar>
|
||||||
|
<nav class="nav nav-pills flex-column gap-1 p-3" aria-label="Navigation Config Desk">
|
||||||
|
<button class="nav-link text-start active" type="button" data-view="overview"><i class="fa-solid fa-gauge-high me-2" aria-hidden="true"></i>Vue d'ensemble</button>
|
||||||
|
<button class="nav-link text-start" type="button" data-view="documents"><i class="fa-solid fa-file-code me-2" aria-hidden="true"></i>Documents</button>
|
||||||
|
<button class="nav-link text-start" type="button" data-view="profiles"><i class="fa-solid fa-layer-group me-2" aria-hidden="true"></i>Profils</button>
|
||||||
|
<button class="nav-link text-start" type="button" data-view="environment"><i class="fa-solid fa-terminal me-2" aria-hidden="true"></i>Environnement / .env</button>
|
||||||
|
<button class="nav-link text-start" type="button" data-view="logging"><i class="fa-solid fa-list-ul me-2" aria-hidden="true"></i>Logging</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<section class="col app-content app-scrollable h-100" data-simplebar>
|
||||||
|
<div class="container-fluid px-4 py-4">
|
||||||
<div class="card app-shell-card mx-auto shadow-sm border-0">
|
<div class="card app-shell-card mx-auto shadow-sm border-0">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<h1 id="viewTitle" class="h3 mb-2">Vue d'ensemble</h1>
|
<h1 id="viewTitle" class="h3 mb-2">Vue d'ensemble</h1>
|
||||||
@@ -547,7 +552,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/sass/_app.scss
|
// file: crates/ksp-app-config-desk/frontend/sass/_app.scss
|
||||||
// version: 5
|
// version: 7
|
||||||
|
|
||||||
$app-header-height: 72px;
|
$app-header-height: 72px;
|
||||||
$app-footer-height: 42px;
|
$app-footer-height: 42px;
|
||||||
|
$app-sidebar-width: 250px;
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
@@ -40,6 +41,11 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-main > .row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.app-scrollable {
|
.app-scrollable {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
@@ -59,13 +65,23 @@ body {
|
|||||||
font-family: var(--bs-font-monospace);
|
font-family: var(--bs-font-monospace);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-nav {
|
.app-sidebar {
|
||||||
min-width: 0;
|
width: $app-sidebar-width;
|
||||||
overflow-x: auto;
|
min-width: $app-sidebar-width;
|
||||||
scrollbar-width: thin;
|
max-width: $app-sidebar-width;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-nav-button {
|
.app-sidebar-scroll,
|
||||||
|
.app-content {
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-content {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar .nav-link {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/sass/_bootswatch.scss
|
// file: crates/ksp-app-config-desk/frontend/sass/_bootswatch.scss
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
// Pulse 5.3.8
|
// Pulse 5.3.8
|
||||||
// Bootswatch
|
// Bootswatch
|
||||||
@@ -157,4 +157,4 @@
|
|||||||
color: $list-group-disabled-color;
|
color: $list-group-disabled-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/sass/_fontawesome.scss
|
// file: crates/ksp-app-config-desk/frontend/sass/_fontawesome.scss
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//@use '@fortawesome/fontawesome-free/scss/variables' with (
|
//@use '@fortawesome/fontawesome-free/scss/variables' with (
|
||||||
// // customizing $font-path - make sure it points to where your webfonts are stored in your project
|
// // customizing $font-path - make sure it points to where your webfonts are stored in your project
|
||||||
@@ -16,4 +16,4 @@
|
|||||||
@use '@fortawesome/fontawesome-free/scss/fa' as fa;
|
@use '@fortawesome/fontawesome-free/scss/fa' as fa;
|
||||||
@use '@fortawesome/fontawesome-free/scss/brands' as fa-brands;
|
@use '@fortawesome/fontawesome-free/scss/brands' as fa-brands;
|
||||||
@use '@fortawesome/fontawesome-free/scss/regular' as fa-regular;
|
@use '@fortawesome/fontawesome-free/scss/regular' as fa-regular;
|
||||||
@use '@fortawesome/fontawesome-free/scss/solid' as fa-solid;
|
@use '@fortawesome/fontawesome-free/scss/solid' as fa-solid;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/sass/_simplebar.scss
|
// file: crates/ksp-app-config-desk/frontend/sass/_simplebar.scss
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
/* Rtl support */
|
/* Rtl support */
|
||||||
[data-simplebar] {
|
[data-simplebar] {
|
||||||
@@ -245,4 +245,4 @@
|
|||||||
|
|
||||||
.simplebar-hover {
|
.simplebar-hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/sass/_variables.scss
|
// file: crates/ksp-app-config-desk/frontend/sass/_variables.scss
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
// Pulse 5.3.8
|
// Pulse 5.3.8
|
||||||
// Bootswatch
|
// Bootswatch
|
||||||
@@ -92,4 +92,4 @@ $list-group-border-color: transparent !default;
|
|||||||
$list-group-hover-bg: lighten($list-group-bg, 10%) !default;
|
$list-group-hover-bg: lighten($list-group-bg, 10%) !default;
|
||||||
$list-group-active-color: $white !default;
|
$list-group-active-color: $white !default;
|
||||||
$list-group-active-bg: $list-group-bg !default;
|
$list-group-active-bg: $list-group-bg !default;
|
||||||
$list-group-disabled-color: lighten($list-group-bg, 30%) !default;
|
$list-group-disabled-color: lighten($list-group-bg, 30%) !default;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/sass/splash.scss
|
// file: crates/ksp-app-config-desk/frontend/sass/splash.scss
|
||||||
// version: 3
|
// version: 5
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Dos Amazigh";
|
font-family: "Dos Amazigh";
|
||||||
@@ -9,13 +9,23 @@
|
|||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
body {
|
body {
|
||||||
display: flex;
|
width: 100%;
|
||||||
width: 100vw;
|
height: 100%;
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -25,8 +35,12 @@ body {
|
|||||||
#splash-container {
|
#splash-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 960px;
|
width: 960px;
|
||||||
|
max-width: 100%;
|
||||||
height: 637px;
|
height: 637px;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
#splash-image {
|
#splash-image {
|
||||||
@@ -35,6 +49,11 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
object-fit: contain;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app-name {
|
#app-name {
|
||||||
@@ -49,17 +68,70 @@ body {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
white-space: nowrap;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#splash-status {
|
#debug-info {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 1rem;
|
top: 0;
|
||||||
bottom: 1rem;
|
left: 0;
|
||||||
left: 1rem;
|
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
padding: 0.55rem 0.75rem;
|
width: 100%;
|
||||||
|
max-height: 30%;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: rgba(0, 0, 0, 0.35);
|
background: rgba(0, 0, 0, 0.18);
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 0.82rem;
|
font-size: 12px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages-container {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 3;
|
||||||
|
max-height: 30%;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px 10px 36px;
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-info::-webkit-scrollbar,
|
||||||
|
#messages-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-message {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
animation: splashMessageFadeIn 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-message.info { background: rgba(33, 150, 243, 0.22); }
|
||||||
|
.splash-message.warning { background: rgba(255, 152, 0, 0.22); }
|
||||||
|
.splash-message.error { background: rgba(244, 67, 54, 0.22); }
|
||||||
|
.splash-message.success { background: rgba(76, 175, 80, 0.22); }
|
||||||
|
|
||||||
|
@keyframes splashMessageFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/frontend/splash.html -->
|
<!-- file: crates/ksp-app-config-desk/frontend/splash.html -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
|
|
||||||
@@ -11,10 +11,11 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="splash-container">
|
<div id="splash-container" style="opacity: 0;">
|
||||||
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP Config Desk">
|
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP Config Desk">
|
||||||
<div id="app-name">KSP</div>
|
<div id="app-name">Config Desk</div>
|
||||||
<div id="splash-status" aria-live="polite">Chargement du shell frontend...</div>
|
<div id="debug-info" aria-live="off" hidden></div>
|
||||||
|
<div id="messages-container" aria-live="polite"></div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="ts/splash.ts" defer></script>
|
<script type="module" src="ts/splash.ts" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/ts/splash.ts
|
// file: crates/ksp-app-config-desk/frontend/ts/splash.ts
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
@@ -9,21 +9,14 @@ import { invokeKsp } from "./invoke";
|
|||||||
|
|
||||||
installFrontendConsoleBridge("splash");
|
installFrontendConsoleBridge("splash");
|
||||||
|
|
||||||
const defaultFadeDurationMs = 300;
|
|
||||||
let activeOpacityFrame: number | null = null;
|
let activeOpacityFrame: number | null = null;
|
||||||
|
|
||||||
function normalizeDurationMs(value: number | null | undefined): number {
|
function easeInOut(value: number): number {
|
||||||
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
return value < 0.5 ? 2 * value * value : 1 - Math.pow(-2 * value + 2, 2) / 2;
|
||||||
return defaultFadeDurationMs;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function easeInOut(progress: number): number {
|
function normalizeDurationMs(value: number | null): number {
|
||||||
if (progress < 0.5) {
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
||||||
return 2 * progress * progress;
|
|
||||||
}
|
|
||||||
return 1 - Math.pow(-2 * progress + 2, 2) / 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpacity: number, durationMs: number): Promise<void> {
|
async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpacity: number, durationMs: number): Promise<void> {
|
||||||
@@ -40,7 +33,7 @@ async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpaci
|
|||||||
const opacityDelta = toOpacity - fromOpacity;
|
const opacityDelta = toOpacity - fromOpacity;
|
||||||
const updateOpacity = (currentTime: number): void => {
|
const updateOpacity = (currentTime: number): void => {
|
||||||
const elapsedMs = currentTime - startedAt;
|
const elapsedMs = currentTime - startedAt;
|
||||||
const rawProgress = Math.min(elapsedMs / durationMs, 1);
|
const rawProgress = durationMs === 0 ? 1 : Math.min(elapsedMs / durationMs, 1);
|
||||||
element.style.opacity = (fromOpacity + opacityDelta * easeInOut(rawProgress)).toString();
|
element.style.opacity = (fromOpacity + opacityDelta * easeInOut(rawProgress)).toString();
|
||||||
if (rawProgress >= 1) {
|
if (rawProgress >= 1) {
|
||||||
element.style.opacity = toOpacity.toString();
|
element.style.opacity = toOpacity.toString();
|
||||||
@@ -56,21 +49,46 @@ async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpaci
|
|||||||
frontendTrace("splash", "Splash opacity animation completed", { toOpacity, durationMs });
|
frontendTrace("splash", "Splash opacity animation completed", { toOpacity, durationMs });
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceStatus(message: string | null): void {
|
function scrollToLatest(element: HTMLElement): void {
|
||||||
if (!message) {
|
element.scrollTop = element.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMessage(message: string, status: string | null): void {
|
||||||
|
const container = document.querySelector<HTMLElement>("#messages-container");
|
||||||
|
if (!container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const status = document.querySelector<HTMLElement>("#splash-status");
|
const line = document.createElement("div");
|
||||||
if (status) {
|
const safeStatus = status === "warning" || status === "error" || status === "success" ? status : "info";
|
||||||
status.textContent = message;
|
line.className = `splash-message ${safeStatus}`;
|
||||||
frontendTrace("splash", "Splash status replaced", { message });
|
line.textContent = message;
|
||||||
|
container.appendChild(line);
|
||||||
|
scrollToLatest(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDebugMessage(message: string): void {
|
||||||
|
const container = document.querySelector<HTMLElement>("#debug-info");
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
container.hidden = false;
|
||||||
|
const line = document.createElement("div");
|
||||||
|
line.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
|
||||||
|
container.appendChild(line);
|
||||||
|
scrollToLatest(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSplashOrder(order: SplashOrderDto): Promise<void> {
|
async function handleSplashOrder(order: SplashOrderDto): Promise<void> {
|
||||||
frontendTrace("splash", "Splash order received", { action: order.action });
|
frontendTrace("splash", "Splash order received", { action: order.action });
|
||||||
const container = document.querySelector<HTMLElement>("#splash-container");
|
const container = document.querySelector<HTMLElement>("#splash-container");
|
||||||
replaceStatus(order.message);
|
if (order.action === "add_message" && order.message) {
|
||||||
|
addMessage(order.message, order.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.action === "add_debug" && order.message) {
|
||||||
|
addDebugMessage(order.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!container) {
|
if (!container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -99,7 +117,7 @@ async function initializeSplash(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
await invokeKsp<void>("splash", "splash_frontend_ready");
|
await invokeKsp<void>("splash", "splash_frontend_ready");
|
||||||
} catch {
|
} catch {
|
||||||
replaceStatus("Le lifecycle du splash n'a pas pu démarrer.");
|
addMessage("Le lifecycle du splash n'a pas pu démarrer.", "error");
|
||||||
if (container) {
|
if (container) {
|
||||||
container.style.opacity = "1";
|
container.style.opacity = "1";
|
||||||
container.style.willChange = "auto";
|
container.style.willChange = "auto";
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "ksp-app-config-desk",
|
"name": "ksp-app-config-desk",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.4",
|
"version": "0.2.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build"
|
||||||
"preview": "vite preview",
|
|
||||||
"tauri": "tauri",
|
|
||||||
"check": "tsc --noEmit"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fltsci/tauri-plugin-tracing": "^0.3",
|
"@fltsci/tauri-plugin-tracing": "^0.3",
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/app_state.rs
|
// file: crates/ksp-app-config-desk/src/app_state.rs
|
||||||
// version: 6
|
// version: 9
|
||||||
|
|
||||||
//! Shared backend state owned by the Tauri application.
|
//! Shared backend state owned by the Tauri application.
|
||||||
|
|
||||||
struct LoggingRuntimeState {
|
|
||||||
guard: ksp_logging_lib::LoggingGuard,
|
|
||||||
active_profile_id: std::option::Option<String>,
|
|
||||||
selection_source: String,
|
|
||||||
generation: u32,
|
|
||||||
fallback_active: bool,
|
|
||||||
startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shared Config Desk application state managed by Tauri.
|
/// Shared Config Desk application state managed by Tauri.
|
||||||
pub(crate) struct AppState {
|
pub(crate) struct AppState {
|
||||||
config_management: ksp_config_lib::ConfigManagement,
|
config_management: ksp_config_lib::ConfigManagement,
|
||||||
@@ -28,7 +19,7 @@ impl AppState {
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let runtime_identity = crate::logging_runtime::launch_identity();
|
let runtime_identity = crate::launch_identity();
|
||||||
let runtime_identity = match runtime_identity {
|
let runtime_identity = match runtime_identity {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
@@ -57,8 +48,10 @@ impl AppState {
|
|||||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
minimum_ms = splash_settings.minimum_ms(),
|
minimum_ms = splash_settings.minimum_ms(),
|
||||||
minimum_source = splash_settings.minimum_source(),
|
minimum_source = splash_settings.minimum_source(),
|
||||||
fade_ms = splash_settings.fade_ms(),
|
fade_in_ms = splash_settings.fade_in_ms(),
|
||||||
fade_source = splash_settings.fade_source(),
|
fade_in_source = splash_settings.fade_in_source(),
|
||||||
|
fade_out_ms = splash_settings.fade_out_ms(),
|
||||||
|
fade_out_source = splash_settings.fade_out_source(),
|
||||||
expected_backend_lifecycle_ms = splash_settings.expected_backend_lifecycle_ms(),
|
expected_backend_lifecycle_ms = splash_settings.expected_backend_lifecycle_ms(),
|
||||||
"resolved Config Desk splash timings"
|
"resolved Config Desk splash timings"
|
||||||
);
|
);
|
||||||
@@ -175,7 +168,7 @@ impl AppState {
|
|||||||
let dropped = runtime.guard.dropped_lines();
|
let dropped = runtime.guard.dropped_lines();
|
||||||
let mut files = std::vec::Vec::<crate::LoggingRuntimeFileDto>::new();
|
let mut files = std::vec::Vec::<crate::LoggingRuntimeFileDto>::new();
|
||||||
for metadata in runtime.guard.active_file_outputs() {
|
for metadata in runtime.guard.active_file_outputs() {
|
||||||
files.push(crate::logging_runtime::project_file(&metadata));
|
files.push(crate::project_logging_file(&metadata));
|
||||||
}
|
}
|
||||||
return std::result::Result::Ok(crate::LoggingRuntimeStatusDto {
|
return std::result::Result::Ok(crate::LoggingRuntimeStatusDto {
|
||||||
active_profile: runtime.active_profile_id.clone(),
|
active_profile: runtime.active_profile_id.clone(),
|
||||||
@@ -185,9 +178,9 @@ impl AppState {
|
|||||||
application_id: identity.application_id().to_owned(),
|
application_id: identity.application_id().to_owned(),
|
||||||
launch_timestamp: identity.launch_timestamp().to_owned(),
|
launch_timestamp: identity.launch_timestamp().to_owned(),
|
||||||
console_enabled,
|
console_enabled,
|
||||||
dropped_console_lines: crate::logging_runtime::count_to_u64(dropped.console()),
|
dropped_console_lines: crate::count_to_u64(dropped.console()),
|
||||||
dropped_file_lines: crate::logging_runtime::count_to_u64(dropped.file()),
|
dropped_file_lines: crate::count_to_u64(dropped.file()),
|
||||||
dropped_total_lines: crate::logging_runtime::count_to_u64(dropped.total()),
|
dropped_total_lines: crate::count_to_u64(dropped.total()),
|
||||||
files,
|
files,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -206,3 +199,12 @@ impl AppState {
|
|||||||
.is_ok();
|
.is_ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LoggingRuntimeState {
|
||||||
|
guard: ksp_logging_lib::LoggingGuard,
|
||||||
|
active_profile_id: std::option::Option<String>,
|
||||||
|
selection_source: String,
|
||||||
|
generation: u32,
|
||||||
|
fallback_active: bool,
|
||||||
|
startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/bootstrap.rs
|
// file: crates/ksp-app-config-desk/src/bootstrap.rs
|
||||||
// version: 3
|
// version: 6
|
||||||
|
|
||||||
//! Config and Logging bootstrap for the desktop application.
|
//! Config and Logging bootstrap for the desktop application.
|
||||||
|
|
||||||
|
/// Crate-internal `LoggingStartup` state shared across the owning crate.
|
||||||
pub(crate) struct LoggingStartup {
|
pub(crate) struct LoggingStartup {
|
||||||
|
/// Stores the guard value for this state.
|
||||||
pub(crate) guard: ksp_logging_lib::LoggingGuard,
|
pub(crate) guard: ksp_logging_lib::LoggingGuard,
|
||||||
|
/// Stores the active profile id value for this state.
|
||||||
pub(crate) active_profile_id: std::option::Option<String>,
|
pub(crate) active_profile_id: std::option::Option<String>,
|
||||||
|
/// Stores the selection source value for this state.
|
||||||
pub(crate) selection_source: String,
|
pub(crate) selection_source: String,
|
||||||
|
/// Stores the fallback active value for this state.
|
||||||
pub(crate) fallback_active: bool,
|
pub(crate) fallback_active: bool,
|
||||||
|
/// Stores the startup diagnostic value for this state.
|
||||||
pub(crate) startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
|
pub(crate) startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +29,7 @@ enum LoggingStartupPlan {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the crate-internal config management operation for the owning module.
|
||||||
pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<ksp_config_lib::ConfigManagement> {
|
pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<ksp_config_lib::ConfigManagement> {
|
||||||
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_args(arguments);
|
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_args(arguments);
|
||||||
let bootstrap = match bootstrap {
|
let bootstrap = match bootstrap {
|
||||||
@@ -38,10 +45,11 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
|
|||||||
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
|
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the crate-internal initialize logging operation for the owning module.
|
||||||
pub(crate) fn initialize_logging(
|
pub(crate) fn initialize_logging(
|
||||||
management: &ksp_config_lib::ConfigManagement,
|
management: &ksp_config_lib::ConfigManagement,
|
||||||
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||||
) -> ksp_core_lib::Result<LoggingStartup> {
|
) -> ksp_core_lib::Result<crate::LoggingStartup> {
|
||||||
let plan = resolve_logging_startup(management);
|
let plan = resolve_logging_startup(management);
|
||||||
return match plan {
|
return match plan {
|
||||||
LoggingStartupPlan::Managed { active_profile_id, settings } => {
|
LoggingStartupPlan::Managed { active_profile_id, settings } => {
|
||||||
@@ -71,6 +79,15 @@ pub(crate) fn initialize_logging(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fallback_logging_settings() -> ksp_logging_lib::LoggingSettings {
|
||||||
|
return ksp_logging_lib::LoggingSettings::new(
|
||||||
|
ksp_logging_lib::LogFilterLevel::Info,
|
||||||
|
ksp_logging_lib::SpanEvents::Off,
|
||||||
|
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()),
|
||||||
|
std::vec::Vec::new(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_logging_startup(management: &ksp_config_lib::ConfigManagement) -> LoggingStartupPlan {
|
fn resolve_logging_startup(management: &ksp_config_lib::ConfigManagement) -> LoggingStartupPlan {
|
||||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||||
let environment = match environment {
|
let environment = match environment {
|
||||||
@@ -93,7 +110,7 @@ fn fallback_startup_plan(initial_error: ksp_core_lib::Error) -> LoggingStartupPl
|
|||||||
fn initialize_fallback_logging(
|
fn initialize_fallback_logging(
|
||||||
initial_error: ksp_core_lib::Error,
|
initial_error: ksp_core_lib::Error,
|
||||||
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||||
) -> ksp_core_lib::Result<LoggingStartup> {
|
) -> ksp_core_lib::Result<crate::LoggingStartup> {
|
||||||
let diagnostic = crate::CommandErrorDto::from_error(&initial_error);
|
let diagnostic = crate::CommandErrorDto::from_error(&initial_error);
|
||||||
return initialize_planned_fallback_logging(initial_error, diagnostic, fallback_logging_settings(), runtime_identity);
|
return initialize_planned_fallback_logging(initial_error, diagnostic, fallback_logging_settings(), runtime_identity);
|
||||||
}
|
}
|
||||||
@@ -103,7 +120,7 @@ fn initialize_planned_fallback_logging(
|
|||||||
diagnostic: crate::CommandErrorDto,
|
diagnostic: crate::CommandErrorDto,
|
||||||
settings: ksp_logging_lib::LoggingSettings,
|
settings: ksp_logging_lib::LoggingSettings,
|
||||||
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||||
) -> ksp_core_lib::Result<LoggingStartup> {
|
) -> ksp_core_lib::Result<crate::LoggingStartup> {
|
||||||
let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
|
let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
|
||||||
let guard = match guard {
|
let guard = match guard {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -132,15 +149,6 @@ fn initialize_planned_fallback_logging(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fallback_logging_settings() -> ksp_logging_lib::LoggingSettings {
|
|
||||||
return ksp_logging_lib::LoggingSettings::new(
|
|
||||||
ksp_logging_lib::LogFilterLevel::Info,
|
|
||||||
ksp_logging_lib::SpanEvents::Off,
|
|
||||||
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()),
|
|
||||||
std::vec::Vec::new(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "../unit_tests/bootstrap.rs"]
|
#[path = "../unit_tests/bootstrap.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
@@ -1,8 +1,28 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/constants.rs
|
// file: crates/ksp-app-config-desk/src/constants.rs
|
||||||
// version: 9
|
// version: 10
|
||||||
|
|
||||||
//! Application-owned tracing targets and domains.
|
//! Application-owned tracing targets and domains.
|
||||||
|
|
||||||
|
/// Structured domain used while bootstrapping Config and Logging.
|
||||||
|
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
|
||||||
|
/// Structured domain for Config document inventory, diagnostics and repair.
|
||||||
|
pub(crate) const TRACING_DOMAIN_DOCUMENTS: &str = "config.documents";
|
||||||
|
/// Structured domain for safe Config environment reports.
|
||||||
|
pub(crate) const TRACING_DOMAIN_ENVIRONMENT: &str = "config.environment";
|
||||||
|
/// Structured domain used by technical frontend events.
|
||||||
|
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||||
|
/// Structured domain for typed Logging editor inspection.
|
||||||
|
pub(crate) const TRACING_DOMAIN_LOGGING_EDITOR: &str = "config.logging_editor";
|
||||||
|
/// Structured domain for active Logging runtime metadata and explicit profile application.
|
||||||
|
pub(crate) const TRACING_DOMAIN_LOGGING_RUNTIME: &str = "config.logging_runtime";
|
||||||
|
/// Structured domain used by controlled Logging test events.
|
||||||
|
pub(crate) const TRACING_DOMAIN_LOGGING_TEST: &str = "config.logging_test";
|
||||||
|
/// Structured domain for Config profile selection and provenance inspection.
|
||||||
|
pub(crate) const TRACING_DOMAIN_PROFILES: &str = "config.profiles";
|
||||||
|
/// Structured domain for explicit privileged Secret reveal operations.
|
||||||
|
pub(crate) const TRACING_DOMAIN_SECRETS: &str = "config.secrets";
|
||||||
|
/// Structured domain used by Tauri window lifecycle operations.
|
||||||
|
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
||||||
/// Owning target for backend events emitted by Config Desk.
|
/// Owning target for backend events emitted by Config Desk.
|
||||||
pub(crate) const TRACING_TARGET: &str = "ksp-app-config-desk";
|
pub(crate) const TRACING_TARGET: &str = "ksp-app-config-desk";
|
||||||
/// Owning target for generic frontend events emitted through the KSP bridge.
|
/// Owning target for generic frontend events emitted through the KSP bridge.
|
||||||
@@ -13,23 +33,3 @@ pub(crate) const TRACING_TARGET_FRONTEND_MAIN: &str = "ksp-app-config-desk.front
|
|||||||
pub(crate) const TRACING_TARGET_FRONTEND_SPLASH: &str = "ksp-app-config-desk.frontend.splash";
|
pub(crate) const TRACING_TARGET_FRONTEND_SPLASH: &str = "ksp-app-config-desk.frontend.splash";
|
||||||
/// Dedicated target used by the controlled Logging test panel.
|
/// Dedicated target used by the controlled Logging test panel.
|
||||||
pub(crate) const TRACING_TARGET_LOGGING_TEST: &str = "ksp-app-config-desk.logging-test";
|
pub(crate) const TRACING_TARGET_LOGGING_TEST: &str = "ksp-app-config-desk.logging-test";
|
||||||
/// Structured domain used while bootstrapping Config and Logging.
|
|
||||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
|
|
||||||
/// Structured domain used by technical frontend events.
|
|
||||||
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
|
||||||
/// Structured domain used by Tauri window lifecycle operations.
|
|
||||||
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
|
||||||
/// Structured domain for Config document inventory, diagnostics and repair.
|
|
||||||
pub(crate) const TRACING_DOMAIN_DOCUMENTS: &str = "config.documents";
|
|
||||||
/// Structured domain for Config profile selection and provenance inspection.
|
|
||||||
pub(crate) const TRACING_DOMAIN_PROFILES: &str = "config.profiles";
|
|
||||||
/// Structured domain for safe Config environment reports.
|
|
||||||
pub(crate) const TRACING_DOMAIN_ENVIRONMENT: &str = "config.environment";
|
|
||||||
/// Structured domain for explicit privileged Secret reveal operations.
|
|
||||||
pub(crate) const TRACING_DOMAIN_SECRETS: &str = "config.secrets";
|
|
||||||
/// Structured domain for typed Logging editor inspection.
|
|
||||||
pub(crate) const TRACING_DOMAIN_LOGGING_EDITOR: &str = "config.logging_editor";
|
|
||||||
/// Structured domain for active Logging runtime metadata and explicit profile application.
|
|
||||||
pub(crate) const TRACING_DOMAIN_LOGGING_RUNTIME: &str = "config.logging_runtime";
|
|
||||||
/// Structured domain used by controlled Logging test events.
|
|
||||||
pub(crate) const TRACING_DOMAIN_LOGGING_TEST: &str = "config.logging_test";
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/documents.rs
|
// file: crates/ksp-app-config-desk/src/documents.rs
|
||||||
// version: 1
|
// version: 3
|
||||||
|
|
||||||
//! Generic Config document inventory, diagnostics and validated repair services.
|
//! Generic Config document inventory, diagnostics and validated repair services.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
const STATUS_VALID: &str = "valid";
|
const STAGE_EFFECTIVE: &str = "effective";
|
||||||
const STATUS_INVALID: &str = "invalid";
|
|
||||||
const STAGE_VALID: &str = "valid";
|
|
||||||
const STAGE_READ: &str = "read";
|
|
||||||
const STAGE_JSON: &str = "json";
|
const STAGE_JSON: &str = "json";
|
||||||
|
const STAGE_OTHER: &str = "other";
|
||||||
|
const STAGE_READ: &str = "read";
|
||||||
const STAGE_SCHEMA: &str = "schema";
|
const STAGE_SCHEMA: &str = "schema";
|
||||||
const STAGE_SEMANTIC: &str = "semantic";
|
const STAGE_SEMANTIC: &str = "semantic";
|
||||||
const STAGE_EFFECTIVE: &str = "effective";
|
const STAGE_VALID: &str = "valid";
|
||||||
const STAGE_OTHER: &str = "other";
|
const STATUS_INVALID: &str = "invalid";
|
||||||
|
const STATUS_VALID: &str = "valid";
|
||||||
|
|
||||||
/// Safe inventory row for one Config document registered by `ksp-config-lib`.
|
/// Safe inventory row for one Config document registered by `ksp-config-lib`.
|
||||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
@@ -78,7 +78,7 @@ impl ConfigDocumentErrorDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lists all Config-kind documents from the Config registry and evaluates their current backend validation state.
|
/// Lists all Config-kind documents from the Config registry and evaluates their current backend validation state.
|
||||||
pub(crate) fn inventory(state: &crate::AppState) -> std::vec::Vec<ConfigDocumentSummaryDto> {
|
pub(crate) fn document_inventory(state: &crate::AppState) -> std::vec::Vec<ConfigDocumentSummaryDto> {
|
||||||
let management = state.config_management();
|
let management = state.config_management();
|
||||||
let engine = management.engine();
|
let engine = management.engine();
|
||||||
let mut documents = std::vec::Vec::new();
|
let mut documents = std::vec::Vec::new();
|
||||||
@@ -98,7 +98,7 @@ pub(crate) fn inventory(state: &crate::AppState) -> std::vec::Vec<ConfigDocument
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Loads one Config document detail, including raw source when Config can read it.
|
/// Loads one Config document detail, including raw source when Config can read it.
|
||||||
pub(crate) fn detail(state: &crate::AppState, file_id_text: &str) -> std::result::Result<ConfigDocumentDetailDto, ConfigDocumentErrorDto> {
|
pub(crate) fn document_detail(state: &crate::AppState, file_id_text: &str) -> std::result::Result<ConfigDocumentDetailDto, ConfigDocumentErrorDto> {
|
||||||
let file_id = parse_config_file_id(file_id_text);
|
let file_id = parse_config_file_id(file_id_text);
|
||||||
let file_id = match file_id {
|
let file_id = match file_id {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -133,7 +133,7 @@ pub(crate) fn detail(state: &crate::AppState, file_id_text: &str) -> std::result
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Validates and atomically persists one raw Config source candidate, then reloads the document detail.
|
/// Validates and atomically persists one raw Config source candidate, then reloads the document detail.
|
||||||
pub(crate) fn save_source(
|
pub(crate) fn save_document_source(
|
||||||
state: &crate::AppState,
|
state: &crate::AppState,
|
||||||
file_id_text: &str,
|
file_id_text: &str,
|
||||||
source: &str,
|
source: &str,
|
||||||
@@ -148,7 +148,7 @@ pub(crate) fn save_source(
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(ConfigDocumentErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => return std::result::Result::Err(ConfigDocumentErrorDto::from_error(&error)),
|
||||||
};
|
};
|
||||||
let document = detail(state, file_id.as_str());
|
let document = crate::document_detail(state, file_id.as_str());
|
||||||
let document = match document {
|
let document = match document {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/dto_common.rs
|
// file: crates/ksp-app-config-desk/src/dto_common.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//! Common Tauri DTOs shared by Config Desk commands.
|
//! Common Tauri DTOs shared by Config Desk commands.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
/// Safe command error projection that never serializes arbitrary KSP error context or source values.
|
/// Safe command error projection that never serializes arbitrary KSP error context or source values.
|
||||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/environment.rs
|
// file: crates/ksp-app-config-desk/src/environment.rs
|
||||||
// version: 2
|
// version: 4
|
||||||
|
|
||||||
//! Safe Config environment reports and `.env` management projections for Config Desk.
|
//! Safe Config environment reports and `.env` management projections for Config Desk.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
/// Safe desired/effective environment view exposed to the ordinary Config Desk frontend.
|
/// Safe desired/effective environment view exposed to the ordinary Config Desk frontend.
|
||||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
@@ -63,17 +63,17 @@ impl EnvironmentMutationOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the safe environment report owned by Config.
|
/// Returns the safe environment report owned by Config.
|
||||||
pub(crate) fn report(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
pub(crate) fn environment_report(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
||||||
return report_from_management(state.config_management());
|
return report_from_management(state.config_management());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates or replaces one `.env` entry exclusively through ConfigManagement.
|
/// Creates or replaces one `.env` entry exclusively through ConfigManagement.
|
||||||
pub(crate) fn set_value(state: &crate::AppState, variable_name: &str, value: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
pub(crate) fn set_environment_value(state: &crate::AppState, variable_name: &str, value: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||||
return set_value_from_management(state.config_management(), variable_name, value);
|
return set_value_from_management(state.config_management(), variable_name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes one `.env` entry exclusively through ConfigManagement.
|
/// Removes one `.env` entry exclusively through ConfigManagement.
|
||||||
pub(crate) fn remove_value(state: &crate::AppState, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
pub(crate) fn remove_environment_value(state: &crate::AppState, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||||
return remove_value_from_management(state.config_management(), variable_name);
|
return remove_value_from_management(state.config_management(), variable_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +1,42 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/errors.rs
|
// file: crates/ksp-app-config-desk/src/errors.rs
|
||||||
// version: 9
|
// version: 10
|
||||||
|
|
||||||
//! Application-local error codes for the configuration desktop shell.
|
//! Application-local error codes for the configuration desktop shell.
|
||||||
|
|
||||||
/// Tauri runtime assembly or execution failed.
|
/// Shared Config Desk application state is internally inconsistent.
|
||||||
pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "tauri_runtime_failed");
|
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "app_state_invalid");
|
||||||
|
/// Shared Config Desk runtime state cannot be locked safely.
|
||||||
|
pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "app_state_lock_failed");
|
||||||
|
/// A Documents command requested a registered file that is not a Config-kind document.
|
||||||
|
pub(crate) const ERROR_CODE_DOCUMENT_KIND_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "document_kind_invalid");
|
||||||
|
/// Frontend logging requested an unsupported level.
|
||||||
|
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_level_invalid");
|
||||||
|
/// Frontend logging requested a target outside the application whitelist.
|
||||||
|
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_target_invalid");
|
||||||
/// Config Desk could not install the managed Logging runtime or its safe fallback.
|
/// Config Desk could not install the managed Logging runtime or its safe fallback.
|
||||||
pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "logging_bootstrap_failed");
|
pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "logging_bootstrap_failed");
|
||||||
/// Config Desk persisted a Logging candidate but could not restore the previous source after runtime application failed.
|
/// Config Desk persisted a Logging candidate but could not restore the previous source after runtime application failed.
|
||||||
pub(crate) const ERROR_CODE_LOGGING_SOURCE_ROLLBACK_FAILED: ksp_core_lib::ErrorCode =
|
pub(crate) const ERROR_CODE_LOGGING_SOURCE_ROLLBACK_FAILED: ksp_core_lib::ErrorCode =
|
||||||
ksp_core_lib::ErrorCode::new("config_desk", "logging_source_rollback_failed");
|
ksp_core_lib::ErrorCode::new("config_desk", "logging_source_rollback_failed");
|
||||||
/// Shared Config Desk application state is internally inconsistent.
|
|
||||||
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "app_state_invalid");
|
|
||||||
/// Shared Config Desk runtime state cannot be locked safely.
|
|
||||||
pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "app_state_lock_failed");
|
|
||||||
/// Frontend logging requested an unsupported level.
|
|
||||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_level_invalid");
|
|
||||||
/// Frontend logging requested a target outside the application whitelist.
|
|
||||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_target_invalid");
|
|
||||||
/// Controlled Logging test request contains an unsupported or invalid selector.
|
/// Controlled Logging test request contains an unsupported or invalid selector.
|
||||||
pub(crate) const ERROR_CODE_LOGGING_TEST_REQUEST_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "logging_test_request_invalid");
|
pub(crate) const ERROR_CODE_LOGGING_TEST_REQUEST_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "logging_test_request_invalid");
|
||||||
|
|
||||||
/// A validated Config document does not expose the standard profile contract expected by the Profiles panel.
|
/// A validated Config document does not expose the standard profile contract expected by the Profiles panel.
|
||||||
pub(crate) const ERROR_CODE_PROFILE_CONTRACT_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "profile_contract_missing");
|
pub(crate) const ERROR_CODE_PROFILE_CONTRACT_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "profile_contract_missing");
|
||||||
/// Config profile data could not be projected safely for the frontend.
|
/// Config profile data could not be projected safely for the frontend.
|
||||||
pub(crate) const ERROR_CODE_PROFILE_PROJECTION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "profile_projection_failed");
|
pub(crate) const ERROR_CODE_PROFILE_PROJECTION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "profile_projection_failed");
|
||||||
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
|
|
||||||
pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_setting_invalid");
|
|
||||||
/// Splash readiness was invoked from a window other than the splash window.
|
|
||||||
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_origin_invalid");
|
|
||||||
/// A required Tauri window is missing from the configured application runtime.
|
|
||||||
pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_missing");
|
|
||||||
/// A Tauri window show/focus/destroy/event operation failed.
|
|
||||||
pub(crate) const ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED: ksp_core_lib::ErrorCode =
|
|
||||||
ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_operation_failed");
|
|
||||||
|
|
||||||
/// A Documents command requested a registered file that is not a Config-kind document.
|
|
||||||
pub(crate) const ERROR_CODE_DOCUMENT_KIND_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "document_kind_invalid");
|
|
||||||
/// Privileged reveal was requested for a non-Secret KSP/KSPB namespace.
|
/// Privileged reveal was requested for a non-Secret KSP/KSPB namespace.
|
||||||
pub(crate) const ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET: ksp_core_lib::ErrorCode =
|
pub(crate) const ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET: ksp_core_lib::ErrorCode =
|
||||||
ksp_core_lib::ErrorCode::new("config_desk", "secret_reveal_requires_secret");
|
ksp_core_lib::ErrorCode::new("config_desk", "secret_reveal_requires_secret");
|
||||||
/// Privileged reveal requested an unsupported source selector.
|
/// Privileged reveal requested an unsupported source selector.
|
||||||
pub(crate) const ERROR_CODE_SECRET_REVEAL_SOURCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "secret_reveal_source_invalid");
|
pub(crate) const ERROR_CODE_SECRET_REVEAL_SOURCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "secret_reveal_source_invalid");
|
||||||
|
/// Splash readiness was invoked from a window other than the splash window.
|
||||||
|
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_origin_invalid");
|
||||||
|
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
|
||||||
|
pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_setting_invalid");
|
||||||
|
/// Tauri runtime assembly or execution failed.
|
||||||
|
pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "tauri_runtime_failed");
|
||||||
|
/// A required Tauri window is missing from the configured application runtime.
|
||||||
|
pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_missing");
|
||||||
|
/// A Tauri window show/focus/destroy/event operation failed.
|
||||||
|
pub(crate) const ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_operation_failed");
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/frontend_logging.rs
|
// file: crates/ksp-app-config-desk/src/frontend_logging.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//! KSP-owned bridge for technical log events emitted by Config Desk frontend scripts.
|
//! KSP-owned bridge for technical log events emitted by Config Desk frontend scripts.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
/// Log payload sent by Config Desk frontend scripts.
|
/// Log payload sent by Config Desk frontend scripts.
|
||||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/lib.rs
|
// file: crates/ksp-app-config-desk/src/lib.rs
|
||||||
// version: 14
|
// version: 16
|
||||||
|
|
||||||
//! Tauri desktop application for managing and validating KSP configuration.
|
//! Tauri desktop application for managing and validating KSP configuration.
|
||||||
|
|
||||||
@@ -28,61 +28,173 @@ mod tw_splash;
|
|||||||
/// Runs the KSP configuration desktop application.
|
/// Runs the KSP configuration desktop application.
|
||||||
pub use self::tauri::run;
|
pub use self::tauri::run;
|
||||||
|
|
||||||
|
/// Shared Config Desk application state managed by Tauri.
|
||||||
pub(crate) use self::app_state::AppState;
|
pub(crate) use self::app_state::AppState;
|
||||||
|
/// Crate-internal `LoggingStartup` state shared across the owning crate.
|
||||||
|
pub(crate) use self::bootstrap::LoggingStartup;
|
||||||
|
/// Executes the crate-internal config management operation for the owning module.
|
||||||
pub(crate) use self::bootstrap::config_management;
|
pub(crate) use self::bootstrap::config_management;
|
||||||
|
/// Executes the crate-internal initialize logging operation for the owning module.
|
||||||
pub(crate) use self::bootstrap::initialize_logging;
|
pub(crate) use self::bootstrap::initialize_logging;
|
||||||
|
/// Structured domain used while bootstrapping Config and Logging.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
||||||
|
/// Structured domain for Config document inventory, diagnostics and repair.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_DOCUMENTS;
|
pub(crate) use self::constants::TRACING_DOMAIN_DOCUMENTS;
|
||||||
|
/// Structured domain for safe Config environment reports.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_ENVIRONMENT;
|
pub(crate) use self::constants::TRACING_DOMAIN_ENVIRONMENT;
|
||||||
|
/// Structured domain used by technical frontend events.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||||
|
/// Structured domain for typed Logging editor inspection.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_EDITOR;
|
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_EDITOR;
|
||||||
|
/// Structured domain for active Logging runtime metadata and explicit profile application.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_RUNTIME;
|
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_RUNTIME;
|
||||||
|
/// Structured domain used by controlled Logging test events.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_TEST;
|
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_TEST;
|
||||||
|
/// Structured domain for Config profile selection and provenance inspection.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_PROFILES;
|
pub(crate) use self::constants::TRACING_DOMAIN_PROFILES;
|
||||||
|
/// Structured domain for explicit privileged Secret reveal operations.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_SECRETS;
|
pub(crate) use self::constants::TRACING_DOMAIN_SECRETS;
|
||||||
|
/// Structured domain used by Tauri window lifecycle operations.
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
||||||
|
/// Owning target for backend events emitted by Config Desk.
|
||||||
pub(crate) use self::constants::TRACING_TARGET;
|
pub(crate) use self::constants::TRACING_TARGET;
|
||||||
|
/// Owning target for generic frontend events emitted through the KSP bridge.
|
||||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
||||||
|
/// Owning target for main-window frontend events.
|
||||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
|
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
|
||||||
|
/// Owning target for splash-window frontend events.
|
||||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
||||||
|
/// Dedicated target used by the controlled Logging test panel.
|
||||||
pub(crate) use self::constants::TRACING_TARGET_LOGGING_TEST;
|
pub(crate) use self::constants::TRACING_TARGET_LOGGING_TEST;
|
||||||
|
/// Detailed management view for one registered Config document.
|
||||||
pub(crate) use self::documents::ConfigDocumentDetailDto;
|
pub(crate) use self::documents::ConfigDocumentDetailDto;
|
||||||
|
/// Safe document-specific command error with backend-owned diagnostic classification.
|
||||||
pub(crate) use self::documents::ConfigDocumentErrorDto;
|
pub(crate) use self::documents::ConfigDocumentErrorDto;
|
||||||
|
/// Result of one validated raw-source repair attempt that reached persistence successfully.
|
||||||
pub(crate) use self::documents::ConfigDocumentSaveResultDto;
|
pub(crate) use self::documents::ConfigDocumentSaveResultDto;
|
||||||
|
/// Safe inventory row for one Config document registered by `ksp-config-lib`.
|
||||||
pub(crate) use self::documents::ConfigDocumentSummaryDto;
|
pub(crate) use self::documents::ConfigDocumentSummaryDto;
|
||||||
|
/// Loads one Config document detail, including raw source when Config can read it.
|
||||||
|
pub(crate) use self::documents::document_detail;
|
||||||
|
/// Lists all Config-kind documents from the Config registry and evaluates their current backend validation state.
|
||||||
|
pub(crate) use self::documents::document_inventory;
|
||||||
|
/// Validates and atomically persists one raw Config source candidate, then reloads the document detail.
|
||||||
|
pub(crate) use self::documents::save_document_source;
|
||||||
|
/// Initial application/runtime snapshot exposed to the frontend.
|
||||||
pub(crate) use self::dto_common::AppSnapshotDto;
|
pub(crate) use self::dto_common::AppSnapshotDto;
|
||||||
|
/// Safe command error projection that never serializes arbitrary KSP error context or source values.
|
||||||
pub(crate) use self::dto_common::CommandErrorDto;
|
pub(crate) use self::dto_common::CommandErrorDto;
|
||||||
|
/// Safe result metadata for one `.env` mutation.
|
||||||
pub(crate) use self::environment::ConfigEnvironmentChangeDto;
|
pub(crate) use self::environment::ConfigEnvironmentChangeDto;
|
||||||
|
/// Safe desired/effective environment view exposed to the ordinary Config Desk frontend.
|
||||||
pub(crate) use self::environment::ConfigEnvironmentReportDto;
|
pub(crate) use self::environment::ConfigEnvironmentReportDto;
|
||||||
|
/// Returns the safe environment report owned by Config.
|
||||||
|
pub(crate) use self::environment::environment_report;
|
||||||
|
/// Removes one `.env` entry exclusively through ConfigManagement.
|
||||||
|
pub(crate) use self::environment::remove_environment_value;
|
||||||
|
/// Creates or replaces one `.env` entry exclusively through ConfigManagement.
|
||||||
|
pub(crate) use self::environment::set_environment_value;
|
||||||
|
/// Shared Config Desk application state is internally inconsistent.
|
||||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||||
|
/// Shared Config Desk runtime state cannot be locked safely.
|
||||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||||
|
/// A Documents command requested a registered file that is not a Config-kind document.
|
||||||
pub(crate) use self::errors::ERROR_CODE_DOCUMENT_KIND_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_DOCUMENT_KIND_INVALID;
|
||||||
|
/// Frontend logging requested an unsupported level.
|
||||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
||||||
|
/// Frontend logging requested a target outside the application whitelist.
|
||||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
|
||||||
|
/// Config Desk could not install the managed Logging runtime or its safe fallback.
|
||||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
|
||||||
|
/// Config Desk persisted a Logging candidate but could not restore the previous source after runtime application failed.
|
||||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_SOURCE_ROLLBACK_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_LOGGING_SOURCE_ROLLBACK_FAILED;
|
||||||
|
/// Controlled Logging test request contains an unsupported or invalid selector.
|
||||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_TEST_REQUEST_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_LOGGING_TEST_REQUEST_INVALID;
|
||||||
|
/// A validated Config document does not expose the standard profile contract expected by the Profiles panel.
|
||||||
pub(crate) use self::errors::ERROR_CODE_PROFILE_CONTRACT_MISSING;
|
pub(crate) use self::errors::ERROR_CODE_PROFILE_CONTRACT_MISSING;
|
||||||
|
/// Config profile data could not be projected safely for the frontend.
|
||||||
pub(crate) use self::errors::ERROR_CODE_PROFILE_PROJECTION_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_PROFILE_PROJECTION_FAILED;
|
||||||
|
/// Privileged reveal was requested for a non-Secret KSP/KSPB namespace.
|
||||||
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET;
|
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET;
|
||||||
|
/// Privileged reveal requested an unsupported source selector.
|
||||||
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_SOURCE_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_SOURCE_INVALID;
|
||||||
|
/// Splash readiness was invoked from a window other than the splash window.
|
||||||
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
|
||||||
|
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
|
||||||
pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID;
|
||||||
|
/// Tauri runtime assembly or execution failed.
|
||||||
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
|
||||||
|
/// A required Tauri window is missing from the configured application runtime.
|
||||||
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_MISSING;
|
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_MISSING;
|
||||||
|
/// A Tauri window show/focus/destroy/event operation failed.
|
||||||
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED;
|
||||||
|
/// Log payload sent by Config Desk frontend scripts.
|
||||||
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
||||||
|
/// Emits one validated frontend event through the KSP Logging facade.
|
||||||
pub(crate) use self::frontend_logging::emit_frontend_log_event;
|
pub(crate) use self::frontend_logging::emit_frontend_log_event;
|
||||||
|
/// Console sink configuration exposed to the editor.
|
||||||
|
pub(crate) use self::logging_editor::LoggingConsoleDto;
|
||||||
|
/// Complete editable Logging candidate accepted from the frontend.
|
||||||
pub(crate) use self::logging_editor::LoggingDocumentCandidateDto;
|
pub(crate) use self::logging_editor::LoggingDocumentCandidateDto;
|
||||||
|
/// Complete typed Logging document exposed to the editor.
|
||||||
pub(crate) use self::logging_editor::LoggingDocumentDto;
|
pub(crate) use self::logging_editor::LoggingDocumentDto;
|
||||||
|
/// Result of one validated typed Logging persistence operation.
|
||||||
pub(crate) use self::logging_editor::LoggingDocumentSaveResultDto;
|
pub(crate) use self::logging_editor::LoggingDocumentSaveResultDto;
|
||||||
pub(crate) use self::logging_runtime::{LoggingRuntimeFileDto, LoggingRuntimeStatusDto};
|
/// One persistent file sink exposed to the editor.
|
||||||
pub(crate) use self::logging_test::{LoggingTestRequestDto, LoggingTestResultDto};
|
pub(crate) use self::logging_editor::LoggingFileDto;
|
||||||
|
/// One Logging sink selector/filter exposed to the editor.
|
||||||
|
pub(crate) use self::logging_editor::LoggingOutputFilterDto;
|
||||||
|
/// One typed Logging profile exposed to the editor.
|
||||||
|
pub(crate) use self::logging_editor::LoggingProfileDto;
|
||||||
|
/// One global Logging target override exposed to the editor.
|
||||||
|
pub(crate) use self::logging_editor::LoggingTargetFilterDto;
|
||||||
|
/// Loads the typed Logging source through ConfigManagement and projects it for the frontend.
|
||||||
|
pub(crate) use self::logging_editor::logging_document;
|
||||||
|
/// Validates, atomically persists and hot-reloads one typed Logging candidate through ConfigManagement and `ksp-logging-lib`.
|
||||||
|
pub(crate) use self::logging_editor::save_logging_document;
|
||||||
|
/// Metadata for one currently active persistent Logging file output.
|
||||||
|
pub(crate) use self::logging_runtime::LoggingRuntimeFileDto;
|
||||||
|
/// Safe observable state of the currently active KSP Logging runtime.
|
||||||
|
pub(crate) use self::logging_runtime::LoggingRuntimeStatusDto;
|
||||||
|
/// Applies one persisted Logging profile explicitly without changing `default_profile` or the source document.
|
||||||
|
pub(crate) use self::logging_runtime::apply_logging_profile;
|
||||||
|
/// Executes the crate-internal count to u64 operation for the owning module.
|
||||||
|
pub(crate) use self::logging_runtime::count_to_u64;
|
||||||
|
/// Creates the stable runtime identity for this Config Desk process launch.
|
||||||
|
pub(crate) use self::logging_runtime::launch_identity;
|
||||||
|
/// Returns safe metadata for the currently active Logging runtime.
|
||||||
|
pub(crate) use self::logging_runtime::logging_runtime_status;
|
||||||
|
/// Executes the crate-internal project logging file operation for the owning module.
|
||||||
|
pub(crate) use self::logging_runtime::project_logging_file;
|
||||||
|
/// Controlled request emitted through the backend KSP Logging facade.
|
||||||
|
pub(crate) use self::logging_test::LoggingTestRequestDto;
|
||||||
|
/// Safe result metadata for one controlled backend Logging test request.
|
||||||
|
pub(crate) use self::logging_test::LoggingTestResultDto;
|
||||||
|
/// Emits one controlled backend test request through `ksp-logging-lib` only.
|
||||||
|
pub(crate) use self::logging_test::emit_logging_test;
|
||||||
|
/// Safe detailed view of one resolved Config profile.
|
||||||
pub(crate) use self::profiles::ConfigProfileDetailDto;
|
pub(crate) use self::profiles::ConfigProfileDetailDto;
|
||||||
|
/// One Config document that exposes the standard `default_profile` / `profiles` contract.
|
||||||
pub(crate) use self::profiles::ConfigProfileDocumentDto;
|
pub(crate) use self::profiles::ConfigProfileDocumentDto;
|
||||||
|
/// Resolves one default or explicitly selected profile into a safe inspection DTO.
|
||||||
|
pub(crate) use self::profiles::profile_detail;
|
||||||
|
/// Lists validated Config documents that expose standard profiles.
|
||||||
|
pub(crate) use self::profiles::profile_inventory;
|
||||||
|
/// Privileged reveal request kept separate from ordinary environment-report DTOs.
|
||||||
pub(crate) use self::secrets::SecretRevealRequestDto;
|
pub(crate) use self::secrets::SecretRevealRequestDto;
|
||||||
|
/// Privileged reveal response. This type deliberately does not derive `Clone` or `Debug`.
|
||||||
pub(crate) use self::secrets::SecretRevealResponseDto;
|
pub(crate) use self::secrets::SecretRevealResponseDto;
|
||||||
|
/// Reveals one real Secret value only after the dedicated Tauri command has been explicitly invoked.
|
||||||
|
pub(crate) use self::secrets::reveal_secret;
|
||||||
|
/// Command emitted by Rust to the splash frontend.
|
||||||
pub(crate) use self::splash::SplashOrderDto;
|
pub(crate) use self::splash::SplashOrderDto;
|
||||||
|
/// Runtime timings used by the common desk splash lifecycle.
|
||||||
pub(crate) use self::splash::SplashSettings;
|
pub(crate) use self::splash::SplashSettings;
|
||||||
pub(crate) use self::tw_main::show_and_focus as show_main_window;
|
/// Resolves the required main window or returns a typed error.
|
||||||
pub(crate) use self::tw_splash::frontend_ready as splash_frontend_ready_service;
|
pub(crate) use self::tw_main::require_main_window;
|
||||||
|
/// Shows main window.
|
||||||
|
pub(crate) use self::tw_main::show_main_window;
|
||||||
|
/// Resolves the required splash window or returns a typed error.
|
||||||
|
pub(crate) use self::tw_splash::require_splash_window;
|
||||||
|
/// Executes the crate-internal splash frontend ready service operation for the owning module.
|
||||||
|
pub(crate) use self::tw_splash::splash_frontend_ready_service;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/logging_editor.rs
|
// file: crates/ksp-app-config-desk/src/logging_editor.rs
|
||||||
// version: 4
|
// version: 6
|
||||||
|
|
||||||
//! Typed Logging document projection and validated persistence for the Config Desk Logging editor.
|
//! Typed Logging document projection and validated persistence for the Config Desk Logging editor.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
/// One Logging sink selector/filter exposed to the editor.
|
/// One Logging sink selector/filter exposed to the editor.
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
|
||||||
@@ -32,7 +32,7 @@ pub(crate) struct LoggingConsoleDto {
|
|||||||
/// Console format source text.
|
/// Console format source text.
|
||||||
pub(crate) format: String,
|
pub(crate) format: String,
|
||||||
/// Sink-local selectors.
|
/// Sink-local selectors.
|
||||||
pub(crate) filter: LoggingOutputFilterDto,
|
pub(crate) filter: crate::LoggingOutputFilterDto,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One persistent file sink exposed to the editor.
|
/// One persistent file sink exposed to the editor.
|
||||||
@@ -53,7 +53,7 @@ pub(crate) struct LoggingFileDto {
|
|||||||
/// Whether ANSI output is requested.
|
/// Whether ANSI output is requested.
|
||||||
pub(crate) ansi: bool,
|
pub(crate) ansi: bool,
|
||||||
/// Sink-local selectors.
|
/// Sink-local selectors.
|
||||||
pub(crate) filter: LoggingOutputFilterDto,
|
pub(crate) filter: crate::LoggingOutputFilterDto,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One global Logging target override exposed to the editor.
|
/// One global Logging target override exposed to the editor.
|
||||||
@@ -79,11 +79,11 @@ pub(crate) struct LoggingProfileDto {
|
|||||||
/// Span lifecycle source text.
|
/// Span lifecycle source text.
|
||||||
pub(crate) span_events: String,
|
pub(crate) span_events: String,
|
||||||
/// Console sink configuration.
|
/// Console sink configuration.
|
||||||
pub(crate) console: LoggingConsoleDto,
|
pub(crate) console: crate::LoggingConsoleDto,
|
||||||
/// Persistent file sinks in source order.
|
/// Persistent file sinks in source order.
|
||||||
pub(crate) files: std::vec::Vec<LoggingFileDto>,
|
pub(crate) files: std::vec::Vec<crate::LoggingFileDto>,
|
||||||
/// Global target overrides in source order.
|
/// Global target overrides in source order.
|
||||||
pub(crate) target_filters: std::vec::Vec<LoggingTargetFilterDto>,
|
pub(crate) target_filters: std::vec::Vec<crate::LoggingTargetFilterDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete typed Logging document exposed to the editor.
|
/// Complete typed Logging document exposed to the editor.
|
||||||
@@ -102,7 +102,7 @@ pub(crate) struct LoggingDocumentDto {
|
|||||||
/// Autonomous default profile identifier.
|
/// Autonomous default profile identifier.
|
||||||
pub(crate) default_profile: String,
|
pub(crate) default_profile: String,
|
||||||
/// Typed profiles in source order.
|
/// Typed profiles in source order.
|
||||||
pub(crate) profiles: std::vec::Vec<LoggingProfileDto>,
|
pub(crate) profiles: std::vec::Vec<crate::LoggingProfileDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete editable Logging candidate accepted from the frontend.
|
/// Complete editable Logging candidate accepted from the frontend.
|
||||||
@@ -115,7 +115,7 @@ pub(crate) struct LoggingDocumentCandidateDto {
|
|||||||
/// Autonomous default profile identifier.
|
/// Autonomous default profile identifier.
|
||||||
pub(crate) default_profile: String,
|
pub(crate) default_profile: String,
|
||||||
/// Typed profiles in source order.
|
/// Typed profiles in source order.
|
||||||
pub(crate) profiles: std::vec::Vec<LoggingProfileDto>,
|
pub(crate) profiles: std::vec::Vec<crate::LoggingProfileDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of one validated typed Logging persistence operation.
|
/// Result of one validated typed Logging persistence operation.
|
||||||
@@ -138,12 +138,12 @@ pub(crate) struct LoggingDocumentSaveResultDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the typed Logging source through ConfigManagement and projects it for the frontend.
|
/// Loads the typed Logging source through ConfigManagement and projects it for the frontend.
|
||||||
pub(crate) fn document(state: &crate::AppState) -> ksp_core_lib::Result<LoggingDocumentDto> {
|
pub(crate) fn logging_document(state: &crate::AppState) -> ksp_core_lib::Result<LoggingDocumentDto> {
|
||||||
return document_from_management(state.config_management());
|
return document_from_management(state.config_management());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates, atomically persists and hot-reloads one typed Logging candidate through ConfigManagement and `ksp-logging-lib`.
|
/// Validates, atomically persists and hot-reloads one typed Logging candidate through ConfigManagement and `ksp-logging-lib`.
|
||||||
pub(crate) fn save(state: &crate::AppState, candidate: LoggingDocumentCandidateDto) -> ksp_core_lib::Result<LoggingDocumentSaveResultDto> {
|
pub(crate) fn save_logging_document(state: &crate::AppState, candidate: LoggingDocumentCandidateDto) -> ksp_core_lib::Result<LoggingDocumentSaveResultDto> {
|
||||||
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_STD_LOGGING);
|
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||||
let file_id = match file_id {
|
let file_id = match file_id {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -255,7 +255,7 @@ fn document_from_management(management: &ksp_config_lib::ConfigManagement) -> ks
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let mut profiles = std::vec::Vec::<LoggingProfileDto>::with_capacity(source.profiles().len());
|
let mut profiles = std::vec::Vec::<crate::LoggingProfileDto>::with_capacity(source.profiles().len());
|
||||||
let mut file_count = 0usize;
|
let mut file_count = 0usize;
|
||||||
let mut target_filter_count = 0usize;
|
let mut target_filter_count = 0usize;
|
||||||
for profile in source.profiles() {
|
for profile in source.profiles() {
|
||||||
@@ -291,7 +291,7 @@ fn config_candidate(candidate: LoggingDocumentCandidateDto) -> ksp_config_lib::L
|
|||||||
return ksp_config_lib::LoggingConfigDocument::new(candidate.logs_directory, candidate.default_profile, profiles);
|
return ksp_config_lib::LoggingConfigDocument::new(candidate.logs_directory, candidate.default_profile, profiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_profile(profile: LoggingProfileDto) -> ksp_config_lib::LoggingProfileConfig {
|
fn config_profile(profile: crate::LoggingProfileDto) -> ksp_config_lib::LoggingProfileConfig {
|
||||||
let mut files = std::vec::Vec::<ksp_config_lib::LoggingFileConfig>::with_capacity(profile.files.len());
|
let mut files = std::vec::Vec::<ksp_config_lib::LoggingFileConfig>::with_capacity(profile.files.len());
|
||||||
for file in profile.files {
|
for file in profile.files {
|
||||||
let mut config =
|
let mut config =
|
||||||
@@ -319,14 +319,14 @@ fn config_profile(profile: LoggingProfileDto) -> ksp_config_lib::LoggingProfileC
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_output_filter(filter: LoggingOutputFilterDto) -> ksp_config_lib::LoggingOutputFilterConfig {
|
fn config_output_filter(filter: crate::LoggingOutputFilterDto) -> ksp_config_lib::LoggingOutputFilterConfig {
|
||||||
return ksp_config_lib::LoggingOutputFilterConfig::new(filter.level, filter.targets, filter.domains);
|
return ksp_config_lib::LoggingOutputFilterConfig::new(filter.level, filter.targets, filter.domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_profile(profile: &ksp_config_lib::LoggingProfileConfig) -> LoggingProfileDto {
|
fn project_profile(profile: &ksp_config_lib::LoggingProfileConfig) -> crate::LoggingProfileDto {
|
||||||
let mut files = std::vec::Vec::<LoggingFileDto>::with_capacity(profile.files().len());
|
let mut files = std::vec::Vec::<crate::LoggingFileDto>::with_capacity(profile.files().len());
|
||||||
for file in profile.files() {
|
for file in profile.files() {
|
||||||
files.push(LoggingFileDto {
|
files.push(crate::LoggingFileDto {
|
||||||
output_id: file.output_id().to_owned(),
|
output_id: file.output_id().to_owned(),
|
||||||
enabled: file.enabled(),
|
enabled: file.enabled(),
|
||||||
path: file.path().to_owned(),
|
path: file.path().to_owned(),
|
||||||
@@ -336,15 +336,15 @@ fn project_profile(profile: &ksp_config_lib::LoggingProfileConfig) -> LoggingPro
|
|||||||
filter: project_output_filter(file.filter()),
|
filter: project_output_filter(file.filter()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut target_filters = std::vec::Vec::<LoggingTargetFilterDto>::with_capacity(profile.target_filters().len());
|
let mut target_filters = std::vec::Vec::<crate::LoggingTargetFilterDto>::with_capacity(profile.target_filters().len());
|
||||||
for target_filter in profile.target_filters() {
|
for target_filter in profile.target_filters() {
|
||||||
target_filters.push(LoggingTargetFilterDto { target_prefix: target_filter.target_prefix().to_owned(), level: target_filter.level().to_owned() });
|
target_filters.push(crate::LoggingTargetFilterDto { target_prefix: target_filter.target_prefix().to_owned(), level: target_filter.level().to_owned() });
|
||||||
}
|
}
|
||||||
return LoggingProfileDto {
|
return crate::LoggingProfileDto {
|
||||||
profile_id: profile.profile_id().to_owned(),
|
profile_id: profile.profile_id().to_owned(),
|
||||||
default_filter: profile.default_filter().to_owned(),
|
default_filter: profile.default_filter().to_owned(),
|
||||||
span_events: profile.span_events().to_owned(),
|
span_events: profile.span_events().to_owned(),
|
||||||
console: LoggingConsoleDto {
|
console: crate::LoggingConsoleDto {
|
||||||
enabled: profile.console().enabled(),
|
enabled: profile.console().enabled(),
|
||||||
output: profile.console().output().to_owned(),
|
output: profile.console().output().to_owned(),
|
||||||
ansi: profile.console().ansi(),
|
ansi: profile.console().ansi(),
|
||||||
@@ -356,8 +356,8 @@ fn project_profile(profile: &ksp_config_lib::LoggingProfileConfig) -> LoggingPro
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_output_filter(filter: &ksp_config_lib::LoggingOutputFilterConfig) -> LoggingOutputFilterDto {
|
fn project_output_filter(filter: &ksp_config_lib::LoggingOutputFilterConfig) -> crate::LoggingOutputFilterDto {
|
||||||
return LoggingOutputFilterDto {
|
return crate::LoggingOutputFilterDto {
|
||||||
level: filter.level().to_owned(),
|
level: filter.level().to_owned(),
|
||||||
targets: filter.targets().to_vec(),
|
targets: filter.targets().to_vec(),
|
||||||
domains: filter.domains().to_vec(),
|
domains: filter.domains().to_vec(),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/logging_runtime.rs
|
// file: crates/ksp-app-config-desk/src/logging_runtime.rs
|
||||||
// version: 2
|
// version: 4
|
||||||
|
|
||||||
//! Runtime Logging metadata, launch identity and explicit profile application for Config Desk.
|
//! Runtime Logging metadata, launch identity and explicit profile application for Config Desk.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
/// Metadata for one currently active persistent Logging file output.
|
/// Metadata for one currently active persistent Logging file output.
|
||||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
@@ -56,12 +56,12 @@ pub(crate) fn launch_identity() -> ksp_core_lib::Result<ksp_logging_lib::Logging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns safe metadata for the currently active Logging runtime.
|
/// Returns safe metadata for the currently active Logging runtime.
|
||||||
pub(crate) fn status(state: &crate::AppState) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
|
pub(crate) fn logging_runtime_status(state: &crate::AppState) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
|
||||||
return state.logging_runtime_status();
|
return state.logging_runtime_status();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies one persisted Logging profile explicitly without changing `default_profile` or the source document.
|
/// Applies one persisted Logging profile explicitly without changing `default_profile` or the source document.
|
||||||
pub(crate) fn apply_profile(state: &crate::AppState, profile_id: &str) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
|
pub(crate) fn apply_logging_profile(state: &crate::AppState, profile_id: &str) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
|
||||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||||
let environment = match environment {
|
let environment = match environment {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -88,7 +88,8 @@ pub(crate) fn apply_profile(state: &crate::AppState, profile_id: &str) -> ksp_co
|
|||||||
return state.logging_runtime_status();
|
return state.logging_runtime_status();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn project_file(metadata: &ksp_logging_lib::RuntimeFileMetadata) -> LoggingRuntimeFileDto {
|
/// Executes the crate-internal project logging file operation for the owning module.
|
||||||
|
pub(crate) fn project_logging_file(metadata: &ksp_logging_lib::RuntimeFileMetadata) -> LoggingRuntimeFileDto {
|
||||||
return LoggingRuntimeFileDto {
|
return LoggingRuntimeFileDto {
|
||||||
output_id: metadata.output_id().to_owned(),
|
output_id: metadata.output_id().to_owned(),
|
||||||
directory: metadata.directory().display().to_string(),
|
directory: metadata.directory().display().to_string(),
|
||||||
@@ -97,7 +98,7 @@ pub(crate) fn project_file(metadata: &ksp_logging_lib::RuntimeFileMetadata) -> L
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn rotation_label(rotation: ksp_logging_lib::FileRotation) -> &'static str {
|
const fn rotation_label(rotation: ksp_logging_lib::FileRotation) -> &'static str {
|
||||||
return match rotation {
|
return match rotation {
|
||||||
ksp_logging_lib::FileRotation::Never => "never",
|
ksp_logging_lib::FileRotation::Never => "never",
|
||||||
ksp_logging_lib::FileRotation::Hourly => "hourly",
|
ksp_logging_lib::FileRotation::Hourly => "hourly",
|
||||||
@@ -105,6 +106,7 @@ pub(crate) const fn rotation_label(rotation: ksp_logging_lib::FileRotation) -> &
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the crate-internal count to u64 operation for the owning module.
|
||||||
pub(crate) fn count_to_u64(value: usize) -> u64 {
|
pub(crate) fn count_to_u64(value: usize) -> u64 {
|
||||||
let converted = u64::try_from(value);
|
let converted = u64::try_from(value);
|
||||||
return match converted {
|
return match converted {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/logging_test.rs
|
// file: crates/ksp-app-config-desk/src/logging_test.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//! Controlled Logging test events for validating KSP runtime routing from Config Desk.
|
//! Controlled Logging test events for validating KSP runtime routing from Config Desk.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
/// Controlled request emitted through the backend KSP Logging facade.
|
/// Controlled request emitted through the backend KSP Logging facade.
|
||||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||||
@@ -56,7 +56,7 @@ enum LoggingTestTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Emits one controlled backend test request through `ksp-logging-lib` only.
|
/// Emits one controlled backend test request through `ksp-logging-lib` only.
|
||||||
pub(crate) fn emit(state: &crate::AppState, request: LoggingTestRequestDto) -> ksp_core_lib::Result<LoggingTestResultDto> {
|
pub(crate) fn emit_logging_test(state: &crate::AppState, request: LoggingTestRequestDto) -> ksp_core_lib::Result<LoggingTestResultDto> {
|
||||||
let level = parse_level(request.level.as_str());
|
let level = parse_level(request.level.as_str());
|
||||||
let level = match level {
|
let level = match level {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/profiles.rs
|
// file: crates/ksp-app-config-desk/src/profiles.rs
|
||||||
// version: 1
|
// version: 4
|
||||||
|
|
||||||
//! Safe Config profile inspection and provenance projections for Config Desk.
|
//! Safe Config profile inspection and provenance projections for Config Desk.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
/// One Config document that exposes the standard `default_profile` / `profiles` contract.
|
/// One Config document that exposes the standard `default_profile` / `profiles` contract.
|
||||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
@@ -77,13 +77,22 @@ pub(crate) struct ConfigProfileDetailDto {
|
|||||||
pub(crate) environment_provenance: std::vec::Vec<ConfigProfileEnvironmentProvenanceDto>,
|
pub(crate) environment_provenance: std::vec::Vec<ConfigProfileEnvironmentProvenanceDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ProfileContract {
|
||||||
|
default_profile: String,
|
||||||
|
profile_ids: std::vec::Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Lists validated Config documents that expose standard profiles.
|
/// Lists validated Config documents that expose standard profiles.
|
||||||
pub(crate) fn inventory(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigProfileDocumentDto>> {
|
pub(crate) fn profile_inventory(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigProfileDocumentDto>> {
|
||||||
return inventory_from_management(state.config_management());
|
return inventory_from_management(state.config_management());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves one default or explicitly selected profile into a safe inspection DTO.
|
/// Resolves one default or explicitly selected profile into a safe inspection DTO.
|
||||||
pub(crate) fn detail(state: &crate::AppState, file_id: &str, requested_profile: std::option::Option<&str>) -> ksp_core_lib::Result<ConfigProfileDetailDto> {
|
pub(crate) fn profile_detail(
|
||||||
|
state: &crate::AppState,
|
||||||
|
file_id: &str,
|
||||||
|
requested_profile: std::option::Option<&str>,
|
||||||
|
) -> ksp_core_lib::Result<ConfigProfileDetailDto> {
|
||||||
return detail_from_management(state.config_management(), file_id, requested_profile);
|
return detail_from_management(state.config_management(), file_id, requested_profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,11 +222,6 @@ fn detail_from_management(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProfileContract {
|
|
||||||
default_profile: String,
|
|
||||||
profile_ids: std::vec::Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn profile_contract(document: &ksp_config_lib::ConfigJsonDocument) -> ksp_core_lib::Result<std::option::Option<ProfileContract>> {
|
fn profile_contract(document: &ksp_config_lib::ConfigJsonDocument) -> ksp_core_lib::Result<std::option::Option<ProfileContract>> {
|
||||||
let root = match document.value().as_object() {
|
let root = match document.value().as_object() {
|
||||||
std::option::Option::Some(value) => value,
|
std::option::Option::Some(value) => value,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/secrets.rs
|
// file: crates/ksp-app-config-desk/src/secrets.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//! Privileged, explicitly requested Config Secret reveal boundary for Config Desk.
|
//! Privileged, explicitly requested Config Secret reveal boundary for Config Desk.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
/// Privileged reveal request kept separate from ordinary environment-report DTOs.
|
/// Privileged reveal request kept separate from ordinary environment-report DTOs.
|
||||||
#[derive(serde::Deserialize, TS)]
|
#[derive(serde::Deserialize, TS)]
|
||||||
@@ -56,7 +56,7 @@ impl SecretRevealSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reveals one real Secret value only after the dedicated Tauri command has been explicitly invoked.
|
/// Reveals one real Secret value only after the dedicated Tauri command has been explicitly invoked.
|
||||||
pub(crate) fn reveal(state: &crate::AppState, request: SecretRevealRequestDto) -> ksp_core_lib::Result<SecretRevealResponseDto> {
|
pub(crate) fn reveal_secret(state: &crate::AppState, request: SecretRevealRequestDto) -> ksp_core_lib::Result<SecretRevealResponseDto> {
|
||||||
return reveal_from_management(state.config_management(), request);
|
return reveal_from_management(state.config_management(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/splash.rs
|
// file: crates/ksp-app-config-desk/src/splash.rs
|
||||||
// version: 3
|
// version: 6
|
||||||
|
|
||||||
//! Common splash settings and frontend event contracts for Config Desk.
|
//! Common splash settings and frontend event contracts for Config Desk.
|
||||||
|
|
||||||
use ts_rs::TS; // rust-rules: derive-import
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
|
const DEFAULT_SPLASH_FADE_IN_MS: u32 = 300;
|
||||||
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
|
const DEFAULT_SPLASH_FADE_OUT_MS: u32 = 300;
|
||||||
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
|
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
|
||||||
const DEFAULT_SPLASH_FADE_MS: u32 = 300;
|
const ENV_SPLASH_FADE_IN_MS: &str = "KSP_DESK_SPLASH_FADE_IN_MS";
|
||||||
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
const ENV_SPLASH_FADE_OUT_MS: &str = "KSP_DESK_SPLASH_FADE_OUT_MS";
|
||||||
|
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
|
||||||
const MAX_SPLASH_FADE_MS: u32 = 10_000;
|
const MAX_SPLASH_FADE_MS: u32 = 10_000;
|
||||||
|
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
||||||
|
|
||||||
/// Runtime timings used by the common desk splash lifecycle.
|
/// Runtime timings used by the common desk splash lifecycle.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub(crate) struct SplashSettings {
|
pub(crate) struct SplashSettings {
|
||||||
|
fade_in_ms: u32,
|
||||||
minimum_ms: u64,
|
minimum_ms: u64,
|
||||||
fade_ms: u32,
|
fade_out_ms: u32,
|
||||||
|
fade_in_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||||
minimum_source: ksp_config_lib::ConfigEnvironmentSource,
|
minimum_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||||
fade_source: ksp_config_lib::ConfigEnvironmentSource,
|
fade_out_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SplashSettings {
|
impl SplashSettings {
|
||||||
@@ -29,13 +33,23 @@ impl SplashSettings {
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
|
let fade_in = environment.resolve_variable(ENV_SPLASH_FADE_IN_MS, std::option::Option::Some("300"));
|
||||||
|
let fade_in = match fade_in {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
let minimum = environment.resolve_variable(ENV_SPLASH_MINIMUM_MS, std::option::Option::Some("1200"));
|
let minimum = environment.resolve_variable(ENV_SPLASH_MINIMUM_MS, std::option::Option::Some("1200"));
|
||||||
let minimum = match minimum {
|
let minimum = match minimum {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let fade = environment.resolve_variable(ENV_SPLASH_FADE_MS, std::option::Option::Some("300"));
|
let fade_out = environment.resolve_variable(ENV_SPLASH_FADE_OUT_MS, std::option::Option::Some("300"));
|
||||||
let fade = match fade {
|
let fade_out = match fade_out {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let fade_in_ms = parse_u32_setting(ENV_SPLASH_FADE_IN_MS, fade_in.value(), MAX_SPLASH_FADE_MS);
|
||||||
|
let fade_in_ms = match fade_in_ms {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
@@ -44,35 +58,56 @@ impl SplashSettings {
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let fade_ms = parse_u32_setting(ENV_SPLASH_FADE_MS, fade.value(), MAX_SPLASH_FADE_MS);
|
let fade_out_ms = parse_u32_setting(ENV_SPLASH_FADE_OUT_MS, fade_out.value(), MAX_SPLASH_FADE_MS);
|
||||||
let fade_ms = match fade_ms {
|
let fade_out_ms = match fade_out_ms {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
return std::result::Result::Ok(Self { minimum_ms, fade_ms, minimum_source: minimum.source(), fade_source: fade.source() });
|
return std::result::Result::Ok(Self {
|
||||||
|
fade_in_ms,
|
||||||
|
minimum_ms,
|
||||||
|
fade_out_ms,
|
||||||
|
fade_in_source: fade_in.source(),
|
||||||
|
minimum_source: minimum.source(),
|
||||||
|
fade_out_source: fade_out.source(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns safe in-memory timings used when the managed environment cannot be resolved.
|
/// Returns safe in-memory timings used when the managed environment cannot be resolved.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) const fn fallback() -> Self {
|
pub(crate) const fn fallback() -> Self {
|
||||||
return Self {
|
return Self {
|
||||||
|
fade_in_ms: DEFAULT_SPLASH_FADE_IN_MS,
|
||||||
minimum_ms: DEFAULT_SPLASH_MINIMUM_MS,
|
minimum_ms: DEFAULT_SPLASH_MINIMUM_MS,
|
||||||
fade_ms: DEFAULT_SPLASH_FADE_MS,
|
fade_out_ms: DEFAULT_SPLASH_FADE_OUT_MS,
|
||||||
|
fade_in_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||||
minimum_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
minimum_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||||
fade_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
fade_out_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the minimum visible duration after splash frontend readiness.
|
/// Returns the configured fade-in duration.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn fade_in_ms(self) -> u32 {
|
||||||
|
return self.fade_in_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum fully-visible splash duration.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) const fn minimum_ms(self) -> u64 {
|
pub(crate) const fn minimum_ms(self) -> u64 {
|
||||||
return self.minimum_ms;
|
return self.minimum_ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the fade duration used for both fade-in and fade-out.
|
/// Returns the configured fade-out duration.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) const fn fade_ms(self) -> u32 {
|
pub(crate) const fn fade_out_ms(self) -> u32 {
|
||||||
return self.fade_ms;
|
return self.fade_out_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the safe provenance code for the fade-in duration.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn fade_in_source(self) -> &'static str {
|
||||||
|
return environment_source_code(self.fade_in_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the safe provenance code for the minimum duration.
|
/// Returns the safe provenance code for the minimum duration.
|
||||||
@@ -81,27 +116,19 @@ impl SplashSettings {
|
|||||||
return environment_source_code(self.minimum_source);
|
return environment_source_code(self.minimum_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the safe provenance code for the fade duration.
|
/// Returns the safe provenance code for the fade-out duration.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) const fn fade_source(self) -> &'static str {
|
pub(crate) const fn fade_out_source(self) -> &'static str {
|
||||||
return environment_source_code(self.fade_source);
|
return environment_source_code(self.fade_out_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the minimum backend lifecycle duration from readiness until main activation.
|
/// Returns the expected additive backend lifecycle from readiness through fade-out.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn expected_backend_lifecycle_ms(self) -> u64 {
|
pub(crate) fn expected_backend_lifecycle_ms(self) -> u64 {
|
||||||
return self.minimum_ms + u64::from(self.fade_ms);
|
return u64::from(self.fade_in_ms) + self.minimum_ms + u64::from(self.fade_out_ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn environment_source_code(source: ksp_config_lib::ConfigEnvironmentSource) -> &'static str {
|
|
||||||
return match source {
|
|
||||||
ksp_config_lib::ConfigEnvironmentSource::Process => "process",
|
|
||||||
ksp_config_lib::ConfigEnvironmentSource::DotEnv => "dotenv",
|
|
||||||
ksp_config_lib::ConfigEnvironmentSource::Fallback => "fallback",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Command emitted by Rust to the splash frontend.
|
/// Command emitted by Rust to the splash frontend.
|
||||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -111,17 +138,33 @@ pub(crate) struct SplashOrderDto {
|
|||||||
pub(crate) action: String,
|
pub(crate) action: String,
|
||||||
/// Optional status text.
|
/// Optional status text.
|
||||||
pub(crate) message: std::option::Option<String>,
|
pub(crate) message: std::option::Option<String>,
|
||||||
|
/// Optional message severity used by the bottom message feed.
|
||||||
|
pub(crate) status: std::option::Option<String>,
|
||||||
/// Optional animation duration in milliseconds.
|
/// Optional animation duration in milliseconds.
|
||||||
pub(crate) duration_ms: std::option::Option<u32>,
|
pub(crate) duration_ms: std::option::Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SplashOrderDto {
|
impl SplashOrderDto {
|
||||||
|
/// Creates a new `SplashOrderDto` value.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn new(action: &str, message: std::option::Option<&str>, duration_ms: std::option::Option<u32>) -> Self {
|
pub(crate) fn new(action: &str, message: std::option::Option<&str>, status: std::option::Option<&str>, duration_ms: std::option::Option<u32>) -> Self {
|
||||||
return Self { action: action.to_owned(), message: message.map(std::string::ToString::to_string), duration_ms };
|
return Self {
|
||||||
|
action: action.to_owned(),
|
||||||
|
message: message.map(std::string::ToString::to_string),
|
||||||
|
status: status.map(std::string::ToString::to_string),
|
||||||
|
duration_ms,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn environment_source_code(source: ksp_config_lib::ConfigEnvironmentSource) -> &'static str {
|
||||||
|
return match source {
|
||||||
|
ksp_config_lib::ConfigEnvironmentSource::Process => "process",
|
||||||
|
ksp_config_lib::ConfigEnvironmentSource::DotEnv => "dotenv",
|
||||||
|
ksp_config_lib::ConfigEnvironmentSource::Fallback => "fallback",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_u64_setting(variable_name: &str, value: &str, maximum: u64) -> ksp_core_lib::Result<u64> {
|
fn parse_u64_setting(variable_name: &str, value: &str, maximum: u64) -> ksp_core_lib::Result<u64> {
|
||||||
let parsed = value.parse::<u64>();
|
let parsed = value.parse::<u64>();
|
||||||
let parsed = match parsed {
|
let parsed = match parsed {
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/tauri.rs
|
// file: crates/ksp-app-config-desk/src/tauri.rs
|
||||||
// version: 15
|
// version: 17
|
||||||
|
|
||||||
//! Tauri runtime assembly for the KSP configuration desktop application.
|
//! Tauri runtime assembly for the KSP configuration desktop application.
|
||||||
|
|
||||||
/// Runs the configuration desktop application.
|
/// Runs the configuration desktop application.
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
||||||
|
let context = tauri::generate_context!();
|
||||||
|
let runtime_layout = configure_packaged_runtime(&context);
|
||||||
|
if let std::result::Result::Err(error) = runtime_layout {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
let app_state = crate::AppState::initialize(arguments);
|
let app_state = crate::AppState::initialize(arguments);
|
||||||
let app_state = match app_state {
|
let app_state = match app_state {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -16,7 +21,7 @@ pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
|||||||
builder = configure_plugins(builder);
|
builder = configure_plugins(builder);
|
||||||
builder = configure_commands(builder);
|
builder = configure_commands(builder);
|
||||||
builder = configure_setup(builder);
|
builder = configure_setup(builder);
|
||||||
let run_result = builder.run(tauri::generate_context!());
|
let run_result = builder.run(context);
|
||||||
return match run_result {
|
return match run_result {
|
||||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(
|
std::result::Result::Err(error) => std::result::Result::Err(
|
||||||
@@ -26,6 +31,36 @@ pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn configure_packaged_runtime(context: &tauri::Context<tauri::Wry>) -> ksp_core_lib::Result<()> {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
let resource_root = tauri::utils::platform::resource_dir(context.package_info(), &tauri::Env::default());
|
||||||
|
let resource_root = match resource_root {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot resolve packaged Tauri resource directory")
|
||||||
|
.with_context("tauri_error", error.to_string()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let layout = ksp_config_lib::prepare_packaged_runtime(resource_root.as_path());
|
||||||
|
let layout = match layout {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let working_directory = std::env::set_current_dir(layout.runtime_root());
|
||||||
|
return match working_directory {
|
||||||
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot activate packaged KSP runtime directory")
|
||||||
|
.with_context("runtime_root", layout.runtime_root().to_string_lossy().into_owned())
|
||||||
|
.with_source(error),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn configure_state(builder: tauri::Builder<tauri::Wry>, app_state: crate::AppState) -> tauri::Builder<tauri::Wry> {
|
fn configure_state(builder: tauri::Builder<tauri::Wry>, app_state: crate::AppState) -> tauri::Builder<tauri::Wry> {
|
||||||
return builder.manage(app_state);
|
return builder.manage(app_state);
|
||||||
}
|
}
|
||||||
@@ -60,11 +95,11 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
|
|||||||
|
|
||||||
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||||
return builder.setup(|app| {
|
return builder.setup(|app| {
|
||||||
let splash = crate::tw_splash::require_window(app);
|
let splash = crate::require_splash_window(app);
|
||||||
if let std::result::Result::Err(error) = splash {
|
if let std::result::Result::Err(error) = splash {
|
||||||
return std::result::Result::Err(std::boxed::Box::new(error));
|
return std::result::Result::Err(std::boxed::Box::new(error));
|
||||||
}
|
}
|
||||||
let main = crate::tw_main::require_window(app);
|
let main = crate::require_main_window(app);
|
||||||
if let std::result::Result::Err(error) = main {
|
if let std::result::Result::Err(error) = main {
|
||||||
return std::result::Result::Err(std::boxed::Box::new(error));
|
return std::result::Result::Err(std::boxed::Box::new(error));
|
||||||
}
|
}
|
||||||
@@ -83,7 +118,7 @@ fn get_app_snapshot(state: tauri::State<'_, crate::AppState>) -> std::result::Re
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_config_documents(state: tauri::State<'_, crate::AppState>) -> std::vec::Vec<crate::ConfigDocumentSummaryDto> {
|
fn get_config_documents(state: tauri::State<'_, crate::AppState>) -> std::vec::Vec<crate::ConfigDocumentSummaryDto> {
|
||||||
return crate::documents::inventory(&state);
|
return crate::document_inventory(&state);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -91,7 +126,7 @@ fn get_config_document_detail(
|
|||||||
file_id: String,
|
file_id: String,
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<crate::ConfigDocumentDetailDto, crate::ConfigDocumentErrorDto> {
|
) -> std::result::Result<crate::ConfigDocumentDetailDto, crate::ConfigDocumentErrorDto> {
|
||||||
return crate::documents::detail(&state, file_id.as_str());
|
return crate::document_detail(&state, file_id.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -100,14 +135,14 @@ fn save_config_document_source(
|
|||||||
source: String,
|
source: String,
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<crate::ConfigDocumentSaveResultDto, crate::ConfigDocumentErrorDto> {
|
) -> std::result::Result<crate::ConfigDocumentSaveResultDto, crate::ConfigDocumentErrorDto> {
|
||||||
return crate::documents::save_source(&state, file_id.as_str(), source.as_str());
|
return crate::save_document_source(&state, file_id.as_str(), source.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_config_profile_documents(
|
fn get_config_profile_documents(
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<std::vec::Vec<crate::ConfigProfileDocumentDto>, crate::CommandErrorDto> {
|
) -> std::result::Result<std::vec::Vec<crate::ConfigProfileDocumentDto>, crate::CommandErrorDto> {
|
||||||
let result = crate::profiles::inventory(&state);
|
let result = crate::profile_inventory(&state);
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -120,7 +155,7 @@ fn get_config_profile_detail(
|
|||||||
profile_id: std::option::Option<String>,
|
profile_id: std::option::Option<String>,
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<crate::ConfigProfileDetailDto, crate::CommandErrorDto> {
|
) -> std::result::Result<crate::ConfigProfileDetailDto, crate::CommandErrorDto> {
|
||||||
let result = crate::profiles::detail(&state, file_id.as_str(), profile_id.as_deref());
|
let result = crate::profile_detail(&state, file_id.as_str(), profile_id.as_deref());
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -131,7 +166,7 @@ fn get_config_profile_detail(
|
|||||||
fn get_environment_report(
|
fn get_environment_report(
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<std::vec::Vec<crate::ConfigEnvironmentReportDto>, crate::CommandErrorDto> {
|
) -> std::result::Result<std::vec::Vec<crate::ConfigEnvironmentReportDto>, crate::CommandErrorDto> {
|
||||||
let result = crate::environment::report(&state);
|
let result = crate::environment_report(&state);
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -144,7 +179,7 @@ fn set_environment_value(
|
|||||||
value: String,
|
value: String,
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
|
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
|
||||||
let result = crate::environment::set_value(&state, variable_name.as_str(), value.as_str());
|
let result = crate::set_environment_value(&state, variable_name.as_str(), value.as_str());
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -156,7 +191,7 @@ fn remove_environment_value(
|
|||||||
variable_name: String,
|
variable_name: String,
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
|
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
|
||||||
let result = crate::environment::remove_value(&state, variable_name.as_str());
|
let result = crate::remove_environment_value(&state, variable_name.as_str());
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -168,7 +203,7 @@ fn reveal_environment_value(
|
|||||||
request: crate::SecretRevealRequestDto,
|
request: crate::SecretRevealRequestDto,
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<crate::SecretRevealResponseDto, crate::CommandErrorDto> {
|
) -> std::result::Result<crate::SecretRevealResponseDto, crate::CommandErrorDto> {
|
||||||
let result = crate::secrets::reveal(&state, request);
|
let result = crate::reveal_secret(&state, request);
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -177,7 +212,7 @@ fn reveal_environment_value(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_logging_document(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::LoggingDocumentDto, crate::CommandErrorDto> {
|
fn get_logging_document(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::LoggingDocumentDto, crate::CommandErrorDto> {
|
||||||
let result = crate::logging_editor::document(&state);
|
let result = crate::logging_document(&state);
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -186,7 +221,7 @@ fn get_logging_document(state: tauri::State<'_, crate::AppState>) -> std::result
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_logging_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::LoggingRuntimeStatusDto, crate::CommandErrorDto> {
|
fn get_logging_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::LoggingRuntimeStatusDto, crate::CommandErrorDto> {
|
||||||
let result = crate::logging_runtime::status(&state);
|
let result = crate::logging_runtime_status(&state);
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -198,7 +233,7 @@ fn apply_logging_profile(
|
|||||||
profile_id: String,
|
profile_id: String,
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<crate::LoggingRuntimeStatusDto, crate::CommandErrorDto> {
|
) -> std::result::Result<crate::LoggingRuntimeStatusDto, crate::CommandErrorDto> {
|
||||||
let result = crate::logging_runtime::apply_profile(&state, profile_id.as_str());
|
let result = crate::apply_logging_profile(&state, profile_id.as_str());
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -210,7 +245,7 @@ fn save_logging_document(
|
|||||||
candidate: crate::LoggingDocumentCandidateDto,
|
candidate: crate::LoggingDocumentCandidateDto,
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<crate::LoggingDocumentSaveResultDto, crate::CommandErrorDto> {
|
) -> std::result::Result<crate::LoggingDocumentSaveResultDto, crate::CommandErrorDto> {
|
||||||
let result = crate::logging_editor::save(&state, candidate);
|
let result = crate::save_logging_document(&state, candidate);
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
@@ -222,7 +257,7 @@ fn emit_logging_test(
|
|||||||
request: crate::LoggingTestRequestDto,
|
request: crate::LoggingTestRequestDto,
|
||||||
state: tauri::State<'_, crate::AppState>,
|
state: tauri::State<'_, crate::AppState>,
|
||||||
) -> std::result::Result<crate::LoggingTestResultDto, crate::CommandErrorDto> {
|
) -> std::result::Result<crate::LoggingTestResultDto, crate::CommandErrorDto> {
|
||||||
let result = crate::logging_test::emit(&state, request);
|
let result = crate::emit_logging_test(&state, request);
|
||||||
return match result {
|
return match result {
|
||||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/tw_main.rs
|
// file: crates/ksp-app-config-desk/src/tw_main.rs
|
||||||
// version: 1
|
// version: 4
|
||||||
|
|
||||||
//! Tauri-window helpers for the Config Desk main window.
|
//! Tauri-window helpers for the Config Desk main window.
|
||||||
|
|
||||||
use tauri::Manager; // rust-rules: trait-import
|
use tauri::Manager; // rust-rules: trait-import
|
||||||
|
|
||||||
|
/// Crate-internal `WINDOW_LABEL_MAIN` constant.
|
||||||
pub(crate) const WINDOW_LABEL_MAIN: &str = "main";
|
pub(crate) const WINDOW_LABEL_MAIN: &str = "main";
|
||||||
|
|
||||||
pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
/// Resolves the required main window or returns a typed error.
|
||||||
|
pub(crate) fn require_main_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||||
let window = manager.get_webview_window(WINDOW_LABEL_MAIN);
|
let window = manager.get_webview_window(WINDOW_LABEL_MAIN);
|
||||||
return match window {
|
return match window {
|
||||||
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
||||||
@@ -18,8 +20,9 @@ pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn show_and_focus(app: &tauri::AppHandle) -> ksp_core_lib::Result<()> {
|
/// Shows main window.
|
||||||
let window = require_window(app);
|
pub(crate) fn show_main_window(app: &tauri::AppHandle) -> ksp_core_lib::Result<()> {
|
||||||
|
let window = crate::require_main_window(app);
|
||||||
let window = match window {
|
let window = match window {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/tw_splash.rs
|
// file: crates/ksp-app-config-desk/src/tw_splash.rs
|
||||||
// version: 2
|
// version: 6
|
||||||
|
|
||||||
//! Tauri-window lifecycle for the Config Desk splash window.
|
//! Tauri-window lifecycle for the Config Desk splash window.
|
||||||
|
|
||||||
use tauri::Emitter; // rust-rules: trait-import
|
use tauri::Emitter; // rust-rules: trait-import
|
||||||
use tauri::Manager; // rust-rules: trait-import
|
use tauri::Manager; // rust-rules: trait-import
|
||||||
|
|
||||||
|
/// Crate-internal splash window label.
|
||||||
pub(crate) const WINDOW_LABEL_SPLASH: &str = "splash";
|
pub(crate) const WINDOW_LABEL_SPLASH: &str = "splash";
|
||||||
|
|
||||||
const SPLASH_EVENT_NAME: &str = "ksp-splash-order";
|
const SPLASH_EVENT_NAME: &str = "ksp-splash-order";
|
||||||
|
|
||||||
pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
/// Resolves the required splash window or returns a typed error.
|
||||||
|
pub(crate) fn require_splash_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||||
let window = manager.get_webview_window(WINDOW_LABEL_SPLASH);
|
let window = manager.get_webview_window(WINDOW_LABEL_SPLASH);
|
||||||
return match window {
|
return match window {
|
||||||
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
||||||
@@ -20,7 +23,12 @@ pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn frontend_ready(app: tauri::AppHandle, invoking_window: tauri::WebviewWindow, state: &crate::AppState) -> ksp_core_lib::Result<()> {
|
/// Starts the one-shot splash lifecycle after the splash frontend reports readiness.
|
||||||
|
pub(crate) async fn splash_frontend_ready_service(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
invoking_window: tauri::WebviewWindow,
|
||||||
|
state: &crate::AppState,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
if invoking_window.label() != WINDOW_LABEL_SPLASH {
|
if invoking_window.label() != WINDOW_LABEL_SPLASH {
|
||||||
return std::result::Result::Err(
|
return std::result::Result::Err(
|
||||||
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_ORIGIN_INVALID, "Splash readiness may only originate from the splash window")
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_ORIGIN_INVALID, "Splash readiness may only originate from the splash window")
|
||||||
@@ -28,12 +36,7 @@ pub(crate) async fn frontend_ready(app: tauri::AppHandle, invoking_window: tauri
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if !state.begin_splash_sequence() {
|
if !state.begin_splash_sequence() {
|
||||||
ksp_logging_lib::trace!(
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, "duplicate splash frontend readiness ignored");
|
||||||
target: crate::TRACING_TARGET,
|
|
||||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
|
||||||
window_label = WINDOW_LABEL_SPLASH,
|
|
||||||
"duplicate splash frontend readiness ignored"
|
|
||||||
);
|
|
||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
let settings = state.splash_settings();
|
let settings = state.splash_settings();
|
||||||
@@ -42,49 +45,60 @@ pub(crate) async fn frontend_ready(app: tauri::AppHandle, invoking_window: tauri
|
|||||||
target: crate::TRACING_TARGET,
|
target: crate::TRACING_TARGET,
|
||||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
window_label = WINDOW_LABEL_SPLASH,
|
window_label = WINDOW_LABEL_SPLASH,
|
||||||
|
fade_in_ms = settings.fade_in_ms(),
|
||||||
|
fade_in_source = settings.fade_in_source(),
|
||||||
minimum_ms = settings.minimum_ms(),
|
minimum_ms = settings.minimum_ms(),
|
||||||
minimum_source = settings.minimum_source(),
|
minimum_source = settings.minimum_source(),
|
||||||
fade_ms = settings.fade_ms(),
|
fade_out_ms = settings.fade_out_ms(),
|
||||||
fade_source = settings.fade_source(),
|
fade_out_source = settings.fade_out_source(),
|
||||||
expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(),
|
expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(),
|
||||||
"splash frontend readiness accepted; starting splash to main lifecycle"
|
"splash frontend readiness accepted; starting splash to main lifecycle"
|
||||||
);
|
);
|
||||||
let fade_in = emit_order(
|
let emitted = emit_order(
|
||||||
&invoking_window,
|
&invoking_window,
|
||||||
crate::SplashOrderDto::new("fade_in", std::option::Option::Some("Initialisation de KSP Config Desk..."), std::option::Option::Some(settings.fade_ms())),
|
crate::SplashOrderDto::new("fade_in", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_in_ms())),
|
||||||
);
|
);
|
||||||
if let std::result::Result::Err(error) = fade_in {
|
if let std::result::Result::Err(error) = emitted {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
let minimum_wait_started = std::time::Instant::now();
|
let emitted = emit_message(&invoking_window, "Initialisation de Config Desk...", "info");
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(settings.minimum_ms())).await;
|
if let std::result::Result::Err(error) = emitted {
|
||||||
ksp_logging_lib::debug!(
|
|
||||||
target: crate::TRACING_TARGET,
|
|
||||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
|
||||||
window_label = WINDOW_LABEL_SPLASH,
|
|
||||||
configured_wait_ms = settings.minimum_ms(),
|
|
||||||
actual_wait_ms = minimum_wait_started.elapsed().as_secs_f64() * 1000.0,
|
|
||||||
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
|
||||||
"splash minimum wait completed"
|
|
||||||
);
|
|
||||||
let fade_out = emit_order(
|
|
||||||
&invoking_window,
|
|
||||||
crate::SplashOrderDto::new("fade_out", std::option::Option::Some("Initialisation terminée."), std::option::Option::Some(settings.fade_ms())),
|
|
||||||
);
|
|
||||||
if let std::result::Result::Err(error) = fade_out {
|
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
let fade_wait_started = std::time::Instant::now();
|
let emitted = emit_debug(&invoking_window, "Listener frontend prêt; fade-in piloté par Rust.");
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(u64::from(settings.fade_ms()))).await;
|
if let std::result::Result::Err(error) = emitted {
|
||||||
ksp_logging_lib::debug!(
|
return std::result::Result::Err(error);
|
||||||
target: crate::TRACING_TARGET,
|
}
|
||||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
sleep_and_log(&invoking_window, "fade-in", u64::from(settings.fade_in_ms()), lifecycle_started).await;
|
||||||
window_label = WINDOW_LABEL_SPLASH,
|
let emitted = emit_message(&invoking_window, "Configuration KSP chargée.", "success");
|
||||||
configured_wait_ms = settings.fade_ms(),
|
if let std::result::Result::Err(error) = emitted {
|
||||||
actual_wait_ms = fade_wait_started.elapsed().as_secs_f64() * 1000.0,
|
return std::result::Result::Err(error);
|
||||||
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
}
|
||||||
"splash fade-out wait completed"
|
let emitted = emit_debug(&invoking_window, "Configuration et provenance des timings résolues via ksp-config-lib.");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let emitted = emit_message(&invoking_window, "Runtime Logging initialisé.", "success");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let emitted = emit_message(&invoking_window, "Gestion Config prête.", "success");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
sleep_and_log(&invoking_window, "minimum", settings.minimum_ms(), lifecycle_started).await;
|
||||||
|
let emitted = emit_message(&invoking_window, "Initialisation terminée.", "success");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let emitted = emit_order(
|
||||||
|
&invoking_window,
|
||||||
|
crate::SplashOrderDto::new("fade_out", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_out_ms())),
|
||||||
);
|
);
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
sleep_and_log(&invoking_window, "fade-out", u64::from(settings.fade_out_ms()), lifecycle_started).await;
|
||||||
let show_main = crate::show_main_window(&app);
|
let show_main = crate::show_main_window(&app);
|
||||||
if let std::result::Result::Err(error) = show_main {
|
if let std::result::Result::Err(error) = show_main {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
@@ -108,18 +122,45 @@ pub(crate) async fn frontend_ready(app: tauri::AppHandle, invoking_window: tauri
|
|||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emit_message(window: &tauri::WebviewWindow, message: &str, status: &str) -> ksp_core_lib::Result<()> {
|
||||||
|
return emit_order(
|
||||||
|
window,
|
||||||
|
crate::SplashOrderDto::new("add_message", std::option::Option::Some(message), std::option::Option::Some(status), std::option::Option::None),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_debug(window: &tauri::WebviewWindow, message: &str) -> ksp_core_lib::Result<()> {
|
||||||
|
if !cfg!(debug_assertions) {
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
return emit_order(
|
||||||
|
window,
|
||||||
|
crate::SplashOrderDto::new("add_debug", std::option::Option::Some(message), std::option::Option::None, std::option::Option::None),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sleep_and_log(window: &tauri::WebviewWindow, phase: &str, duration_ms: u64, lifecycle_started: std::time::Instant) {
|
||||||
|
let wait_started = std::time::Instant::now();
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await;
|
||||||
|
ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
window_label = window.label(),
|
||||||
|
phase,
|
||||||
|
configured_wait_ms = duration_ms,
|
||||||
|
actual_wait_ms = wait_started.elapsed().as_secs_f64() * 1000.0,
|
||||||
|
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
||||||
|
"splash lifecycle wait completed"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fn emit_order(window: &tauri::WebviewWindow, order: crate::SplashOrderDto) -> ksp_core_lib::Result<()> {
|
fn emit_order(window: &tauri::WebviewWindow, order: crate::SplashOrderDto) -> ksp_core_lib::Result<()> {
|
||||||
let action = order.action.clone();
|
let action = order.action.clone();
|
||||||
let emitted = window.emit(SPLASH_EVENT_NAME, order);
|
let emitted = window.emit(SPLASH_EVENT_NAME, order);
|
||||||
return match emitted {
|
return match emitted {
|
||||||
std::result::Result::Ok(()) => {
|
std::result::Result::Ok(()) => {
|
||||||
ksp_logging_lib::trace!(
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, action = action.as_str(), "splash order emitted");
|
||||||
target: crate::TRACING_TARGET,
|
|
||||||
domain = crate::TRACING_DOMAIN_WINDOWS,
|
|
||||||
window_label = WINDOW_LABEL_SPLASH,
|
|
||||||
action = action.as_str(),
|
|
||||||
"splash order emitted"
|
|
||||||
);
|
|
||||||
std::result::Result::Ok(())
|
std::result::Result::Ok(())
|
||||||
},
|
},
|
||||||
std::result::Result::Err(error) => std::result::Result::Err(
|
std::result::Result::Err(error) => std::result::Result::Err(
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "KSP Config Desk",
|
"productName": "KSP Config Desk",
|
||||||
"version": "0.1.4",
|
"version": "0.2.6",
|
||||||
"identifier": "com.sasedev.ksp-app-config-desk",
|
"identifier": "com.sasedev.ksp-app-config-desk",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": {
|
||||||
|
"script": "npm run dev",
|
||||||
|
"cwd": "."
|
||||||
|
},
|
||||||
"devUrl": "http://localhost:1430",
|
"devUrl": "http://localhost:1430",
|
||||||
"beforeBuildCommand": "npm run build",
|
"beforeBuildCommand": {
|
||||||
"frontendDist": "../../builds/khadhroony-solana-project/ksp-app-config-desk/dist"
|
"script": "npm run build",
|
||||||
|
"cwd": "."
|
||||||
|
},
|
||||||
|
"frontendDist": "../../../builds/khadhroony-solana-project/ksp-app-config-desk/dist"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
@@ -45,6 +51,16 @@
|
|||||||
"icon": [
|
"icon": [
|
||||||
"icons/favicon.png",
|
"icons/favicon.png",
|
||||||
"icons/favicon.ico"
|
"icons/favicon.ico"
|
||||||
]
|
],
|
||||||
|
"resources": {
|
||||||
|
"../../config/composite.ksp-app-wallet-desk.json": "config/composite.ksp-app-wallet-desk.json",
|
||||||
|
"../../config/std.logging.json": "config/std.logging.json",
|
||||||
|
"../../config/std.transport.json": "config/std.transport.json",
|
||||||
|
"../../config/std.wallet.json": "config/std.wallet.json",
|
||||||
|
"../../config/schemas/composite.schema.json": "config/schemas/composite.schema.json",
|
||||||
|
"../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.schema.json",
|
||||||
|
"../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.schema.json",
|
||||||
|
"../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/tests/desktop_contract.rs
|
// file: crates/ksp-app-config-desk/tests/desktop_contract.rs
|
||||||
// version: 2
|
// version: 6
|
||||||
|
|
||||||
//! Desktop build/shell contract audits for Config Desk.
|
//! Desktop build/shell contract audits for Config Desk.
|
||||||
|
|
||||||
@@ -31,8 +31,10 @@ fn tauri_and_frontend_build_contracts_remain_explicit() {
|
|||||||
let root = app_root();
|
let root = app_root();
|
||||||
let tauri = read_json(root.join("tauri.conf.json").as_path());
|
let tauri = read_json(root.join("tauri.conf.json").as_path());
|
||||||
assert_eq!(tauri.pointer("/build/devUrl").and_then(serde_json::Value::as_str), std::option::Option::Some("http://localhost:1430"));
|
assert_eq!(tauri.pointer("/build/devUrl").and_then(serde_json::Value::as_str), std::option::Option::Some("http://localhost:1430"));
|
||||||
assert_eq!(tauri.pointer("/build/beforeDevCommand").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev"));
|
assert_eq!(tauri.pointer("/build/beforeDevCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev"));
|
||||||
assert_eq!(tauri.pointer("/build/beforeBuildCommand").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run build"));
|
assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
|
||||||
|
assert_eq!(tauri.pointer("/build/beforeBuildCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run build"));
|
||||||
|
assert_eq!(tauri.pointer("/build/beforeBuildCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
|
||||||
let windows = tauri.pointer("/app/windows").and_then(serde_json::Value::as_array);
|
let windows = tauri.pointer("/app/windows").and_then(serde_json::Value::as_array);
|
||||||
assert!(windows.is_some());
|
assert!(windows.is_some());
|
||||||
let windows = match windows {
|
let windows = match windows {
|
||||||
@@ -47,11 +49,13 @@ fn tauri_and_frontend_build_contracts_remain_explicit() {
|
|||||||
if let std::option::Option::Some(main) = main {
|
if let std::option::Option::Some(main) = main {
|
||||||
assert_eq!(main.get("visible").and_then(serde_json::Value::as_bool), std::option::Option::Some(false));
|
assert_eq!(main.get("visible").and_then(serde_json::Value::as_bool), std::option::Option::Some(false));
|
||||||
}
|
}
|
||||||
|
let manifest = read_text(root.join("Cargo.toml").as_path());
|
||||||
|
assert!(manifest.contains("name = \"ksp-app-config-desk\""));
|
||||||
let package = read_json(root.join("package.json").as_path());
|
let package = read_json(root.join("package.json").as_path());
|
||||||
let build = package.pointer("/scripts/build").and_then(serde_json::Value::as_str);
|
let build = package.pointer("/scripts/build").and_then(serde_json::Value::as_str);
|
||||||
let check = package.pointer("/scripts/check").and_then(serde_json::Value::as_str);
|
|
||||||
assert!(build.is_some_and(|value| return value.contains("tsc") && value.contains("vite build")));
|
assert!(build.is_some_and(|value| return value.contains("tsc") && value.contains("vite build")));
|
||||||
assert_eq!(check, std::option::Option::Some("tsc --noEmit"));
|
assert!(package.pointer("/scripts/check").is_none());
|
||||||
|
assert!(package.pointer("/scripts/preview").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -70,3 +74,52 @@ fn shell_registry_owns_view_activation_and_specialized_editor_dispatch() {
|
|||||||
assert!(documents.contains("requestViewActivation"));
|
assert!(documents.contains("requestViewActivation"));
|
||||||
assert!(html.contains("id=\"openSpecializedEditor\""));
|
assert!(html.contains("id=\"openSpecializedEditor\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_014_template_uses_sidebar_navigation_and_kbot_style_splash_contract() {
|
||||||
|
let root = app_root();
|
||||||
|
let main = read_text(root.join("frontend/main.html").as_path());
|
||||||
|
let splash = read_text(root.join("frontend/splash.html").as_path());
|
||||||
|
let app_style = read_text(root.join("frontend/sass/_app.scss").as_path());
|
||||||
|
let splash_style = read_text(root.join("frontend/sass/splash.scss").as_path());
|
||||||
|
let splash_ts = read_text(root.join("frontend/ts/splash.ts").as_path());
|
||||||
|
assert!(main.starts_with("<!-- file: crates/ksp-app-config-desk/frontend/main.html -->"));
|
||||||
|
assert!(main.contains("app-sidebar"));
|
||||||
|
assert!(main.contains("row g-0 h-100 flex-nowrap"));
|
||||||
|
assert!(app_style.contains("flex-wrap: nowrap"));
|
||||||
|
assert!(app_style.contains("min-width: 0"));
|
||||||
|
assert!(main.contains("nav nav-pills flex-column"));
|
||||||
|
assert!(splash.starts_with("<!-- file: crates/ksp-app-config-desk/frontend/splash.html -->"));
|
||||||
|
assert!(splash.contains("id=\"debug-info\""));
|
||||||
|
assert!(splash.contains("id=\"messages-container\""));
|
||||||
|
assert!(splash.contains("sass/splash.scss"));
|
||||||
|
assert!(splash_style.contains("overflow: hidden"));
|
||||||
|
assert!(splash_style.contains("width: 100%"));
|
||||||
|
assert!(splash_style.contains("user-select: none"));
|
||||||
|
assert!(splash_ts.contains("add_debug"));
|
||||||
|
assert!(splash_ts.contains("add_message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_018_packaged_runtime_bundles_config_resources_and_activates_shared_writable_root() {
|
||||||
|
let root = app_root();
|
||||||
|
let expected_version = env!("CARGO_PKG_VERSION");
|
||||||
|
let tauri = read_json(root.join("tauri.conf.json").as_path());
|
||||||
|
assert_eq!(tauri.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some(expected_version));
|
||||||
|
let package = read_json(root.join("package.json").as_path());
|
||||||
|
assert_eq!(package.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some(expected_version));
|
||||||
|
let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object);
|
||||||
|
assert!(resources.is_some(), "packaged Config resources map must exist");
|
||||||
|
if let std::option::Option::Some(resources) = resources {
|
||||||
|
assert_eq!(resources.len(), 8);
|
||||||
|
assert_eq!(resources.get("../../config/std.logging.json").and_then(serde_json::Value::as_str), std::option::Option::Some("config/std.logging.json"));
|
||||||
|
assert_eq!(
|
||||||
|
resources.get("../../config/schemas/std.wallet.schema.json").and_then(serde_json::Value::as_str),
|
||||||
|
std::option::Option::Some("config/schemas/std.wallet.schema.json"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let tauri_source = read_text(root.join("src/tauri.rs").as_path());
|
||||||
|
assert!(tauri_source.contains("ksp_config_lib::prepare_packaged_runtime"));
|
||||||
|
assert!(tauri_source.contains("tauri::utils::platform::resource_dir"));
|
||||||
|
assert!(tauri_source.contains("std::env::set_current_dir(layout.runtime_root())"));
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,4 +28,4 @@
|
|||||||
"frontend",
|
"frontend",
|
||||||
"vite.config.ts"
|
"vite.config.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/documents.rs
|
// file: crates/ksp-app-config-desk/unit_tests/documents.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn diagnostic_classifier_distinguishes_json_schema_semantic_and_effective_errors() {
|
fn diagnostic_classifier_distinguishes_json_schema_semantic_and_effective_errors() {
|
||||||
let json = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_JSON_SYNTAX_INVALID, "json");
|
let json = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_JSON_SYNTAX_INVALID, "json");
|
||||||
assert_eq!(classify_error(&json), STAGE_JSON);
|
assert_eq!(super::classify_error(&json), super::STAGE_JSON);
|
||||||
let schema = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_SCHEMA_VALIDATION_FAILED, "schema");
|
let schema = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_SCHEMA_VALIDATION_FAILED, "schema");
|
||||||
assert_eq!(classify_error(&schema), STAGE_SCHEMA);
|
assert_eq!(super::classify_error(&schema), super::STAGE_SCHEMA);
|
||||||
let semantic = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID, "semantic");
|
let semantic = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID, "semantic");
|
||||||
assert_eq!(classify_error(&semantic), STAGE_SEMANTIC);
|
assert_eq!(super::classify_error(&semantic), super::STAGE_SEMANTIC);
|
||||||
let effective = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_EFFECTIVE_CONFIG_INVALID, "effective");
|
let effective = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_EFFECTIVE_CONFIG_INVALID, "effective");
|
||||||
assert_eq!(classify_error(&effective), STAGE_EFFECTIVE);
|
assert_eq!(super::classify_error(&effective), super::STAGE_EFFECTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn schema_source_read_errors_are_classified_as_schema_failures() {
|
fn schema_source_read_errors_are_classified_as_schema_failures() {
|
||||||
let error = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_JSON_FILE_READ_FAILED, "missing schema").with_context("file_id", "schema.std.logging");
|
let error = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_JSON_FILE_READ_FAILED, "missing schema").with_context("file_id", "schema.std.logging");
|
||||||
assert_eq!(classify_error(&error), STAGE_SCHEMA);
|
assert_eq!(super::classify_error(&error), super::STAGE_SCHEMA);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn document_error_projection_does_not_expose_arbitrary_error_context() {
|
fn document_error_projection_does_not_expose_arbitrary_error_context() {
|
||||||
let error = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_JSON_SYNTAX_INVALID, "invalid source").with_context("detail", "sensitive-canary");
|
let error = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_JSON_SYNTAX_INVALID, "invalid source").with_context("detail", "sensitive-canary");
|
||||||
let projection = ConfigDocumentErrorDto::from_error(&error);
|
let projection = crate::ConfigDocumentErrorDto::from_error(&error);
|
||||||
assert_eq!(projection.diagnostic_stage, STAGE_JSON);
|
assert_eq!(projection.diagnostic_stage, super::STAGE_JSON);
|
||||||
assert_eq!(projection.error.domain, "config");
|
assert_eq!(projection.error.domain, "config");
|
||||||
assert_eq!(projection.error.code, "json_syntax_invalid");
|
assert_eq!(projection.error.code, "json_syntax_invalid");
|
||||||
assert_eq!(projection.error.message, "invalid source");
|
assert_eq!(projection.error.message, "invalid source");
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/dto_common.rs
|
// file: crates/ksp-app-config-desk/unit_tests/dto_common.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn command_error_projection_excludes_context_values() {
|
fn command_error_projection_excludes_context_values() {
|
||||||
let error = ksp_core_lib::Error::new(ksp_core_lib::ErrorCode::new("test", "failure"), "safe message").with_context("secret_canary", "must-not-be-exported");
|
let error = ksp_core_lib::Error::new(ksp_core_lib::ErrorCode::new("test", "failure"), "safe message").with_context("secret_canary", "must-not-be-exported");
|
||||||
let dto = super::CommandErrorDto::from_error(&error);
|
let dto = crate::CommandErrorDto::from_error(&error);
|
||||||
assert_eq!(dto.domain, "test");
|
assert_eq!(dto.domain, "test");
|
||||||
assert_eq!(dto.code, "failure");
|
assert_eq!(dto.code, "failure");
|
||||||
assert_eq!(dto.message, "safe message");
|
assert_eq!(dto.message, "safe message");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/environment.rs
|
// file: crates/ksp-app-config-desk/unit_tests/environment.rs
|
||||||
// version: 2
|
// version: 4
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn namespace_projection_distinguishes_ksp_and_kspb_sensitivity_families() {
|
fn namespace_projection_distinguishes_ksp_and_kspb_sensitivity_families() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/logging_editor.rs
|
// file: crates/ksp-app-config-desk/unit_tests/logging_editor.rs
|
||||||
// version: 4
|
// version: 7
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn committed_logging_document_maps_complete_read_only_editor_contract() {
|
fn committed_logging_document_maps_complete_read_only_editor_contract() {
|
||||||
@@ -66,7 +66,7 @@ fn editor_candidate_round_trips_through_typed_config_contract() {
|
|||||||
let document = super::document_from_management(&management);
|
let document = super::document_from_management(&management);
|
||||||
assert!(document.is_ok(), "Logging editor document should load: {document:?}");
|
assert!(document.is_ok(), "Logging editor document should load: {document:?}");
|
||||||
if let std::result::Result::Ok(document) = document {
|
if let std::result::Result::Ok(document) = document {
|
||||||
let candidate = super::LoggingDocumentCandidateDto {
|
let candidate = crate::LoggingDocumentCandidateDto {
|
||||||
logs_directory: document.logs_directory.clone(),
|
logs_directory: document.logs_directory.clone(),
|
||||||
default_profile: document.default_profile.clone(),
|
default_profile: document.default_profile.clone(),
|
||||||
profiles: document.profiles.clone(),
|
profiles: document.profiles.clone(),
|
||||||
@@ -89,42 +89,42 @@ fn editor_candidate_round_trips_through_typed_config_contract() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn profile_candidate_preserves_multi_file_and_target_filter_shape() {
|
fn profile_candidate_preserves_multi_file_and_target_filter_shape() {
|
||||||
let profile = super::LoggingProfileDto {
|
let profile = crate::LoggingProfileDto {
|
||||||
profile_id: "clone_test".to_owned(),
|
profile_id: "clone_test".to_owned(),
|
||||||
default_filter: "info".to_owned(),
|
default_filter: "info".to_owned(),
|
||||||
span_events: "full".to_owned(),
|
span_events: "full".to_owned(),
|
||||||
console: super::LoggingConsoleDto {
|
console: crate::LoggingConsoleDto {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
output: "stdout".to_owned(),
|
output: "stdout".to_owned(),
|
||||||
ansi: false,
|
ansi: false,
|
||||||
format: "human".to_owned(),
|
format: "human".to_owned(),
|
||||||
filter: super::LoggingOutputFilterDto { level: "info".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] },
|
filter: crate::LoggingOutputFilterDto { level: "info".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] },
|
||||||
},
|
},
|
||||||
files: std::vec![
|
files: std::vec![
|
||||||
super::LoggingFileDto {
|
crate::LoggingFileDto {
|
||||||
output_id: "file.one".to_owned(),
|
output_id: "file.one".to_owned(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
path: "one.log".to_owned(),
|
path: "one.log".to_owned(),
|
||||||
rotation: "daily".to_owned(),
|
rotation: "daily".to_owned(),
|
||||||
format: "human".to_owned(),
|
format: "human".to_owned(),
|
||||||
ansi: false,
|
ansi: false,
|
||||||
filter: super::LoggingOutputFilterDto { level: "debug".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] },
|
filter: crate::LoggingOutputFilterDto { level: "debug".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] },
|
||||||
},
|
},
|
||||||
super::LoggingFileDto {
|
crate::LoggingFileDto {
|
||||||
output_id: "file.two".to_owned(),
|
output_id: "file.two".to_owned(),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
path: "two.jsonl".to_owned(),
|
path: "two.jsonl".to_owned(),
|
||||||
rotation: "hourly".to_owned(),
|
rotation: "hourly".to_owned(),
|
||||||
format: "json".to_owned(),
|
format: "json".to_owned(),
|
||||||
ansi: false,
|
ansi: false,
|
||||||
filter: super::LoggingOutputFilterDto {
|
filter: crate::LoggingOutputFilterDto {
|
||||||
level: "error".to_owned(),
|
level: "error".to_owned(),
|
||||||
targets: std::vec!["ksp-config-lib".to_owned()],
|
targets: std::vec!["ksp-config-lib".to_owned()],
|
||||||
domains: std::vec!["config".to_owned()],
|
domains: std::vec!["config".to_owned()],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
target_filters: std::vec![super::LoggingTargetFilterDto { target_prefix: "ksp-app-config-desk".to_owned(), level: "trace".to_owned() }],
|
target_filters: std::vec![crate::LoggingTargetFilterDto { target_prefix: "ksp-app-config-desk".to_owned(), level: "trace".to_owned() }],
|
||||||
};
|
};
|
||||||
let config = super::config_profile(profile);
|
let config = super::config_profile(profile);
|
||||||
assert_eq!(config.profile_id(), "clone_test");
|
assert_eq!(config.profile_id(), "clone_test");
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/logging_runtime.rs
|
// file: crates/ksp-app-config-desk/unit_tests/logging_runtime.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn launch_identity_is_safe_and_contains_application_and_process_identity() {
|
fn launch_identity_is_safe_and_contains_application_and_process_identity() {
|
||||||
let identity = super::launch_identity();
|
let identity = crate::launch_identity();
|
||||||
assert!(identity.is_ok());
|
assert!(identity.is_ok());
|
||||||
if let std::result::Result::Ok(identity) = identity {
|
if let std::result::Result::Ok(identity) = identity {
|
||||||
assert_eq!(identity.application_id(), crate::TRACING_TARGET);
|
assert_eq!(identity.application_id(), crate::TRACING_TARGET);
|
||||||
@@ -21,6 +21,6 @@ fn runtime_rotation_labels_are_stable() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runtime_count_projection_is_non_lossy_for_normal_values() {
|
fn runtime_count_projection_is_non_lossy_for_normal_values() {
|
||||||
assert_eq!(super::count_to_u64(0), 0);
|
assert_eq!(crate::count_to_u64(0), 0);
|
||||||
assert_eq!(super::count_to_u64(42), 42);
|
assert_eq!(crate::count_to_u64(42), 42);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/logging_test.rs
|
// file: crates/ksp-app-config-desk/unit_tests/logging_test.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
use super::{LoggingTestLevel, LoggingTestTarget};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn controlled_logging_test_levels_include_all_five_levels_and_batch_mode() {
|
fn controlled_logging_test_levels_include_all_five_levels_and_batch_mode() {
|
||||||
assert!(matches!(super::parse_level("trace"), std::result::Result::Ok(LoggingTestLevel::Trace)));
|
assert!(matches!(super::parse_level("trace"), std::result::Result::Ok(super::LoggingTestLevel::Trace)));
|
||||||
assert!(matches!(super::parse_level("debug"), std::result::Result::Ok(LoggingTestLevel::Debug)));
|
assert!(matches!(super::parse_level("debug"), std::result::Result::Ok(super::LoggingTestLevel::Debug)));
|
||||||
assert!(matches!(super::parse_level("info"), std::result::Result::Ok(LoggingTestLevel::Info)));
|
assert!(matches!(super::parse_level("info"), std::result::Result::Ok(super::LoggingTestLevel::Info)));
|
||||||
assert!(matches!(super::parse_level("warn"), std::result::Result::Ok(LoggingTestLevel::Warn)));
|
assert!(matches!(super::parse_level("warn"), std::result::Result::Ok(super::LoggingTestLevel::Warn)));
|
||||||
assert!(matches!(super::parse_level("error"), std::result::Result::Ok(LoggingTestLevel::Error)));
|
assert!(matches!(super::parse_level("error"), std::result::Result::Ok(super::LoggingTestLevel::Error)));
|
||||||
assert!(matches!(super::parse_level("all"), std::result::Result::Ok(LoggingTestLevel::All)));
|
assert!(matches!(super::parse_level("all"), std::result::Result::Ok(super::LoggingTestLevel::All)));
|
||||||
assert!(super::parse_level("fatal").is_err());
|
assert!(super::parse_level("fatal").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn controlled_logging_test_targets_are_static_ksp_targets() {
|
fn controlled_logging_test_targets_are_static_ksp_targets() {
|
||||||
assert!(matches!(super::parse_target("app"), std::result::Result::Ok(LoggingTestTarget::App)));
|
assert!(matches!(super::parse_target("app"), std::result::Result::Ok(super::LoggingTestTarget::App)));
|
||||||
assert!(matches!(super::parse_target("logging_test"), std::result::Result::Ok(LoggingTestTarget::Dedicated)));
|
assert!(matches!(super::parse_target("logging_test"), std::result::Result::Ok(super::LoggingTestTarget::Dedicated)));
|
||||||
assert_eq!(super::target_value(LoggingTestTarget::App), crate::TRACING_TARGET);
|
assert_eq!(super::target_value(super::LoggingTestTarget::App), crate::TRACING_TARGET);
|
||||||
assert_eq!(super::target_value(LoggingTestTarget::Dedicated), crate::TRACING_TARGET_LOGGING_TEST);
|
assert_eq!(super::target_value(super::LoggingTestTarget::Dedicated), crate::TRACING_TARGET_LOGGING_TEST);
|
||||||
assert!(super::parse_target("external").is_err());
|
assert!(super::parse_target("external").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,32 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/profiles.rs
|
// file: crates/ksp-app-config-desk/unit_tests/profiles.rs
|
||||||
// version: 2
|
// version: 6
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn profile_inventory_exposes_logging_default_and_available_profiles() {
|
fn profile_inventory_exposes_registered_profile_documents() {
|
||||||
let management = fixture_management();
|
let management = fixture_management();
|
||||||
assert!(management.is_ok(), "fixture management should construct: {management:?}");
|
assert!(management.is_ok(), "fixture management should construct: {management:?}");
|
||||||
if let std::result::Result::Ok(management) = management {
|
if let std::result::Result::Ok(management) = management {
|
||||||
let inventory = super::inventory_from_management(&management);
|
let inventory = super::inventory_from_management(&management);
|
||||||
assert!(inventory.is_ok(), "profile inventory should resolve: {inventory:?}");
|
assert!(inventory.is_ok(), "profile inventory should resolve: {inventory:?}");
|
||||||
if let std::result::Result::Ok(inventory) = inventory {
|
if let std::result::Result::Ok(inventory) = inventory {
|
||||||
assert_eq!(inventory.len(), 1);
|
assert!(inventory.iter().any(|document| -> bool {
|
||||||
assert_eq!(inventory[0].file_id, ksp_config_lib::FILE_ID_STD_LOGGING);
|
return document.file_id == ksp_config_lib::FILE_ID_STD_LOGGING;
|
||||||
assert!(!inventory[0].default_profile.is_empty());
|
|
||||||
assert!(inventory[0].profile_ids.iter().any(|profile_id| {
|
|
||||||
return profile_id == &inventory[0].default_profile;
|
|
||||||
}));
|
}));
|
||||||
|
assert!(inventory.iter().any(|document| -> bool {
|
||||||
|
return document.file_id == ksp_config_lib::FILE_ID_STD_TRANSPORT;
|
||||||
|
}));
|
||||||
|
assert!(inventory.iter().any(|document| -> bool {
|
||||||
|
return document.file_id == ksp_config_lib::FILE_ID_STD_WALLET;
|
||||||
|
}));
|
||||||
|
assert!(inventory.iter().any(|document| -> bool {
|
||||||
|
return document.file_id == ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK;
|
||||||
|
}));
|
||||||
|
for document in inventory {
|
||||||
|
assert!(!document.default_profile.is_empty());
|
||||||
|
assert!(document.profile_ids.iter().any(|profile_id| -> bool {
|
||||||
|
return profile_id == &document.default_profile;
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/secrets.rs
|
// file: crates/ksp-app-config-desk/unit_tests/secrets.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reveal_policy_accepts_only_secret_names_and_supported_sources() {
|
fn reveal_policy_accepts_only_secret_names_and_supported_sources() {
|
||||||
@@ -21,7 +21,7 @@ fn reveal_policy_accepts_only_secret_names_and_supported_sources() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reveal_response_type_does_not_require_debug_or_clone_contracts() {
|
fn reveal_response_type_does_not_require_debug_or_clone_contracts() {
|
||||||
let response = super::SecretRevealResponseDto {
|
let response = crate::SecretRevealResponseDto {
|
||||||
variable_name: "KSP_SECRET_TEST_VALUE".to_owned(),
|
variable_name: "KSP_SECRET_TEST_VALUE".to_owned(),
|
||||||
source: "dotenv".to_owned(),
|
source: "dotenv".to_owned(),
|
||||||
value: std::option::Option::Some("secret-canary".to_owned()),
|
value: std::option::Option::Some("secret-canary".to_owned()),
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
// file: crates/ksp-app-config-desk/unit_tests/splash.rs
|
// file: crates/ksp-app-config-desk/unit_tests/splash.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fallback_splash_settings_are_short_and_ordered() {
|
fn fallback_splash_settings_are_short_and_ordered() {
|
||||||
let settings = crate::SplashSettings::fallback();
|
let settings = crate::SplashSettings::fallback();
|
||||||
|
assert_eq!(settings.fade_in_ms(), 300);
|
||||||
assert_eq!(settings.minimum_ms(), 1200);
|
assert_eq!(settings.minimum_ms(), 1200);
|
||||||
assert_eq!(settings.fade_ms(), 300);
|
assert_eq!(settings.fade_out_ms(), 300);
|
||||||
|
assert_eq!(settings.expected_backend_lifecycle_ms(), 1800);
|
||||||
|
assert_eq!(settings.fade_in_source(), "fallback");
|
||||||
assert_eq!(settings.minimum_source(), "fallback");
|
assert_eq!(settings.minimum_source(), "fallback");
|
||||||
assert_eq!(settings.fade_source(), "fallback");
|
assert_eq!(settings.fade_out_source(), "fallback");
|
||||||
assert_eq!(settings.expected_backend_lifecycle_ms(), 1500);
|
|
||||||
assert!(settings.minimum_ms() >= u64::from(settings.fade_ms()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn splash_order_contract_keeps_action_message_and_duration() {
|
fn splash_order_contract_keeps_action_message_status_and_duration() {
|
||||||
let order = crate::SplashOrderDto::new("fade_out", std::option::Option::Some("done"), std::option::Option::Some(300));
|
let order =
|
||||||
assert_eq!(order.action, "fade_out");
|
crate::SplashOrderDto::new("add_message", std::option::Option::Some("ready"), std::option::Option::Some("success"), std::option::Option::Some(300));
|
||||||
assert_eq!(order.message.as_deref(), std::option::Option::Some("done"));
|
assert_eq!(order.action, "add_message");
|
||||||
|
assert_eq!(order.message.as_deref(), std::option::Option::Some("ready"));
|
||||||
|
assert_eq!(order.status.as_deref(), std::option::Option::Some("success"));
|
||||||
assert_eq!(order.duration_ms, std::option::Option::Some(300));
|
assert_eq!(order.duration_ms, std::option::Option::Some(300));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,14 +27,10 @@ fn splash_order_contract_keeps_action_message_and_duration() {
|
|||||||
fn splash_duration_parser_rejects_invalid_or_unbounded_values() {
|
fn splash_duration_parser_rejects_invalid_or_unbounded_values() {
|
||||||
let malformed = super::parse_u64_setting("KSP_DESK_SPLASH_MINIMUM_MS", "invalid", 60_000);
|
let malformed = super::parse_u64_setting("KSP_DESK_SPLASH_MINIMUM_MS", "invalid", 60_000);
|
||||||
let oversized_minimum = super::parse_u64_setting("KSP_DESK_SPLASH_MINIMUM_MS", "60001", 60_000);
|
let oversized_minimum = super::parse_u64_setting("KSP_DESK_SPLASH_MINIMUM_MS", "60001", 60_000);
|
||||||
let oversized_fade = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_MS", "10001", 10_000);
|
let oversized_fade_in = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_IN_MS", "10001", 10_000);
|
||||||
|
let oversized_fade_out = super::parse_u32_setting("KSP_DESK_SPLASH_FADE_OUT_MS", "10001", 10_000);
|
||||||
assert!(malformed.is_err());
|
assert!(malformed.is_err());
|
||||||
assert!(oversized_minimum.is_err());
|
assert!(oversized_minimum.is_err());
|
||||||
assert!(oversized_fade.is_err());
|
assert!(oversized_fade_in.is_err());
|
||||||
if let std::result::Result::Err(error) = oversized_minimum {
|
assert!(oversized_fade_out.is_err());
|
||||||
assert!(error.context().iter().any(|field| return field.key() == "maximum_ms" && field.value() == "60000"));
|
|
||||||
}
|
|
||||||
if let std::result::Result::Err(error) = oversized_fade {
|
|
||||||
assert!(error.context().iter().any(|field| return field.key() == "maximum_ms" && field.value() == "10000"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { defineConfig, normalizePath } from "vite";
|
|||||||
|
|
||||||
const appRoot = fileURLToPath(new URL(".", import.meta.url));
|
const appRoot = fileURLToPath(new URL(".", import.meta.url));
|
||||||
const frontendRoot = normalizePath(resolve(appRoot, "frontend"));
|
const frontendRoot = normalizePath(resolve(appRoot, "frontend"));
|
||||||
const frontendDist = normalizePath(resolve(appRoot, "../../builds/khadhroony-solana-project/ksp-app-config-desk/dist"));
|
const frontendDist = normalizePath(resolve(appRoot, "../../../builds/khadhroony-solana-project/ksp-app-config-desk/dist"));
|
||||||
const devHost = process.env.TAURI_DEV_HOST;
|
const devHost = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|||||||
44
crates/ksp-app-wallet-desk/Cargo.toml
Normal file
44
crates/ksp-app-wallet-desk/Cargo.toml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# file: crates/ksp-app-wallet-desk/Cargo.toml
|
||||||
|
# version: 5
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "ksp-app-wallet-desk"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "ksp_app_wallet_desk_lib"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "ksp-app-wallet-desk"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = { workspace = true, features = ["std", "now"] }
|
||||||
|
fs2.workspace = true
|
||||||
|
ksp-config-lib = { path = "../ksp-config-lib" }
|
||||||
|
ksp-core-lib = { path = "../ksp-core-lib" }
|
||||||
|
ksp-logging-lib = { path = "../ksp-logging-lib" }
|
||||||
|
ksp-onchain-transport-lib = { path = "../ksp-onchain-transport-lib" }
|
||||||
|
ksp-wallet-lib = { path = "../ksp-wallet-lib" }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
tauri.workspace = true
|
||||||
|
tauri-plugin-dialog.workspace = true
|
||||||
|
tauri-plugin-tracing.workspace = true
|
||||||
|
tokio = { workspace = true, features = ["fs", "io-util", "macros", "rt", "time"] }
|
||||||
|
ts-rs.workspace = true
|
||||||
|
zeroize.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
136
crates/ksp-app-wallet-desk/README.md
Normal file
136
crates/ksp-app-wallet-desk/README.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<!-- file: crates/ksp-app-wallet-desk/README.md -->
|
||||||
|
<!-- version: 2 -->
|
||||||
|
|
||||||
|
# `ksp-app-wallet-desk`
|
||||||
|
|
||||||
|
`ksp-app-wallet-desk` est l'application desktop spécialisée stable d'administration et de validation des wallets KSP depuis KSP `0.2.6`.
|
||||||
|
|
||||||
|
La crate est un package Tauri mixte :
|
||||||
|
|
||||||
|
```text
|
||||||
|
package : ksp-app-wallet-desk
|
||||||
|
lib : ksp_app_wallet_desk_lib
|
||||||
|
bin : ksp-app-wallet-desk
|
||||||
|
```
|
||||||
|
|
||||||
|
## Responsabilités
|
||||||
|
|
||||||
|
Wallet Desk reste une couche d'interface et de composition. Il ne possède ni le wire `.kspwallet`, ni la cryptographie Wallet, ni la résolution Config, ni le transport Solana :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-config-lib -> configuration/composite/.env
|
||||||
|
ksp-wallet-lib -> format, crypto, VIEW/OWNER, import/export, migration
|
||||||
|
ksp-onchain-transport-lib -> RPC Solana HTTP
|
||||||
|
ksp-logging-lib -> logging/tracing applicatif
|
||||||
|
ksp-app-wallet-desk -> orchestration Tauri + projections sûres
|
||||||
|
```
|
||||||
|
|
||||||
|
Le frontend ne reçoit jamais les keypairs, ciphertexts, passwords Config, paths complets import/export ou handles VIEW/OWNER. Les opérations privilégiées sont exécutées côté Rust.
|
||||||
|
|
||||||
|
## `.kspwallet` V1/V2
|
||||||
|
|
||||||
|
La release `0.2.6` conserve V1 et ajoute V2 :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V1 : JSON UTF-8 historique, lecture explicite toujours supportée
|
||||||
|
V2 : wire binaire KSP, format de création/import par défaut
|
||||||
|
```
|
||||||
|
|
||||||
|
Wallet Desk consomme uniquement les APIs non versionnées de `ksp-wallet-lib` :
|
||||||
|
|
||||||
|
- création/import -> format par défaut `V2` ;
|
||||||
|
- inventory/inspection/open -> détection V1/V2 ;
|
||||||
|
- mutations OWNER/VIEW -> restent dans le format natif ouvert ;
|
||||||
|
- ouverture d'un V1 -> aucune migration implicite.
|
||||||
|
|
||||||
|
`ksp-wallet-lib` conserve parallèlement les APIs explicites `_v1` / `_v2` pour les consumers qui doivent imposer un format. `DEFAULT_WALLET_FORMAT` et `LATEST_SUPPORTED_WALLET_FORMAT` sont des politiques distinctes ; l'apparition future d'un V3 n'impose donc pas de modifier automatiquement le format créé par les APIs génériques.
|
||||||
|
|
||||||
|
La migration V1 -> V2 est une opération explicite OWNER-authentifiée possédée par `ksp-wallet-lib`. Wallet Desk `0.2.6` ne migre jamais silencieusement un wallet lors de sa sélection, inspection ou ouverture.
|
||||||
|
|
||||||
|
## Capacités fonctionnelles `0.2.6`
|
||||||
|
|
||||||
|
La surface validée comprend :
|
||||||
|
|
||||||
|
- inventory root-scoped des `.kspwallet` ;
|
||||||
|
- create/import Solana CLI JSON et Base58 ;
|
||||||
|
- sélection et inspection locked ;
|
||||||
|
- ouverture VIEW et OWNER, manuelle ou via candidats secrets Config ;
|
||||||
|
- affichage Pubkey/alias/notes uniquement après autorisation ;
|
||||||
|
- `getBalance` via le Transport HTTP KSP ;
|
||||||
|
- mutation alias/notes OWNER ;
|
||||||
|
- rotations OWNER et VIEW ;
|
||||||
|
- self-rotation VIEW ;
|
||||||
|
- disable/recreate VIEW fort par OWNER ;
|
||||||
|
- export OWNER Solana CLI JSON/Base58 no-clobber ;
|
||||||
|
- wire V2 par défaut tout en conservant la compatibilité V1.
|
||||||
|
|
||||||
|
## Frontières desktop
|
||||||
|
|
||||||
|
Le frontend utilise Bootstrap, Font Awesome, DataTables/Select, SimpleBar et le bridge Logging KSP. Les capabilities Tauri du guest restent minimales :
|
||||||
|
|
||||||
|
```text
|
||||||
|
core:default
|
||||||
|
tracing:default
|
||||||
|
```
|
||||||
|
|
||||||
|
Les pickers import/export sont invoqués côté Rust. Aucun accès direct filesystem ou réseau Solana n'est accordé au frontend.
|
||||||
|
|
||||||
|
## Développement
|
||||||
|
|
||||||
|
Le workspace contient plusieurs applications Tauri. `-c/--config` n'est pas un sélecteur de crate ; le cycle doit être lancé depuis le répertoire applicatif :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(cd crates/ksp-app-wallet-desk && cargo tauri dev)
|
||||||
|
```
|
||||||
|
|
||||||
|
En développement, le launcher Rust normalise ensuite son current working directory vers la racine du workspace avant le bootstrap Config. Les chemins relatifs `config/`, `config/schemas/`, `.env`, `logs/` et `wallets/` restent ainsi cohérents avec le checkout de développement sans déplacer leur ownership hors de `ksp-config-lib`.
|
||||||
|
|
||||||
|
Les ports réservés sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Vite HTTP : 1432
|
||||||
|
Vite WS : 1433
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runtime packagé
|
||||||
|
|
||||||
|
Une application distribuée ne dépend pas du checkout source ni du répertoire depuis lequel l'utilisateur lance le binaire.
|
||||||
|
|
||||||
|
Les documents Config et schemas enregistrés sont embarqués comme resources Tauri. Au démarrage release :
|
||||||
|
|
||||||
|
1. Tauri résout son répertoire de resources ;
|
||||||
|
2. `ksp-config-lib` résout un répertoire de données utilisateur KSP writable via `ProjectDirs` ;
|
||||||
|
3. les documents Config packagés sont copiés uniquement lorsqu'ils sont absents ;
|
||||||
|
4. les schemas, possédés par le package courant, sont resynchronisés à chaque lancement ;
|
||||||
|
5. le process adopte ce répertoire KSP writable comme current working directory avant `AppState::initialize`.
|
||||||
|
|
||||||
|
Le `.env` n'est jamais embarqué ni prérempli. Les Config déjà modifiées par l'utilisateur ne sont jamais écrasées silencieusement. Les chemins relatifs de logs et wallets restent ancrés dans ce runtime writable, sauf override explicite par Config.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Les validations Rust courantes sont :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-app-wallet-desk
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Le parcours fonctionnel utilise exclusivement Tauri :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(cd crates/ksp-app-wallet-desk && cargo tauri dev)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le build production de la release est exécuté seulement après tous les autres gates :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(cd crates/ksp-app-wallet-desk && cargo tauri build)
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette commande reste l'absolue dernière opération de validation lorsqu'une publication Wallet Desk est candidate au tag stable.
|
||||||
|
|
||||||
|
Voir également [`USAGE.md`](USAGE.md), [`../../docs/formats/KSPWALLET_V1.md`](../../docs/formats/KSPWALLET_V1.md), [`../../docs/formats/KSPWALLET_V2.md`](../../docs/formats/KSPWALLET_V2.md) et [`../../docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md`](../../docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md).
|
||||||
140
crates/ksp-app-wallet-desk/USAGE.md
Normal file
140
crates/ksp-app-wallet-desk/USAGE.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<!-- file: crates/ksp-app-wallet-desk/USAGE.md -->
|
||||||
|
<!-- version: 2 -->
|
||||||
|
|
||||||
|
# Utilisation de `ksp-app-wallet-desk`
|
||||||
|
|
||||||
|
## 1. Lancement de développement
|
||||||
|
|
||||||
|
Depuis la racine du workspace :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(cd crates/ksp-app-wallet-desk && cargo tauri dev)
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour isoler les wallets d'un parcours manuel :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(cd crates/ksp-app-wallet-desk && \
|
||||||
|
KSP_WALLETS_DIRECTORY=var/wallet-desk-manual cargo tauri dev)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le processus Rust debug recale son current working directory sur la racine du workspace avant le bootstrap Config. `KSP_WALLETS_DIRECTORY`, les autres variables KSP et le `.env` restent résolus exclusivement par `ksp-config-lib`.
|
||||||
|
|
||||||
|
## 2. Format natif
|
||||||
|
|
||||||
|
Wallet Desk ne sélectionne pas directement une version de wire : il utilise la façade générique de `ksp-wallet-lib`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
nouveau wallet -> V2
|
||||||
|
import Solana CLI/Base58 -> V2
|
||||||
|
inspection/ouverture -> auto-détection V1/V2
|
||||||
|
mutation d'un V1 ouvert -> V1
|
||||||
|
mutation d'un V2 ouvert -> V2
|
||||||
|
```
|
||||||
|
|
||||||
|
La présence de V2 ne réécrit pas automatiquement les fichiers V1 existants.
|
||||||
|
|
||||||
|
## 3. Création / import
|
||||||
|
|
||||||
|
Dans **Create / Import** :
|
||||||
|
|
||||||
|
- la création génère une nouvelle identité Solana et un `.kspwallet` V2 ;
|
||||||
|
- l'import accepte les formats de transfert Solana retenus, puis crée un `.kspwallet` V2 ;
|
||||||
|
- OWNER est toujours requis ; VIEW est optionnel ;
|
||||||
|
- les passwords sont request-only et ne sont jamais renvoyés par IPC.
|
||||||
|
|
||||||
|
L'import natif utilise un picker Rust. Le path source complet et les bytes de keypair ne traversent pas le frontend.
|
||||||
|
|
||||||
|
## 4. Inventory et ouverture
|
||||||
|
|
||||||
|
L'inventory n'affiche que des projections locked sûres. La sélection ne déverrouille jamais automatiquement un secret Config.
|
||||||
|
|
||||||
|
L'ouverture peut ensuite utiliser :
|
||||||
|
|
||||||
|
- un password saisi manuellement ;
|
||||||
|
- un candidat secret Config explicitement sélectionné côté backend.
|
||||||
|
|
||||||
|
VIEW et OWNER produisent des handles Rust-only. La Pubkey, l'alias et les notes ne sont projetés qu'après autorisation.
|
||||||
|
|
||||||
|
## 5. Administration
|
||||||
|
|
||||||
|
OWNER permet notamment :
|
||||||
|
|
||||||
|
- modifier l'alias ;
|
||||||
|
- ajouter/modifier/supprimer des notes ;
|
||||||
|
- changer le password OWNER ;
|
||||||
|
- changer, désactiver ou recréer VIEW ;
|
||||||
|
- exporter la keypair en Solana CLI JSON ou Base58.
|
||||||
|
|
||||||
|
VIEW peut effectuer sa self-rotation lorsqu'il est actif. Les opérations destructives/privilégiées utilisent les modals Bootstrap du shell et non `window.confirm`/`alert`.
|
||||||
|
|
||||||
|
## 6. Migration V1 -> V2
|
||||||
|
|
||||||
|
La migration existe dans `ksp-wallet-lib`, mais n'est jamais un effet secondaire d'une ouverture Wallet Desk.
|
||||||
|
|
||||||
|
Les APIs bibliothèque dédiées sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
migrate_wallet_v1_to_v2
|
||||||
|
migrate_wallet_file_v1_to_v2
|
||||||
|
migrate_wallet_file_v1_to_v2_in_place
|
||||||
|
```
|
||||||
|
|
||||||
|
Elles authentifient OWNER, reconstruisent un document cryptographique V2 neuf et conservent l'identité Solana ainsi que les metadata/note IDs. Lorsque VIEW est activé, un password VIEW cible doit être fourni. La copie est no-clobber ; le remplacement in-place est stale-protected.
|
||||||
|
|
||||||
|
## 7. Balance Solana
|
||||||
|
|
||||||
|
Une session VIEW ou OWNER autorisée peut demander `getBalance` via `ksp-onchain-transport-lib`. Le frontend ne choisit ni Pubkey arbitraire ni URL RPC : ces valeurs sont dérivées de l'état Rust autorisé et de Config.
|
||||||
|
|
||||||
|
## 8. Runtime packagé
|
||||||
|
|
||||||
|
En build release, les Config/schemas enregistrés sont des resources du bundle Tauri. Avant le bootstrap applicatif, `ksp-config-lib` prépare un répertoire de données utilisateur commun à KSP :
|
||||||
|
|
||||||
|
```text
|
||||||
|
<runtime KSP writable>/
|
||||||
|
├── config/
|
||||||
|
│ ├── composite.ksp-app-wallet-desk.json
|
||||||
|
│ ├── std.logging.json
|
||||||
|
│ ├── std.transport.json
|
||||||
|
│ ├── std.wallet.json
|
||||||
|
│ └── schemas/
|
||||||
|
├── .env # créé/modifié uniquement par Config si nécessaire
|
||||||
|
├── logs/ # par défaut si path relatif
|
||||||
|
└── wallets/ # par défaut si path relatif
|
||||||
|
```
|
||||||
|
|
||||||
|
Le chemin physique exact dépend de la plateforme et est résolu par `ProjectDirs`; l'application ne suppose aucune home directory particulière.
|
||||||
|
|
||||||
|
Les Config existantes sont conservées lors d'une mise à jour. Les schemas sont synchronisés depuis le package courant. Le `.env` et les secrets ne sont jamais embarqués dans le bundle.
|
||||||
|
|
||||||
|
## 9. Validation de publication
|
||||||
|
|
||||||
|
Avant le build :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-config-lib
|
||||||
|
cargo test -p ksp-wallet-lib
|
||||||
|
cargo test -p ksp-app-config-desk
|
||||||
|
cargo test -p ksp-app-wallet-desk
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis parcours fonctionnel :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(cd crates/ksp-app-wallet-desk && cargo tauri dev)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le smoke Devnet opt-in, s'il est rejoué, doit l'être avant le build final.
|
||||||
|
|
||||||
|
L'absolue dernière opération est :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(cd crates/ksp-app-wallet-desk && cargo tauri build)
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune commande de validation ne doit être exécutée après ce build avant la décision de publication/tag, afin de conserver la preuve d'ordre du gate.
|
||||||
12
crates/ksp-app-wallet-desk/build.rs
Normal file
12
crates/ksp-app-wallet-desk/build.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/build.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Build script for the KSP wallet desktop application.
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![deny(unreachable_pub)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
13
crates/ksp-app-wallet-desk/capabilities/default.json
Normal file
13
crates/ksp-app-wallet-desk/capabilities/default.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "Default capability for KSP Wallet Desk",
|
||||||
|
"windows": [
|
||||||
|
"splash",
|
||||||
|
"main"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"tracing:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
crates/ksp-app-wallet-desk/frontend/fonts/DOS_Amazigh.ttf
Normal file
BIN
crates/ksp-app-wallet-desk/frontend/fonts/DOS_Amazigh.ttf
Normal file
Binary file not shown.
25
crates/ksp-app-wallet-desk/frontend/fonts/README.md
Normal file
25
crates/ksp-app-wallet-desk/frontend/fonts/README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!-- file: crates/ksp-app-wallet-desk/frontend/fonts/README.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Fonts du splash Wallet Desk
|
||||||
|
|
||||||
|
Wallet Desk réutilise le gabarit de splash KSP déjà stabilisé dans Config Desk et conserve la police locale :
|
||||||
|
|
||||||
|
```text
|
||||||
|
DOS_Amazigh.ttf
|
||||||
|
font-family: Dos Amazigh
|
||||||
|
```
|
||||||
|
|
||||||
|
Destination :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-app-wallet-desk/frontend/fonts/DOS_Amazigh.ttf
|
||||||
|
```
|
||||||
|
|
||||||
|
Empreinte SHA-256 de l'asset repris du gabarit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
a01ff4b5a0d699db7cefcf5336bf0e379ff6fbf0d096087571a13c8782f9aa02
|
||||||
|
```
|
||||||
|
|
||||||
|
`splash.scss` déclare `Dos Amazigh` avec `@font-face` et l'applique au titre `#app-name`.
|
||||||
BIN
crates/ksp-app-wallet-desk/frontend/imgs/logo.png
Normal file
BIN
crates/ksp-app-wallet-desk/frontend/imgs/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
BIN
crates/ksp-app-wallet-desk/frontend/imgs/splash.png
Normal file
BIN
crates/ksp-app-wallet-desk/frontend/imgs/splash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 611 KiB |
502
crates/ksp-app-wallet-desk/frontend/main.html
Normal file
502
crates/ksp-app-wallet-desk/frontend/main.html
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
<!-- file: crates/ksp-app-wallet-desk/frontend/main.html -->
|
||||||
|
<!-- version: 5 -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" href="imgs/logo.png">
|
||||||
|
<link rel="stylesheet" href="sass/main.scss">
|
||||||
|
<title>Wallet Desk — Dashboard</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="app-header">
|
||||||
|
<nav class="navbar h-100 py-0 bg-light text-dark">
|
||||||
|
<div class="container-fluid px-4 flex-nowrap">
|
||||||
|
<div class="navbar-brand d-flex align-items-center me-4 flex-nowrap">
|
||||||
|
<img alt="KSP" src="imgs/logo.png" class="app-logo">
|
||||||
|
<span class="ps-2 fs-4 fw-semibold text-primary text-nowrap">Wallet Desk</span>
|
||||||
|
<span class="mx-2 fs-5 text-body-secondary" aria-hidden="true">—</span>
|
||||||
|
<span id="headerViewTitle" class="fs-5 text-body text-nowrap">Dashboard</span>
|
||||||
|
</div>
|
||||||
|
<div class="ms-auto d-flex align-items-center gap-2 flex-nowrap">
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="button" data-shell-action="deselect" disabled>
|
||||||
|
<i class="fa-solid fa-xmark me-2" aria-hidden="true"></i>Deselect
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-primary btn-sm" type="button" data-shell-action="lock" disabled>
|
||||||
|
<i class="fa-solid fa-lock me-2" aria-hidden="true"></i>Lock
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main class="app-main container-fluid p-0">
|
||||||
|
<div class="row g-0 h-100 flex-nowrap">
|
||||||
|
<aside class="col-auto app-sidebar border-end bg-body-tertiary">
|
||||||
|
<div class="app-sidebar-scroll h-100" data-simplebar>
|
||||||
|
<nav class="nav nav-pills flex-column gap-1 p-3" aria-label="Navigation Wallet Desk">
|
||||||
|
<button class="nav-link text-start active" type="button" data-view="dashboard"><i class="fa-solid fa-gauge-high me-2" aria-hidden="true"></i>Dashboard</button>
|
||||||
|
<button class="nav-link text-start" type="button" data-view="wallets"><i class="fa-solid fa-wallet me-2" aria-hidden="true"></i>Wallets</button>
|
||||||
|
<button class="nav-link text-start" type="button" data-view="create-import"><i class="fa-solid fa-circle-plus me-2" aria-hidden="true"></i>Create / Import</button>
|
||||||
|
<button class="nav-link text-start" type="button" data-view="details"><i class="fa-solid fa-id-card me-2" aria-hidden="true"></i>Details</button>
|
||||||
|
<button class="nav-link text-start" type="button" data-view="security"><i class="fa-solid fa-shield-halved me-2" aria-hidden="true"></i>Security</button>
|
||||||
|
<button class="nav-link text-start" type="button" data-view="diagnostics"><i class="fa-solid fa-stethoscope me-2" aria-hidden="true"></i>Diagnostics</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<section class="col app-content app-scrollable h-100" data-simplebar>
|
||||||
|
<div class="container-fluid px-4 py-4">
|
||||||
|
<div class="card app-shell-card mx-auto shadow-sm border-0">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||||
|
<div>
|
||||||
|
<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</span>
|
||||||
|
</div>
|
||||||
|
<section data-view-panel="dashboard">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-xl-8">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header fw-semibold">Runtime</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row mb-0 app-runtime-list">
|
||||||
|
<dt class="col-sm-5">Version</dt><dd id="runtimeVersion" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Phase</dt><dd id="runtimeShellPhase" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Documents Config enregistrés</dt><dd id="runtimeConfigDocuments" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Profil composite</dt><dd id="runtimeCompositeProfile" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Profil Logging</dt><dd id="runtimeLoggingProfile" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Profil Transport</dt><dd id="runtimeTransportProfile" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Cluster Transport</dt><dd id="runtimeTransportClusters" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Provider Transport</dt><dd id="runtimeTransportProviders" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Endpoints Transport</dt><dd id="runtimeTransportEndpoints" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Rôle Transport</dt><dd id="runtimeTransportRole" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Profil Wallet</dt><dd id="runtimeWalletProfile" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Fallback Logging</dt><dd id="runtimeLoggingFallback" class="col-sm-7">—</dd>
|
||||||
|
<dt class="col-sm-5">Racine Wallet</dt><dd id="runtimeWalletsDirectory" class="col-sm-7 text-break">—</dd>
|
||||||
|
<dt class="col-sm-5">Sous-répertoire Wallet</dt><dd id="runtimeWalletsSubdirectory" class="col-sm-7 text-break">—</dd>
|
||||||
|
<dt class="col-sm-5">Répertoire effectif</dt><dd id="runtimeEffectiveWalletsDirectory" class="col-sm-7 text-break">—</dd>
|
||||||
|
<dt class="col-sm-5">Créé au démarrage</dt><dd id="runtimeWalletDirectoryCreated" class="col-sm-7">—</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-4">
|
||||||
|
<div class="card shadow-sm h-100">
|
||||||
|
<div class="card-header fw-semibold">Wallet courant</div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<i id="currentWalletIcon" class="fa-solid fa-lock fa-2x mb-3 text-body-secondary" aria-hidden="true"></i>
|
||||||
|
<p id="currentWalletFilename" class="fw-semibold mb-2">Aucun wallet sélectionné.</p>
|
||||||
|
<dl class="row small text-start mb-0">
|
||||||
|
<dt class="col-5">État</dt><dd id="currentWalletState" class="col-7">—</dd>
|
||||||
|
<dt class="col-5">Format</dt><dd id="currentWalletFormat" class="col-7">—</dd>
|
||||||
|
<dt class="col-5">VIEW</dt><dd id="currentWalletView" class="col-7">—</dd>
|
||||||
|
<dt class="col-5">Secrets Config</dt><dd id="currentWalletSecretCandidates" class="col-7">0</dd>
|
||||||
|
<dt class="col-5">Pubkey</dt><dd id="currentWalletPubkey" class="col-7 text-break">—</dd>
|
||||||
|
<dt class="col-5">Alias</dt><dd id="currentWalletAlias" class="col-7 text-break">—</dd>
|
||||||
|
<dt class="col-5">Notes</dt><dd id="currentWalletNotes" class="col-7 text-break">—</dd>
|
||||||
|
<dt class="col-5">Balance SOL</dt><dd id="currentWalletBalanceSol" class="col-7">—</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section data-view-panel="wallets" hidden>
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<span class="fw-semibold">Wallets disponibles</span>
|
||||||
|
<small class="text-body-secondary"><i class="fa-solid fa-lock me-1" aria-hidden="true"></i>verrouillé · <i class="fa-solid fa-lock-open me-1" aria-hidden="true"></i>ouvert</small>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm" type="button" data-shell-action="refresh-wallets">
|
||||||
|
<i class="fa-solid fa-rotate me-2" aria-hidden="true"></i>Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table id="walletInventoryTable" class="table table-striped table-hover align-middle w-100">
|
||||||
|
<thead><tr><th>État</th><th>Filename</th><th>Format</th><th>VIEW</th><th>Inspection</th></tr></thead>
|
||||||
|
<tbody id="walletInventoryBody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section data-view-panel="create-import" hidden>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-xl-8">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header fw-semibold">Créer un wallet natif KSP</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="createWalletForm" autocomplete="off">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="createWalletFilename">Filename</label>
|
||||||
|
<input id="createWalletFilename" class="form-control" type="text" value="wallet-01.kspwallet" required spellcheck="false">
|
||||||
|
<div class="form-text">Un seul filename sous le répertoire Wallet effectif, suffixe exact .kspwallet.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="createWalletAlias">Alias interne optionnel</label>
|
||||||
|
<input id="createWalletAlias" class="form-control" type="text" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="createWalletOwnerPassword">OWNER password</label>
|
||||||
|
<input id="createWalletOwnerPassword" 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="createWalletEnableView" class="form-check-input" type="checkbox">
|
||||||
|
<label class="form-check-label" for="createWalletEnableView">Créer aussi une capability VIEW</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="createWalletViewPassword">VIEW password</label>
|
||||||
|
<input id="createWalletViewPassword" class="form-control" type="password" autocomplete="new-password" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label" for="createWalletInitialNote">Note initiale optionnelle</label>
|
||||||
|
<textarea id="createWalletInitialNote" class="form-control" rows="3" autocomplete="off"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex align-items-center gap-3">
|
||||||
|
<button id="createWalletSubmit" class="btn btn-primary" type="submit">
|
||||||
|
<i class="fa-solid fa-circle-plus me-2" aria-hidden="true"></i>Create wallet
|
||||||
|
</button>
|
||||||
|
<span id="createWalletStatus" class="text-body-secondary" role="status">Prêt.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-4">
|
||||||
|
<div class="alert alert-info mb-0">
|
||||||
|
<i class="fa-solid fa-shield-halved me-2" aria-hidden="true"></i>
|
||||||
|
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>
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header d-flex align-items-center justify-content-between">
|
||||||
|
<span class="fw-semibold">Détails autorisés et balance réseau</span>
|
||||||
|
<button id="refreshWalletBalance" class="btn btn-outline-primary btn-sm" type="button" disabled>
|
||||||
|
<i class="fa-solid fa-coins me-2" aria-hidden="true"></i>Refresh balance
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row mb-3">
|
||||||
|
<dt class="col-md-4">Pubkey</dt><dd id="detailsWalletPubkey" class="col-md-8 text-break">—</dd>
|
||||||
|
<dt class="col-md-4">Alias</dt><dd id="detailsWalletAlias" class="col-md-8 text-break">—</dd>
|
||||||
|
<dt class="col-md-4">Notes</dt><dd id="detailsWalletNotes" class="col-md-8 text-break">—</dd>
|
||||||
|
<dt class="col-md-4">Lamports</dt><dd id="detailsBalanceLamports" class="col-md-8">—</dd>
|
||||||
|
<dt class="col-md-4">SOL</dt><dd id="detailsBalanceSol" class="col-md-8">—</dd>
|
||||||
|
<dt class="col-md-4">RPC slot</dt><dd id="detailsBalanceSlot" class="col-md-8">—</dd>
|
||||||
|
<dt class="col-md-4">RPC api_version</dt><dd id="detailsBalanceApiVersion" class="col-md-8">—</dd>
|
||||||
|
<dt class="col-md-4">Transport</dt><dd id="detailsBalanceTransport" class="col-md-8">—</dd>
|
||||||
|
</dl>
|
||||||
|
<div id="balanceStatus" class="alert alert-secondary mb-0" role="status">Unlock VIEW ou OWNER pour interroger getBalance.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card shadow-sm mt-3">
|
||||||
|
<div class="card-header fw-semibold">Administration metadata OWNER</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-body-secondary">Alias et notes sont protégés dans le wallet. Les contrôles restent désactivés tant qu’une session OWNER n’est pas ouverte.</p>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<label class="form-label" for="ownerWalletAlias">Alias interne</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="ownerWalletAlias" class="form-control" type="text" autocomplete="off" data-owner-metadata-control disabled>
|
||||||
|
<button id="saveOwnerAlias" class="btn btn-outline-primary" type="button" data-owner-metadata-control disabled>Save</button>
|
||||||
|
<button id="clearOwnerAlias" class="btn btn-outline-secondary" type="button" data-owner-metadata-control disabled>Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">Limite V1 : 256 octets UTF-8. La validation autoritative reste dans ksp-wallet-lib.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<label class="form-label" for="ownerNewNote">Nouvelle note</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<textarea id="ownerNewNote" class="form-control" rows="2" autocomplete="off" data-owner-metadata-control disabled></textarea>
|
||||||
|
<button id="addOwnerNote" class="btn btn-outline-primary" type="button" data-owner-metadata-control disabled>Add note</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span class="fw-semibold">Notes autorisées</span>
|
||||||
|
<small class="text-body-secondary">64 notes maximum en V1</small>
|
||||||
|
</div>
|
||||||
|
<div id="ownerNotesList" class="vstack gap-2"><span class="text-body-secondary">Ouvrir OWNER pour administrer les notes.</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div id="ownerMetadataStatus" class="alert alert-secondary mb-0" role="status">Session OWNER requise.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section data-view-panel="security" hidden>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-xl-7">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header fw-semibold">Unlock manuel du wallet sélectionné</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-body-secondary">Chaque tentative Argon2 est explicite et peut prendre plusieurs secondes. Le password est envoyé uniquement frontend → Rust puis purgé.</p>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="unlockWalletViewPassword">VIEW password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="unlockWalletViewPassword" class="form-control" type="password" autocomplete="current-password">
|
||||||
|
<button id="unlockViewManual" class="btn btn-outline-primary" type="button" disabled>Unlock VIEW</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="unlockWalletOwnerPassword">OWNER password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="unlockWalletOwnerPassword" class="form-control" type="password" autocomplete="current-password">
|
||||||
|
<button id="unlockOwnerManual" class="btn btn-outline-danger" type="button" disabled>Unlock OWNER</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-5">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header fw-semibold">Secrets configurés</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="mb-2">Candidats effectifs : <strong id="unlockSecretCandidateCount">0</strong></p>
|
||||||
|
<p class="small text-body-secondary">Les noms, suffixes et valeurs des secrets Wallet configurés restent exclusivement côté Rust/Config. Cette action tente les candidats uniquement sur demande.</p>
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<button id="unlockViewConfigured" class="btn btn-outline-primary" type="button" disabled>Unlock VIEW avec secret configuré</button>
|
||||||
|
<button id="unlockOwnerConfigured" class="btn btn-outline-danger" type="button" disabled>Unlock OWNER avec secret configuré</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header fw-semibold">Rotation credentials OWNER / VIEW</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="small text-body-secondary">OWNER peut administrer OWNER et VIEW sans ancien password VIEW. Une session VIEW peut self-rotate son propre password VIEW. Les rotations modifient le fichier Wallet sans changer automatiquement les secrets Config.</p>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<label class="form-label" for="rotateOwnerPassword">Nouveau OWNER password</label>
|
||||||
|
<input id="rotateOwnerPassword" class="form-control mb-2" type="password" autocomplete="new-password" data-owner-rotation-control disabled>
|
||||||
|
<label class="form-label" for="rotateOwnerPasswordConfirm">Confirmer OWNER password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="rotateOwnerPasswordConfirm" class="form-control" type="password" autocomplete="new-password" data-owner-rotation-control disabled>
|
||||||
|
<button id="rotateOwnerPasswordSubmit" class="btn btn-outline-danger" type="button" data-owner-rotation-control disabled>Rotate OWNER</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<label class="form-label" for="rotateViewPassword">Nouveau VIEW password</label>
|
||||||
|
<input id="rotateViewPassword" class="form-control mb-2" type="password" autocomplete="new-password" data-view-rotation-control disabled>
|
||||||
|
<label class="form-label" for="rotateViewPasswordConfirm">Confirmer VIEW password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="rotateViewPasswordConfirm" class="form-control" type="password" autocomplete="new-password" data-view-rotation-control disabled>
|
||||||
|
<button id="rotateViewPasswordSubmit" class="btn btn-outline-primary" type="button" data-view-rotation-control disabled>Rotate VIEW</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="rotationStatus" class="alert alert-secondary mt-3 mb-0" role="status">Session VIEW ou OWNER requise pour une rotation VIEW ; OWNER requis pour une rotation OWNER.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card shadow-sm border-warning">
|
||||||
|
<div class="card-header fw-semibold">Administration forte VIEW — OWNER uniquement</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="small text-body-secondary">Ces opérations changent l’autorité VIEW courante, pas seulement son password. Disable retire VIEW et rekey les metadata ; Recreate génère une nouvelle metadata key, un nouveau slot VIEW et un nouveau password. La keypair Solana, l’OWNER, l’alias et les notes restent inchangés.</p>
|
||||||
|
<div class="row g-3 align-items-end">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<button id="disableWalletView" class="btn btn-outline-danger w-100" type="button" disabled>Strong disable VIEW</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<label class="form-label" for="recreateViewPassword">Nouveau VIEW password</label>
|
||||||
|
<input id="recreateViewPassword" class="form-control" type="password" autocomplete="new-password" data-view-security-recreate-control disabled>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<label class="form-label" for="recreateViewPasswordConfirm">Confirmer VIEW password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="recreateViewPasswordConfirm" class="form-control" type="password" autocomplete="new-password" data-view-security-recreate-control disabled>
|
||||||
|
<button id="recreateWalletView" class="btn btn-outline-warning" type="button" data-view-security-recreate-control disabled>Strong recreate</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="viewSecurityStatus" class="alert alert-secondary mt-3 mb-0" role="status">Session OWNER requise pour administrer fortement VIEW.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card shadow-sm border-danger-subtle">
|
||||||
|
<div class="card-header fw-semibold">Export keypair Solana — OWNER uniquement</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="small text-body-secondary">L’export produit un fichier secret non chiffré contenant la keypair Solana complète. Le contenu et le chemin complet restent exclusivement côté Rust ; le frontend choisit seulement le format puis confirme l’opération.</p>
|
||||||
|
<div class="row g-3 align-items-end">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<label class="form-label" for="walletExportFormat">Format d’export</label>
|
||||||
|
<select id="walletExportFormat" class="form-select" data-owner-export-control disabled>
|
||||||
|
<option value="solana_cli_json">Solana CLI JSON</option>
|
||||||
|
<option value="solana_keypair_base58">Keypair Base58 complet</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 d-grid">
|
||||||
|
<button id="exportWalletKeypair" class="btn btn-outline-danger" type="button" data-owner-export-control disabled>Export secret keypair file</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="walletExportStatus" class="alert alert-secondary mt-3 mb-0" role="status">Session OWNER requise. Aucun export texte ou clipboard n’est disponible.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div id="unlockStatus" class="alert alert-secondary mb-0" role="status">Sélectionner un wallet verrouillé.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section data-view-panel="diagnostics" hidden>
|
||||||
|
<div class="card shadow-sm"><div class="card-header fw-semibold">Diagnostics Transport sûrs</div><div class="card-body"><dl class="row mb-0">
|
||||||
|
<dt class="col-md-4">Profil</dt><dd id="diagnosticsTransportProfile" class="col-md-8">—</dd>
|
||||||
|
<dt class="col-md-4">Clusters</dt><dd id="diagnosticsTransportClusters" class="col-md-8">—</dd>
|
||||||
|
<dt class="col-md-4">Providers</dt><dd id="diagnosticsTransportProviders" class="col-md-8">—</dd>
|
||||||
|
<dt class="col-md-4">Endpoints disponibles</dt><dd id="diagnosticsTransportEndpoints" class="col-md-8">—</dd>
|
||||||
|
<dt class="col-md-4">Rôle balance</dt><dd id="diagnosticsTransportRole" class="col-md-8">—</dd>
|
||||||
|
</dl></div></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div class="modal fade" id="deleteWalletNoteModal" tabindex="-1" aria-labelledby="deleteWalletNoteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title fs-5" id="deleteWalletNoteModalLabel">Supprimer la note protégée ?</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">Cette mutation OWNER est définitive pour la note sélectionnée. Le contenu protégé n’est pas recopié dans la confirmation.</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button id="confirmDeleteWalletNote" type="button" class="btn btn-danger">Delete note</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" id="disableWalletViewModal" tabindex="-1" aria-labelledby="disableWalletViewModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title fs-5" id="disableWalletViewModalLabel">Strong disable VIEW ?</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">Le slot VIEW courant sera supprimé et les metadata seront rekeyées. Le password VIEW actuel ne pourra plus ouvrir l’état courant. Les copies historiques du fichier restent hors de la garantie de révocation.</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button id="confirmDisableWalletView" type="button" class="btn btn-danger">Disable VIEW</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" id="recreateWalletViewModal" tabindex="-1" aria-labelledby="recreateWalletViewModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title fs-5" id="recreateWalletViewModalLabel">Strong recreate VIEW ?</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">Une nouvelle metadata key et un nouveau slot VIEW seront créés. Tout credential VIEW courant sera remplacé. Le nouveau password n’est jamais recopié dans cette confirmation.</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button id="confirmRecreateWalletView" type="button" class="btn btn-warning">Recreate VIEW</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" id="exportWalletKeypairModal" tabindex="-1" aria-labelledby="exportWalletKeypairModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title fs-5" id="exportWalletKeypairModalLabel">Exporter la keypair Solana ?</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">Cette opération OWNER crée un fichier secret non chiffré contenant les 64 octets de la keypair Solana. Le fichier doit être protégé comme une clé privée. Un fichier existant ne sera jamais écrasé par Wallet.</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button id="confirmExportWalletKeypair" type="button" class="btn btn-danger">Choose destination and export</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="app-footer bg-dark text-light">
|
||||||
|
<div class="container h-100 d-flex align-items-center justify-content-center">
|
||||||
|
<small>© 2026 SASEDEV</small>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script type="module" src="ts/main.ts" defer></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
111
crates/ksp-app-wallet-desk/frontend/sass/_app.scss
Normal file
111
crates/ksp-app-wallet-desk/frontend/sass/_app.scss
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/sass/_app.scss
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
$app-header-height: 72px;
|
||||||
|
$app-footer-height: 42px;
|
||||||
|
$app-sidebar-width: 250px;
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: $gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0 0 auto;
|
||||||
|
height: $app-header-height;
|
||||||
|
z-index: 1020;
|
||||||
|
border-bottom: 2px solid rgba($primary, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-footer {
|
||||||
|
position: fixed;
|
||||||
|
inset: auto 0 0;
|
||||||
|
height: $app-footer-height;
|
||||||
|
z-index: 1020;
|
||||||
|
border-top: 2px solid rgba($primary, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main {
|
||||||
|
position: relative;
|
||||||
|
height: calc(100vh - $app-header-height - $app-footer-height);
|
||||||
|
margin-top: $app-header-height;
|
||||||
|
margin-bottom: $app-footer-height;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main > .row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-logo {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-scrollable {
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell-card {
|
||||||
|
max-width: 1320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-runtime-list dd,
|
||||||
|
#walletInventoryTable td:nth-child(2),
|
||||||
|
#walletInventoryTable td:nth-child(3) {
|
||||||
|
font-family: var(--bs-font-monospace);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar {
|
||||||
|
width: $app-sidebar-width;
|
||||||
|
min-width: $app-sidebar-width;
|
||||||
|
max-width: $app-sidebar-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar-scroll,
|
||||||
|
.app-content {
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-content {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar .nav-link {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-placeholder {
|
||||||
|
min-height: 320px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 2rem;
|
||||||
|
border: 1px dashed var(--bs-border-color);
|
||||||
|
border-radius: var(--bs-border-radius-lg);
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
background: var(--bs-body-bg);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-placeholder i {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#walletInventoryTable tbody tr {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
160
crates/ksp-app-wallet-desk/frontend/sass/_bootswatch.scss
Normal file
160
crates/ksp-app-wallet-desk/frontend/sass/_bootswatch.scss
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/sass/_bootswatch.scss
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
// Pulse 5.3.8
|
||||||
|
// Bootswatch
|
||||||
|
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:active,
|
||||||
|
&:active:focus,
|
||||||
|
&.active:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-secondary {
|
||||||
|
color: $gray-900;
|
||||||
|
background-color: $white;
|
||||||
|
border-color: #ccc;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $gray-900;
|
||||||
|
background-color: $gray-300;
|
||||||
|
border-color: $gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
color: tint-color($gray-900, 5%);
|
||||||
|
background-color: $white;
|
||||||
|
border-color: tint-color(#ccc, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-warning {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-primary:focus {
|
||||||
|
box-shadow: 0 0 5px tint-color($primary, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-secondary:focus {
|
||||||
|
box-shadow: 0 0 5px $gray-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-success:focus {
|
||||||
|
box-shadow: 0 0 5px tint-color($success, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-info:focus {
|
||||||
|
box-shadow: 0 0 5px tint-color($info, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-warning:focus {
|
||||||
|
box-shadow: 0 0 5px tint-color($warning, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-danger:focus {
|
||||||
|
box-shadow: 0 0 5px tint-color($danger, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
|
||||||
|
.table .thead-dark th {
|
||||||
|
background-color: $secondary;
|
||||||
|
border-color: $table-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-primary,
|
||||||
|
.table-secondary,
|
||||||
|
.table-success,
|
||||||
|
.table-warning,
|
||||||
|
.table-danger,
|
||||||
|
.table-info,
|
||||||
|
.table-light {
|
||||||
|
--#{$prefix}table-color: #{$body-color};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forms
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
box-shadow: 0 0 5px rgba(100, 65, 164, .4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navs
|
||||||
|
|
||||||
|
.nav-tabs {
|
||||||
|
|
||||||
|
.nav-link,
|
||||||
|
.nav-link.active {
|
||||||
|
border-width: 0 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover,
|
||||||
|
.nav-link.active,
|
||||||
|
.nav-link.active:hover,
|
||||||
|
.nav-link.active:focus {
|
||||||
|
border-bottom: 1px solid $primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item+.nav-item {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
&-item.active {
|
||||||
|
color: $gray-700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicators
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
&.bg-light {
|
||||||
|
color: $dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress bars
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Containers
|
||||||
|
|
||||||
|
.list-group {
|
||||||
|
&-item {
|
||||||
|
color: rgba(255, 255, 255, .8);
|
||||||
|
|
||||||
|
&.active,
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $list-group-hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled:hover {
|
||||||
|
color: $list-group-disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
crates/ksp-app-wallet-desk/frontend/sass/_fontawesome.scss
Normal file
19
crates/ksp-app-wallet-desk/frontend/sass/_fontawesome.scss
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/sass/_fontawesome.scss
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//@use '@fortawesome/fontawesome-free/scss/variables' with (
|
||||||
|
// // customizing $font-path - make sure it points to where your webfonts are stored in your project
|
||||||
|
// $font-path: '../webfonts',
|
||||||
|
//);
|
||||||
|
@use '@fortawesome/fontawesome-free/scss/variables' with (
|
||||||
|
// use fonts from @fortawesome/fontawesome-free
|
||||||
|
$font-path: '@fortawesome/fontawesome-free/webfonts',
|
||||||
|
);
|
||||||
|
// load Font Awesome core
|
||||||
|
@use '@fortawesome/fontawesome-free/scss/fontawesome';
|
||||||
|
|
||||||
|
// load and make available Font Awesome helpers (mixins, functions, and variables)
|
||||||
|
@use '@fortawesome/fontawesome-free/scss/fa' as fa;
|
||||||
|
@use '@fortawesome/fontawesome-free/scss/brands' as fa-brands;
|
||||||
|
@use '@fortawesome/fontawesome-free/scss/regular' as fa-regular;
|
||||||
|
@use '@fortawesome/fontawesome-free/scss/solid' as fa-solid;
|
||||||
248
crates/ksp-app-wallet-desk/frontend/sass/_simplebar.scss
Normal file
248
crates/ksp-app-wallet-desk/frontend/sass/_simplebar.scss
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/sass/_simplebar.scss
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/* Rtl support */
|
||||||
|
[data-simplebar] {
|
||||||
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
max-width: inherit;
|
||||||
|
max-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-mask {
|
||||||
|
direction: inherit;
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: auto !important;
|
||||||
|
height: auto !important;
|
||||||
|
// z-index: 0;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-offset {
|
||||||
|
direction: inherit !important;
|
||||||
|
box-sizing: inherit !important;
|
||||||
|
resize: none !important;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-content-wrapper {
|
||||||
|
direction: inherit;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-hide-scrollbar {
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-content {
|
||||||
|
&:before {
|
||||||
|
content: ' ';
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: ' ';
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-placeholder {
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-height-auto-observer-wrapper {
|
||||||
|
box-sizing: inherit !important;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1px;
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
max-height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
// z-index: -1;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
flex-grow: inherit;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-basis: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-height-auto-observer {
|
||||||
|
box-sizing: inherit;
|
||||||
|
display: block;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 1000%;
|
||||||
|
width: 1000%;
|
||||||
|
min-height: 1px;
|
||||||
|
min-width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
// z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-track {
|
||||||
|
// z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-simplebar].simplebar-dragging {
|
||||||
|
pointer-events: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.simplebar-content {
|
||||||
|
pointer-events: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-track {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-scrollbar {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
min-height: 10px;
|
||||||
|
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
background: black;
|
||||||
|
border-radius: 7px;
|
||||||
|
left: 2px;
|
||||||
|
right: 2px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s 0.5s linear;
|
||||||
|
top: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-scrollbar.simplebar-visible {
|
||||||
|
&:before {
|
||||||
|
opacity: 0.5;
|
||||||
|
transition-delay: 0s;
|
||||||
|
transition-duration: 0s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-track.simplebar-vertical {
|
||||||
|
top: 0;
|
||||||
|
width: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-track.simplebar-horizontal {
|
||||||
|
left: 0;
|
||||||
|
height: 11px;
|
||||||
|
|
||||||
|
.simplebar-scrollbar {
|
||||||
|
right: auto;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 10px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-simplebar-direction='rtl'] {
|
||||||
|
.simplebar-track.simplebar-vertical {
|
||||||
|
right: auto;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-dummy-scrollbar-size {
|
||||||
|
direction: rtl;
|
||||||
|
position: fixed;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
height: 500px;
|
||||||
|
width: 500px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: scroll;
|
||||||
|
-ms-overflow-style: scrollbar !important;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.simplebar-hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
95
crates/ksp-app-wallet-desk/frontend/sass/_variables.scss
Normal file
95
crates/ksp-app-wallet-desk/frontend/sass/_variables.scss
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/sass/_variables.scss
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
// Pulse 5.3.8
|
||||||
|
// Bootswatch
|
||||||
|
|
||||||
|
$theme: "pulse" !default;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Color system
|
||||||
|
//
|
||||||
|
|
||||||
|
$white: #fff !default;
|
||||||
|
$gray-100: #fafafa !default;
|
||||||
|
$gray-200: #f9f8fc !default;
|
||||||
|
$gray-300: #ededed !default;
|
||||||
|
$gray-400: #cbc8d0 !default;
|
||||||
|
$gray-500: #adb5bd !default;
|
||||||
|
$gray-600: #868e96 !default;
|
||||||
|
$gray-700: #444 !default;
|
||||||
|
$gray-800: #343a40 !default;
|
||||||
|
$gray-900: #17141f !default;
|
||||||
|
$black: #000 !default;
|
||||||
|
|
||||||
|
$blue: #007bff !default;
|
||||||
|
$indigo: #6610f2 !default;
|
||||||
|
$purple: #593196 !default;
|
||||||
|
$pink: #e83e8c !default;
|
||||||
|
$red: #fc3939 !default;
|
||||||
|
$orange: #fd7e14 !default;
|
||||||
|
$yellow: #efa31d !default;
|
||||||
|
$green: #13b955 !default;
|
||||||
|
$teal: #20c997 !default;
|
||||||
|
$cyan: #009cdc !default;
|
||||||
|
|
||||||
|
$primary: $purple !default;
|
||||||
|
$secondary: #a991d4 !default;
|
||||||
|
$success: $green !default;
|
||||||
|
$info: $cyan !default;
|
||||||
|
$warning: $yellow !default;
|
||||||
|
$danger: $red !default;
|
||||||
|
$light: $gray-200 !default;
|
||||||
|
$dark: $gray-900 !default;
|
||||||
|
|
||||||
|
$min-contrast-ratio: 2.1 !default;
|
||||||
|
|
||||||
|
// Options
|
||||||
|
|
||||||
|
$enable-rounded: false !default;
|
||||||
|
|
||||||
|
// Body
|
||||||
|
|
||||||
|
$body-color: $gray-700 !default;
|
||||||
|
|
||||||
|
// Links
|
||||||
|
|
||||||
|
$link-hover-color: $primary !default;
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
|
||||||
|
$table-color: initial !default;
|
||||||
|
|
||||||
|
$table-border-color: rgba(0, 0, 0, .05) !default;
|
||||||
|
|
||||||
|
// Forms
|
||||||
|
|
||||||
|
$input-focus-border-color: $primary !default;
|
||||||
|
|
||||||
|
// Dropdowns
|
||||||
|
|
||||||
|
$dropdown-link-hover-color: $white !default;
|
||||||
|
$dropdown-link-hover-bg: $primary !default;
|
||||||
|
|
||||||
|
// Navs
|
||||||
|
|
||||||
|
$nav-tabs-border-color: $gray-300 !default;
|
||||||
|
$nav-tabs-link-hover-border-color: $primary !default;
|
||||||
|
|
||||||
|
// Navbar
|
||||||
|
|
||||||
|
$navbar-padding-y: 1.2rem !default;
|
||||||
|
|
||||||
|
// Progress bars
|
||||||
|
|
||||||
|
$progress-bg: $gray-300 !default;
|
||||||
|
$progress-bar-bg: $primary !default;
|
||||||
|
|
||||||
|
// List group
|
||||||
|
|
||||||
|
$list-group-bg: $gray-900 !default;
|
||||||
|
$list-group-border-color: transparent !default;
|
||||||
|
$list-group-hover-bg: lighten($list-group-bg, 10%) !default;
|
||||||
|
$list-group-active-color: $white !default;
|
||||||
|
$list-group-active-bg: $list-group-bg !default;
|
||||||
|
$list-group-disabled-color: lighten($list-group-bg, 30%) !default;
|
||||||
10
crates/ksp-app-wallet-desk/frontend/sass/main.scss
Normal file
10
crates/ksp-app-wallet-desk/frontend/sass/main.scss
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/sass/main.scss
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
@import "bootstrap/scss/functions";
|
||||||
|
@import "variables";
|
||||||
|
@import "fontawesome";
|
||||||
|
@import "simplebar";
|
||||||
|
@import "bootstrap/scss/bootstrap";
|
||||||
|
@import "bootswatch";
|
||||||
|
@import "app";
|
||||||
137
crates/ksp-app-wallet-desk/frontend/sass/splash.scss
Normal file
137
crates/ksp-app-wallet-desk/frontend/sass/splash.scss
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/sass/splash.scss
|
||||||
|
// version: 3
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Dos Amazigh";
|
||||||
|
src: url("../fonts/DOS_Amazigh.ttf") format("truetype");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: transparent;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-container {
|
||||||
|
position: relative;
|
||||||
|
width: 960px;
|
||||||
|
max-width: 100%;
|
||||||
|
height: 637px;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-image {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
object-fit: contain;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app-name {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
z-index: 2;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: #fff;
|
||||||
|
font-family: "Dos Amazigh", sans-serif;
|
||||||
|
font-size: 76px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
white-space: nowrap;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-info {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 3;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 30%;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages-container {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 3;
|
||||||
|
max-height: 30%;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px 10px 36px;
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-info::-webkit-scrollbar,
|
||||||
|
#messages-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-message {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
animation: splashMessageFadeIn 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-message.info { background: rgba(33, 150, 243, 0.22); }
|
||||||
|
.splash-message.warning { background: rgba(255, 152, 0, 0.22); }
|
||||||
|
.splash-message.error { background: rgba(244, 67, 54, 0.22); }
|
||||||
|
.splash-message.success { background: rgba(76, 175, 80, 0.22); }
|
||||||
|
|
||||||
|
@keyframes splashMessageFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
crates/ksp-app-wallet-desk/frontend/splash.html
Normal file
23
crates/ksp-app-wallet-desk/frontend/splash.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!-- file: crates/ksp-app-wallet-desk/frontend/splash.html -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
|
<link rel="stylesheet" href="sass/splash.scss">
|
||||||
|
<title>KSP Wallet Desk — Chargement</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="splash-container" style="opacity: 0;">
|
||||||
|
<img id="splash-image" src="imgs/splash.png" alt="Chargement de KSP Wallet Desk">
|
||||||
|
<div id="app-name">Wallet Desk</div>
|
||||||
|
<div id="debug-info" aria-live="off" hidden></div>
|
||||||
|
<div id="messages-container" aria-live="polite"></div>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="ts/splash.ts" defer></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
110
crates/ksp-app-wallet-desk/frontend/ts/frontend_log.ts
Normal file
110
crates/ksp-app-wallet-desk/frontend/ts/frontend_log.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/ts/frontend_log.ts
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import type { FrontendLogPayloadDto } from "./bindings/ksp_app_wallet_desk/frontend_logging/FrontendLogPayloadDto.ts";
|
||||||
|
|
||||||
|
export type FrontendLogLevel = "trace" | "debug" | "info" | "warn" | "error";
|
||||||
|
export type FrontendLogTargetId = "frontend" | "main" | "splash";
|
||||||
|
|
||||||
|
type ConsoleMethod = (...items: unknown[]) => void;
|
||||||
|
|
||||||
|
const originalConsole = {
|
||||||
|
trace: console.trace.bind(console),
|
||||||
|
debug: console.debug.bind(console),
|
||||||
|
log: console.log.bind(console),
|
||||||
|
info: console.info.bind(console),
|
||||||
|
warn: console.warn.bind(console),
|
||||||
|
error: console.error.bind(console),
|
||||||
|
};
|
||||||
|
|
||||||
|
function stringifyItem(item: unknown): string {
|
||||||
|
if (item instanceof Error) {
|
||||||
|
return item.stack ?? item.message;
|
||||||
|
}
|
||||||
|
if (typeof item === "string") {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const serialized = JSON.stringify(item);
|
||||||
|
return serialized ?? String(item);
|
||||||
|
} catch {
|
||||||
|
return String(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMessage(items: unknown[]): string {
|
||||||
|
return items.map(item => stringifyItem(item)).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTargetId, message: string): Promise<void> {
|
||||||
|
const payload: FrontendLogPayloadDto = {
|
||||||
|
level,
|
||||||
|
targetId,
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
await invoke("emit_frontend_log", { payload });
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeOriginalConsole(level: FrontendLogLevel, message: string): void {
|
||||||
|
return level === "trace"
|
||||||
|
? originalConsole.trace(message)
|
||||||
|
: level === "debug"
|
||||||
|
? originalConsole.debug(message)
|
||||||
|
: level === "info"
|
||||||
|
? originalConsole.info(message)
|
||||||
|
: level === "warn"
|
||||||
|
? originalConsole.warn(message)
|
||||||
|
: originalConsole.error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function emitFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTargetId, message: string): Promise<void> {
|
||||||
|
writeOriginalConsole(level, message);
|
||||||
|
await sendFrontendLog(level, targetId, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function frontendTrace(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
|
const message = formatMessage(items);
|
||||||
|
originalConsole.trace(message);
|
||||||
|
void sendFrontendLog("trace", targetId, message).catch(caughtError => originalConsole.error("KSP frontend logging bridge failed", caughtError));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function frontendDebug(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
|
const message = formatMessage(items);
|
||||||
|
originalConsole.debug(message);
|
||||||
|
void sendFrontendLog("debug", targetId, message).catch(caughtError => originalConsole.error("KSP frontend logging bridge failed", caughtError));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function frontendInfo(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
|
const message = formatMessage(items);
|
||||||
|
originalConsole.info(message);
|
||||||
|
void sendFrontendLog("info", targetId, message).catch(caughtError => originalConsole.error("KSP frontend logging bridge failed", caughtError));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function frontendWarn(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
|
const message = formatMessage(items);
|
||||||
|
originalConsole.warn(message);
|
||||||
|
void sendFrontendLog("warn", targetId, message).catch(caughtError => originalConsole.error("KSP frontend logging bridge failed", caughtError));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function frontendError(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
|
const message = formatMessage(items);
|
||||||
|
originalConsole.error(message);
|
||||||
|
void sendFrontendLog("error", targetId, message).catch(caughtError => originalConsole.error("KSP frontend logging bridge failed", caughtError));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildConsoleBridge(level: FrontendLogLevel, targetId: FrontendLogTargetId, original: ConsoleMethod): ConsoleMethod {
|
||||||
|
return (...items: unknown[]) => {
|
||||||
|
original(...items);
|
||||||
|
void sendFrontendLog(level, targetId, formatMessage(items)).catch(caughtError => originalConsole.error("KSP frontend logging bridge failed", caughtError));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function installFrontendConsoleBridge(targetId: FrontendLogTargetId): void {
|
||||||
|
console.trace = buildConsoleBridge("trace", targetId, originalConsole.trace);
|
||||||
|
console.debug = buildConsoleBridge("debug", targetId, originalConsole.debug);
|
||||||
|
console.log = buildConsoleBridge("info", targetId, originalConsole.log);
|
||||||
|
console.info = buildConsoleBridge("info", targetId, originalConsole.info);
|
||||||
|
console.warn = buildConsoleBridge("warn", targetId, originalConsole.warn);
|
||||||
|
console.error = buildConsoleBridge("error", targetId, originalConsole.error);
|
||||||
|
}
|
||||||
17
crates/ksp-app-wallet-desk/frontend/ts/invoke.ts
Normal file
17
crates/ksp-app-wallet-desk/frontend/ts/invoke.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/ts/invoke.ts
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { frontendDebug, frontendError, frontendTrace, type FrontendLogTargetId } from "./frontend_log";
|
||||||
|
|
||||||
|
export async function invokeKsp<T>(targetId: FrontendLogTargetId, command: string, args?: Record<string, unknown>): Promise<T> {
|
||||||
|
frontendDebug(targetId, "Frontend IPC command requested", { command });
|
||||||
|
try {
|
||||||
|
const result = await invoke<T>(command, args);
|
||||||
|
frontendTrace(targetId, "Frontend IPC command completed", { command });
|
||||||
|
return result;
|
||||||
|
} catch (caughtError) {
|
||||||
|
frontendError(targetId, "Frontend IPC command failed", { command });
|
||||||
|
throw caughtError;
|
||||||
|
}
|
||||||
|
}
|
||||||
1507
crates/ksp-app-wallet-desk/frontend/ts/main.ts
Normal file
1507
crates/ksp-app-wallet-desk/frontend/ts/main.ts
Normal file
File diff suppressed because it is too large
Load Diff
131
crates/ksp-app-wallet-desk/frontend/ts/splash.ts
Normal file
131
crates/ksp-app-wallet-desk/frontend/ts/splash.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/frontend/ts/splash.ts
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
|
import type { SplashOrderDto } from "./bindings/ksp_app_wallet_desk/splash/SplashOrderDto.ts";
|
||||||
|
import { frontendDebug, frontendError, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log";
|
||||||
|
import { invokeKsp } from "./invoke";
|
||||||
|
|
||||||
|
installFrontendConsoleBridge("splash");
|
||||||
|
|
||||||
|
let activeOpacityFrame: number | null = null;
|
||||||
|
|
||||||
|
function easeInOut(value: number): number {
|
||||||
|
return value < 0.5 ? 2 * value * value : 1 - Math.pow(-2 * value + 2, 2) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDurationMs(value: number | null): number {
|
||||||
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpacity: number, durationMs: number): Promise<void> {
|
||||||
|
frontendTrace("splash", "Splash opacity animation started", { fromOpacity, toOpacity, durationMs });
|
||||||
|
if (activeOpacityFrame !== null) {
|
||||||
|
cancelAnimationFrame(activeOpacityFrame);
|
||||||
|
activeOpacityFrame = null;
|
||||||
|
}
|
||||||
|
element.style.opacity = fromOpacity.toString();
|
||||||
|
element.style.willChange = "opacity";
|
||||||
|
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
const startedAt = performance.now();
|
||||||
|
const opacityDelta = toOpacity - fromOpacity;
|
||||||
|
const updateOpacity = (currentTime: number): void => {
|
||||||
|
const elapsedMs = currentTime - startedAt;
|
||||||
|
const rawProgress = durationMs === 0 ? 1 : Math.min(elapsedMs / durationMs, 1);
|
||||||
|
element.style.opacity = (fromOpacity + opacityDelta * easeInOut(rawProgress)).toString();
|
||||||
|
if (rawProgress >= 1) {
|
||||||
|
element.style.opacity = toOpacity.toString();
|
||||||
|
element.style.willChange = "auto";
|
||||||
|
activeOpacityFrame = null;
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activeOpacityFrame = requestAnimationFrame(updateOpacity);
|
||||||
|
};
|
||||||
|
activeOpacityFrame = requestAnimationFrame(updateOpacity);
|
||||||
|
});
|
||||||
|
frontendTrace("splash", "Splash opacity animation completed", { toOpacity, durationMs });
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToLatest(element: HTMLElement): void {
|
||||||
|
element.scrollTop = element.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMessage(message: string, status: string | null): void {
|
||||||
|
const container = document.querySelector<HTMLElement>("#messages-container");
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const line = document.createElement("div");
|
||||||
|
const safeStatus = status === "warning" || status === "error" || status === "success" ? status : "info";
|
||||||
|
line.className = `splash-message ${safeStatus}`;
|
||||||
|
line.textContent = message;
|
||||||
|
container.appendChild(line);
|
||||||
|
scrollToLatest(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDebugMessage(message: string): void {
|
||||||
|
const container = document.querySelector<HTMLElement>("#debug-info");
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
container.hidden = false;
|
||||||
|
const line = document.createElement("div");
|
||||||
|
line.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
|
||||||
|
container.appendChild(line);
|
||||||
|
scrollToLatest(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSplashOrder(order: SplashOrderDto): Promise<void> {
|
||||||
|
frontendTrace("splash", "Splash order received", { action: order.action });
|
||||||
|
const container = document.querySelector<HTMLElement>("#splash-container");
|
||||||
|
if (order.action === "add_message" && order.message) {
|
||||||
|
addMessage(order.message, order.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.action === "add_debug" && order.message) {
|
||||||
|
addDebugMessage(order.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.action === "fade_in") {
|
||||||
|
await animateOpacity(container, 0, 1, normalizeDurationMs(order.durationMs));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (order.action === "fade_out") {
|
||||||
|
await animateOpacity(container, 1, 0, normalizeDurationMs(order.durationMs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeSplash(): Promise<void> {
|
||||||
|
const windowLabel = getCurrentWindow().label;
|
||||||
|
frontendInfo("splash", "Wallet Desk splash frontend loaded", { windowLabel });
|
||||||
|
const container = document.querySelector<HTMLElement>("#splash-container");
|
||||||
|
if (container) {
|
||||||
|
container.style.opacity = "0";
|
||||||
|
container.style.willChange = "opacity";
|
||||||
|
frontendTrace("splash", "Splash container prepared for managed fade-in");
|
||||||
|
}
|
||||||
|
await listen<SplashOrderDto>("ksp-splash-order", event => {
|
||||||
|
void handleSplashOrder(event.payload);
|
||||||
|
});
|
||||||
|
frontendDebug("splash", "Splash lifecycle listener installed");
|
||||||
|
try {
|
||||||
|
await invokeKsp<void>("splash", "splash_frontend_ready");
|
||||||
|
} catch {
|
||||||
|
addMessage("Le lifecycle du splash n'a pas pu démarrer.", "error");
|
||||||
|
if (container) {
|
||||||
|
container.style.opacity = "1";
|
||||||
|
container.style.willChange = "auto";
|
||||||
|
}
|
||||||
|
frontendError("splash", "Splash readiness command failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
void initializeSplash();
|
||||||
|
});
|
||||||
BIN
crates/ksp-app-wallet-desk/icons/favicon.ico
Normal file
BIN
crates/ksp-app-wallet-desk/icons/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 448 KiB |
BIN
crates/ksp-app-wallet-desk/icons/favicon.png
Normal file
BIN
crates/ksp-app-wallet-desk/icons/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
28
crates/ksp-app-wallet-desk/package.json
Normal file
28
crates/ksp-app-wallet-desk/package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "ksp-app-wallet-desk",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.2.6",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fltsci/tauri-plugin-tracing": "^0.3",
|
||||||
|
"@fortawesome/fontawesome-free": "^7.3",
|
||||||
|
"@tauri-apps/api": "^2.11",
|
||||||
|
"bootstrap": "^5.3",
|
||||||
|
"datatables.net-bs5": "^3.0",
|
||||||
|
"datatables.net-select-bs5": "^4.0",
|
||||||
|
"resize-observer-polyfill": "^1.5",
|
||||||
|
"simplebar": "^6.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.11",
|
||||||
|
"@types/bootstrap": "^5.2",
|
||||||
|
"@types/node": "^26.1",
|
||||||
|
"sass-embedded": "^1.102",
|
||||||
|
"typescript": "^7.0",
|
||||||
|
"vite": "^8.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1288
crates/ksp-app-wallet-desk/src/app_state.rs
Normal file
1288
crates/ksp-app-wallet-desk/src/app_state.rs
Normal file
File diff suppressed because it is too large
Load Diff
195
crates/ksp-app-wallet-desk/src/bootstrap.rs
Normal file
195
crates/ksp-app-wallet-desk/src/bootstrap.rs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/bootstrap.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
//! Config composite and Logging bootstrap for Wallet Desk.
|
||||||
|
|
||||||
|
/// Crate-internal Logging startup state shared by the application state.
|
||||||
|
pub(crate) struct LoggingStartup {
|
||||||
|
/// Guard that keeps the installed Logging runtime alive.
|
||||||
|
pub(crate) guard: ksp_logging_lib::LoggingGuard,
|
||||||
|
/// Active managed profile when Config resolved one successfully.
|
||||||
|
pub(crate) active_profile_id: std::option::Option<String>,
|
||||||
|
/// Whether a transient in-memory fallback was installed.
|
||||||
|
pub(crate) fallback_active: bool,
|
||||||
|
/// Safe startup diagnostic retained for the shell.
|
||||||
|
pub(crate) startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LoggingStartupPlan {
|
||||||
|
Managed {
|
||||||
|
active_profile_id: String,
|
||||||
|
settings: ksp_logging_lib::LoggingSettings,
|
||||||
|
},
|
||||||
|
Fallback {
|
||||||
|
initial_error: ksp_core_lib::Error,
|
||||||
|
diagnostic: crate::CommandErrorDto,
|
||||||
|
settings: ksp_logging_lib::LoggingSettings,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the Config management facade from the common KSP CLI bootstrap contract.
|
||||||
|
pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<ksp_config_lib::ConfigManagement> {
|
||||||
|
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_args(arguments);
|
||||||
|
let bootstrap = match bootstrap {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let registry = ksp_config_lib::ConfigFileRegistry::from_args(arguments);
|
||||||
|
let registry = match registry {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let engine = ksp_config_lib::ConfigDocumentEngine::new(bootstrap, registry);
|
||||||
|
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the Logging runtime from the Wallet Desk composite, with the same bounded fallback policy as Config Desk.
|
||||||
|
pub(crate) fn initialize_logging(
|
||||||
|
management: &ksp_config_lib::ConfigManagement,
|
||||||
|
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||||
|
) -> ksp_core_lib::Result<crate::LoggingStartup> {
|
||||||
|
let plan = resolve_logging_startup(management);
|
||||||
|
return match plan {
|
||||||
|
LoggingStartupPlan::Managed { active_profile_id, settings } => {
|
||||||
|
let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
|
||||||
|
match guard {
|
||||||
|
std::result::Result::Ok(guard) => {
|
||||||
|
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, active_profile = active_profile_id.as_str(), "initialized Wallet Desk logging from composite-managed configuration");
|
||||||
|
std::result::Result::Ok(LoggingStartup {
|
||||||
|
guard,
|
||||||
|
active_profile_id: std::option::Option::Some(active_profile_id),
|
||||||
|
fallback_active: false,
|
||||||
|
startup_diagnostic: std::option::Option::None,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
std::result::Result::Err(error) => initialize_fallback_logging(error, runtime_identity),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LoggingStartupPlan::Fallback { initial_error, diagnostic, settings } => {
|
||||||
|
initialize_planned_fallback_logging(initial_error, diagnostic, settings, runtime_identity)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads the concrete Wallet Desk Config composite using its registered logical `file_id` and autonomous default profile.
|
||||||
|
pub(crate) fn load_wallet_desk_composite(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigComposite> {
|
||||||
|
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
|
||||||
|
let file_id = match file_id {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return management.engine().load_resolved_composite(&file_id, std::option::Option::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns one required standard profile from the Wallet Desk composite after validating the referenced logical `file_id`.
|
||||||
|
pub(crate) fn required_composite_component_profile(
|
||||||
|
composite: &ksp_config_lib::ResolvedConfigComposite,
|
||||||
|
component_id: &str,
|
||||||
|
expected_file_id: &str,
|
||||||
|
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigProfile> {
|
||||||
|
let component = composite.component(component_id);
|
||||||
|
let component = match component {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Wallet Desk composite is missing a required component")
|
||||||
|
.with_context("composite_file_id", composite.file_id().as_str())
|
||||||
|
.with_context("composite_profile_id", composite.profile_id())
|
||||||
|
.with_context("component_id", component_id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if component.resolved().file_id().as_str() != expected_file_id {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Wallet Desk composite component references an unexpected Config document")
|
||||||
|
.with_context("composite_file_id", composite.file_id().as_str())
|
||||||
|
.with_context("composite_profile_id", composite.profile_id())
|
||||||
|
.with_context("component_id", component_id)
|
||||||
|
.with_context("expected_file_id", expected_file_id)
|
||||||
|
.with_context("actual_file_id", component.resolved().file_id().as_str()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(component.resolved().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fallback_logging_settings() -> ksp_logging_lib::LoggingSettings {
|
||||||
|
return ksp_logging_lib::LoggingSettings::new(
|
||||||
|
ksp_logging_lib::LogFilterLevel::Info,
|
||||||
|
ksp_logging_lib::SpanEvents::Off,
|
||||||
|
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()),
|
||||||
|
std::vec::Vec::new(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_logging_startup(management: &ksp_config_lib::ConfigManagement) -> LoggingStartupPlan {
|
||||||
|
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||||
|
let environment = match environment {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||||
|
};
|
||||||
|
let composite = crate::load_wallet_desk_composite(management);
|
||||||
|
let composite = match composite {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||||
|
};
|
||||||
|
let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||||
|
let logging_profile = match logging_profile {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||||
|
};
|
||||||
|
let transport_profile =
|
||||||
|
crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
|
||||||
|
if let std::result::Result::Err(error) = transport_profile {
|
||||||
|
return fallback_startup_plan(error);
|
||||||
|
}
|
||||||
|
let wallet_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_WALLET, ksp_config_lib::FILE_ID_STD_WALLET);
|
||||||
|
if let std::result::Result::Err(error) = wallet_profile {
|
||||||
|
return fallback_startup_plan(error);
|
||||||
|
}
|
||||||
|
let resolved = management.engine().resolve_logging_config_profile(&logging_profile, &environment);
|
||||||
|
let resolved = match resolved {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||||
|
};
|
||||||
|
return LoggingStartupPlan::Managed { active_profile_id: resolved.profile_id().to_owned(), settings: resolved.into_settings() };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fallback_startup_plan(initial_error: ksp_core_lib::Error) -> LoggingStartupPlan {
|
||||||
|
let diagnostic = crate::CommandErrorDto::from_error(&initial_error);
|
||||||
|
return LoggingStartupPlan::Fallback { initial_error, diagnostic, settings: fallback_logging_settings() };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize_fallback_logging(
|
||||||
|
initial_error: ksp_core_lib::Error,
|
||||||
|
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||||
|
) -> ksp_core_lib::Result<crate::LoggingStartup> {
|
||||||
|
let diagnostic = crate::CommandErrorDto::from_error(&initial_error);
|
||||||
|
return initialize_planned_fallback_logging(initial_error, diagnostic, fallback_logging_settings(), runtime_identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize_planned_fallback_logging(
|
||||||
|
initial_error: ksp_core_lib::Error,
|
||||||
|
diagnostic: crate::CommandErrorDto,
|
||||||
|
settings: ksp_logging_lib::LoggingSettings,
|
||||||
|
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||||
|
) -> ksp_core_lib::Result<crate::LoggingStartup> {
|
||||||
|
let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
|
||||||
|
let guard = match guard {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(fallback_error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED, "Cannot initialize Wallet Desk fallback Logging runtime")
|
||||||
|
.with_context("initial_error_domain", initial_error.code().domain())
|
||||||
|
.with_context("initial_error_code", initial_error.code().code())
|
||||||
|
.with_source(fallback_error),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, error_domain = diagnostic.domain.as_str(), error_code = diagnostic.code.as_str(), "managed Logging configuration is unavailable; using transient in-memory fallback");
|
||||||
|
return std::result::Result::Ok(LoggingStartup {
|
||||||
|
guard,
|
||||||
|
active_profile_id: std::option::Option::None,
|
||||||
|
fallback_active: true,
|
||||||
|
startup_diagnostic: std::option::Option::Some(diagnostic),
|
||||||
|
});
|
||||||
|
}
|
||||||
43
crates/ksp-app-wallet-desk/src/constants.rs
Normal file
43
crates/ksp-app-wallet-desk/src/constants.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/constants.rs
|
||||||
|
// version: 8
|
||||||
|
|
||||||
|
//! Logging targets, domains and composite component identifiers owned by Wallet Desk.
|
||||||
|
|
||||||
|
/// Composite-local identifier for the Logging standard document.
|
||||||
|
pub(crate) const COMPOSITE_COMPONENT_ID_LOGGING: &str = "logging";
|
||||||
|
/// Composite-local identifier for the HTTP Transport standard document.
|
||||||
|
pub(crate) const COMPOSITE_COMPONENT_ID_TRANSPORT: &str = "transport";
|
||||||
|
/// Composite-local identifier for the Wallet standard document.
|
||||||
|
pub(crate) const COMPOSITE_COMPONENT_ID_WALLET: &str = "wallet";
|
||||||
|
/// Structured domain used while bootstrapping Config and Logging.
|
||||||
|
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "wallet.bootstrap";
|
||||||
|
/// Structured domain used by technical frontend events.
|
||||||
|
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||||
|
/// Structured domain used by the Wallet Desk shell.
|
||||||
|
pub(crate) const TRACING_DOMAIN_SHELL: &str = "wallet.shell";
|
||||||
|
/// Structured domain used by Wallet Desk HTTP Transport and balance operations.
|
||||||
|
pub(crate) const TRACING_DOMAIN_TRANSPORT: &str = "wallet.transport";
|
||||||
|
/// Structured domain used while preparing Wallet filesystem roots.
|
||||||
|
pub(crate) const TRACING_DOMAIN_WALLET_CONFIG: &str = "wallet.config";
|
||||||
|
/// Structured domain used by native-save-picker OWNER transfer export operations.
|
||||||
|
pub(crate) const TRACING_DOMAIN_WALLET_EXPORT: &str = "wallet.export";
|
||||||
|
/// Structured domain used by native-picker Wallet transfer import operations.
|
||||||
|
pub(crate) const TRACING_DOMAIN_WALLET_IMPORT: &str = "wallet.import";
|
||||||
|
/// Structured domain used while enumerating and inspecting locked Wallet files.
|
||||||
|
pub(crate) const TRACING_DOMAIN_WALLET_INVENTORY: &str = "wallet.inventory";
|
||||||
|
/// Structured domain used by Config-owned Wallet password candidate operations.
|
||||||
|
pub(crate) const TRACING_DOMAIN_WALLET_SECRET: &str = "wallet.secret";
|
||||||
|
/// Structured domain used by durable Wallet session, creation and unlock operations.
|
||||||
|
pub(crate) const TRACING_DOMAIN_WALLET_SESSION: &str = "wallet.session";
|
||||||
|
/// Structured domain used by Tauri window lifecycle operations.
|
||||||
|
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
||||||
|
/// Owning target for backend events emitted by Wallet Desk.
|
||||||
|
pub(crate) const TRACING_TARGET: &str = "ksp-app-wallet-desk";
|
||||||
|
/// Owning target for generic frontend events emitted through the KSP bridge.
|
||||||
|
pub(crate) const TRACING_TARGET_FRONTEND: &str = "ksp-app-wallet-desk.frontend";
|
||||||
|
/// Owning target for main-window frontend events.
|
||||||
|
pub(crate) const TRACING_TARGET_FRONTEND_MAIN: &str = "ksp-app-wallet-desk.frontend.main";
|
||||||
|
/// Owning target for splash-window frontend events.
|
||||||
|
pub(crate) const TRACING_TARGET_FRONTEND_SPLASH: &str = "ksp-app-wallet-desk.frontend.splash";
|
||||||
|
/// Native Wallet filename suffix accepted by the Wallet Desk inventory.
|
||||||
|
pub(crate) const WALLET_FILE_SUFFIX: &str = ".kspwallet";
|
||||||
80
crates/ksp-app-wallet-desk/src/dto_common.rs
Normal file
80
crates/ksp-app-wallet-desk/src/dto_common.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/dto_common.rs
|
||||||
|
// version: 4
|
||||||
|
|
||||||
|
//! Common Tauri DTOs shared by Wallet Desk shell commands.
|
||||||
|
|
||||||
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
|
/// Safe command error projection that never serializes arbitrary KSP error context or source values.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/dto_common/CommandErrorDto.ts")]
|
||||||
|
pub(crate) struct CommandErrorDto {
|
||||||
|
/// Stable KSP error domain.
|
||||||
|
pub(crate) domain: String,
|
||||||
|
/// Stable KSP error code within the domain.
|
||||||
|
pub(crate) code: String,
|
||||||
|
/// Human-readable error message without arbitrary context fields.
|
||||||
|
pub(crate) message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandErrorDto {
|
||||||
|
/// Builds a bounded safe projection from a KSP error.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn from_error(error: &ksp_core_lib::Error) -> Self {
|
||||||
|
return Self {
|
||||||
|
domain: error.code().domain().to_owned(),
|
||||||
|
code: error.code().code().to_owned(),
|
||||||
|
message: error.message().to_owned(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safe application/runtime snapshot exposed to the Wallet Desk shell.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/dto_common/RuntimeStatusDto.ts")]
|
||||||
|
pub(crate) struct RuntimeStatusDto {
|
||||||
|
/// Cargo application version.
|
||||||
|
pub(crate) application_version: String,
|
||||||
|
/// Wallet Desk composite profile selected during bootstrap.
|
||||||
|
pub(crate) active_composite_profile: String,
|
||||||
|
/// Active configured Logging profile, or `None` while the transient fallback runtime is active.
|
||||||
|
pub(crate) active_logging_profile: std::option::Option<String>,
|
||||||
|
/// Standard Transport profile selected by the active composite.
|
||||||
|
pub(crate) active_transport_profile: String,
|
||||||
|
/// Standard Wallet profile selected by the active composite.
|
||||||
|
pub(crate) active_wallet_profile: String,
|
||||||
|
/// Number of logical Config resources registered by the current application runtime.
|
||||||
|
pub(crate) config_document_count: u32,
|
||||||
|
/// Effective Wallet directory after applying the optional profile subdirectory.
|
||||||
|
pub(crate) effective_wallets_directory: String,
|
||||||
|
/// Whether bootstrap created any missing component of the effective Wallet profile directory.
|
||||||
|
pub(crate) effective_wallets_directory_created_on_startup: bool,
|
||||||
|
/// Whether Wallet Desk had to install its transient in-memory Logging fallback.
|
||||||
|
pub(crate) fallback_logging_active: bool,
|
||||||
|
/// Whether bootstrap created the configured global Wallet root.
|
||||||
|
pub(crate) root_wallets_directory_created_on_startup: bool,
|
||||||
|
/// Current implementation phase exposed by the Wallet Desk shell.
|
||||||
|
pub(crate) shell_phase: String,
|
||||||
|
/// Safe startup diagnostic that caused fallback Logging, when applicable.
|
||||||
|
pub(crate) startup_diagnostic: std::option::Option<CommandErrorDto>,
|
||||||
|
/// Number of Transport endpoints currently eligible for normal routing.
|
||||||
|
pub(crate) transport_available_endpoint_count: u32,
|
||||||
|
/// Safe cluster labels configured by the selected Transport profile.
|
||||||
|
pub(crate) transport_clusters: std::vec::Vec<String>,
|
||||||
|
/// Total number of configured Transport endpoints.
|
||||||
|
pub(crate) transport_endpoint_count: u32,
|
||||||
|
/// Safe provider labels configured by the selected Transport profile.
|
||||||
|
pub(crate) transport_providers: std::vec::Vec<String>,
|
||||||
|
/// Logical Transport role used by Wallet Desk balance reads.
|
||||||
|
pub(crate) transport_role: String,
|
||||||
|
/// Resolved global Wallet root configured by `cfg.std.wallet`.
|
||||||
|
pub(crate) wallets_directory: String,
|
||||||
|
/// Optional profile-relative Wallet subdirectory selected by the active composite.
|
||||||
|
pub(crate) wallets_subdirectory: std::option::Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../unit_tests/dto_common.rs"]
|
||||||
|
mod tests;
|
||||||
54
crates/ksp-app-wallet-desk/src/errors.rs
Normal file
54
crates/ksp-app-wallet-desk/src/errors.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/errors.rs
|
||||||
|
// version: 8
|
||||||
|
|
||||||
|
//! Application-local error codes for Wallet Desk composition and desktop runtime surfaces.
|
||||||
|
|
||||||
|
/// Shared Wallet Desk application state is internally inconsistent.
|
||||||
|
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "app_state_invalid");
|
||||||
|
/// Shared Wallet Desk runtime state cannot be locked safely.
|
||||||
|
pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "app_state_lock_failed");
|
||||||
|
/// The Wallet Desk Config composite does not expose one of its required standard components with the expected `file_id`.
|
||||||
|
pub(crate) const ERROR_CODE_CONFIG_COMPOSITE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "config_composite_invalid");
|
||||||
|
/// Frontend logging requested an unsupported level.
|
||||||
|
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "frontend_log_level_invalid");
|
||||||
|
/// Frontend logging requested a target outside the application whitelist.
|
||||||
|
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "frontend_log_target_invalid");
|
||||||
|
/// Wallet Desk could not install the managed Logging runtime or its safe fallback.
|
||||||
|
pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "logging_bootstrap_failed");
|
||||||
|
/// Splash readiness was invoked from a window other than the splash window.
|
||||||
|
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "splash_origin_invalid");
|
||||||
|
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
|
||||||
|
pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "splash_setting_invalid");
|
||||||
|
/// Tauri runtime assembly or execution failed.
|
||||||
|
pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "tauri_runtime_failed");
|
||||||
|
/// A required Tauri window is missing from the configured application runtime.
|
||||||
|
pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "tauri_window_missing");
|
||||||
|
/// A Tauri window show/focus/destroy/event operation failed.
|
||||||
|
pub(crate) const ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("wallet_desk", "tauri_window_operation_failed");
|
||||||
|
/// A Wallet balance request requires an authorized VIEW or OWNER session.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_AUTHORIZATION_REQUIRED: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_authorization_required");
|
||||||
|
/// The configured Wallet root or effective profile directory resolves to an unsupported filesystem object.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_DIRECTORY_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_directory_invalid");
|
||||||
|
/// Wallet Desk could not inspect or create the configured Wallet directory tree.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_directory_prepare_failed");
|
||||||
|
/// A native save-picker Wallet export destination is not a usable local filesystem path.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_EXPORT_DESTINATION_INVALID: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_export_destination_invalid");
|
||||||
|
/// A native-picker Wallet transfer source is not a supported regular file or cannot be staged safely.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_IMPORT_SOURCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_import_source_invalid");
|
||||||
|
/// Wallet import was requested without one previously inspected native-picker source.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_IMPORT_SOURCE_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_import_source_missing");
|
||||||
|
/// Wallet Desk cannot enumerate or inspect its effective Wallet inventory directory.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_INVENTORY_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_inventory_failed");
|
||||||
|
/// No effective Config-owned Wallet password candidate is available for the explicit configured-secret unlock action.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_SECRET_CANDIDATES_MISSING: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_secret_candidates_missing");
|
||||||
|
/// A requested Wallet inventory identifier is unsafe or does not resolve to an eligible regular `.kspwallet` file.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_SELECTION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_selection_invalid");
|
||||||
|
/// A Wallet session lifecycle operation is incompatible with the current state.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_SESSION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_session_invalid");
|
||||||
|
/// The selected Wallet does not expose an enabled VIEW capability.
|
||||||
|
pub(crate) const ERROR_CODE_WALLET_VIEW_DISABLED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_view_disabled");
|
||||||
145
crates/ksp-app-wallet-desk/src/frontend_logging.rs
Normal file
145
crates/ksp-app-wallet-desk/src/frontend_logging.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/frontend_logging.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! KSP-owned bridge for technical log events emitted by Wallet Desk frontend scripts.
|
||||||
|
|
||||||
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
|
/// Log payload sent by Wallet Desk frontend scripts.
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/frontend_logging/FrontendLogPayloadDto.ts")]
|
||||||
|
pub(crate) struct FrontendLogPayloadDto {
|
||||||
|
/// Lowercase KSP log level.
|
||||||
|
pub(crate) level: String,
|
||||||
|
/// Whitelisted logical frontend target identifier.
|
||||||
|
pub(crate) target_id: String,
|
||||||
|
/// Rendered technical frontend message.
|
||||||
|
pub(crate) message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum FrontendLogLevel {
|
||||||
|
Debug,
|
||||||
|
Error,
|
||||||
|
Info,
|
||||||
|
Trace,
|
||||||
|
Warn,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum FrontendLogTarget {
|
||||||
|
Frontend,
|
||||||
|
Main,
|
||||||
|
Splash,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits one validated frontend event through the KSP Logging facade.
|
||||||
|
pub(crate) fn emit_frontend_log_event(payload: FrontendLogPayloadDto) -> ksp_core_lib::Result<()> {
|
||||||
|
let level = parse_level(payload.level.as_str());
|
||||||
|
let level = match level {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let target = parse_target(payload.target_id.as_str());
|
||||||
|
let target = match target {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
emit_validated_frontend_log(level, target, payload.message.as_str());
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_level(level: &str) -> ksp_core_lib::Result<FrontendLogLevel> {
|
||||||
|
return match level.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"debug" => std::result::Result::Ok(FrontendLogLevel::Debug),
|
||||||
|
"error" => std::result::Result::Ok(FrontendLogLevel::Error),
|
||||||
|
"info" => std::result::Result::Ok(FrontendLogLevel::Info),
|
||||||
|
"trace" => std::result::Result::Ok(FrontendLogLevel::Trace),
|
||||||
|
"warn" => std::result::Result::Ok(FrontendLogLevel::Warn),
|
||||||
|
_ => std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID, "Frontend log level is not supported")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_target(target_id: &str) -> ksp_core_lib::Result<FrontendLogTarget> {
|
||||||
|
return match target_id.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"frontend" => std::result::Result::Ok(FrontendLogTarget::Frontend),
|
||||||
|
"main" => std::result::Result::Ok(FrontendLogTarget::Main),
|
||||||
|
"splash" => std::result::Result::Ok(FrontendLogTarget::Splash),
|
||||||
|
_ => {
|
||||||
|
std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID, "Frontend log target identifier is not supported"))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_validated_frontend_log(level: FrontendLogLevel, target: FrontendLogTarget, message: &str) {
|
||||||
|
return match target {
|
||||||
|
FrontendLogTarget::Frontend => emit_frontend_target(level, message),
|
||||||
|
FrontendLogTarget::Main => emit_main_target(level, message),
|
||||||
|
FrontendLogTarget::Splash => emit_splash_target(level, message),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_frontend_target(level: FrontendLogLevel, message: &str) {
|
||||||
|
return match level {
|
||||||
|
FrontendLogLevel::Debug => {
|
||||||
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET_FRONTEND, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "frontend", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Error => {
|
||||||
|
ksp_logging_lib::error!(target: crate::TRACING_TARGET_FRONTEND, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "frontend", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Info => {
|
||||||
|
ksp_logging_lib::info!(target: crate::TRACING_TARGET_FRONTEND, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "frontend", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Trace => {
|
||||||
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET_FRONTEND, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "frontend", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Warn => {
|
||||||
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET_FRONTEND, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "frontend", "{message}")
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_main_target(level: FrontendLogLevel, message: &str) {
|
||||||
|
return match level {
|
||||||
|
FrontendLogLevel::Debug => {
|
||||||
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET_FRONTEND_MAIN, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "main", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Error => {
|
||||||
|
ksp_logging_lib::error!(target: crate::TRACING_TARGET_FRONTEND_MAIN, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "main", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Info => {
|
||||||
|
ksp_logging_lib::info!(target: crate::TRACING_TARGET_FRONTEND_MAIN, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "main", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Trace => {
|
||||||
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET_FRONTEND_MAIN, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "main", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Warn => {
|
||||||
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET_FRONTEND_MAIN, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "main", "{message}")
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_splash_target(level: FrontendLogLevel, message: &str) {
|
||||||
|
return match level {
|
||||||
|
FrontendLogLevel::Debug => {
|
||||||
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET_FRONTEND_SPLASH, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "splash", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Error => {
|
||||||
|
ksp_logging_lib::error!(target: crate::TRACING_TARGET_FRONTEND_SPLASH, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "splash", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Info => {
|
||||||
|
ksp_logging_lib::info!(target: crate::TRACING_TARGET_FRONTEND_SPLASH, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "splash", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Trace => {
|
||||||
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET_FRONTEND_SPLASH, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "splash", "{message}")
|
||||||
|
},
|
||||||
|
FrontendLogLevel::Warn => {
|
||||||
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET_FRONTEND_SPLASH, domain = crate::TRACING_DOMAIN_FRONTEND, action = "frontend_log", target_id = "splash", "{message}")
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../unit_tests/frontend_logging.rs"]
|
||||||
|
mod tests;
|
||||||
241
crates/ksp-app-wallet-desk/src/lib.rs
Normal file
241
crates/ksp-app-wallet-desk/src/lib.rs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/lib.rs
|
||||||
|
// version: 13
|
||||||
|
|
||||||
|
//! Tauri desktop application shell for KSP Wallet management and inspection.
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![deny(unreachable_pub)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
mod app_state;
|
||||||
|
mod bootstrap;
|
||||||
|
mod constants;
|
||||||
|
mod dto_common;
|
||||||
|
mod errors;
|
||||||
|
mod frontend_logging;
|
||||||
|
mod logging_runtime;
|
||||||
|
mod splash;
|
||||||
|
mod tauri;
|
||||||
|
mod transport_runtime;
|
||||||
|
mod tw_main;
|
||||||
|
mod tw_splash;
|
||||||
|
mod wallet_balance;
|
||||||
|
mod wallet_config;
|
||||||
|
mod wallet_export;
|
||||||
|
mod wallet_import;
|
||||||
|
mod wallet_inventory;
|
||||||
|
mod wallet_metadata;
|
||||||
|
mod wallet_secrets;
|
||||||
|
mod wallet_security;
|
||||||
|
mod wallet_session;
|
||||||
|
mod wallet_transfer;
|
||||||
|
|
||||||
|
/// Runs the KSP wallet desktop application.
|
||||||
|
pub use self::tauri::run;
|
||||||
|
|
||||||
|
/// Shared Wallet Desk application state managed by Tauri.
|
||||||
|
pub(crate) use self::app_state::AppState;
|
||||||
|
/// Crate-internal Logging startup state shared by the application state.
|
||||||
|
pub(crate) use self::bootstrap::LoggingStartup;
|
||||||
|
/// Builds the Config management facade from the common KSP CLI bootstrap contract.
|
||||||
|
pub(crate) use self::bootstrap::config_management;
|
||||||
|
/// Initializes the Logging runtime from Config, with the same bounded fallback policy as Config Desk.
|
||||||
|
pub(crate) use self::bootstrap::initialize_logging;
|
||||||
|
/// Loads the concrete Wallet Desk Config composite.
|
||||||
|
pub(crate) use self::bootstrap::load_wallet_desk_composite;
|
||||||
|
/// Resolves and validates one required standard-document component from the Wallet Desk composite.
|
||||||
|
pub(crate) use self::bootstrap::required_composite_component_profile;
|
||||||
|
/// Composite-local identifier for the Logging standard document.
|
||||||
|
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_LOGGING;
|
||||||
|
/// Composite-local identifier for the HTTP Transport standard document.
|
||||||
|
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT;
|
||||||
|
/// Composite-local identifier for the Wallet standard document.
|
||||||
|
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_WALLET;
|
||||||
|
/// Structured domain used while bootstrapping Config and Logging.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
||||||
|
/// Structured domain used by technical frontend events.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||||
|
/// Structured domain used by the Wallet Desk shell.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
|
||||||
|
/// Structured domain used by Wallet Desk HTTP Transport and balance operations.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_TRANSPORT;
|
||||||
|
/// Structured domain used while preparing Wallet filesystem roots.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_CONFIG;
|
||||||
|
/// Structured domain used by native-save-picker OWNER transfer export operations.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_EXPORT;
|
||||||
|
/// Structured domain used by native-picker Wallet transfer import operations.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_IMPORT;
|
||||||
|
/// Structured domain used while enumerating and inspecting locked Wallet files.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_INVENTORY;
|
||||||
|
/// Structured domain used by Config-owned Wallet password candidate operations.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_SECRET;
|
||||||
|
/// Structured domain used by durable Wallet session, creation and unlock operations.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_SESSION;
|
||||||
|
/// Structured domain used by Tauri window lifecycle operations.
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
||||||
|
/// Owning target for backend events emitted by Wallet Desk.
|
||||||
|
pub(crate) use self::constants::TRACING_TARGET;
|
||||||
|
/// Owning target for generic frontend events emitted through the KSP bridge.
|
||||||
|
pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
||||||
|
/// Owning target for main-window frontend events.
|
||||||
|
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
|
||||||
|
/// Owning target for splash-window frontend events.
|
||||||
|
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
||||||
|
/// Native Wallet filename suffix accepted by inventory operations.
|
||||||
|
pub(crate) use self::constants::WALLET_FILE_SUFFIX;
|
||||||
|
/// Safe command error projection exposed to Tauri commands.
|
||||||
|
pub(crate) use self::dto_common::CommandErrorDto;
|
||||||
|
/// Initial application/runtime snapshot exposed to the Wallet Desk shell.
|
||||||
|
pub(crate) use self::dto_common::RuntimeStatusDto;
|
||||||
|
/// Shared Wallet Desk application state is internally inconsistent.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||||
|
/// Shared Wallet Desk runtime state cannot be locked safely.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||||
|
/// The Wallet Desk Config composite is missing or misroutes one required standard component.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_INVALID;
|
||||||
|
/// Frontend logging requested an unsupported level.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
||||||
|
/// Frontend logging requested a target outside the application whitelist.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
|
||||||
|
/// Wallet Desk could not install the managed Logging runtime or its safe fallback.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
|
||||||
|
/// Splash readiness was invoked from a window other than the splash window.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
|
||||||
|
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID;
|
||||||
|
/// Tauri runtime assembly or execution failed.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
|
||||||
|
/// A required Tauri window is missing from the configured application runtime.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_MISSING;
|
||||||
|
/// A Tauri window show/focus/destroy/event operation failed.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED;
|
||||||
|
/// Wallet balance access requires an authorized VIEW or OWNER session.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_AUTHORIZATION_REQUIRED;
|
||||||
|
/// The configured Wallet root or effective profile directory resolves to an unsupported filesystem object.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_DIRECTORY_INVALID;
|
||||||
|
/// Wallet Desk could not inspect or create the configured Wallet directory tree.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED;
|
||||||
|
/// Native save-picker Wallet export destination is invalid.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_EXPORT_DESTINATION_INVALID;
|
||||||
|
/// Native-picker transfer source is invalid or cannot be staged.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_IMPORT_SOURCE_INVALID;
|
||||||
|
/// Wallet import has no staged native-picker source.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_IMPORT_SOURCE_MISSING;
|
||||||
|
/// Wallet inventory filesystem operation failed.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_INVENTORY_FAILED;
|
||||||
|
/// No effective Config-owned Wallet password candidate is available.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_SECRET_CANDIDATES_MISSING;
|
||||||
|
/// Wallet selection identifier is invalid or no longer eligible.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_SELECTION_INVALID;
|
||||||
|
/// Wallet session lifecycle operation is incompatible with the current state.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_SESSION_INVALID;
|
||||||
|
/// Selected Wallet does not expose an enabled VIEW capability.
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_WALLET_VIEW_DISABLED;
|
||||||
|
/// Log payload sent by Wallet Desk frontend scripts.
|
||||||
|
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
||||||
|
/// Emits one validated frontend event through the KSP Logging facade.
|
||||||
|
pub(crate) use self::frontend_logging::emit_frontend_log_event;
|
||||||
|
/// Creates the stable runtime identity for this Wallet Desk process launch.
|
||||||
|
pub(crate) use self::logging_runtime::launch_identity;
|
||||||
|
/// Command emitted by Rust to the splash frontend.
|
||||||
|
pub(crate) use self::splash::SplashOrderDto;
|
||||||
|
/// Runtime timings used by the common desk splash lifecycle.
|
||||||
|
pub(crate) use self::splash::SplashSettings;
|
||||||
|
/// Safe and executable Transport runtime retained by the application state.
|
||||||
|
pub(crate) use self::transport_runtime::TransportRuntime;
|
||||||
|
/// Resolves the composite-selected Transport profile and builds the executable HTTP pool.
|
||||||
|
pub(crate) use self::transport_runtime::initialize_transport;
|
||||||
|
/// Resolves the required main window or returns a typed error.
|
||||||
|
pub(crate) use self::tw_main::require_main_window;
|
||||||
|
/// Shows and focuses the main Wallet Desk window.
|
||||||
|
pub(crate) use self::tw_main::show_main_window;
|
||||||
|
/// Resolves the required splash window or returns a typed error.
|
||||||
|
pub(crate) use self::tw_splash::require_splash_window;
|
||||||
|
/// Starts the one-shot splash lifecycle after the splash frontend reports readiness.
|
||||||
|
pub(crate) use self::tw_splash::splash_frontend_ready_service;
|
||||||
|
/// Safe balance snapshot for the currently authorized Wallet.
|
||||||
|
pub(crate) use self::wallet_balance::WalletBalanceDto;
|
||||||
|
/// Formats exact lamports as a decimal SOL string without floating point.
|
||||||
|
pub(crate) use self::wallet_balance::format_lamports_as_sol;
|
||||||
|
/// Resolved Wallet Config and directory preparation status captured during application bootstrap.
|
||||||
|
pub(crate) use self::wallet_config::WalletConfigStartup;
|
||||||
|
/// Resolves the composite-selected Wallet Config and prepares its application-owned directory tree.
|
||||||
|
pub(crate) use self::wallet_config::initialize_wallet_config;
|
||||||
|
/// OWNER transfer export request carrying only the format selected by the frontend.
|
||||||
|
pub(crate) use self::wallet_export::WalletExportRequestDto;
|
||||||
|
/// Safe OWNER transfer export result without path or key material.
|
||||||
|
pub(crate) use self::wallet_export::WalletExportResultDto;
|
||||||
|
/// Builds the deterministic native save-picker filename for the current OWNER Wallet.
|
||||||
|
pub(crate) use self::wallet_export::wallet_export_default_filename;
|
||||||
|
/// Extracts a safe basename from the Rust-only native save-picker destination.
|
||||||
|
pub(crate) use self::wallet_export::wallet_export_destination_name;
|
||||||
|
/// Rust-only staged import source containing bounded zeroizing transfer bytes.
|
||||||
|
pub(crate) use self::wallet_import::PendingWalletImport;
|
||||||
|
/// Import request whose new native Wallet credentials move frontend -> Rust only.
|
||||||
|
pub(crate) use self::wallet_import::WalletImportRequestDto;
|
||||||
|
/// Native picker request containing only the expected transfer format.
|
||||||
|
pub(crate) use self::wallet_import::WalletImportSourceRequestDto;
|
||||||
|
/// Safe inspection projection for one staged external transfer source.
|
||||||
|
pub(crate) use self::wallet_import::WalletTransferInspectionDto;
|
||||||
|
/// Loads and validates one native-picker import source into bounded zeroizing Rust memory.
|
||||||
|
pub(crate) use self::wallet_import::stage_wallet_import_source;
|
||||||
|
/// Safe locked Wallet projection returned after selection.
|
||||||
|
pub(crate) use self::wallet_inventory::LockedWalletDto;
|
||||||
|
/// Locked inspection outcome exposed by Wallet inventory rows in crate unit tests.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) use self::wallet_inventory::WalletInspectionStatusDto;
|
||||||
|
/// Safe Wallet inventory row projection.
|
||||||
|
pub(crate) use self::wallet_inventory::WalletInventoryEntryDto;
|
||||||
|
/// Visual lock/error state exposed by Wallet inventory rows in crate unit tests.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) use self::wallet_inventory::WalletInventoryStateDto;
|
||||||
|
/// Request DTO used for one root-scoped Wallet selection.
|
||||||
|
pub(crate) use self::wallet_inventory::WalletSelectionRequestDto;
|
||||||
|
/// Enumerates native Wallet files under the effective Config-managed root.
|
||||||
|
pub(crate) use self::wallet_inventory::list_wallet_inventory;
|
||||||
|
/// Builds a validated destination path for one new root-scoped Wallet.
|
||||||
|
pub(crate) use self::wallet_inventory::new_wallet_destination;
|
||||||
|
/// Re-resolves one locked Wallet into Rust-only path state plus safe DTO.
|
||||||
|
pub(crate) use self::wallet_inventory::resolve_locked_wallet;
|
||||||
|
/// Re-inspects one root-scoped Wallet selection in crate unit tests.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) use self::wallet_inventory::select_locked_wallet;
|
||||||
|
/// OWNER alias update request carrying protected metadata only frontend -> Rust.
|
||||||
|
pub(crate) use self::wallet_metadata::WalletAliasUpdateRequestDto;
|
||||||
|
/// OWNER note append request carrying protected metadata only frontend -> Rust.
|
||||||
|
pub(crate) use self::wallet_metadata::WalletNoteAddRequestDto;
|
||||||
|
/// OWNER note delete request selecting one authorized stable note identifier.
|
||||||
|
pub(crate) use self::wallet_metadata::WalletNoteDeleteRequestDto;
|
||||||
|
/// OWNER note update request carrying protected metadata only frontend -> Rust.
|
||||||
|
pub(crate) use self::wallet_metadata::WalletNoteUpdateRequestDto;
|
||||||
|
/// Discovers Config-owned Wallet password candidates without exposing their names or values.
|
||||||
|
pub(crate) use self::wallet_secrets::discover_wallet_secret_candidates;
|
||||||
|
/// Returns the configured Wallet password candidate count without exposing their names or values.
|
||||||
|
pub(crate) use self::wallet_secrets::wallet_secret_candidate_count;
|
||||||
|
/// New credential request moved frontend -> Rust only for one explicit VIEW/OWNER-authorized rotation.
|
||||||
|
pub(crate) use self::wallet_security::WalletPasswordRotationRequestDto;
|
||||||
|
/// Safe VIEW security state returned after OWNER-authorized strong disable/recreate operations.
|
||||||
|
pub(crate) use self::wallet_security::WalletViewSecurityStatusDto;
|
||||||
|
/// Builds the safe strong VIEW administration status from the resulting OWNER projection.
|
||||||
|
pub(crate) use self::wallet_security::view_security_status_from_authorized;
|
||||||
|
/// Authorized OWNER projection exposed only after successful creation.
|
||||||
|
pub(crate) use self::wallet_session::WalletAuthorizedDto;
|
||||||
|
/// Create request whose password strings move frontend -> Rust only.
|
||||||
|
pub(crate) use self::wallet_session::WalletCreateRequestDto;
|
||||||
|
/// Durable backend Wallet session retaining full paths and handles only in Rust.
|
||||||
|
pub(crate) use self::wallet_session::WalletSession;
|
||||||
|
/// Minimal safe projection of the durable Wallet session.
|
||||||
|
pub(crate) use self::wallet_session::WalletSessionDto;
|
||||||
|
/// Safe state code for the durable Wallet session in crate unit tests.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) use self::wallet_session::WalletSessionStateDto;
|
||||||
|
/// Capability targeted by one explicit unlock operation.
|
||||||
|
pub(crate) use self::wallet_session::WalletUnlockCapability;
|
||||||
|
/// Manual unlock request whose password moves frontend -> Rust only.
|
||||||
|
pub(crate) use self::wallet_session::WalletUnlockRequestDto;
|
||||||
|
/// Builds an authorized OWNER DTO from a Rust-only Wallet handle.
|
||||||
|
pub(crate) use self::wallet_session::owner_projection;
|
||||||
|
/// Builds an authorized VIEW DTO from a Rust-only Wallet handle.
|
||||||
|
pub(crate) use self::wallet_session::view_projection;
|
||||||
|
/// Shared import/export transfer format selected by Wallet Desk.
|
||||||
|
pub(crate) use self::wallet_transfer::WalletTransferFormatDto;
|
||||||
10
crates/ksp-app-wallet-desk/src/logging_runtime.rs
Normal file
10
crates/ksp-app-wallet-desk/src/logging_runtime.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/logging_runtime.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Wallet Desk Logging runtime identity helpers.
|
||||||
|
|
||||||
|
/// Creates the stable runtime identity for this Wallet Desk process launch.
|
||||||
|
pub(crate) fn launch_identity() -> ksp_core_lib::Result<ksp_logging_lib::LoggingRuntimeIdentity> {
|
||||||
|
let timestamp = format!("{}-p{}", chrono::Utc::now().format("%Y%m%d-%H%M%S%.3fZ"), std::process::id());
|
||||||
|
return ksp_logging_lib::LoggingRuntimeIdentity::new(crate::TRACING_TARGET, timestamp);
|
||||||
|
}
|
||||||
58
crates/ksp-app-wallet-desk/src/main.rs
Normal file
58
crates/ksp-app-wallet-desk/src/main.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/main.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Binary entry point for the KSP wallet desktop application.
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![deny(unreachable_pub)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
use fs2::FileExt; // rust-rules: trait-import
|
||||||
|
|
||||||
|
fn main() -> std::process::ExitCode {
|
||||||
|
let working_directory = configure_runtime_working_directory();
|
||||||
|
if let std::result::Result::Err(error) = working_directory {
|
||||||
|
eprintln!("cannot configure Wallet Desk runtime working directory: {error}");
|
||||||
|
return std::process::ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
let mut lock_path = std::env::temp_dir();
|
||||||
|
lock_path.push("com_sasedev_ksp_app_wallet_desk.lock");
|
||||||
|
let lock_file = match std::fs::OpenOptions::new().read(true).write(true).create(true).truncate(false).open(&lock_path) {
|
||||||
|
std::result::Result::Ok(file) => file,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
eprintln!("cannot create application lock '{}': {error}", lock_path.display());
|
||||||
|
return std::process::ExitCode::FAILURE;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if let std::result::Result::Err(error) = lock_file.try_lock_exclusive() {
|
||||||
|
if error.kind() == std::io::ErrorKind::WouldBlock {
|
||||||
|
eprintln!("another ksp-app-wallet-desk instance is already running");
|
||||||
|
return std::process::ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
eprintln!("cannot acquire application lock '{}': {error}", lock_path.display());
|
||||||
|
return std::process::ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
let _lock_file = lock_file;
|
||||||
|
let arguments = std::env::args_os().collect::<std::vec::Vec<std::ffi::OsString>>();
|
||||||
|
let run_result = ksp_app_wallet_desk_lib::run(arguments.as_slice());
|
||||||
|
return match run_result {
|
||||||
|
std::result::Result::Ok(()) => std::process::ExitCode::SUCCESS,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
eprintln!("application error: {error}");
|
||||||
|
std::process::ExitCode::FAILURE
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_runtime_working_directory() -> std::io::Result<()> {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||||
|
return std::env::set_current_dir(workspace_root);
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
214
crates/ksp-app-wallet-desk/src/splash.rs
Normal file
214
crates/ksp-app-wallet-desk/src/splash.rs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/splash.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
//! Common splash settings and frontend event contracts for Wallet Desk.
|
||||||
|
|
||||||
|
use ts_rs::TS; // rust-rules: trait-import
|
||||||
|
|
||||||
|
const DEFAULT_SPLASH_FADE_IN_MS: u32 = 300;
|
||||||
|
const DEFAULT_SPLASH_FADE_OUT_MS: u32 = 300;
|
||||||
|
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
|
||||||
|
const ENV_SPLASH_FADE_IN_MS: &str = "KSP_DESK_SPLASH_FADE_IN_MS";
|
||||||
|
const ENV_SPLASH_FADE_OUT_MS: &str = "KSP_DESK_SPLASH_FADE_OUT_MS";
|
||||||
|
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
|
||||||
|
const MAX_SPLASH_FADE_MS: u32 = 10_000;
|
||||||
|
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
||||||
|
|
||||||
|
/// Runtime timings used by the common desk splash lifecycle.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct SplashSettings {
|
||||||
|
fade_in_ms: u32,
|
||||||
|
minimum_ms: u64,
|
||||||
|
fade_out_ms: u32,
|
||||||
|
fade_in_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||||
|
minimum_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||||
|
fade_out_source: ksp_config_lib::ConfigEnvironmentSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SplashSettings {
|
||||||
|
/// Resolves splash timings through the Config-owned environment snapshot.
|
||||||
|
pub(crate) fn load() -> ksp_core_lib::Result<Self> {
|
||||||
|
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||||
|
let environment = match environment {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let fade_in = environment.resolve_variable(ENV_SPLASH_FADE_IN_MS, std::option::Option::Some("300"));
|
||||||
|
let fade_in = match fade_in {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let minimum = environment.resolve_variable(ENV_SPLASH_MINIMUM_MS, std::option::Option::Some("1200"));
|
||||||
|
let minimum = match minimum {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let fade_out = environment.resolve_variable(ENV_SPLASH_FADE_OUT_MS, std::option::Option::Some("300"));
|
||||||
|
let fade_out = match fade_out {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let fade_in_ms = parse_u32_setting(ENV_SPLASH_FADE_IN_MS, fade_in.value(), MAX_SPLASH_FADE_MS);
|
||||||
|
let fade_in_ms = match fade_in_ms {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let minimum_ms = parse_u64_setting(ENV_SPLASH_MINIMUM_MS, minimum.value(), MAX_SPLASH_MINIMUM_MS);
|
||||||
|
let minimum_ms = match minimum_ms {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let fade_out_ms = parse_u32_setting(ENV_SPLASH_FADE_OUT_MS, fade_out.value(), MAX_SPLASH_FADE_MS);
|
||||||
|
let fade_out_ms = match fade_out_ms {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(Self {
|
||||||
|
fade_in_ms,
|
||||||
|
minimum_ms,
|
||||||
|
fade_out_ms,
|
||||||
|
fade_in_source: fade_in.source(),
|
||||||
|
minimum_source: minimum.source(),
|
||||||
|
fade_out_source: fade_out.source(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns safe in-memory timings used when the managed environment cannot be resolved.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn fallback() -> Self {
|
||||||
|
return Self {
|
||||||
|
fade_in_ms: DEFAULT_SPLASH_FADE_IN_MS,
|
||||||
|
minimum_ms: DEFAULT_SPLASH_MINIMUM_MS,
|
||||||
|
fade_out_ms: DEFAULT_SPLASH_FADE_OUT_MS,
|
||||||
|
fade_in_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||||
|
minimum_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||||
|
fade_out_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the configured fade-in duration.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn fade_in_ms(self) -> u32 {
|
||||||
|
return self.fade_in_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum fully-visible splash duration.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn minimum_ms(self) -> u64 {
|
||||||
|
return self.minimum_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the configured fade-out duration.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn fade_out_ms(self) -> u32 {
|
||||||
|
return self.fade_out_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the safe provenance code for the fade-in duration.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn fade_in_source(self) -> &'static str {
|
||||||
|
return environment_source_code(self.fade_in_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the safe provenance code for the minimum duration.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn minimum_source(self) -> &'static str {
|
||||||
|
return environment_source_code(self.minimum_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the safe provenance code for the fade-out duration.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn fade_out_source(self) -> &'static str {
|
||||||
|
return environment_source_code(self.fade_out_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the expected additive backend lifecycle from readiness through fade-out.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn expected_backend_lifecycle_ms(self) -> u64 {
|
||||||
|
return u64::from(self.fade_in_ms) + self.minimum_ms + u64::from(self.fade_out_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command emitted by Rust to the splash frontend.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/splash/SplashOrderDto.ts")]
|
||||||
|
pub(crate) struct SplashOrderDto {
|
||||||
|
/// Splash action name.
|
||||||
|
pub(crate) action: String,
|
||||||
|
/// Optional status text.
|
||||||
|
pub(crate) message: std::option::Option<String>,
|
||||||
|
/// Optional message severity used by the bottom message feed.
|
||||||
|
pub(crate) status: std::option::Option<String>,
|
||||||
|
/// Optional animation duration in milliseconds.
|
||||||
|
pub(crate) duration_ms: std::option::Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SplashOrderDto {
|
||||||
|
/// Creates a new `SplashOrderDto` value.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn new(action: &str, message: std::option::Option<&str>, status: std::option::Option<&str>, duration_ms: std::option::Option<u32>) -> Self {
|
||||||
|
return Self {
|
||||||
|
action: action.to_owned(),
|
||||||
|
message: message.map(std::string::ToString::to_string),
|
||||||
|
status: status.map(std::string::ToString::to_string),
|
||||||
|
duration_ms,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn environment_source_code(source: ksp_config_lib::ConfigEnvironmentSource) -> &'static str {
|
||||||
|
return match source {
|
||||||
|
ksp_config_lib::ConfigEnvironmentSource::Process => "process",
|
||||||
|
ksp_config_lib::ConfigEnvironmentSource::DotEnv => "dotenv",
|
||||||
|
ksp_config_lib::ConfigEnvironmentSource::Fallback => "fallback",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_u64_setting(variable_name: &str, value: &str, maximum: u64) -> ksp_core_lib::Result<u64> {
|
||||||
|
let parsed = value.parse::<u64>();
|
||||||
|
let parsed = match parsed {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration is not a valid unsigned integer")
|
||||||
|
.with_context("variable_name", variable_name)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if parsed > maximum {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration exceeds the allowed bound")
|
||||||
|
.with_context("variable_name", variable_name)
|
||||||
|
.with_context("maximum_ms", maximum.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_u32_setting(variable_name: &str, value: &str, maximum: u32) -> ksp_core_lib::Result<u32> {
|
||||||
|
let parsed = value.parse::<u32>();
|
||||||
|
let parsed = match parsed {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration is not a valid unsigned integer")
|
||||||
|
.with_context("variable_name", variable_name)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if parsed > maximum {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration exceeds the allowed bound")
|
||||||
|
.with_context("variable_name", variable_name)
|
||||||
|
.with_context("maximum_ms", maximum.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../unit_tests/splash.rs"]
|
||||||
|
mod tests;
|
||||||
470
crates/ksp-app-wallet-desk/src/tauri.rs
Normal file
470
crates/ksp-app-wallet-desk/src/tauri.rs
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/tauri.rs
|
||||||
|
// version: 11
|
||||||
|
|
||||||
|
//! Tauri runtime assembly for the KSP wallet desktop application.
|
||||||
|
|
||||||
|
use tauri_plugin_dialog::DialogExt; // rust-rules: trait-import
|
||||||
|
|
||||||
|
/// Runs the Wallet Desk application.
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
||||||
|
let context = tauri::generate_context!();
|
||||||
|
let runtime_layout = configure_packaged_runtime(&context);
|
||||||
|
if let std::result::Result::Err(error) = runtime_layout {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let app_state = crate::AppState::initialize(arguments);
|
||||||
|
let app_state = match app_state {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let mut builder = tauri::Builder::default();
|
||||||
|
builder = configure_state(builder, app_state);
|
||||||
|
builder = configure_plugins(builder);
|
||||||
|
builder = configure_commands(builder);
|
||||||
|
builder = configure_setup(builder);
|
||||||
|
let run_result = builder.run(context);
|
||||||
|
return match run_result {
|
||||||
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot run KSP Wallet Desk Tauri runtime")
|
||||||
|
.with_context("tauri_error", error.to_string()),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_packaged_runtime(context: &tauri::Context<tauri::Wry>) -> ksp_core_lib::Result<()> {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
let resource_root = tauri::utils::platform::resource_dir(context.package_info(), &tauri::Env::default());
|
||||||
|
let resource_root = match resource_root {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot resolve packaged Tauri resource directory")
|
||||||
|
.with_context("tauri_error", error.to_string()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let layout = ksp_config_lib::prepare_packaged_runtime(resource_root.as_path());
|
||||||
|
let layout = match layout {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let working_directory = std::env::set_current_dir(layout.runtime_root());
|
||||||
|
return match working_directory {
|
||||||
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot activate packaged KSP runtime directory")
|
||||||
|
.with_context("runtime_root", layout.runtime_root().to_string_lossy().into_owned())
|
||||||
|
.with_source(error),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_state(builder: tauri::Builder<tauri::Wry>, app_state: crate::AppState) -> tauri::Builder<tauri::Wry> {
|
||||||
|
return builder.manage(app_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||||
|
let tracing_plugin = tauri_plugin_tracing::Builder::new().build::<tauri::Wry>();
|
||||||
|
return builder.plugin(tauri_plugin_dialog::init()).plugin(tracing_plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for async command dispatch.
|
||||||
|
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||||
|
return builder.invoke_handler(tauri::generate_handler![
|
||||||
|
add_wallet_note,
|
||||||
|
clear_import_source,
|
||||||
|
create_wallet,
|
||||||
|
delete_wallet_note,
|
||||||
|
deselect_wallet,
|
||||||
|
disable_wallet_view,
|
||||||
|
emit_frontend_log,
|
||||||
|
export_wallet_transfer,
|
||||||
|
get_runtime_status,
|
||||||
|
import_wallet,
|
||||||
|
inspect_import_source,
|
||||||
|
list_wallets,
|
||||||
|
lock_wallet,
|
||||||
|
recreate_wallet_view,
|
||||||
|
refresh_wallet_balance,
|
||||||
|
refresh_wallets,
|
||||||
|
rotate_owner_password,
|
||||||
|
rotate_view_password,
|
||||||
|
select_wallet,
|
||||||
|
splash_frontend_ready,
|
||||||
|
unlock_wallet_owner_configured,
|
||||||
|
unlock_wallet_owner_manual,
|
||||||
|
unlock_wallet_view_configured,
|
||||||
|
unlock_wallet_view_manual,
|
||||||
|
update_wallet_alias,
|
||||||
|
update_wallet_note,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||||
|
return builder.setup(|app| {
|
||||||
|
let splash = crate::require_splash_window(app);
|
||||||
|
if let std::result::Result::Err(error) = splash {
|
||||||
|
return std::result::Result::Err(std::boxed::Box::new(error));
|
||||||
|
}
|
||||||
|
let main = crate::require_main_window(app);
|
||||||
|
if let std::result::Result::Err(error) = main {
|
||||||
|
return std::result::Result::Err(std::boxed::Box::new(error));
|
||||||
|
}
|
||||||
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_SHELL, "Wallet Desk Tauri shell setup completed");
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn add_wallet_note(
|
||||||
|
request: crate::WalletNoteAddRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.add_wallet_note(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn clear_import_source(state: tauri::State<'_, crate::AppState>) -> std::result::Result<(), crate::CommandErrorDto> {
|
||||||
|
let result = state.clear_import_source();
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn create_wallet(
|
||||||
|
request: crate::WalletCreateRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.create_wallet(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn delete_wallet_note(
|
||||||
|
request: crate::WalletNoteDeleteRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.delete_wallet_note(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn deselect_wallet(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::WalletSessionDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.deselect_wallet();
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn disable_wallet_view(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::WalletViewSecurityStatusDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.disable_wallet_view().await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
|
||||||
|
let result = crate::emit_frontend_log_event(payload);
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn export_wallet_transfer(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
request: crate::WalletExportRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<std::option::Option<crate::WalletExportResultDto>, crate::CommandErrorDto> {
|
||||||
|
let format = request.format;
|
||||||
|
let default_filename = state.wallet_export_picker_filename(format);
|
||||||
|
let default_filename = match default_filename {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
let extensions = [format.default_extension()];
|
||||||
|
let selected = app
|
||||||
|
.dialog()
|
||||||
|
.file()
|
||||||
|
.set_title("Export Solana keypair — secret material")
|
||||||
|
.set_file_name(default_filename)
|
||||||
|
.add_filter("Solana keypair transfer", &extensions)
|
||||||
|
.blocking_save_file();
|
||||||
|
let selected = match selected {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => {
|
||||||
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_EXPORT, transfer_format = format.code(), "Wallet OWNER transfer export native save picker cancelled");
|
||||||
|
return std::result::Result::Ok(std::option::Option::None);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let path = selected.into_path();
|
||||||
|
let path = match path {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => {
|
||||||
|
let error = ksp_core_lib::Error::new(
|
||||||
|
crate::ERROR_CODE_WALLET_EXPORT_DESTINATION_INVALID,
|
||||||
|
"Selected Wallet transfer export destination is not a local filesystem path",
|
||||||
|
);
|
||||||
|
return std::result::Result::Err(crate::CommandErrorDto::from_error(&error));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let result = state.export_wallet_transfer(path, request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(std::option::Option::Some(value)),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn get_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::RuntimeStatusDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.runtime_status();
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn import_wallet(
|
||||||
|
request: crate::WalletImportRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.import_wallet(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn inspect_import_source(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
request: crate::WalletImportSourceRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<std::option::Option<crate::WalletTransferInspectionDto>, crate::CommandErrorDto> {
|
||||||
|
let cleared = state.clear_import_source();
|
||||||
|
if let std::result::Result::Err(error) = cleared {
|
||||||
|
return std::result::Result::Err(crate::CommandErrorDto::from_error(&error));
|
||||||
|
}
|
||||||
|
let format = request.format;
|
||||||
|
let extensions = [format.default_extension()];
|
||||||
|
let selected = app
|
||||||
|
.dialog()
|
||||||
|
.file()
|
||||||
|
.set_title("Select Solana keypair transfer source")
|
||||||
|
.add_filter("Solana keypair transfer", &extensions)
|
||||||
|
.blocking_pick_file();
|
||||||
|
let selected = match selected {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => {
|
||||||
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_IMPORT, transfer_format = format.code(), "Wallet import native picker cancelled");
|
||||||
|
return std::result::Result::Ok(std::option::Option::None);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let path = selected.into_path();
|
||||||
|
let path = match path {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => {
|
||||||
|
let error =
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_WALLET_IMPORT_SOURCE_INVALID, "Selected Wallet transfer source is not a local filesystem path");
|
||||||
|
return std::result::Result::Err(crate::CommandErrorDto::from_error(&error));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let result = state.stage_import_source(path, format).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(std::option::Option::Some(value)),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn recreate_wallet_view(
|
||||||
|
request: crate::WalletPasswordRotationRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletViewSecurityStatusDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.recreate_wallet_view(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn refresh_wallet_balance(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::WalletBalanceDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.refresh_wallet_balance().await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn list_wallets(state: tauri::State<'_, crate::AppState>) -> std::result::Result<std::vec::Vec<crate::WalletInventoryEntryDto>, crate::CommandErrorDto> {
|
||||||
|
let root = state.wallet_inventory_root().to_path_buf();
|
||||||
|
let result = crate::list_wallet_inventory(root.as_path()).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn lock_wallet(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::LockedWalletDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.lock_wallet().await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn refresh_wallets(
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<std::vec::Vec<crate::WalletInventoryEntryDto>, crate::CommandErrorDto> {
|
||||||
|
let deselected = state.deselect_wallet();
|
||||||
|
if let std::result::Result::Err(error) = deselected {
|
||||||
|
return std::result::Result::Err(crate::CommandErrorDto::from_error(&error));
|
||||||
|
}
|
||||||
|
let root = state.wallet_inventory_root().to_path_buf();
|
||||||
|
let result = crate::list_wallet_inventory(root.as_path()).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn rotate_owner_password(
|
||||||
|
request: crate::WalletPasswordRotationRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.rotate_owner_password(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn rotate_view_password(
|
||||||
|
request: crate::WalletPasswordRotationRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.rotate_view_password(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn select_wallet(
|
||||||
|
request: crate::WalletSelectionRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::LockedWalletDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.select_wallet(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn splash_frontend_ready(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
window: tauri::WebviewWindow,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<(), crate::CommandErrorDto> {
|
||||||
|
let result = crate::splash_frontend_ready_service(app, window, &state).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn unlock_wallet_owner_configured(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.unlock_wallet_owner_configured().await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn unlock_wallet_owner_manual(
|
||||||
|
request: crate::WalletUnlockRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.unlock_wallet_owner_manual(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn unlock_wallet_view_configured(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.unlock_wallet_view_configured().await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn unlock_wallet_view_manual(
|
||||||
|
request: crate::WalletUnlockRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.unlock_wallet_view_manual(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn update_wallet_alias(
|
||||||
|
request: crate::WalletAliasUpdateRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.update_wallet_alias(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn update_wallet_note(
|
||||||
|
request: crate::WalletNoteUpdateRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::WalletAuthorizedDto, crate::CommandErrorDto> {
|
||||||
|
let result = state.update_wallet_note(request).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
97
crates/ksp-app-wallet-desk/src/transport_runtime.rs
Normal file
97
crates/ksp-app-wallet-desk/src/transport_runtime.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/transport_runtime.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Composite-selected HTTP Transport runtime owned by Wallet Desk.
|
||||||
|
|
||||||
|
/// Safe and executable Transport runtime retained by the application state.
|
||||||
|
pub(crate) struct TransportRuntime {
|
||||||
|
pool: ksp_onchain_transport_lib::HttpTransportPool,
|
||||||
|
profile_id: String,
|
||||||
|
role: ksp_onchain_transport_lib::HttpRoleName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransportRuntime {
|
||||||
|
/// Returns the shareable HTTP Transport pool.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn pool(&self) -> &ksp_onchain_transport_lib::HttpTransportPool {
|
||||||
|
return &self.pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the composite-selected Transport profile identifier.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn profile_id(&self) -> &str {
|
||||||
|
return self.profile_id.as_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the logical role used by Wallet Desk read-only RPC calls.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) const fn role(&self) -> &ksp_onchain_transport_lib::HttpRoleName {
|
||||||
|
return &self.role;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns safe configured cluster labels without endpoint URLs.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn clusters(&self) -> std::vec::Vec<String> {
|
||||||
|
let mut values = self.pool.snapshot().endpoints().iter().map(|endpoint| return endpoint.cluster().to_owned()).collect::<std::vec::Vec<_>>();
|
||||||
|
values.sort();
|
||||||
|
values.dedup();
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns safe configured provider labels without endpoint URLs.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn providers(&self) -> std::vec::Vec<String> {
|
||||||
|
let mut values = self.pool.snapshot().endpoints().iter().map(|endpoint| return endpoint.provider().to_owned()).collect::<std::vec::Vec<_>>();
|
||||||
|
values.sort();
|
||||||
|
values.dedup();
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves the composite-selected Transport profile and builds the executable HTTP pool.
|
||||||
|
pub(crate) fn initialize_transport(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<TransportRuntime> {
|
||||||
|
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||||
|
let environment = match environment {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let composite = crate::load_wallet_desk_composite(management);
|
||||||
|
let composite = match composite {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
|
||||||
|
let profile = match profile {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let resolved = management.engine().resolve_transport_config_profile(&profile, &environment);
|
||||||
|
let resolved = match resolved {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let profile_id = resolved.profile_id().to_owned();
|
||||||
|
let pool = ksp_onchain_transport_lib::HttpTransportPool::new(resolved.into_settings());
|
||||||
|
let pool = match pool {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let role = ksp_onchain_transport_lib::HttpRoleName::new("default");
|
||||||
|
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method("getBalance");
|
||||||
|
let descriptor = match descriptor {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => {
|
||||||
|
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||||
|
crate::ERROR_CODE_APP_STATE_INVALID,
|
||||||
|
"Wallet Desk Transport registry does not expose getBalance",
|
||||||
|
));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let selection = pool.select_for_method(&role, descriptor);
|
||||||
|
if let std::result::Result::Err(error) = selection {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let snapshot = pool.snapshot();
|
||||||
|
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_TRANSPORT, transport_profile = profile_id.as_str(), transport_role = role.as_str(), endpoint_count = snapshot.endpoint_count(), available_endpoint_count = snapshot.available_endpoint_count(), "initialized Wallet Desk HTTP Transport from composite-managed configuration");
|
||||||
|
return std::result::Result::Ok(TransportRuntime { pool, profile_id, role });
|
||||||
|
}
|
||||||
48
crates/ksp-app-wallet-desk/src/tw_main.rs
Normal file
48
crates/ksp-app-wallet-desk/src/tw_main.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/tw_main.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Tauri-window helpers for the Wallet Desk main window.
|
||||||
|
|
||||||
|
use tauri::Manager; // rust-rules: trait-import
|
||||||
|
|
||||||
|
/// Crate-internal main window label.
|
||||||
|
pub(crate) const WINDOW_LABEL_MAIN: &str = "main";
|
||||||
|
|
||||||
|
/// Resolves the required main window or returns a typed error.
|
||||||
|
pub(crate) fn require_main_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||||
|
let window = manager.get_webview_window(WINDOW_LABEL_MAIN);
|
||||||
|
return match window {
|
||||||
|
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
||||||
|
std::option::Option::None => std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_MISSING, "Wallet Desk main window is missing")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_MAIN),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows and focuses the main Wallet Desk window.
|
||||||
|
pub(crate) fn show_main_window(app: &tauri::AppHandle) -> ksp_core_lib::Result<()> {
|
||||||
|
let window = crate::require_main_window(app);
|
||||||
|
let window = match window {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let shown = window.show();
|
||||||
|
if let std::result::Result::Err(error) = shown {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot show Wallet Desk main window")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_MAIN)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let focused = window.set_focus();
|
||||||
|
if let std::result::Result::Err(error) = focused {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot focus Wallet Desk main window")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_MAIN)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_MAIN, "main window shown and focused");
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
173
crates/ksp-app-wallet-desk/src/tw_splash.rs
Normal file
173
crates/ksp-app-wallet-desk/src/tw_splash.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// file: crates/ksp-app-wallet-desk/src/tw_splash.rs
|
||||||
|
// version: 2
|
||||||
|
|
||||||
|
//! Tauri-window lifecycle for the Wallet Desk splash window.
|
||||||
|
|
||||||
|
use tauri::Emitter; // rust-rules: trait-import
|
||||||
|
use tauri::Manager; // rust-rules: trait-import
|
||||||
|
|
||||||
|
/// Crate-internal splash window label.
|
||||||
|
pub(crate) const WINDOW_LABEL_SPLASH: &str = "splash";
|
||||||
|
|
||||||
|
const SPLASH_EVENT_NAME: &str = "ksp-splash-order";
|
||||||
|
|
||||||
|
/// Resolves the required splash window or returns a typed error.
|
||||||
|
pub(crate) fn require_splash_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||||
|
let window = manager.get_webview_window(WINDOW_LABEL_SPLASH);
|
||||||
|
return match window {
|
||||||
|
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
||||||
|
std::option::Option::None => std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_MISSING, "Wallet Desk splash window is missing")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_SPLASH),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts the one-shot splash lifecycle after the splash frontend reports readiness.
|
||||||
|
pub(crate) async fn splash_frontend_ready_service(
|
||||||
|
app: tauri::AppHandle,
|
||||||
|
invoking_window: tauri::WebviewWindow,
|
||||||
|
state: &crate::AppState,
|
||||||
|
) -> ksp_core_lib::Result<()> {
|
||||||
|
if invoking_window.label() != WINDOW_LABEL_SPLASH {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_ORIGIN_INVALID, "Splash readiness may only originate from the splash window")
|
||||||
|
.with_context("window_label", invoking_window.label()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !state.begin_splash_sequence() {
|
||||||
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, "duplicate splash frontend readiness ignored");
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
let settings = state.splash_settings();
|
||||||
|
let lifecycle_started = std::time::Instant::now();
|
||||||
|
ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
window_label = WINDOW_LABEL_SPLASH,
|
||||||
|
fade_in_ms = settings.fade_in_ms(),
|
||||||
|
fade_in_source = settings.fade_in_source(),
|
||||||
|
minimum_ms = settings.minimum_ms(),
|
||||||
|
minimum_source = settings.minimum_source(),
|
||||||
|
fade_out_ms = settings.fade_out_ms(),
|
||||||
|
fade_out_source = settings.fade_out_source(),
|
||||||
|
expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(),
|
||||||
|
"splash frontend readiness accepted; starting splash to main lifecycle"
|
||||||
|
);
|
||||||
|
let emitted = emit_order(
|
||||||
|
&invoking_window,
|
||||||
|
crate::SplashOrderDto::new("fade_in", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_in_ms())),
|
||||||
|
);
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let emitted = emit_message(&invoking_window, "Initialisation de Wallet Desk...", "info");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let emitted = emit_debug(&invoking_window, "Listener frontend prêt; fade-in piloté par Rust.");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
sleep_and_log(&invoking_window, "fade-in", u64::from(settings.fade_in_ms()), lifecycle_started).await;
|
||||||
|
let emitted = emit_message(&invoking_window, "Configuration KSP chargée.", "success");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let emitted = emit_debug(&invoking_window, "Configuration et provenance des timings résolues via ksp-config-lib.");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let emitted = emit_message(&invoking_window, "Runtime Logging initialisé.", "success");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let emitted = emit_message(&invoking_window, "Wallet et Transport prêts.", "success");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
sleep_and_log(&invoking_window, "minimum", settings.minimum_ms(), lifecycle_started).await;
|
||||||
|
let emitted = emit_message(&invoking_window, "Initialisation terminée.", "success");
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let emitted = emit_order(
|
||||||
|
&invoking_window,
|
||||||
|
crate::SplashOrderDto::new("fade_out", std::option::Option::None, std::option::Option::None, std::option::Option::Some(settings.fade_out_ms())),
|
||||||
|
);
|
||||||
|
if let std::result::Result::Err(error) = emitted {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
sleep_and_log(&invoking_window, "fade-out", u64::from(settings.fade_out_ms()), lifecycle_started).await;
|
||||||
|
let show_main = crate::show_main_window(&app);
|
||||||
|
if let std::result::Result::Err(error) = show_main {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let destroyed = invoking_window.destroy();
|
||||||
|
if let std::result::Result::Err(error) = destroyed {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot destroy Wallet Desk splash window")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_SPLASH)
|
||||||
|
.with_source(error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
window_label = WINDOW_LABEL_SPLASH,
|
||||||
|
expected_backend_lifecycle_ms = settings.expected_backend_lifecycle_ms(),
|
||||||
|
actual_backend_lifecycle_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
||||||
|
"splash window destroyed after main activation"
|
||||||
|
);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_message(window: &tauri::WebviewWindow, message: &str, status: &str) -> ksp_core_lib::Result<()> {
|
||||||
|
return emit_order(
|
||||||
|
window,
|
||||||
|
crate::SplashOrderDto::new("add_message", std::option::Option::Some(message), std::option::Option::Some(status), std::option::Option::None),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_debug(window: &tauri::WebviewWindow, message: &str) -> ksp_core_lib::Result<()> {
|
||||||
|
if !cfg!(debug_assertions) {
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
return emit_order(
|
||||||
|
window,
|
||||||
|
crate::SplashOrderDto::new("add_debug", std::option::Option::Some(message), std::option::Option::None, std::option::Option::None),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sleep_and_log(window: &tauri::WebviewWindow, phase: &str, duration_ms: u64, lifecycle_started: std::time::Instant) {
|
||||||
|
let wait_started = std::time::Instant::now();
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await;
|
||||||
|
ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
||||||
|
window_label = window.label(),
|
||||||
|
phase,
|
||||||
|
configured_wait_ms = duration_ms,
|
||||||
|
actual_wait_ms = wait_started.elapsed().as_secs_f64() * 1000.0,
|
||||||
|
lifecycle_elapsed_ms = lifecycle_started.elapsed().as_secs_f64() * 1000.0,
|
||||||
|
"splash lifecycle wait completed"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_order(window: &tauri::WebviewWindow, order: crate::SplashOrderDto) -> ksp_core_lib::Result<()> {
|
||||||
|
let action = order.action.clone();
|
||||||
|
let emitted = window.emit(SPLASH_EVENT_NAME, order);
|
||||||
|
return match emitted {
|
||||||
|
std::result::Result::Ok(()) => {
|
||||||
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, window_label = WINDOW_LABEL_SPLASH, action = action.as_str(), "splash order emitted");
|
||||||
|
std::result::Result::Ok(())
|
||||||
|
},
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(
|
||||||
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED, "Cannot emit Wallet Desk splash order")
|
||||||
|
.with_context("window_label", WINDOW_LABEL_SPLASH)
|
||||||
|
.with_context("action", action)
|
||||||
|
.with_source(error),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user