Name waiting apps, align card actions, and update Angor icon
Demo images / Build & push demo images (push) Failing after 39s
Demo images / Build & push demo images (push) Failing after 39s
This commit is contained in:
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Named the app in compact readiness messages and kept app-card actions aligned at the bottom.
|
||||||
|
- Removed duplicate Mempool cards caused by frontend container aliases in restored inventory.
|
||||||
|
|
||||||
- Kept installed apps visible through restarts and hard refreshes, and delayed app launches until their web interface is ready.
|
- Kept installed apps visible through restarts and hard refreshes, and delayed app launches until their web interface is ready.
|
||||||
- Made Bitcoin version selection readable and usable in the ThinkPad kiosk, above the pruning settings.
|
- Made Bitcoin version selection readable and usable in the ThinkPad kiosk, above the pruning settings.
|
||||||
- Restored GitWorkshop build files in installation/update payloads and made slow image-pull progress clearer.
|
- Restored GitWorkshop build files in installation/update payloads and made slow image-pull progress clearer.
|
||||||
|
|||||||
@@ -658,7 +658,7 @@
|
|||||||
],
|
],
|
||||||
"category": "money",
|
"category": "money",
|
||||||
"tier": "optional",
|
"tier": "optional",
|
||||||
"icon": "/assets/img/app-icons/angor.svg",
|
"icon": "/assets/img/app-icons/angor-green.png",
|
||||||
"repoUrl": "https://github.com/block-core/angor"
|
"repoUrl": "https://github.com/block-core/angor"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -671,7 +671,7 @@
|
|||||||
"requires": [],
|
"requires": [],
|
||||||
"category": "nostr",
|
"category": "nostr",
|
||||||
"tier": "optional",
|
"tier": "optional",
|
||||||
"icon": "/assets/img/app-icons/angor.svg",
|
"icon": "/assets/img/app-icons/angor-green.png",
|
||||||
"repoUrl": "https://github.com/hoytech/strfry"
|
"repoUrl": "https://github.com/hoytech/strfry"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ Mempool recreation does not require editing IP addresses or restarting this app.
|
|||||||
No app-specific Rust installer is required.
|
No app-specific Rust installer is required.
|
||||||
|
|
||||||
Source documentation: [Angor's official deployment guide](https://github.com/block-core/angor/blob/869dd43cf38332dd7128a284a6bf4c1cac44c1a7/docker/DEPLOY-INDEXER-AND-RELAY.md).
|
Source documentation: [Angor's official deployment guide](https://github.com/block-core/angor/blob/869dd43cf38332dd7128a284a6bf4c1cac44c1a7/docker/DEPLOY-INDEXER-AND-RELAY.md).
|
||||||
The unmodified icon comes from [angor.io/images/logo-text.svg](https://angor.io/images/logo-text.svg), retrieved 2026-09-30.
|
The app icon is based on [Angor’s dark-mode app icon](https://angor.io/images/app-icon-dark-mode.png), retrieved 2026-09-30. At the operator’s request, the outer corners use the same green as the background. The built-in imagegen edit preserved the black mark and filled the square green; the project asset is `neode-ui/public/assets/img/app-icons/angor-green.png`.
|
||||||
|
|
||||||
Tests and release acceptance are recorded in the next-release checklist. The
|
Tests and release acceptance are recorded in the next-release checklist. The
|
||||||
health probe establishes backend availability, not a guarantee that every
|
health probe establishes backend availability, not a guarantee that every
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ app:
|
|||||||
sync_required: true
|
sync_required: true
|
||||||
pruning_support: false
|
pruning_support: false
|
||||||
metadata:
|
metadata:
|
||||||
icon: /assets/img/app-icons/angor.svg
|
icon: /assets/img/app-icons/angor-green.png
|
||||||
tier: optional
|
tier: optional
|
||||||
repo: https://github.com/block-core/angor
|
repo: https://github.com/block-core/angor
|
||||||
features:
|
features:
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ app:
|
|||||||
protocol: http
|
protocol: http
|
||||||
path: /
|
path: /
|
||||||
metadata:
|
metadata:
|
||||||
icon: /assets/img/app-icons/angor.svg
|
icon: /assets/img/app-icons/angor-green.png
|
||||||
tier: optional
|
tier: optional
|
||||||
repo: https://github.com/hoytech/strfry
|
repo: https://github.com/hoytech/strfry
|
||||||
features:
|
features:
|
||||||
|
|||||||
@@ -120,3 +120,25 @@ smart-HTTP response type, current branch ref and Compose contents. Its
|
|||||||
slirp4netns route and all production app containers survived. The temporary
|
slirp4netns route and all production app containers survived. The temporary
|
||||||
Portainer fixture was absent. This verifies the repaired production route
|
Portainer fixture was absent. This verifies the repaired production route
|
||||||
across reboot; it does not substitute for final new-runtime delivery checks.
|
across reboot; it does not substitute for final new-runtime delivery checks.
|
||||||
|
|
||||||
|
## Follow-up acceptance: app cards and Angor icon
|
||||||
|
|
||||||
|
- Mempool duplicate traced to `archy-mempool-web` durable inventory alias being
|
||||||
|
restored beside the real `mempool` frontend. Shared scanner canonicalization
|
||||||
|
fixes live and absent-container paths without deleting installed markers.
|
||||||
|
Frontend suppresses aliases only while a canonical tile exists.
|
||||||
|
- Readiness text names the app: “Waiting for Gitea…”. It shares the status row,
|
||||||
|
with full text available through its title; card actions use bottom alignment.
|
||||||
|
- Angor uses the operator-supplied dark-mode icon with green outer corners.
|
||||||
|
Built-in imagegen prompt: fill transparent/white corners with the existing
|
||||||
|
flat green, preserve the black symbol, square opaque PNG, no added details.
|
||||||
|
- Backend alias suite: 1608 passed, 4 ignored. Focused readiness/frame UI tests:
|
||||||
|
30 passed. Production UI build passed and is live on dev. Browser checks at
|
||||||
|
1440 and 1024 pixels verified named waiting text, bottom-aligned actions,
|
||||||
|
equal row heights, no overflow and one Mempool card after hard refresh.
|
||||||
|
The 390-pixel mobile icon layout also passed hard refresh. Final-source
|
||||||
|
isolated Mempool alias regression passed after the scanner simplification.
|
||||||
|
- Managed Angor adapter acceptance: five stop/start/restart cycles, missing
|
||||||
|
prerequisite refusal, management restart and cleanup all passed. Both temporary
|
||||||
|
fixtures and their network were removed. Actual dev API verification remains
|
||||||
|
pending after removing an incomplete legacy-created adapter.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 948 KiB |
@@ -658,7 +658,7 @@
|
|||||||
],
|
],
|
||||||
"category": "money",
|
"category": "money",
|
||||||
"tier": "optional",
|
"tier": "optional",
|
||||||
"icon": "/assets/img/app-icons/angor.svg",
|
"icon": "/assets/img/app-icons/angor-green.png",
|
||||||
"repoUrl": "https://github.com/block-core/angor"
|
"repoUrl": "https://github.com/block-core/angor"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -671,7 +671,7 @@
|
|||||||
"requires": [],
|
"requires": [],
|
||||||
"category": "nostr",
|
"category": "nostr",
|
||||||
"tier": "optional",
|
"tier": "optional",
|
||||||
"icon": "/assets/img/app-icons/angor.svg",
|
"icon": "/assets/img/app-icons/angor-green.png",
|
||||||
"repoUrl": "https://github.com/hoytech/strfry"
|
"repoUrl": "https://github.com/hoytech/strfry"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
:data-controller-launch="canLaunch(pkg) ? '' : undefined"
|
:data-controller-launch="canLaunch(pkg) ? '' : undefined"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
role="link"
|
role="link"
|
||||||
class="glass-card p-6 transition-all hover:-translate-y-1 cursor-pointer relative min-w-0 overflow-hidden"
|
class="glass-card flex flex-col h-full p-6 transition-all hover:-translate-y-1 cursor-pointer relative min-w-0 overflow-hidden"
|
||||||
:class="{ 'card-stagger': showStagger }"
|
:class="{ 'card-stagger': showStagger }"
|
||||||
:style="{ '--stagger-index': index }"
|
:style="{ '--stagger-index': index }"
|
||||||
@click="$emit('goToApp', id)"
|
@click="$emit('goToApp', id)"
|
||||||
@@ -56,9 +56,9 @@
|
|||||||
{{ description }}
|
{{ description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="!isInstalling && !isUninstalling && pkg.state !== 'installing'" class="flex items-center gap-2">
|
<div v-if="!isInstalling && !isUninstalling && pkg.state !== 'installing'" class="flex items-center gap-2 min-w-0">
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium"
|
class="shrink-0 inline-flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium"
|
||||||
:class="getStatusClass(pkg.state, pkg.health, pkg['exit-code'])"
|
:class="getStatusClass(pkg.state, pkg.health, pkg['exit-code'])"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -74,14 +74,14 @@
|
|||||||
<span v-if="pkg.state === 'running' && pkg.health === 'unhealthy'" class="w-1.5 h-1.5 rounded-full bg-orange-400 animate-pulse"></span>
|
<span v-if="pkg.state === 'running' && pkg.health === 'unhealthy'" class="w-1.5 h-1.5 rounded-full bg-orange-400 animate-pulse"></span>
|
||||||
{{ getStatusLabel(pkg.state, pkg.health, pkg['exit-code']) }}
|
{{ getStatusLabel(pkg.state, pkg.health, pkg['exit-code']) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
<p v-if="blockedReason" :title="blockedReason" class="min-w-0 truncate text-xs leading-snug text-yellow-200/80">
|
||||||
<p v-if="blockedReason" class="mt-2 text-xs leading-snug text-yellow-200/80">
|
|
||||||
{{ blockedReason }}
|
{{ blockedReason }}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Quick Actions — icon buttons in uniform dark containers -->
|
<!-- Quick Actions — icon buttons in uniform dark containers -->
|
||||||
<!-- Installing progress — replaces action buttons -->
|
<!-- Installing progress — replaces action buttons -->
|
||||||
<div v-if="isInstalling || pkg.state === 'installing'" class="mt-4">
|
<div v-if="isInstalling || pkg.state === 'installing'" class="mt-auto pt-4">
|
||||||
<div class="flex items-center justify-between mb-1.5">
|
<div class="flex items-center justify-between mb-1.5">
|
||||||
<span class="text-xs text-white/70 flex items-center gap-1.5">
|
<span class="text-xs text-white/70 flex items-center gap-1.5">
|
||||||
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
|
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Uninstalling progress — truthful stage-driven bar (mirrors install) -->
|
<!-- Uninstalling progress — truthful stage-driven bar (mirrors install) -->
|
||||||
<div v-else-if="isUninstalling" class="mt-4">
|
<div v-else-if="isUninstalling" class="mt-auto pt-4">
|
||||||
<div class="flex items-center justify-between mb-1.5">
|
<div class="flex items-center justify-between mb-1.5">
|
||||||
<span class="text-xs text-white/70 flex items-center gap-1.5">
|
<span class="text-xs text-white/70 flex items-center gap-1.5">
|
||||||
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
|
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="mt-4 flex gap-2">
|
<div v-else class="mt-auto pt-4 flex gap-2">
|
||||||
<!-- Update available -->
|
<!-- Update available -->
|
||||||
<button
|
<button
|
||||||
v-if="pkg['available-update'] && pkg.state !== 'updating'"
|
v-if="pkg['available-update'] && pkg.state !== 'updating'"
|
||||||
|
|||||||
@@ -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)).toContain('Starting up')
|
expect(launchBlockedReason(pkg.manifest.id, pkg)).toBe('Waiting for GitWorkshop…')
|
||||||
pkg.health = 'healthy'
|
pkg.health = 'healthy'
|
||||||
expect(canLaunch(pkg)).toBe(true)
|
expect(canLaunch(pkg)).toBe(true)
|
||||||
})
|
})
|
||||||
@@ -202,7 +202,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)).toContain('Waiting')
|
expect(launchBlockedReason(id, pkg)).toBe(`Waiting for ${resolveAppTitle(id, pkg)}…`)
|
||||||
pkg['ui-ready'] = true
|
pkg['ui-ready'] = true
|
||||||
expect(isAppReadyForLaunch(pkg)).toBe(true)
|
expect(isAppReadyForLaunch(pkg)).toBe(true)
|
||||||
}
|
}
|
||||||
@@ -224,3 +224,13 @@ describe('manifest-generated service names', () => {
|
|||||||
expect(resolveAppTitle('unknown-service', makePkg('unknown-service', 'unknown-service', ''))).toBe('unknown-service')
|
expect(resolveAppTitle('unknown-service', makePkg('unknown-service', 'unknown-service', ''))).toBe('unknown-service')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('does not display a stale Mempool frontend alias beside its live package', () => {
|
||||||
|
const main = makePkg('mempool', 'Mempool', 'money')
|
||||||
|
const alias = makePkg('mempool-web', 'Mempool', 'money')
|
||||||
|
alias.state = PackageState.Restarting
|
||||||
|
const entries: Array<[string, PackageDataEntry]> = [['mempool', main], ['mempool-web', alias]]
|
||||||
|
const shown = [...filterEntriesForTab(entries, 'apps', 'all'), ...filterEntriesForTab(entries, 'services', 'all')]
|
||||||
|
expect(shown.map(([id]) => id)).toEqual(['mempool'])
|
||||||
|
expect(filterEntriesForTab([['mempool-web', alias]], 'apps', 'all').map(([id]) => id)).toEqual(['mempool-web'])
|
||||||
|
})
|
||||||
|
|||||||
@@ -111,7 +111,12 @@ export function filterEntriesForTab(
|
|||||||
activeTab: AppsTab,
|
activeTab: AppsTab,
|
||||||
selectedCategory: string,
|
selectedCategory: string,
|
||||||
): Array<[string, PackageDataEntry]> {
|
): Array<[string, PackageDataEntry]> {
|
||||||
|
const hasMempool = entries.some(([id]) => id === 'mempool')
|
||||||
return entries.filter(([id, pkg]) => {
|
return entries.filter(([id, pkg]) => {
|
||||||
|
// Older daemons can retain the frontend manifest alias during a restart.
|
||||||
|
// Keep one tile while the updated scanner converges; a legacy-only node
|
||||||
|
// must still be able to see and operate its sole Mempool entry.
|
||||||
|
if (hasMempool && ['mempool-web', 'mempool-frontend', 'archy-mempool-web'].includes(id)) return false
|
||||||
if (isInternalToolingPackage(id, pkg)) return false
|
if (isInternalToolingPackage(id, pkg)) return false
|
||||||
const wantsWebsites = activeTab === 'websites' || activeTab === 'services'
|
const wantsWebsites = activeTab === 'websites' || activeTab === 'services'
|
||||||
const isWebsite = isWebsitePackage(id, pkg)
|
const isWebsite = isWebsitePackage(id, pkg)
|
||||||
@@ -231,6 +236,7 @@ function serviceParentIcon(id: string): string | undefined {
|
|||||||
export const DEFAULT_APP_ICON = '/assets/icon/favico-black-v2.svg'
|
export const DEFAULT_APP_ICON = '/assets/icon/favico-black-v2.svg'
|
||||||
|
|
||||||
export function resolveAppIcon(id: string, pkg: PackageDataEntry, curatedIcon?: string): string {
|
export function resolveAppIcon(id: string, pkg: PackageDataEntry, curatedIcon?: string): string {
|
||||||
|
if (id === 'angor-indexer' || id === 'angor-relay') return '/assets/img/app-icons/angor-green.png'
|
||||||
const rawIcon = (pkg["static-files"]?.icon || "").trim()
|
const rawIcon = (pkg["static-files"]?.icon || "").trim()
|
||||||
const icon = rawIcon === '/assets/img/favico.png' ? '' : rawIcon
|
const icon = rawIcon === '/assets/img/favico.png' ? '' : rawIcon
|
||||||
if (
|
if (
|
||||||
@@ -296,9 +302,10 @@ 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
|
||||||
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 the app to be ready…'
|
return `Waiting for ${title}…`
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
(appId === 'fedimint' || appId === 'fedimintd') &&
|
(appId === 'fedimint' || appId === 'fedimintd') &&
|
||||||
@@ -307,7 +314,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 'Starting up — Launch will appear when the app is ready.'
|
return `Waiting for ${title}…`
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user