archy/neode-ui/src/views/appDetails/appDetailsData.ts
Dorian 36a6101026 release(v1.7.38-alpha): onboarding auto-heal + silent returning logins + app-store trim
- auth.rs now infers onboarding-complete from setup_complete + password_hash so
  nodes stop bouncing users through the intro wizard after browser clear / update
  / reboot; the flag self-heals to disk on next check
- frontend: "backend uncertain" no longer defaults to /onboarding/intro —
  useOnboarding returns null + callers poll / retry instead of flashing the wizard
- login sounds (synthwave, welcome voice, pop, whoosh, oomph) gated by
  isFirstInstallPhase(); typing sounds unaffected
- removed FIPS app, Nostr Relay, Nostr VPN, Routstr, Penpot from catalog,
  frontend config, Rust AppMetadata + install dispatch + install_penpot_stack;
  docker/fips-ui + docker/nostr-vpn-ui + apps/penpot dirs and 5 icons deleted;
  15 image versions deleted from tx1138, .168, gitea-local registries (.160
  Gitea was 502 at release time — follow-up)
- AIUI baked into frontend release tarball via demo/aiui/; deploy-to-target
  falls back to demo/aiui/ when the AIUI sibling checkout is missing
- prebuild hook syncs app-catalog/catalog.json → public/catalog.json so the
  two copies can no longer drift (was the source of the "apps still visible"
  bug — public/ had stale data)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 13:02:24 -04:00

169 lines
8.1 KiB
TypeScript

