fix(lnd-ui,bitcoin-ui): OTA-breaking lnd-ui spec, 404 channels link, iframe copy, node URI
All four found by verifying on archi-dev-box rather than assuming.
container-specs.sh: archy-lnd-ui was specified as a BRIDGE container with
SPEC_PORTS="18083:80", but docker/lnd-ui/nginx.conf listens on 18083
directly (it must, to proxy the backend on 127.0.0.1:5678 same-origin).
Recreating from that spec publishes host 18083 to container port 80, where
nothing listens. Reproduced on the node: the app came back with :18083
refusing connections, HTTP 000. This never fired before because the running
containers are created by first-boot-containers.sh, which is host-networked
and never reads this file; the spec is only consulted when self-update.sh
rebuilds a UI image, and that only happens when a file under docker/lnd-ui/
changes — which is exactly what the previous two commits did. So the next
OTA would have taken lnd-ui down on every node. Now SPEC_NETWORK="host"
with no port mapping, matching what actually runs. NET_BIND_SERVICE dropped
with it: 18083 is unprivileged.
lnd-ui channels link: pointed at /apps/lnd/channels, but that route is a
CHILD of the /dashboard record in neode-ui's router, so the real path is
/dashboard/apps/lnd/channels. nginx's SPA fallback returns 200 for the
wrong path, so it failed as vue-router's NotFound view rather than an HTTP
404 — both the Payment Channels card and the Manage Channels button.
Both apps, copy buttons: navigator.clipboard only exists in a secure
context, and nodes serve these apps over plain http; the main UI also
embeds them in an iframe, where the async Clipboard API is separately gated
by the clipboard-write permission policy. Every copy button silently did
nothing there. Added an execCommand('copy') fallback behind a copyText()
helper and routed all six call sites through it.
lnd-ui Node ID: showed the bare pubkey whenever getinfo.uris was empty,
which is the common case — LND only populates uris once it is advertising
an external address. The bare pubkey is not what a peer pastes to open a
channel. The full pubkey@host:9735 URI is now built from the Tor onion
where available, falling back to this node's address, with a hint saying
which and what its reachability is. The QR encodes the URI too.
Verified on archi-dev-box: both images rebuilt and containers recreated
from the specs; lnd-ui and bitcoin-ui both serve 200 with the new assets;
and the RPCs the new tabs depend on all answer on the live node —
getblockstats returns every field the charts read, getpeerinfo returns 11
peers carrying relaytxes and network values the classifier handles.
Note for whoever tests bitcoin-ui's Insights/Peers tabs: /bitcoin-rpc/ now
sits behind auth_request /_session_check (a05956c4 et al), so it answers
401 to an unauthenticated curl by design. A logged-in browser sends the
session cookie same-origin, which is how those tabs get their data.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5c9d5dc424
commit
aaa89789d2
@@ -1171,6 +1171,38 @@
|
||||
return body.result;
|
||||
}
|
||||
|
||||
// Clipboard with a fallback. navigator.clipboard only exists in a secure
|
||||
// context, and most nodes serve this app over plain http; on top of
|
||||
// that, the main UI embeds these apps in an iframe, where the async
|
||||
// Clipboard API is additionally gated by the clipboard-write permission
|
||||
// policy. execCommand('copy') still works in both situations, so it is
|
||||
// the fallback rather than letting the button silently do nothing.
|
||||
function legacyCopy(text) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.setAttribute('readonly', '');
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.top = '0';
|
||||
ta.style.left = '0';
|
||||
ta.style.opacity = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
ta.setSelectionRange(0, ta.value.length);
|
||||
const ok = document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
if (ok) resolve(); else reject(new Error('copy rejected'));
|
||||
} catch (e) { reject(e); }
|
||||
});
|
||||
}
|
||||
function copyText(text) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
return navigator.clipboard.writeText(text).catch(() => legacyCopy(text));
|
||||
}
|
||||
return legacyCopy(text);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '').replace(/[&<>"']/g, char => ({
|
||||
'&': '&',
|
||||
@@ -1652,11 +1684,11 @@
|
||||
function copyRPCInfo() {
|
||||
// No password here. It is a manifest-declared generated secret that
|
||||
// the orchestrator renders straight into this app's nginx upstream;
|
||||
// the browser never receives it. The old version of this function
|
||||
// pasted a hardcoded "archipelago123", which was not the real
|
||||
// credential and only ever misled whoever copied it.
|
||||
// the browser never receives it. This function used to paste a
|
||||
// hardcoded placeholder that was not the real credential and only
|
||||
// ever misled whoever copied it.
|
||||
const info = `RPC Host: ${window.location.hostname}:8332\nRPC User: archipelago\nRPC Password: (held in the node secret store)\nRPC Endpoint: ${RPC_ENDPOINT}`;
|
||||
navigator.clipboard.writeText(info).then(() => {
|
||||
copyText(info).then(() => {
|
||||
alert('RPC info copied to clipboard!');
|
||||
});
|
||||
}
|
||||
@@ -1986,7 +2018,7 @@ RPC server active on port 8332`;
|
||||
function copyEl(id, btn) {
|
||||
const text = document.getElementById(id)?.textContent.trim();
|
||||
if (!text || text === '-') return;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
copyText(text).then(() => {
|
||||
const orig = btn.innerHTML;
|
||||
btn.innerHTML = '<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>';
|
||||
btn.style.color = '#4ade80';
|
||||
|
||||
Reference in New Issue
Block a user