From a0f70b3949f75405404820ccb5e06070a0964b4b Mon Sep 17 00:00:00 2001 From: archipelago Date: Mon, 22 Jun 2026 13:55:50 -0400 Subject: [PATCH] feat(demo): black-theme app UIs w/ icons, real ElectrumX UI, Core/Knots split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mock app UIs (ElectrumX, LND, Fedimint, Bitcoin Core) + the "Not available" notice now use the Archipelago black theme and show the app's My-Apps icon. - Bitcoin Core gets its own UI (/app/bitcoin-core/) so it no longer shows Bitcoin Knots branding; the Knots-branded bitcoin-ui shell is reserved for Bitcoin Knots. - ElectrumX now serves the real electrs-ui shell (+ qrcode.js + a dummy /electrs-status) with the correct ElectrumX icon; "Electrs" renamed to ElectrumX. - My Apps: pre-install Bitcoin Knots again, drop ThunderHub, rename Electrs→ElectrumX. - App store no longer shows "Checking…" forever in demo — non-demoable apps show "No demo" immediately (skip the container-scan state). - Relay endpoint no longer reveals a real domain (randomised host). - Dockerfile/.dockerignore copy docker/electrs-ui into the backend image. Co-Authored-By: Claude Opus 4.8 (1M context) --- .dockerignore | 3 +- neode-ui/Dockerfile.backend | 1 + neode-ui/docker/nginx-demo.conf | 8 + neode-ui/mock-backend.js | 152 ++++++++++++------- neode-ui/src/composables/useDemoIntro.ts | 4 +- neode-ui/src/views/discover/AppGrid.vue | 4 +- neode-ui/src/views/discover/FeaturedApps.vue | 2 +- neode-ui/vite.config.ts | 2 + 8 files changed, 113 insertions(+), 63 deletions(-) diff --git a/.dockerignore b/.dockerignore index 37fa8d33..32d336cb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,10 +7,11 @@ # Allow demo assets (AIUI pre-built dist) !demo/ -# Allow the Bitcoin UI mock shell (backend serves it from /docker/bitcoin-ui) +# Allow the Bitcoin UI + ElectrumX UI mock shells (served from /docker/*) !docker/ docker/* !docker/bitcoin-ui/ +!docker/electrs-ui/ # Allow backend source for ISO source builds !core/ diff --git a/neode-ui/Dockerfile.backend b/neode-ui/Dockerfile.backend index fde87eb3..3b4af25b 100644 --- a/neode-ui/Dockerfile.backend +++ b/neode-ui/Dockerfile.backend @@ -17,6 +17,7 @@ COPY neode-ui/ ./ # Sibling assets the mock backend reads relative to /app (../docker, ../demo): # the Bitcoin UI mock shell and any curated cloud files dropped into demo/files. COPY docker/bitcoin-ui /docker/bitcoin-ui +COPY docker/electrs-ui /docker/electrs-ui COPY demo/files /demo/files # Expose port diff --git a/neode-ui/docker/nginx-demo.conf b/neode-ui/docker/nginx-demo.conf index f8ec0819..ba042f16 100644 --- a/neode-ui/docker/nginx-demo.conf +++ b/neode-ui/docker/nginx-demo.conf @@ -62,6 +62,14 @@ http { proxy_set_header X-Real-IP $remote_addr; } + # ElectrumX UI status (polled by the electrs-ui shell) + location /electrs-status { + proxy_pass http://neode-backend:5959; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + # Proxy FileBrowser API to mock backend (demo mode) location /app/filebrowser/ { client_max_body_size 10G; diff --git a/neode-ui/mock-backend.js b/neode-ui/mock-backend.js index 2a4a58a7..06e8bd45 100755 --- a/neode-ui/mock-backend.js +++ b/neode-ui/mock-backend.js @@ -150,7 +150,7 @@ const SEED_BTCRELAY = { allow_tor: true, selected_peer_pubkey: '03d9b8a8db6b4f4d8b8d04c7a467c101f04c0ecbabc0e29e4dcb812a3b1c5f8f04', http_endpoint: '', - https_endpoint: 'https://shard.tx1138.com/', + https_endpoint: 'https://relay-' + randomHex(5) + '.example.net/', tor_endpoint: 'http://btc-relay-demoabcdefghijklmnop.onion/', }, trusted_nodes: [ @@ -758,19 +758,27 @@ function staticApp({ id, title, version, shortDesc, longDesc, license, state, la } // Static dev apps (always shown in My Apps when using mock backend). -// Only one Bitcoin implementation is pre-installed (Bitcoin Core); Bitcoin Knots -// remains installable from the marketplace. const staticDevApps = { bitcoin: staticApp({ id: 'bitcoin', title: 'Bitcoin Core', - version: '27.1', + version: '28.4.0', shortDesc: 'Full Bitcoin node', longDesc: 'Validate every transaction and block. Full consensus enforcement — the bedrock of sovereignty.', state: 'running', lanPort: 8332, icon: '/assets/img/app-icons/bitcoin-core.png', }), + 'bitcoin-knots': staticApp({ + id: 'bitcoin-knots', + title: 'Bitcoin Knots', + version: '28.1.0', + shortDesc: 'Full Bitcoin node', + longDesc: 'Validate and relay Bitcoin blocks and transactions with the Archipelago custom node UI.', + state: 'running', + lanPort: 8334, + icon: '/assets/img/app-icons/bitcoin-knots.webp', + }), lnd: staticApp({ id: 'lnd', title: 'LND', @@ -779,15 +787,17 @@ const staticDevApps = { longDesc: 'Instant Bitcoin payments with near-zero fees. Open channels, route payments, earn sats.', state: 'running', lanPort: 8080, + icon: '/assets/img/app-icons/lnd.svg', }), - electrs: staticApp({ - id: 'electrs', - title: 'Electrs', - version: '0.10.6', - shortDesc: 'Electrum Server in Rust', + electrumx: staticApp({ + id: 'electrumx', + title: 'ElectrumX', + version: '1.18.0', + shortDesc: 'Electrum server', longDesc: 'Private blockchain indexing for wallet lookups. Connect Sparrow, BlueWallet, or any Electrum-compatible wallet.', state: 'running', - lanPort: 50001, + lanPort: 50002, + icon: '/assets/img/app-icons/electrumx.webp', }), mempool: staticApp({ id: 'mempool', @@ -829,15 +839,6 @@ const staticDevApps = { lanPort: 8083, icon: '/assets/img/app-icons/file-browser.webp', }), - thunderhub: staticApp({ - id: 'thunderhub', - title: 'ThunderHub', - version: '0.13.31', - shortDesc: 'Lightning node management UI', - longDesc: 'Full Lightning node management — channels, payments, routing fees, and node health. Connect to your LND and manage everything from one dashboard.', - state: 'running', - lanPort: 3010, - }), fedimint: staticApp({ id: 'fedimint', title: 'Fedimint', @@ -979,31 +980,38 @@ app.get('/app/bitcoin-ui/', async (_req, res) => { }) // ── Mock app UIs served in the in-app iframe (DEMO) ───────────────────────── -function demoAppShell(title, accent, bodyHtml) { +function demoAppShell(title, sub, iconPath, bodyHtml) { + const accent = '#f7931a' // Archipelago orange return ` ${title}
${bodyHtml}
-
Archipelago demo · signet
` + th{color:#8a8f9a;font-weight:500;font-size:11px;text-transform:uppercase} + .demo-tag{position:fixed;bottom:14px;right:16px;font-size:11px;color:#5b6070} +
+
+ +

${title}

${sub}
+
+ ${bodyHtml} +
Archipelago demo · signet
` } // 12 trusted/federated nodes for the demo Federation view. @@ -1047,29 +1055,40 @@ function demoFederationNodes() { }) } -app.get(['/app/electrumx/', '/app/electrs/', '/app/archy-electrs-ui/'], (_req, res) => { - res.type('html').send(demoAppShell('Electrs — Electrum Server', '#22d3ee', ` -