/**
* AppDetails data: URL maps, route-to-package mappings, aliases, and status helpers.
* Extracted from AppDetails.vue to keep the view under 500 lines.
*/
import { PackageState } from '@/types/api'
/** Web-only app detection (no container -- external websites) */
export const WEB_ONLY_APP_URLS: Record<string, string> = {
'indeedhub': `${window.location.protocol}//${window.location.hostname}:7777`,
'nwnn': 'https://nwnn.l484.com',
'484-kitchen': 'https://484.kitchen',
'call-the-operator': 'https://cta.tx1138.com',
'arch-presentation': 'https://present.l484.com',
'syntropy-institute': 'https://syntropy.institute',
't-zero': 'https://teeminuszero.net',
}
/** Map route/marketplace app IDs to backend package keys (container names). */
export const ROUTE_TO_PACKAGE_KEY: Record<string, string> = {
mempool: 'mempool-web',
'mempool-electrs': 'mempool-electrs',
electrs: 'mempool-electrs',
btcpay: 'btcpay-server',
'btcpay-server': 'btcpay-server',
fedimint: 'fedimint',
'fedimint-gateway': 'fedimint-gateway',
lnd: 'lnd',
'lnd-ui': 'lnd',
bitcoin: 'bitcoin-knots',
'bitcoin-knots': 'bitcoin-knots',
homeassistant: 'homeassistant',
'home-assistant': 'homeassistant',
grafana: 'grafana',
searxng: 'searxng',
ollama: 'ollama',
onlyoffice: 'onlyoffice',
nextcloud: 'nextcloud',
vaultwarden: 'vaultwarden',
jellyfin: 'jellyfin',
photoprism: 'photoprism',
immich: 'immich',
filebrowser: 'filebrowser',
'nginx-proxy-manager': 'nginx-proxy-manager',
portainer: 'portainer',
'uptime-kuma': 'uptime-kuma',
tailscale: 'tailscale',
}
/** Backend may register under variant container names */
export const PACKAGE_ALIASES: Record<string, string[]> = {
immich: ['immich_server', 'immich-server'],
nextcloud: ['nextcloud-aio', 'nextcloud-server'],
}
export function resolvePackageKey(routeId: string): string {
return ROUTE_TO_PACKAGE_KEY[routeId] ?? routeId
}
/** Apps that depend on Bitcoin being synced */
export const BITCOIN_DEPENDENT_APPS = ['lnd', 'electrumx', 'electrs', 'mempool-electrs', 'btcpay-server', 'btcpayserver']
/** App launch URLs for dev and prod environments */
export const APP_URLS: Record<string, { dev: string; prod: string }> = {
'lorabell': { dev: 'http://192.168.1.166', prod: 'http://192.168.1.166' },
'atob': { dev: 'http://localhost:8102', prod: 'https://app.atobitcoin.io' },
'k484': { dev: 'http://localhost:8103', prod: 'http://localhost:8103' },
'indeedhub': { dev: 'https://archipelago.indeehub.studio', prod: 'https://archipelago.indeehub.studio' },
'bitcoin': { dev: 'http://localhost:8332', prod: 'http://localhost:8332' },
'btcpay-server': { dev: 'http://localhost:23000', prod: 'http://localhost:23000' },
'homeassistant': { dev: 'http://localhost:8123', prod: 'http://localhost:8123' },
'grafana': { dev: 'http://localhost:3000', prod: 'http://localhost:3000' },
'endurain': { dev: 'http://localhost:8080', prod: 'http://localhost:8080' },
'fedimint': { dev: 'http://localhost:8175', prod: 'http://192.168.1.228:8175' },
'fedimint-gateway': { dev: 'http://localhost:8176', prod: 'http://192.168.1.228:8176' },
'morphos-server': { dev: 'http://localhost:8081', prod: 'http://localhost:8081' },
'lightning-stack': { dev: 'http://localhost:9735', prod: 'http://localhost:9735' },
'mempool': { dev: 'http://localhost:4080', prod: 'http://localhost:4080' },
'ollama': { dev: 'http://localhost:11434', prod: 'http://localhost:11434' },
'searxng': { dev: 'http://localhost:8888', prod: 'http://localhost:8888' },
'onlyoffice': { dev: 'http://localhost:9980', prod: 'http://localhost:9980' },
'nextcloud': { dev: 'http://localhost:8085', prod: 'http://localhost:8085' },
'vaultwarden': { dev: 'http://localhost:8082', prod: 'http://localhost:8082' },
'jellyfin': { dev: 'http://localhost:8096', prod: 'http://localhost:8096' },
'photoprism': { dev: 'http://localhost:2342', prod: 'http://localhost:2342' },
'immich': { dev: 'http://localhost:2283', prod: 'http://localhost:2283' },
'filebrowser': { dev: 'http://localhost:8083', prod: 'http://localhost:8083' },
'nginx-proxy-manager': { dev: 'http://localhost:81', prod: 'http://localhost:81' },
'portainer': { dev: 'http://localhost:9000', prod: 'http://localhost:9000' },
'uptime-kuma': { dev: 'http://localhost:3001', prod: 'http://localhost:3001' },
'tailscale': { dev: 'http://localhost:8240', prod: 'http://localhost:8240' },
'lnd': { dev: 'http://localhost:8081', prod: 'http://localhost:8081' },
'bitcoin-knots': { dev: 'http://localhost:8334', prod: 'http://localhost:8334' },
'botfights': { dev: 'http://localhost:9100', prod: 'http://localhost:9100' },
'nwnn': { dev: 'https://nwnn.l484.com', prod: 'https://nwnn.l484.com' },
'484-kitchen': { dev: 'https://484.kitchen', prod: 'https://484.kitchen' },
'call-the-operator': { dev: 'https://cta.tx1138.com', prod: 'https://cta.tx1138.com' },
'arch-presentation': { dev: 'https://present.l484.com', prod: 'https://present.l484.com' },
'syntropy-institute': { dev: 'https://syntropy.institute', prod: 'https://syntropy.institute' },
't-zero': { dev: 'https://teeminuszero.net', prod: 'https://teeminuszero.net' },
}
/** V3 onion addresses are 56+ chars + .onion. Placeholders like "btcpay.onion" are not real. */
export function isRealOnionAddress(addr: string | undefined): boolean {
return !!(addr && addr.endsWith('.onion') && addr.length >= 60 && addr.length <= 70)
}
export function getStatusClass(state: PackageState, health?: string | null, exitCode?: number | null): string {
if (state === PackageState.Running && health === 'starting') return 'bg-yellow-500/20 text-yellow-200 border border-yellow-500/30'
if (state === PackageState.Running && health === 'unhealthy') return 'bg-orange-500/20 text-orange-200 border border-orange-500/30'
switch (state) {
case PackageState.Running:
return 'bg-green-500/20 text-green-200 border border-green-500/30'
case PackageState.Stopped:
return 'bg-gray-500/20 text-gray-200 border border-gray-500/30'
case PackageState.Exited:
return exitCode != null && exitCode !== 0
? 'bg-red-500/20 text-red-200 border border-red-500/30'
: 'bg-gray-500/20 text-gray-200 border border-gray-500/30'
case PackageState.Starting:
case PackageState.Stopping:
case PackageState.Restarting:
return 'bg-yellow-500/20 text-yellow-200 border border-yellow-500/30'
case PackageState.Installing:
return 'bg-blue-500/20 text-blue-200 border border-blue-500/30'
case PackageState.Updating:
return 'bg-orange-500/20 text-orange-200 border border-orange-500/30'
default:
return 'bg-gray-500/20 text-gray-200 border border-gray-500/30'
}
}
export function getStatusDotClass(state: PackageState, health?: string | null, exitCode?: number | null): string {
if (state === PackageState.Running && health === 'starting') return 'bg-yellow-400 animate-pulse'
if (state === PackageState.Running && health === 'unhealthy') return 'bg-orange-400 animate-pulse'
switch (state) {
case PackageState.Running:
return 'bg-green-400'
case PackageState.Stopped:
return 'bg-gray-400'
case PackageState.Exited:
return exitCode != null && exitCode !== 0
? 'bg-red-400 animate-pulse'
: 'bg-gray-400'
case PackageState.Starting:
case PackageState.Stopping:
case PackageState.Restarting:
return 'bg-yellow-400 animate-pulse'
case PackageState.Installing:
return 'bg-blue-400 animate-pulse'
case PackageState.Updating:
return 'bg-orange-400 animate-pulse'
default:
return 'bg-gray-400'
}
}
export function getStatusLabel(state: PackageState, health?: string | null, exitCode?: number | null): string {
if (state === PackageState.Updating) return 'updating...'
if (state === PackageState.Running && health === 'starting') return 'starting up'
if (state === PackageState.Running && health === 'unhealthy') return 'unhealthy'
if (state === PackageState.Running && health === 'healthy') return 'healthy'
if (state === PackageState.Exited) {
if (exitCode === 137) return 'killed (OOM)'
if (exitCode != null && exitCode !== 0) return 'crashed'
return 'stopped'
}
return state
}