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
|
## 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.
|
- 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.
|
- Named the app in compact readiness messages and kept app-card actions aligned at the bottom.
|
||||||
|
|||||||
@@ -172,13 +172,11 @@ impl DockerPackageScanner {
|
|||||||
} else {
|
} else {
|
||||||
// Prefer the known web UI port over arbitrary first binding
|
// Prefer the known web UI port over arbitrary first binding
|
||||||
// (for example Gitea exposes SSH on 2222 before web on 3001).
|
// (for example Gitea exposes SSH on 2222 before web on 3001).
|
||||||
let candidate = if uses_allocated_launch_port(&app_id) {
|
let candidate = package_launch_candidate(
|
||||||
extract_lan_address(&container.ports)
|
&app_id,
|
||||||
.or_else(|| PodmanClient::lan_address_for(&app_id))
|
&container.ports,
|
||||||
} else {
|
PodmanClient::lan_address_for(&app_id),
|
||||||
PodmanClient::lan_address_for(&app_id)
|
);
|
||||||
.or_else(|| extract_lan_address(&container.ports))
|
|
||||||
};
|
|
||||||
reachable_lan_address(&app_id, candidate).await
|
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 {
|
fn uses_allocated_launch_port(app_id: &str) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
app_id,
|
app_id,
|
||||||
@@ -1189,7 +1204,42 @@ mod tor_service_name_tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod extract_lan_address_tests {
|
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]
|
#[test]
|
||||||
fn skips_ssh_port_when_web_port_is_published() {
|
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
|
`/tmp/archy-runtime-order-live-check.py` to deploy with rollback, remove the
|
||||||
candidate manifest override, and verify five normal management startups and
|
candidate manifest override, and verify five normal management startups and
|
||||||
managed Angor restarts. No new release version/tag, OTA or ISO has been created.
|
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
|
pkg.health = null
|
||||||
expect(isAppReadyForLaunch(pkg)).toBe(false)
|
expect(isAppReadyForLaunch(pkg)).toBe(false)
|
||||||
expect(canLaunch(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'
|
pkg.health = 'healthy'
|
||||||
expect(canLaunch(pkg)).toBe(true)
|
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', () => {
|
describe('HTTP readiness independent of container health', () => {
|
||||||
it('blocks fixed launch URLs while the HTTP upstream is unavailable', () => {
|
it('blocks fixed launch URLs while the HTTP upstream is unavailable', () => {
|
||||||
for (const id of ['gitea', 'filebrowser', 'fedimint', 'lnd']) {
|
for (const id of ['gitea', 'filebrowser', 'fedimint', 'lnd']) {
|
||||||
@@ -202,7 +214,7 @@ describe('HTTP readiness independent of container health', () => {
|
|||||||
pkg.health = 'healthy'
|
pkg.health = 'healthy'
|
||||||
expect(canLaunch(pkg)).toBe(false)
|
expect(canLaunch(pkg)).toBe(false)
|
||||||
expect(isAppReadyForLaunch(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
|
pkg['ui-ready'] = true
|
||||||
expect(isAppReadyForLaunch(pkg)).toBe(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 {
|
export function launchBlockedReason(id: string, pkg?: PackageDataEntry | null): string {
|
||||||
const appId = pkg?.manifest?.id || id
|
const appId = pkg?.manifest?.id || id
|
||||||
const title = pkg ? resolveAppTitle(appId, pkg) : 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?.['ui-ready'] === false && !isServicePackage(appId, pkg)) {
|
||||||
if (pkg.state === PackageState.Stopped || pkg.state === PackageState.Exited) return 'App is stopped. Start it to open it.'
|
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 (
|
if (
|
||||||
(appId === 'fedimint' || appId === 'fedimintd') &&
|
(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.'
|
return 'Guardian opens a wait page until Bitcoin finishes initial sync.'
|
||||||
}
|
}
|
||||||
if (pkg && pkg.state === PackageState.Running && !isAppReadyForLaunch(pkg)) {
|
if (pkg && pkg.state === PackageState.Running && !isAppReadyForLaunch(pkg)) {
|
||||||
return `Waiting for ${title}…`
|
return `Waiting for ${title}’s web interface…`
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user