Electrs

Electrum server in Rust · signet
-
-
Status
● Serving clients
-
Indexed height
902,418
-
RPC port
50002 (SSL)
-
Connected wallets
3
-
Mempool txs
12,840
-
DB size
58.2 GB
-
-
Recent client subscriptions
- - - - -
WalletAddress typeSubscribed
SparrowP2WPKH2 min ago
BlueWalletP2TR9 min ago
ElectrumP2WPKH21 min ago
-
`)) +// ElectrumX: serve the real electrs-ui shell (its qrcode.js sibling + the +// /electrs-status endpoint it polls are served below with dummy data). +app.get(['/app/electrumx/', '/app/electrs/', '/app/archy-electrs-ui/'], async (_req, res) => { + try { + const html = await fs.readFile(path.join(__dirname, '..', 'docker', 'electrs-ui', 'index.html'), 'utf8') + res.type('html').send(html) + } catch (error) { + res.status(500).type('text/plain').send(`Unable to load ElectrumX UI mock: ${error.message}`) + } +}) +app.get(['/app/electrumx/qrcode.js', '/app/electrs/qrcode.js', '/app/archy-electrs-ui/qrcode.js'], async (_req, res) => { + try { + const js = await fs.readFile(path.join(__dirname, '..', 'docker', 'electrs-ui', 'qrcode.js'), 'utf8') + res.type('application/javascript').send(js) + } catch { res.status(404).end() } +}) +// Dummy status for both the electrs-ui shell and the in-app ElectrumX sync screen. +app.get('/electrs-status', (_req, res) => { + res.json({ + status: 'ready', + synced: true, + stale: false, + error: null, + bitcoin_height: 902418, + network_height: 902418, + indexed_height: 902418, + progress_pct: 100, + index_size: '58.2 GB', + tor_onion: 'electrumx' + randomHex(20) + '.onion', + }) }) app.get(['/app/lnd/', '/app/lnd-ui/', '/app/archy-lnd-ui/', '/app/thunderhub/'], (_req, res) => { - res.type('html').send(demoAppShell('LND — Lightning', '#f7931a', ` -

