fix(apps): self-host netbird and stabilize app sessions
This commit is contained in:
@@ -93,6 +93,8 @@ const PORT_TO_APP_ID: Record<string, string> = {
|
||||
'8334': 'bitcoin-knots',
|
||||
'8888': 'searxng',
|
||||
'9000': 'portainer',
|
||||
'8087': 'netbird',
|
||||
'8086': 'netbird',
|
||||
'9980': 'onlyoffice',
|
||||
'11434': 'ollama',
|
||||
'2283': 'immich',
|
||||
@@ -151,13 +153,20 @@ export const useAppLauncherStore = defineStore('appLauncher', () => {
|
||||
const panelAppId = ref<string | null>(null)
|
||||
|
||||
/** Open app in session view — panel mode uses store, overlay/fullscreen uses route */
|
||||
function dashboardReturnPath(): string {
|
||||
const current = router.currentRoute.value
|
||||
const fullPath = current.fullPath || '/dashboard/apps'
|
||||
if (!fullPath.startsWith('/dashboard') || current.name === 'app-session') return '/dashboard/apps'
|
||||
return fullPath
|
||||
}
|
||||
|
||||
function openSession(appId: string) {
|
||||
const mode = localStorage.getItem(DISPLAY_MODE_KEY) || 'panel'
|
||||
if (mode === 'panel' && !isMobileViewport()) {
|
||||
panelAppId.value = appId
|
||||
} else {
|
||||
panelAppId.value = null
|
||||
router.push({ name: 'app-session', params: { appId } })
|
||||
router.push({ name: 'app-session', params: { appId }, query: { returnTo: dashboardReturnPath() } })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -165,11 +165,6 @@ function closeRouteSession() {
|
||||
const fallbackPath = typeof fallback === 'string' && fallback.startsWith('/dashboard')
|
||||
? fallback
|
||||
: '/dashboard/apps'
|
||||
const previous = router.options.history.state.back
|
||||
if (typeof previous === 'string' && previous.startsWith('/dashboard') && router.resolve(previous).name !== 'app-session') {
|
||||
router.back()
|
||||
return
|
||||
}
|
||||
router.replace(fallbackPath).catch(() => {})
|
||||
}
|
||||
|
||||
@@ -193,7 +188,8 @@ function setMode(mode: DisplayMode) {
|
||||
if (isInlinePanel.value && mode !== 'panel') {
|
||||
const id = appId.value
|
||||
emit('close')
|
||||
router.push({ name: 'app-session', params: { appId: id } })
|
||||
const returnTo = route.fullPath.startsWith('/dashboard') ? route.fullPath : '/dashboard/apps'
|
||||
router.push({ name: 'app-session', params: { appId: id }, query: { returnTo } })
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ export const APP_PORTS: Record<string, number> = {
|
||||
'nginx-proxy-manager': 8081,
|
||||
'gitea': 3001,
|
||||
'portainer': 9000,
|
||||
'netbird': 8087,
|
||||
'tailscale': 8240,
|
||||
'uptime-kuma': 3002,
|
||||
'fedimint': 8175,
|
||||
|
||||
@@ -142,7 +142,7 @@ const mobileTabBar = ref<HTMLElement | null>(null)
|
||||
|
||||
// App sessions own their mobile controls. Normal mobile launches use the route
|
||||
// session; keeping this guard also protects any desktop-panel state on resize.
|
||||
const isAppSessionActive = computed(() => route.name === 'app-session' || !!appLauncher.panelAppId)
|
||||
const isAppSessionActive = computed(() => route.name === 'app-session')
|
||||
|
||||
// Show persistent tabs for Apps/Marketplace on mobile
|
||||
const showAppsTabs = computed(() => {
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<aside
|
||||
v-show="!chatFullscreen"
|
||||
data-controller-zone="sidebar"
|
||||
class="hidden md:flex w-[256px] flex-shrink-0 relative flex-col z-10"
|
||||
class="hidden md:flex w-[256px] h-screen flex-shrink-0 sticky top-0 relative flex-col z-10"
|
||||
:class="{ 'sidebar-animate': showZoomIn }"
|
||||
>
|
||||
<div class="sidebar-shell">
|
||||
<div class="sidebar-inner flex flex-col min-h-full">
|
||||
<div class="sidebar-inner flex flex-col h-full min-h-0">
|
||||
<div class="sidebar-logo flex items-center gap-3 mb-8 p-6 pb-0 shrink-0">
|
||||
<AnimatedLogo />
|
||||
<div class="min-w-0 flex-1">
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav flex-1 min-h-0 space-y-2 p-6 pt-4" :aria-label="t('dashboard.mainNav')">
|
||||
<nav class="sidebar-nav flex-1 min-h-0 overflow-y-auto overscroll-contain space-y-2 px-6 py-4" :aria-label="t('dashboard.mainNav')">
|
||||
<RouterLink
|
||||
v-for="(item, idx) in desktopNavItems"
|
||||
:key="item.path"
|
||||
@@ -74,21 +74,23 @@
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-controller px-6 pb-2 shrink-0">
|
||||
<ControllerIndicator />
|
||||
<CompanionIndicator />
|
||||
</div>
|
||||
|
||||
<!-- Online status -->
|
||||
<div class="px-6 pb-2 shrink-0">
|
||||
<div class="rounded-lg bg-white/5 border border-white/10 px-4 py-2.5">
|
||||
<OnlineStatusPill />
|
||||
<div class="sidebar-bottom shrink-0">
|
||||
<div class="sidebar-controller px-6 pb-2">
|
||||
<ControllerIndicator />
|
||||
<CompanionIndicator />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mode switcher -->
|
||||
<div class="px-6 pb-6 shrink-0">
|
||||
<ModeSwitcher />
|
||||
<!-- Online status -->
|
||||
<div class="px-6 pb-2">
|
||||
<div class="rounded-lg bg-white/5 border border-white/10 px-4 py-2.5">
|
||||
<OnlineStatusPill />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mode switcher -->
|
||||
<div class="px-6 pb-6">
|
||||
<ModeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
.sidebar-shell {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
min-height: 0;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
@@ -85,6 +85,24 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.24) transparent;
|
||||
}
|
||||
|
||||
.sidebar-nav::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.sidebar-nav::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.sidebar-bottom {
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.18), transparent 100%);
|
||||
}
|
||||
|
||||
/* Only hide sidebar content when doing the login entrance animation */
|
||||
.sidebar-animate .sidebar-inner {
|
||||
opacity: 0;
|
||||
|
||||
@@ -96,7 +96,7 @@ export function getCuratedAppList(): MarketplaceApp[] {
|
||||
{ id: 'portainer', title: 'Portainer', version: '2.19.4', description: 'Container management UI. Manage your containerized services through the web.', icon: '/assets/img/app-icons/portainer.webp', author: 'Portainer', dockerImage: `${R}/portainer:latest`, repoUrl: 'https://github.com/portainer/portainer' },
|
||||
{ id: 'uptime-kuma', title: 'Uptime Kuma', version: '1.23.0', description: 'Self-hosted uptime monitoring. Track HTTP, TCP, DNS, and more.', icon: '/assets/img/app-icons/uptime-kuma.webp', author: 'Uptime Kuma', dockerImage: `${R}/uptime-kuma:1`, repoUrl: 'https://github.com/louislam/uptime-kuma' },
|
||||
{ id: 'tailscale', title: 'Tailscale', version: '1.78.0', description: 'Zero-config VPN. Secure remote access with WireGuard mesh networking.', icon: '/assets/img/app-icons/tailscale.webp', author: 'Tailscale', dockerImage: `${R}/tailscale:stable`, repoUrl: 'https://github.com/tailscale/tailscale' },
|
||||
{ id: 'netbird', title: 'NetBird', version: '0.71.2', description: 'WireGuard mesh VPN client. Connect this node through NetBird Cloud or your own NetBird management server.', icon: '/assets/img/app-icons/netbird.svg', author: 'NetBird', dockerImage: 'docker.io/netbirdio/netbird:0.71.2', repoUrl: 'https://github.com/netbirdio/netbird' },
|
||||
{ id: 'netbird', title: 'NetBird', version: '0.71.2', description: 'Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN.', icon: '/assets/img/app-icons/netbird.svg', author: 'NetBird', dockerImage: 'docker.io/netbirdio/dashboard:v2.38.0', repoUrl: 'https://github.com/netbirdio/netbird' },
|
||||
{ id: 'electrumx', title: 'ElectrumX', version: '1.18.0', description: 'Electrum protocol server. Index the blockchain for fast wallet lookups, privately.', icon: '/assets/img/app-icons/electrumx.png', author: 'Luke Childs', dockerImage: `${R}/electrumx:v1.18.0`, repoUrl: 'https://github.com/spesmilo/electrumx' },
|
||||
{ id: 'fedimint', title: 'Fedimint', version: '0.10.0', description: 'Federated Bitcoin mint. Private, scalable Bitcoin through federated guardians.', icon: '/assets/img/app-icons/fedimint.png', author: 'Fedimint', dockerImage: `${R}/fedimintd:v0.10.0`, repoUrl: 'https://github.com/fedimint/fedimint' },
|
||||
{ id: 'indeedhub', title: 'Indeehub', version: '1.0.0', description: 'Bitcoin documentary streaming with Nostr identity. Stream sovereignty content.', icon: '/assets/img/app-icons/indeedhub.png', author: 'Indeehub Team', dockerImage: `${R}/indeedhub:1.0.0`, repoUrl: 'https://github.com/indeedhub/indeedhub' },
|
||||
|
||||
@@ -370,10 +370,10 @@ export function getCuratedAppList(): MarketplaceApp[] {
|
||||
id: 'netbird',
|
||||
title: 'NetBird',
|
||||
version: '0.71.2',
|
||||
description: 'WireGuard mesh VPN client. Connect this node through NetBird Cloud or a self-hosted management server.',
|
||||
description: 'Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN.',
|
||||
icon: '/assets/img/app-icons/netbird.svg',
|
||||
author: 'NetBird',
|
||||
dockerImage: 'docker.io/netbirdio/netbird:0.71.2',
|
||||
dockerImage: 'docker.io/netbirdio/dashboard:v2.38.0',
|
||||
manifestUrl: undefined,
|
||||
repoUrl: 'https://github.com/netbirdio/netbird'
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user