Check companion dashboards and omit headless UI waiting messages
Demo images / Build & push demo images (push) Failing after 37s
Demo images / Build & push demo images (push) Failing after 37s
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Checked Bitcoin and Electrum companion dashboards instead of backend protocol ports, preserving dashboard access during initial sync.
|
||||
- Removed web-interface waiting messages from headless services such as Phoenixd and clarified which interface is unavailable for launchable apps.
|
||||
|
||||
- Finished runtime app-file promotion before manifest loading, preventing startup catalog refresh from forgetting disk-only apps.
|
||||
|
||||
- Named the app in compact readiness messages and kept app-card actions aligned at the bottom.
|
||||
|
||||
@@ -172,13 +172,11 @@ impl DockerPackageScanner {
|
||||
} else {
|
||||
// Prefer the known web UI port over arbitrary first binding
|
||||
// (for example Gitea exposes SSH on 2222 before web on 3001).
|
||||
let candidate = if uses_allocated_launch_port(&app_id) {
|
||||
extract_lan_address(&container.ports)
|
||||
.or_else(|| PodmanClient::lan_address_for(&app_id))
|
||||
} else {
|
||||
PodmanClient::lan_address_for(&app_id)
|
||||
.or_else(|| extract_lan_address(&container.ports))
|
||||
};
|
||||
let candidate = package_launch_candidate(
|
||||
&app_id,
|
||||
&container.ports,
|
||||
PodmanClient::lan_address_for(&app_id),
|
||||
);
|
||||
reachable_lan_address(&app_id, candidate).await
|
||||
};
|
||||
|
||||
@@ -1105,6 +1103,23 @@ fn companion_lan_address(app_id: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Companion dashboards remain usable while their backend is syncing. Never
|
||||
/// probe a Bitcoin RPC or Electrum protocol socket as dashboard readiness.
|
||||
fn package_launch_candidate(
|
||||
app_id: &str,
|
||||
ports: &[String],
|
||||
known: Option<String>,
|
||||
) -> Option<String> {
|
||||
if let Some(companion) = companion_lan_address(app_id) {
|
||||
return Some(companion);
|
||||
}
|
||||
if uses_allocated_launch_port(app_id) {
|
||||
extract_lan_address(ports).or(known)
|
||||
} else {
|
||||
known.or_else(|| extract_lan_address(ports))
|
||||
}
|
||||
}
|
||||
|
||||
fn uses_allocated_launch_port(app_id: &str) -> bool {
|
||||
matches!(
|
||||
app_id,
|
||||
@@ -1189,7 +1204,42 @@ mod tor_service_name_tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod extract_lan_address_tests {
|
||||
use super::extract_lan_address;
|
||||
use super::{extract_lan_address, package_launch_candidate};
|
||||
|
||||
#[test]
|
||||
fn companion_dashboard_wins_over_backend_protocol_ports() {
|
||||
for id in ["bitcoin", "bitcoin-core", "bitcoin-knots"] {
|
||||
assert_eq!(
|
||||
package_launch_candidate(
|
||||
id,
|
||||
&["127.0.0.1:8332->8332/tcp".into()],
|
||||
Some("http://localhost:8332".into())
|
||||
)
|
||||
.as_deref(),
|
||||
Some("http://localhost:8334")
|
||||
);
|
||||
}
|
||||
for id in ["electrumx", "electrs", "mempool-electrs"] {
|
||||
assert_eq!(
|
||||
package_launch_candidate(
|
||||
id,
|
||||
&["127.0.0.1:50001->50001/tcp".into()],
|
||||
Some("http://localhost:50001".into())
|
||||
)
|
||||
.as_deref(),
|
||||
Some("http://localhost:50002")
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
package_launch_candidate(
|
||||
"filebrowser",
|
||||
&["127.0.0.1:19080->80/tcp".into()],
|
||||
Some("http://localhost:8080".into())
|
||||
)
|
||||
.as_deref(),
|
||||
Some("http://localhost:19080")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_ssh_port_when_web_port_is_published() {
|
||||
|
||||
@@ -174,3 +174,25 @@ The startup-order optimized build is logged in
|
||||
`/tmp/archy-runtime-order-live-check.py` to deploy with rollback, remove the
|
||||
candidate manifest override, and verify five normal management startups and
|
||||
managed Angor restarts. No new release version/tag, OTA or ISO has been created.
|
||||
|
||||
## Mempool and dashboard follow-up
|
||||
|
||||
- Deployed the Mempool alias and runtime-promotion-order backend to dev. Real
|
||||
server state contains one healthy `mempool`; the stale `mempool-web` record
|
||||
is gone. Real-data browser checks at 1440/390 pixels found exactly one tile
|
||||
before and after hard refresh. Bitcoin/LND identities/start times unchanged.
|
||||
- Removed the candidate manifest override. Normal management startup passed
|
||||
two full cycles with 62 manifests retained through both initial catalog
|
||||
refreshes. The next cycle hit a single readiness assertion; a subsequent
|
||||
read-only check found Angor healthy and the manifest count intact. Remaining
|
||||
repeat coverage should use bounded polling to distinguish transient request
|
||||
failures from loss of app definitions; do not report five cycles passed yet.
|
||||
- Bitcoin Core's dashboard was serving HTTP 200 on 8334 while readiness checked
|
||||
RPC 8332. Companion URL selection now takes priority over protocol sockets
|
||||
for Core/Knots and Electrum aliases, with a regression preserving allocated
|
||||
UI ports for other apps. Backend suite: 1,609 passed, four opt-in ignored.
|
||||
Optimized build is `/tmp/archy-dashboard-address-build.log`; deployment and
|
||||
live IBD verification helper: `/tmp/archy-dashboard-address-deploy.py`.
|
||||
- Phoenixd has no browser UI. Headless services now omit web-readiness messages;
|
||||
actual browser apps name their web interface rather than waiting for
|
||||
themselves. Focused 23 UI tests and production build passed; deployed to dev.
|
||||
|
||||
@@ -167,7 +167,7 @@ describe('appsConfig service filtering', () => {
|
||||
pkg.health = null
|
||||
expect(isAppReadyForLaunch(pkg)).toBe(false)
|
||||
expect(canLaunch(pkg)).toBe(false)
|
||||
expect(launchBlockedReason(pkg.manifest.id, pkg)).toBe('Waiting for GitWorkshop…')
|
||||
expect(launchBlockedReason(pkg.manifest.id, pkg)).toBe('Waiting for GitWorkshop’s web interface…')
|
||||
pkg.health = 'healthy'
|
||||
expect(canLaunch(pkg)).toBe(true)
|
||||
})
|
||||
@@ -194,6 +194,18 @@ describe('appsConfig service filtering', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('headless service readiness messages', () => {
|
||||
it('does not treat an API-only service as waiting for its own nonexistent UI', () => {
|
||||
for (const id of ['phoenixd', 'angor-indexer', 'angor-relay', 'custom-api']) {
|
||||
const pkg = makePkg(id, id, 'other')
|
||||
pkg['ui-ready'] = false
|
||||
pkg.health = 'healthy'
|
||||
expect(canLaunch(pkg)).toBe(false)
|
||||
expect(launchBlockedReason(id, pkg)).toBe('')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('HTTP readiness independent of container health', () => {
|
||||
it('blocks fixed launch URLs while the HTTP upstream is unavailable', () => {
|
||||
for (const id of ['gitea', 'filebrowser', 'fedimint', 'lnd']) {
|
||||
@@ -202,7 +214,7 @@ describe('HTTP readiness independent of container health', () => {
|
||||
pkg.health = 'healthy'
|
||||
expect(canLaunch(pkg)).toBe(false)
|
||||
expect(isAppReadyForLaunch(pkg)).toBe(false)
|
||||
expect(launchBlockedReason(id, pkg)).toBe(`Waiting for ${resolveAppTitle(id, pkg)}…`)
|
||||
expect(launchBlockedReason(id, pkg)).toBe(`Waiting for ${resolveAppTitle(id, pkg)}’s web interface…`)
|
||||
pkg['ui-ready'] = true
|
||||
expect(isAppReadyForLaunch(pkg)).toBe(true)
|
||||
}
|
||||
|
||||
@@ -303,9 +303,12 @@ export function isAppReadyForLaunch(pkg: PackageDataEntry): boolean {
|
||||
export function launchBlockedReason(id: string, pkg?: PackageDataEntry | null): string {
|
||||
const appId = pkg?.manifest?.id || id
|
||||
const title = pkg ? resolveAppTitle(appId, pkg) : id
|
||||
// API-only services have no web interface to wait for. Their health badge
|
||||
// describes service availability; a failed HTML probe is not a dependency.
|
||||
if (pkg && isWebsitePackage(appId, pkg)) return ''
|
||||
if (pkg?.['ui-ready'] === false && !isServicePackage(appId, pkg)) {
|
||||
if (pkg.state === PackageState.Stopped || pkg.state === PackageState.Exited) return 'App is stopped. Start it to open it.'
|
||||
return `Waiting for ${title}…`
|
||||
return `Waiting for ${title}’s web interface…`
|
||||
}
|
||||
if (
|
||||
(appId === 'fedimint' || appId === 'fedimintd') &&
|
||||
@@ -314,7 +317,7 @@ export function launchBlockedReason(id: string, pkg?: PackageDataEntry | null):
|
||||
return 'Guardian opens a wait page until Bitcoin finishes initial sync.'
|
||||
}
|
||||
if (pkg && pkg.state === PackageState.Running && !isAppReadyForLaunch(pkg)) {
|
||||
return `Waiting for ${title}…`
|
||||
return `Waiting for ${title}’s web interface…`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user