LND

Lightning Network Daemon · signet
+ res.type('html').send(demoAppShell('LND', 'Lightning Network Daemon · signet', '/assets/img/app-icons/lnd.svg', `
Node
● Synced to chain
Channel balance
8,250,000 sat
@@ -1088,9 +1107,27 @@ app.get(['/app/lnd/', '/app/lnd-ui/', '/app/archy-lnd-ui/', '/app/thunderhub/'],
`)) }) +app.get(['/app/bitcoin-core/'], (_req, res) => { + res.type('html').send(demoAppShell('Bitcoin Core', 'Full node · signet', '/assets/img/app-icons/bitcoin-core.png', ` +
+
Status
● Synced
+
Block height
902,418
+
Chain
signet
+
Connections
18
+
Mempool
12,840 txs
+
Version
v28.4.0
+
+
Peers
+ + + + +
AddressTypeHeight
node1.signet…onionoutbound902,418
node2.signet…onionoutbound902,418
192.168.1.42inbound902,417
+
`)) +}) + app.get(['/app/fedimint/', '/app/fedimintd/'], (_req, res) => { - res.type('html').send(demoAppShell('Fedimint — Guardian', '#a78bfa', ` -

Fedimint Guardian

Archipelago Federation · 4-of-5 consensus
+ res.type('html').send(demoAppShell('Fedimint Guardian', 'Archipelago Federation · 4-of-5 consensus', '/assets/img/app-icons/fedimint.png', `
Consensus
● Online
Epoch
48,217
@@ -4065,12 +4102,13 @@ app.get(['/app/:id', '/app/:id/'], (req, res) => { res.type('html').send(` ${title}

${title}

diff --git a/neode-ui/src/composables/useDemoIntro.ts b/neode-ui/src/composables/useDemoIntro.ts index 26c65f8b..eeb5c022 100644 --- a/neode-ui/src/composables/useDemoIntro.ts +++ b/neode-ui/src/composables/useDemoIntro.ts @@ -62,8 +62,8 @@ const DEMO_EXTERNAL_URLS: Record = { // Apps with a same-origin mock UI served by the demo backend. const DEMO_MOCK_UI: Record = { 'bitcoin-knots': '/app/bitcoin-ui/', - 'bitcoin-core': '/app/bitcoin-ui/', - bitcoin: '/app/bitcoin-ui/', + 'bitcoin-core': '/app/bitcoin-core/', + bitcoin: '/app/bitcoin-core/', 'bitcoin-ui': '/app/bitcoin-ui/', electrs: '/app/electrumx/', electrumx: '/app/electrumx/', diff --git a/neode-ui/src/views/discover/AppGrid.vue b/neode-ui/src/views/discover/AppGrid.vue index 23e5d7f4..032c528d 100644 --- a/neode-ui/src/views/discover/AppGrid.vue +++ b/neode-ui/src/views/discover/AppGrid.vue @@ -102,9 +102,9 @@ @click.stop="$emit('launch', app)" class="px-4 py-2 glass-button glass-button-sm rounded-lg text-sm font-medium" >Launch - + diff --git a/neode-ui/src/views/discover/FeaturedApps.vue b/neode-ui/src/views/discover/FeaturedApps.vue index aabf7ba8..9738d105 100644 --- a/neode-ui/src/views/discover/FeaturedApps.vue +++ b/neode-ui/src/views/discover/FeaturedApps.vue @@ -64,7 +64,7 @@ Starting...