fix(demo): /app proxy (fixes 404s), mempool iframe, LND UI, icons

- nginx-demo.conf + vite proxy now route every /app/<id>/ to the mock backend, so
  the per-app mock UIs and the generic "Not available in the demo" notice render
  (previously only /app/filebrowser was proxied → most apps 404'd).
- Mempool and IndeeHub now load in the in-app iframe (not a new tab).
- Add an LND Lightning mock UI (channels, balances, routing) with dummy data;
  lnd/thunderhub are demoable. Notice page reworded to "Not available in the demo".
- Fix missing icons: Bitcoin Core → bitcoin-core.png, Mempool → mempool.webp.
- Pre-install only Bitcoin Core (drop duplicate Bitcoin Knots; still installable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-06-22 12:39:33 -04:00
parent c9341baa35
commit 4cc808c73e
4 changed files with 54 additions and 17 deletions

View File

@ -72,6 +72,16 @@ http {
proxy_request_buffering off;
}
# Proxy every other app UI (/app/<id>/) to the mock backend, which serves
# the per-app mock UIs (bitcoin-ui, electrumx, lnd, fedimint) and the
# generic "Not available in the demo" notice for the rest.
location /app/ {
proxy_pass http://neode-backend:5959;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Serve AIUI SPA
location /aiui/ {
alias /usr/share/nginx/html/aiui/;

View File

@ -757,18 +757,10 @@ function staticApp({ id, title, version, shortDesc, longDesc, license, state, la
}
}
// Static dev apps (always shown in My Apps when using mock backend)
// 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-knots': staticApp({
id: 'bitcoin-knots',
title: 'Bitcoin Knots',
version: '27.1',
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',
}),
bitcoin: staticApp({
id: 'bitcoin',
title: 'Bitcoin Core',
@ -777,6 +769,7 @@ const staticDevApps = {
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',
}),
lnd: staticApp({
id: 'lnd',
@ -805,6 +798,7 @@ const staticDevApps = {
license: 'AGPL-3.0',
state: 'running',
lanPort: 4080,
icon: '/assets/img/app-icons/mempool.webp',
}),
lorabell: staticApp({
id: 'lorabell',
@ -1073,6 +1067,27 @@ app.get(['/app/electrumx/', '/app/electrs/', '/app/archy-electrs-ui/'], (_req, r
</div>`))
})
app.get(['/app/lnd/', '/app/lnd-ui/', '/app/archy-lnd-ui/', '/app/thunderhub/'], (_req, res) => {
res.type('html').send(demoAppShell('LND — Lightning', '#f7931a', `
<div class="hd"><span class="dot"></span><div><h1>LND</h1><div class="sub">Lightning Network Daemon · signet</div></div></div>
<div class="grid">
<div class="card"><div class="k">Node</div><div class="v"><span class="badge"> Synced to chain</span></div></div>
<div class="card"><div class="k">Channel balance</div><div class="v">8,250,000 sat</div></div>
<div class="card"><div class="k">On-chain balance</div><div class="v">2,350,000 sat</div></div>
<div class="card"><div class="k">Active channels</div><div class="v">4</div></div>
<div class="card"><div class="k">Peers</div><div class="v">11</div></div>
<div class="card"><div class="k">Routing (30d)</div><div class="v">1,284 sat</div></div>
</div>
<div class="card"><div class="k" style="margin-bottom:8px">Channels</div>
<table><thead><tr><th>Peer</th><th>Capacity</th><th>Local / Remote</th><th>State</th></tr></thead><tbody>
<tr><td>ACINQ</td><td>5,000,000</td><td>2,450,000 / 2,550,000</td><td><span class="badge">active</span></td></tr>
<tr><td>Wallet of Satoshi</td><td>2,000,000</td><td>1,200,000 / 800,000</td><td><span class="badge">active</span></td></tr>
<tr><td>Voltage</td><td>10,000,000</td><td>4,500,000 / 5,500,000</td><td><span class="badge">active</span></td></tr>
<tr><td>Kraken</td><td>3,000,000</td><td>1,800,000 / 1,200,000</td><td><span class="badge">active</span></td></tr>
</tbody></table>
</div>`))
})
app.get(['/app/fedimint/', '/app/fedimintd/'], (_req, res) => {
res.type('html').send(demoAppShell('Fedimint — Guardian', '#a78bfa', `
<div class="hd"><span class="dot"></span><div><h1>Fedimint Guardian</h1><div class="sub">Archipelago Federation · 4-of-5 consensus</div></div></div>
@ -4059,8 +4074,8 @@ p{color:#8b93a7;font-size:14px;line-height:1.5}
</style></head><body><div class="card">
<img src="/assets/img/app-icons/${id}.png" onerror="this.onerror=null;this.src='/assets/icon/favico-black.svg'" alt="">
<h1>${title}</h1>
<p>This app isn't interactive in the demo, but it runs fully on a real Archipelago node.</p>
<div class="tag">Demo preview</div>
<p>Not available in the demo.<br>This app runs fully on a real Archipelago node.</p>
<div class="tag">Demo</div>
</div></body></html>`)
})

View File

@ -53,7 +53,7 @@ export function clearDemoIntroSeen(): void {
// external site). Everything else shows "No demo" on a disabled install button
// and is not launchable.
const DEMO_EXTERNAL_URLS: Record<string, string> = {
// Real, public sites that we open instead of mocking.
// Real, public sites loaded in the in-app iframe.
indeedhub: 'https://indee.tx1138.com/',
mempool: 'https://mempool.space/testnet',
'mempool-web': 'https://mempool.space/testnet',
@ -68,14 +68,21 @@ const DEMO_MOCK_UI: Record<string, string> = {
electrs: '/app/electrumx/',
electrumx: '/app/electrumx/',
'archy-electrs-ui': '/app/electrumx/',
lnd: '/app/lnd/',
'lnd-ui': '/app/lnd/',
'archy-lnd-ui': '/app/lnd/',
thunderhub: '/app/lnd/',
fedimint: '/app/fedimint/',
fedimintd: '/app/fedimint/',
filebrowser: '/app/filebrowser/',
}
/** Apps that open in a new tab (external real sites) rather than an iframe. */
export function isDemoExternal(appId: string): boolean {
return appId === 'mempool' || appId === 'mempool-web'
/**
* Whether a demo app opens in a new tab. Nothing does now both real external
* sites (mempool.space, indee.tx1138.com) load in the in-app iframe.
*/
export function isDemoExternal(_appId: string): boolean {
return false
}
/** Can this app be launched/installed in the demo? */

View File

@ -151,6 +151,11 @@ export default defineConfig({
changeOrigin: true,
secure: false,
},
// Demo mock app UIs (electrumx, lnd, fedimint) + generic notice page.
'/app/electrumx': { target: process.env.BACKEND_URL || 'http://localhost:5959', changeOrigin: true, secure: false },
'/app/electrs': { target: process.env.BACKEND_URL || 'http://localhost:5959', changeOrigin: true, secure: false },
'/app/lnd': { target: process.env.BACKEND_URL || 'http://localhost:5959', changeOrigin: true, secure: false },
'/app/fedimint': { target: process.env.BACKEND_URL || 'http://localhost:5959', changeOrigin: true, secure: false },
// Serve the node's deployed AIUI same-origin like production (set VITE_AIUI_URL=/aiui/)
'/aiui': {
target: process.env.AIUI_PROXY_TARGET || 'http://127.0.0.1:80',