iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

commit 1811d713a382aceec23c1c73d0e10ae5ceaba846
parent 792052ec2a1f8a9306884108d27689c39f30c4e0
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Sun,  2 Aug 2026 21:57:27 +0200

Add initial setup node mode choice

Diffstat:
Msrc/adapters/http.rs | 40+++++++++++++++++++++++++++++++++++++---
Mwww/assets/iuna-ui.js | 40++++++++++++++++++++++++++++++++++++++--
2 files changed, 75 insertions(+), 5 deletions(-)

diff --git a/src/adapters/http.rs b/src/adapters/http.rs @@ -2595,7 +2595,9 @@ const INDEX_HTML: &str = r#"<!doctype html> .setup-feedback.error { color: #ffb1a8; background: #2a1717; border-color: #713434; } .setup-grid { width: 100%; display: grid; grid-template-columns: minmax(0, .9fr) minmax(320px, .7fr); gap: 12px; align-items: start; } .setup-section { border: 1px solid #2f363c; border-radius: 8px; padding: 13px; background: #111316; } - .setup-network, .setup-wallet-section { grid-column: 1 / -1; } + .setup-node-mode, .setup-network, .setup-wallet-section { grid-column: 1 / -1; } + .segmented.setup-mode-picker { width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); } + .setup-mode-picker button { min-width: 0; min-height: 38px; white-space: normal; } .setup-network-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; align-items: end; } .setup-network-copy { margin-top: 8px; color: #a8b2b8; line-height: 1.45; } .setup-network-link { color: #d5f55f; font-size: 12px; font-weight: 900; text-decoration: none; } @@ -2856,6 +2858,7 @@ const INDEX_HTML: &str = r#"<!doctype html> .metrics-head { align-items: flex-start; flex-direction: column; } .metrics-range { width: 100%; } .metrics-range button { flex: 1 1 0; } + .segmented.setup-mode-picker { grid-template-columns: 1fr; } .metrics-grid { grid-template-columns: 1fr; } input { min-width: 0; width: 100%; } .switch input { width: auto; } @@ -2863,7 +2866,7 @@ const INDEX_HTML: &str = r#"<!doctype html> .block-card { flex-basis: 108px; } } </style> - <script defer src="/assets/iuna-ui.js?v=74"></script> + <script defer src="/assets/iuna-ui.js?v=75"></script> <script defer src="/assets/alpine.min.js"></script> </head> <body x-data="iunaApp()" x-init="init()" @keydown.window.escape="closeModals()" x-cloak> @@ -3617,6 +3620,18 @@ const INDEX_HTML: &str = r#"<!doctype html> </div> <div class="setup-feedback" :class="setupFeedback?.kind" x-show="setupFeedback" x-transition x-text="setupFeedback?.message"></div> <div class="setup-grid"> + <div class="setup-section setup-node-mode"> + <div class="panel-head"> + <h3>Mode</h3> + <span class="pill">Change later in Settings</span> + </div> + <div class="segmented setup-mode-picker" role="tablist" aria-label="Initial node mode"> + <button type="button" :class="{ active: setupNodeMode === 'wallet' }" @click="selectSetupNodeMode('wallet')">Wallet</button> + <button type="button" :class="{ active: setupNodeMode === 'non-listening' }" @click="selectSetupNodeMode('non-listening')">Non-listening node</button> + <button type="button" :class="{ active: setupNodeMode === 'listening' }" @click="selectSetupNodeMode('listening')">Listening node</button> + </div> + <div class="setup-network-copy" x-text="setupNodeModeCopy()"></div> + </div> <div class="setup-section setup-network"> <div class="panel-head"> <h3>Network</h3> @@ -4742,7 +4757,7 @@ mod tests { #[test] fn metrics_screen_includes_block_range_filter() { - assert!(super::INDEX_HTML.contains("iuna-ui.js?v=74")); + assert!(super::INDEX_HTML.contains("iuna-ui.js?v=75")); assert!(super::INDEX_HTML.contains("aria-label=\"Metrics block range\"")); assert!(super::INDEX_HTML.contains("setMetricsRange(100)")); assert!(super::INDEX_HTML.contains("setMetricsRange(1000)")); @@ -4750,6 +4765,15 @@ mod tests { } #[test] + fn initial_setup_includes_node_mode_choices() { + assert!(super::INDEX_HTML.contains("aria-label=\"Initial node mode\"")); + assert!(super::INDEX_HTML.contains("selectSetupNodeMode('wallet')")); + assert!(super::INDEX_HTML.contains("selectSetupNodeMode('non-listening')")); + assert!(super::INDEX_HTML.contains("selectSetupNodeMode('listening')")); + assert!(super::INDEX_HTML.contains("Change later in Settings")); + } + + #[test] fn setup_completion_refreshes_chain_data() { let app_js = include_str!("../../www/assets/iuna-ui.js"); assert!(app_js.contains("await this.refresh();\n this.setupFeedback = null;")); @@ -4759,6 +4783,16 @@ mod tests { } #[test] + fn setup_completion_applies_selected_node_mode() { + let app_js = include_str!("../../www/assets/iuna-ui.js"); + assert!(app_js.contains("setupNodeMode: \"wallet\"")); + assert!(app_js.contains("async applySetupNodeMode()")); + assert!(app_js.contains("await this.applySetupNodeMode();")); + assert!(app_js.contains("\"/api/settings/p2p-inbound\"")); + assert!(app_js.contains("this.setUiMode(mode === \"wallet\" ? \"basic\" : \"advanced\")")); + } + + #[test] fn polling_refreshes_paged_datasets_without_visible_loaders() { let app_js = include_str!("../../www/assets/iuna-ui.js"); assert!(app_js.contains("setInterval(() => this.refresh({ silent: true }), 5000)")); diff --git a/www/assets/iuna-ui.js b/www/assets/iuna-ui.js @@ -52,6 +52,7 @@ window.iunaApp = function iunaApp() { p2pAnnounceAddr: "", p2pAnnounceDirty: false, setupWallet: { address: null, seed_phrase: null, dev_verify_bypass: false, requires_peer: false }, + setupNodeMode: "wallet", setupWalletMode: "create", setupSeedStep: "write", generatedSeedPhrase: "", @@ -226,6 +227,23 @@ window.iunaApp = function iunaApp() { return this.walletVerified && (!this.setupRequiresPeer() || this.setupHasPeer()); }, + selectSetupNodeMode(mode) { + this.setupNodeMode = ["wallet", "non-listening", "listening"].includes(mode) + ? mode + : "wallet"; + this.setupFeedback = null; + }, + + setupNodeModeCopy() { + if (this.setupNodeMode === "listening") { + return "Listening node shows mining and P2P controls and accepts inbound P2P connections when TCP port 9444 is reachable."; + } + if (this.setupNodeMode === "non-listening") { + return "Non-listening node shows mining and P2P controls, connects out to peers, and keeps inbound P2P closed."; + } + return "Wallet mode keeps the interface focused on your wallet and chain, while this node only connects out to peers."; + }, + async refreshAuth() { this.auth = await this.fetchJson("/api/auth/status"); this.authLoaded = true; @@ -479,6 +497,7 @@ window.iunaApp = function iunaApp() { if (this.setupRequiresPeer() && !this.setupHasPeer()) { throw new Error("Add a bootstrap peer before continuing"); } + await this.applySetupNodeMode(); const response = await fetch("/api/config", { method: "POST", headers: { @@ -508,6 +527,18 @@ window.iunaApp = function iunaApp() { } }, + async applySetupNodeMode() { + const mode = ["wallet", "non-listening", "listening"].includes(this.setupNodeMode) + ? this.setupNodeMode + : "wallet"; + const acceptInbound = mode === "listening"; + if (this.p2pAcceptInbound !== acceptInbound) { + await this.submitForm("/api/settings/p2p-inbound", { enabled: acceptInbound }); + this.p2pAcceptInbound = acceptInbound; + } + this.setUiMode(mode === "wallet" ? "basic" : "advanced"); + }, + async refresh(options = {}) { try { const [config, status, blocks, p2pMetrics, blockchainMetrics, networkHealth] = await Promise.all([ @@ -900,6 +931,12 @@ window.iunaApp = function iunaApp() { }, async postForm(path, fields, successMessage, method = "POST") { + await this.submitForm(path, fields, method); + await this.refresh(); + this.showFlash(successMessage, "success"); + }, + + async submitForm(path, fields, method = "POST") { const body = new URLSearchParams(); for (const [key, value] of Object.entries(fields)) { if (Array.isArray(value)) { @@ -925,8 +962,7 @@ window.iunaApp = function iunaApp() { if (!response.ok || !payload.ok) { throw new Error(payload?.error || `${path} returned ${response.status}`); } - await this.refresh(); - this.showFlash(successMessage, "success"); + return payload; }, scheduleFeeEstimates() {