From c4aa72dccc17f2585fa864ae1971033f2e46da96 Mon Sep 17 00:00:00 2001 From: archipelago Date: Sat, 12 Sep 2026 15:07:33 -0400 Subject: [PATCH] fix: route installs to apps or services --- neode-ui/src/views/Discover.vue | 17 +++++++++++++---- neode-ui/src/views/Marketplace.vue | 16 ++++++++++++++-- neode-ui/src/views/apps/appsConfig.ts | 2 +- neode-ui/src/views/apps/serviceNames.ts | 2 ++ neode-ui/src/views/discover/curatedApps.ts | 1 + .../src/views/marketplace/marketplaceData.ts | 1 + 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/neode-ui/src/views/Discover.vue b/neode-ui/src/views/Discover.vue index f4708489..1c3e7b13 100644 --- a/neode-ui/src/views/Discover.vue +++ b/neode-ui/src/views/Discover.vue @@ -365,6 +365,7 @@ import AppGrid from './discover/AppGrid.vue' import InstallVersionModal from '@/components/InstallVersionModal.vue' import type { MarketplaceApp, FeaturedApp } from './discover/types' import { getCuratedAppList, INSTALLED_ALIASES, FEATURED_DEFINITIONS, categorizeCommunityApp, fetchAppCatalog, type CatalogFeatured, type CatalogStorefront } from './discover/curatedApps' +import { isServiceContainer } from './apps/serviceNames' const router = useRouter() const store = useAppStore() @@ -733,6 +734,16 @@ onBeforeUnmount(() => { const toast = useToast() +function installToast(app: MarketplaceApp) { + const service = isServiceContainer(app.id) + const destination = service ? 'Services' : 'My Apps' + toast.action( + `Installing ${app.title ?? app.id} — it will appear in ${destination}`, + { label: `View ${destination}`, onClick: () => router.push({ path: '/dashboard/apps', query: service ? { tab: 'services' } : {} }) }, + { variant: 'info', duration: 15000 }, + ) +} + function installBlockedReason(appId: string): string | undefined { if (!bitcoinPruned.value) return undefined if (appId !== 'electrumx' && appId !== 'electrs' && appId !== 'mempool-electrs') return undefined @@ -766,8 +777,7 @@ function failInstall(app: MarketplaceApp, err: unknown) { async function installApp(app: MarketplaceApp, versionOverride?: string) { if (installingApps.has(app.id) || isInstalled(app.id)) return queueInstall(app) - toast.info("Installing " + (app.title ?? app.id) + " - check My Apps") - router.push('/dashboard/apps').catch(() => {}) + installToast(app) try { const installUrl = app.url || app.manifestUrl || app.s9pkUrl await rpcClient.call({ method: 'package.install', params: { id: app.id, url: installUrl, version: versionOverride || app.version }, timeout: 600000 }) @@ -780,8 +790,7 @@ async function installApp(app: MarketplaceApp, versionOverride?: string) { async function installCommunityApp(app: MarketplaceApp, versionOverride?: string) { if (installingApps.has(app.id) || isInstalled(app.id) || !app.dockerImage) return queueInstall(app) - toast.info("Installing " + (app.title ?? app.id) + " - check My Apps") - router.push('/dashboard/apps').catch(() => {}) + installToast(app) try { const installParams: Record = { id: app.id, dockerImage: app.dockerImage, version: versionOverride || app.version } if ((app as Record).containerConfig) { diff --git a/neode-ui/src/views/Marketplace.vue b/neode-ui/src/views/Marketplace.vue index 7ce2a00b..5b6dffc4 100644 --- a/neode-ui/src/views/Marketplace.vue +++ b/neode-ui/src/views/Marketplace.vue @@ -185,6 +185,7 @@ import { getCuratedAppList, } from './marketplace/marketplaceData' import { fetchAppCatalog } from './discover/curatedApps' +import { isServiceContainer } from './apps/serviceNames' const router = useRouter() const route = useRoute() @@ -207,6 +208,17 @@ const appStoreSections = computed(() => APP_STORE_SECTIONS) const installingApps = server.installingApps const electrumxArchiveWarning = 'You need a full archival bitcoin node before downloading ElectrumX' +function installToast(app: MarketplaceApp) { + const service = isServiceContainer(app.id) + const tab = service ? 'services' : 'apps' + const destination = service ? 'Services' : 'My Apps' + toast.action( + `Installing ${app.title ?? app.id} — it will appear in ${destination}`, + { label: `View ${destination}`, onClick: () => router.push({ path: '/dashboard/apps', query: service ? { tab } : {} }) }, + { variant: 'info', duration: 15000 }, + ) +} + // Install progress tracking is now in serverStore (global watcher on WebSocket data) // so it works regardless of which page is active @@ -518,7 +530,7 @@ async function installApp(app: MarketplaceApp) { // Stay on the store page: the tile itself shows install progress via the // global watcher, and a forced jump to My Apps yanked the user out of the // page they were deliberately browsing. - toast.info("Installing " + (app.title ?? app.id) + " — it will appear in My Apps") + installToast(app) try { const installUrl = app.url || app.manifestUrl || app.s9pkUrl @@ -543,7 +555,7 @@ async function installCommunityApp(app: MarketplaceApp) { queueInstall(app) // Stay on the store page (see installApp). - toast.info("Installing " + (app.title ?? app.id) + " — it will appear in My Apps") + installToast(app) try { const installParams: Record = { id: app.id, dockerImage: app.dockerImage, version: app.version } diff --git a/neode-ui/src/views/apps/appsConfig.ts b/neode-ui/src/views/apps/appsConfig.ts index 94da9181..8a487160 100644 --- a/neode-ui/src/views/apps/appsConfig.ts +++ b/neode-ui/src/views/apps/appsConfig.ts @@ -37,7 +37,7 @@ export function isServicePackage(id: string, pkg?: PackageDataEntry): boolean { // Known app -> category mappings (matches App Store categorisation) export const APP_CATEGORY_MAP: Record = { - 'bitcoin-core': 'money', 'bitcoin-knots': 'money', 'bitcoin-ui': 'money', 'electrumx': 'money', 'electrs': 'money', + 'bitcoin-core': 'money', 'bitcoin-knots': 'money', 'bitcoin-ui': 'money', 'cuprate-ui': 'money', 'electrumx': 'money', 'electrs': 'money', 'lnd': 'money', 'mempool': 'money', 'mempool-web': 'money', 'btcpay-server': 'commerce', 'fedimint': 'money', 'fedimint-gateway': 'money', 'indeedhub': 'media', 'jellyfin': 'media', 'photoprism': 'media', 'immich': 'media', diff --git a/neode-ui/src/views/apps/serviceNames.ts b/neode-ui/src/views/apps/serviceNames.ts index 15c17922..ddb8e489 100644 --- a/neode-ui/src/views/apps/serviceNames.ts +++ b/neode-ui/src/views/apps/serviceNames.ts @@ -14,6 +14,8 @@ // SERVICE_NAMES set that used to live in appsConfig.ts verbatim. export const SERVICE_NAMES = new Set([ 'dwn', 'archy-mempool-db', 'archy-btcpay-db', 'archy-nbxplorer', 'archy-tor', + // Cuprate's daemon is the backend dependency of the Cuprate UI app. + 'cuprate', // Headless backends with no user-facing UI: the Fedimint ecash client daemon, // the Nostr relay, and the Meshtastic LoRa daemon (its chat UI lives in the // built-in Mesh tab) belong in Services, not My Apps. diff --git a/neode-ui/src/views/discover/curatedApps.ts b/neode-ui/src/views/discover/curatedApps.ts index df67b830..a1aa650e 100644 --- a/neode-ui/src/views/discover/curatedApps.ts +++ b/neode-ui/src/views/discover/curatedApps.ts @@ -308,6 +308,7 @@ export function getCuratedAppList(): MarketplaceApp[] { // Supporting containers (DBs, caches, workers) do NOT — having only a DB // without the main app should not mark the app as installed in the UI. export const INSTALLED_ALIASES: Record = { + 'cuprate-ui': ['cuprate-ui', 'cuprate'], mempool: ['mempool', 'mempool-web', 'archy-mempool-web'], bitcoin: ['bitcoin-knots'], btcpay: ['btcpay-server'], diff --git a/neode-ui/src/views/marketplace/marketplaceData.ts b/neode-ui/src/views/marketplace/marketplaceData.ts index d2759946..ac659399 100644 --- a/neode-ui/src/views/marketplace/marketplaceData.ts +++ b/neode-ui/src/views/marketplace/marketplaceData.ts @@ -68,6 +68,7 @@ const REGISTRY = 'source.archipelago-foundation.org/lfg2025' /** Marketplace app ID -> backend package keys (for "Already Installed" when first-boot/deploy created them) */ export const INSTALLED_ALIASES: Record = { + 'cuprate-ui': ['cuprate-ui', 'cuprate'], mempool: ['mempool-web', 'mempool-api', 'archy-mempool-web', 'archy-mempool-db'], bitcoin: ['bitcoin-knots'], btcpay: ['btcpay-server', 'archy-btcpay-db', 'archy-nbxplorer'],