fix(apps): repair netbird install and app icons
This commit is contained in:
parent
cede77f3bc
commit
f0bd49d03d
@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.7.71-alpha (2026-05-19)
|
||||||
|
|
||||||
|
- NetBird stack installs now pre-create `/var/lib/archipelago/netbird/data` before binding it into `netbird-server`, fixing the failed install/start path seen on `100.70.96.88` where Podman rejected the missing host directory.
|
||||||
|
- NetBird start/restart ordering now starts `netbird-server` before the dashboard container so lifecycle actions bring the control plane up before the UI.
|
||||||
|
- App-session invalid IDs and panel-mode fallbacks now return to `/dashboard/apps`, avoiding the stale `/apps` route that could render a 404.
|
||||||
|
- Mobile launches for apps that block iframes now stay inside the Archipelago app-session fallback instead of automatically opening an external browser tab.
|
||||||
|
- Installed Gitea containers now report the packaged Gitea icon, and app icon masks use a rounder radius on mobile grids, app cards, and detail headers.
|
||||||
|
- Validation passed with `npm run type-check`, focused Vitest app-session/app-grid tests, `cargo fmt --all --check --manifest-path core/Cargo.toml`, and `cargo check -p archipelago --manifest-path core/Cargo.toml`.
|
||||||
|
|
||||||
## v1.7.70-alpha (2026-05-19)
|
## v1.7.70-alpha (2026-05-19)
|
||||||
|
|
||||||
- NetBird is being corrected from the peer/client daemon image to the self-hosted NetBird control-plane stack with a launchable dashboard on port `8087`, a combined management/signal/relay server on `8086`, and STUN on UDP `3478`.
|
- NetBird is being corrected from the peer/client daemon image to the self-hosted NetBird control-plane stack with a launchable dashboard on port `8087`, a combined management/signal/relay server on `8086`, and STUN on UDP `3478`.
|
||||||
|
|||||||
@ -288,6 +288,7 @@ pub(super) fn startup_order(package_id: &str) -> &'static [&'static str] {
|
|||||||
"btcpay-server" | "btcpayserver" | "btcpay" => {
|
"btcpay-server" | "btcpayserver" | "btcpay" => {
|
||||||
&["archy-btcpay-db", "archy-nbxplorer", "btcpay-server"]
|
&["archy-btcpay-db", "archy-nbxplorer", "btcpay-server"]
|
||||||
}
|
}
|
||||||
|
"netbird" => &["netbird-server", "netbird"],
|
||||||
"penpot" | "penpot-frontend" => &[
|
"penpot" | "penpot-frontend" => &[
|
||||||
"penpot-postgres",
|
"penpot-postgres",
|
||||||
"penpot-valkey",
|
"penpot-valkey",
|
||||||
@ -389,6 +390,11 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn netbird_start_order_starts_server_before_dashboard() {
|
||||||
|
assert_eq!(startup_order("netbird"), &["netbird-server", "netbird"]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unpruned_bitcoin_required_for_electrum_indexers_and_mempool() {
|
fn unpruned_bitcoin_required_for_electrum_indexers_and_mempool() {
|
||||||
for package_id in [
|
for package_id in [
|
||||||
|
|||||||
@ -1400,7 +1400,7 @@ impl RpcHandler {
|
|||||||
self.set_install_phase("netbird", InstallPhase::CreatingContainer)
|
self.set_install_phase("netbird", InstallPhase::CreatingContainer)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
tokio::fs::create_dir_all("/var/lib/archipelago/netbird")
|
tokio::fs::create_dir_all("/var/lib/archipelago/netbird/data")
|
||||||
.await
|
.await
|
||||||
.context("Failed to create NetBird data directory")?;
|
.context("Failed to create NetBird data directory")?;
|
||||||
|
|
||||||
|
|||||||
@ -487,6 +487,13 @@ fn get_app_metadata(app_id: &str) -> AppMetadata {
|
|||||||
repo: "https://github.com/netbirdio/netbird".to_string(),
|
repo: "https://github.com/netbirdio/netbird".to_string(),
|
||||||
tier: "",
|
tier: "",
|
||||||
},
|
},
|
||||||
|
"gitea" => AppMetadata {
|
||||||
|
title: "Gitea".to_string(),
|
||||||
|
description: "Self-hosted Git service with repository and package hosting".to_string(),
|
||||||
|
icon: "/assets/img/app-icons/gitea.svg".to_string(),
|
||||||
|
repo: "https://gitea.com".to_string(),
|
||||||
|
tier: "",
|
||||||
|
},
|
||||||
"indeedhub" | "indeehub" => AppMetadata {
|
"indeedhub" | "indeehub" => AppMetadata {
|
||||||
title: "IndeedHub".to_string(),
|
title: "IndeedHub".to_string(),
|
||||||
description: "Decentralized media streaming platform".to_string(),
|
description: "Decentralized media streaming platform".to_string(),
|
||||||
|
|||||||
@ -2098,7 +2098,7 @@ html:has(body.video-background-active)::before {
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border-radius: 14px;
|
border-radius: 18px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
background: rgba(255, 255, 255, 0.08);
|
background: rgba(255, 255, 255, 0.08);
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
@ -2108,7 +2108,16 @@ html:has(body.video-background-active)::before {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 14px;
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card-icon {
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-detail-icon {
|
||||||
|
border-radius: 22px;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status dot — top-right of icon */
|
/* Status dot — top-right of icon */
|
||||||
@ -2140,7 +2149,7 @@ html:has(body.video-background-active)::before {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0, 0, 0, 0.6);
|
||||||
border-radius: 14px;
|
border-radius: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-icon-label {
|
.app-icon-label {
|
||||||
|
|||||||
@ -158,7 +158,7 @@ const { t } = useI18n()
|
|||||||
const appId = computed(() => {
|
const appId = computed(() => {
|
||||||
const id = route.params.id
|
const id = route.params.id
|
||||||
if (typeof id !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/.test(id) || id.length > 64) {
|
if (typeof id !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/.test(id) || id.length > 64) {
|
||||||
router.replace('/apps')
|
router.replace('/dashboard/apps')
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return id
|
return id
|
||||||
|
|||||||
@ -138,7 +138,7 @@ const displayMode = ref<DisplayMode>(
|
|||||||
const appId = computed(() => {
|
const appId = computed(() => {
|
||||||
const id = props.appIdProp || (route.params.appId as string)
|
const id = props.appIdProp || (route.params.appId as string)
|
||||||
if (typeof id !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/.test(id) || id.length > 64) {
|
if (typeof id !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/.test(id) || id.length > 64) {
|
||||||
router.replace('/apps')
|
router.replace('/dashboard/apps')
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return id
|
return id
|
||||||
@ -146,7 +146,7 @@ const appId = computed(() => {
|
|||||||
|
|
||||||
const appTitle = computed(() => resolveAppTitle(appId.value))
|
const appTitle = computed(() => resolveAppTitle(appId.value))
|
||||||
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768
|
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768
|
||||||
const mustOpenNewTab = computed(() => !isMobile && NEW_TAB_APPS.has(appId.value))
|
const mustOpenNewTab = computed(() => NEW_TAB_APPS.has(appId.value))
|
||||||
const screensaverReason = computed(() => `app-session:${appId.value}`)
|
const screensaverReason = computed(() => `app-session:${appId.value}`)
|
||||||
const screensaverSuppressedApps = new Set([
|
const screensaverSuppressedApps = new Set([
|
||||||
'indeedhub',
|
'indeedhub',
|
||||||
@ -197,7 +197,11 @@ function setMode(mode: DisplayMode) {
|
|||||||
if (!isInlinePanel.value && mode === 'panel') {
|
if (!isInlinePanel.value && mode === 'panel') {
|
||||||
const id = appId.value
|
const id = appId.value
|
||||||
const launcher = useAppLauncherStore()
|
const launcher = useAppLauncherStore()
|
||||||
router.push({ name: 'apps' }).then(() => {
|
const fallback = route.query.returnTo
|
||||||
|
const fallbackPath = typeof fallback === 'string' && fallback.startsWith('/dashboard')
|
||||||
|
? fallback
|
||||||
|
: '/dashboard/apps'
|
||||||
|
router.push(fallbackPath).then(() => {
|
||||||
launcher.panelAppId = id
|
launcher.panelAppId = id
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@ -337,8 +341,9 @@ watch(displayMode, (mode) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Apps that block iframes (X-Frame-Options) -- open in new tab, close session
|
// Desktop apps that block iframes open externally. Mobile keeps the user in
|
||||||
if (mustOpenNewTab.value && appUrl.value) {
|
// Archipelago and shows the explicit fallback instead of leaving the shell.
|
||||||
|
if (!isMobile && mustOpenNewTab.value && appUrl.value) {
|
||||||
window.open(appUrl.value, '_blank', 'noopener,noreferrer')
|
window.open(appUrl.value, '_blank', 'noopener,noreferrer')
|
||||||
if (isInlinePanel.value) emit('close')
|
if (isInlinePanel.value) emit('close')
|
||||||
else closeRouteSession()
|
else closeRouteSession()
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<img
|
<img
|
||||||
:src="icon"
|
:src="icon"
|
||||||
:alt="pkg.manifest.title"
|
:alt="pkg.manifest.title"
|
||||||
class="w-20 h-20 rounded-xl shadow-xl flex-shrink-0"
|
class="app-detail-icon w-20 h-20 shadow-xl flex-shrink-0"
|
||||||
@error="handleImageError"
|
@error="handleImageError"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -119,7 +119,7 @@
|
|||||||
<img
|
<img
|
||||||
:src="icon"
|
:src="icon"
|
||||||
:alt="pkg.manifest.title"
|
:alt="pkg.manifest.title"
|
||||||
class="w-20 h-20 rounded-xl shadow-xl flex-shrink-0"
|
class="app-detail-icon w-20 h-20 shadow-xl flex-shrink-0"
|
||||||
@error="handleImageError"
|
@error="handleImageError"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
<img
|
<img
|
||||||
:src="icon"
|
:src="icon"
|
||||||
:alt="title"
|
:alt="title"
|
||||||
class="w-14 h-14 rounded-lg object-cover bg-white/10"
|
class="app-card-icon w-14 h-14 object-cover bg-white/10"
|
||||||
@error="handleImageError"
|
@error="handleImageError"
|
||||||
/>
|
/>
|
||||||
<div class="flex-1 min-w-0 overflow-hidden">
|
<div class="flex-1 min-w-0 overflow-hidden">
|
||||||
|
|||||||
@ -35,6 +35,11 @@ describe('AppIconGrid', () => {
|
|||||||
setActivePinia(createPinia())
|
setActivePinia(createPinia())
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
|
Object.defineProperty(window, 'innerWidth', {
|
||||||
|
value: 1024,
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
})
|
||||||
Object.defineProperty(window, 'location', {
|
Object.defineProperty(window, 'location', {
|
||||||
value: { hostname: '192.168.1.198' },
|
value: { hostname: '192.168.1.198' },
|
||||||
writable: true,
|
writable: true,
|
||||||
@ -55,4 +60,23 @@ describe('AppIconGrid', () => {
|
|||||||
expect(mockWindowOpen).not.toHaveBeenCalled()
|
expect(mockWindowOpen).not.toHaveBeenCalled()
|
||||||
expect(useAppLauncherStore().panelAppId).toBe('lnd')
|
expect(useAppLauncherStore().panelAppId).toBe('lnd')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('opens desktop new-tab apps through app session on mobile', async () => {
|
||||||
|
Object.defineProperty(window, 'innerWidth', {
|
||||||
|
value: 390,
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const wrapper = mount(AppIconGrid, {
|
||||||
|
props: { apps: [['gitea', makePkg('gitea')]] },
|
||||||
|
global: {
|
||||||
|
plugins: [createPinia()],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.get('.app-icon-item').trigger('click')
|
||||||
|
|
||||||
|
expect(mockWindowOpen).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -166,7 +166,8 @@ const APP_ICON_FALLBACKS: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function resolveAppIcon(id: string, pkg: PackageDataEntry, curatedIcon?: string): string {
|
export function resolveAppIcon(id: string, pkg: PackageDataEntry, curatedIcon?: string): string {
|
||||||
const icon = (pkg["static-files"]?.icon || "").trim()
|
const rawIcon = (pkg["static-files"]?.icon || "").trim()
|
||||||
|
const icon = rawIcon === '/assets/img/favico.png' ? '' : rawIcon
|
||||||
if (
|
if (
|
||||||
icon.startsWith("/") ||
|
icon.startsWith("/") ||
|
||||||
icon.startsWith("http://") ||
|
icon.startsWith("http://") ||
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user