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:
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() {