2026-03-22 03:30:21 +00:00
|
|
|
// Server store — computed server state and RPC action proxies
|
|
|
|
|
|
|
|
|
|
import { defineStore } from 'pinia'
|
2026-04-07 19:44:00 +01:00
|
|
|
import { computed, ref, watch } from 'vue'
|
2026-03-22 03:30:21 +00:00
|
|
|
import { rpcClient } from '../api/rpc-client'
|
|
|
|
|
import { useSyncStore } from './sync'
|
2026-03-29 00:13:39 +00:00
|
|
|
import type { InstallProgress } from '../views/marketplace/marketplaceData'
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
import type { InstallPhase } from '../types/api'
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Phase-to-UI mapping. Each backend pipeline phase maps to a fixed
|
|
|
|
|
* progress percentage (so the bar only ever advances forward) and a
|
|
|
|
|
* descriptive label the user can actually understand. This is the
|
|
|
|
|
* source of truth — byte counters from `install-progress.size/downloaded`
|
|
|
|
|
* are a fallback for the rare cases where podman does emit parseable
|
|
|
|
|
* progress on a piped stderr.
|
|
|
|
|
*
|
|
|
|
|
* Percentages chosen so:
|
|
|
|
|
* - the bar is never fully empty (users panic)
|
|
|
|
|
* - the bar visibly advances at every phase boundary
|
|
|
|
|
* - the slowest phases (PullingImage, WaitingHealthy) get the widest
|
|
|
|
|
* bands so shimmer/indeterminate treatment has room
|
|
|
|
|
* - 100% is reserved for "Done" / terminal success
|
|
|
|
|
*/
|
|
|
|
|
const PHASE_INFO: Record<InstallPhase, { progress: number; message: string; status: InstallProgress['status'] }> = {
|
|
|
|
|
'preparing': { progress: 5, message: 'Preparing…', status: 'downloading' },
|
|
|
|
|
'pulling-image': { progress: 20, message: 'Downloading image…', status: 'downloading' },
|
|
|
|
|
'creating-container': { progress: 70, message: 'Creating container…', status: 'installing' },
|
|
|
|
|
'starting-container': { progress: 80, message: 'Starting container…', status: 'starting' },
|
|
|
|
|
'waiting-healthy': { progress: 88, message: 'Waiting for container…', status: 'starting' },
|
|
|
|
|
'post-install': { progress: 95, message: 'Finalizing…', status: 'installing' },
|
|
|
|
|
'done': { progress: 100, message: 'Installed', status: 'complete' },
|
|
|
|
|
}
|
2026-03-22 03:30:21 +00:00
|
|
|
|
|
|
|
|
export const useServerStore = defineStore('server', () => {
|
|
|
|
|
const sync = useSyncStore()
|
|
|
|
|
|
2026-03-31 21:00:01 +01:00
|
|
|
// Global install/uninstall tracking — persists across navigation
|
2026-03-29 00:13:39 +00:00
|
|
|
const installingApps = ref<Map<string, InstallProgress>>(new Map())
|
2026-03-31 21:00:01 +01:00
|
|
|
const uninstallingApps = ref<Set<string>>(new Set())
|
2026-03-29 00:13:39 +00:00
|
|
|
|
2026-04-07 19:44:00 +01:00
|
|
|
// Watch WebSocket data for real install progress — runs globally, not just on Marketplace page
|
|
|
|
|
watch(() => sync.packages, (packages) => {
|
|
|
|
|
if (!packages) return
|
|
|
|
|
for (const [appId, pkg] of Object.entries(packages)) {
|
2026-04-09 11:47:35 +02:00
|
|
|
if ((pkg.state as string) === 'installing' || (pkg.state as string) === 'updating') {
|
2026-04-07 19:44:00 +01:00
|
|
|
// Backend confirms it's installing — update or create tracking entry
|
|
|
|
|
if (!installingApps.value.has(appId)) {
|
|
|
|
|
installingApps.value.set(appId, {
|
|
|
|
|
id: appId,
|
|
|
|
|
title: pkg.manifest?.title || appId,
|
|
|
|
|
status: 'downloading',
|
|
|
|
|
progress: 0,
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
message: 'Installing…',
|
2026-04-07 19:44:00 +01:00
|
|
|
attempt: 0,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
const progress = pkg['install-progress']
|
|
|
|
|
if (progress) {
|
|
|
|
|
const current = installingApps.value.get(appId)!
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
// Primary source: the pipeline phase. Each phase maps to a
|
|
|
|
|
// fixed progress% and a user-facing label.
|
|
|
|
|
if (progress.phase) {
|
|
|
|
|
const info = PHASE_INFO[progress.phase]
|
|
|
|
|
if (info) {
|
2026-04-29 14:50:33 -04:00
|
|
|
// Within the PullingImage band (20→70%), interpolate the
|
|
|
|
|
// bar based on how many images / bytes have landed so far.
|
|
|
|
|
// Without this, multi-container stacks (indeedhub: 7,
|
|
|
|
|
// mempool: 3, btcpay: 4) just sit at 20% for the entire
|
|
|
|
|
// pull duration — exactly what the user reported as
|
|
|
|
|
// "Downloading sticks at 20% mostly". X-of-N progress
|
|
|
|
|
// comes from set_install_progress(i, n) in stacks.rs.
|
|
|
|
|
let bandProgress = info.progress
|
|
|
|
|
if (progress.phase === 'pulling-image' && progress.size > 0) {
|
|
|
|
|
const fraction = Math.min(progress.downloaded / progress.size, 1)
|
|
|
|
|
// PullingImage band: 20% → 70%, so 50pp to interpolate over.
|
|
|
|
|
bandProgress = 20 + Math.round(fraction * 50)
|
|
|
|
|
}
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
// Only advance forward — never let the bar step backward
|
|
|
|
|
// between patches (can happen briefly during scan merges).
|
2026-04-29 14:50:33 -04:00
|
|
|
const nextProgress = Math.max(current.progress, bandProgress)
|
|
|
|
|
// Show explicit message when set (e.g. install-fail descriptions
|
|
|
|
|
// surfaced via install_progress.message) — otherwise PHASE_INFO label.
|
|
|
|
|
const label = progress.message || info.message
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
installingApps.value.set(appId, {
|
|
|
|
|
...current,
|
|
|
|
|
status: info.status,
|
|
|
|
|
progress: nextProgress,
|
2026-04-29 14:50:33 -04:00
|
|
|
message: label,
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-29 14:50:33 -04:00
|
|
|
// No phase but message is set (install-fail path) — show the message
|
|
|
|
|
// even if PHASE_INFO doesn't apply. Status stays whatever the watcher
|
|
|
|
|
// currently has.
|
|
|
|
|
if (progress.message) {
|
|
|
|
|
installingApps.value.set(appId, {
|
|
|
|
|
...current,
|
|
|
|
|
message: progress.message,
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
// Fallback: byte counters (rare — podman usually doesn't
|
|
|
|
|
// emit parseable progress on a piped stderr).
|
2026-04-07 19:44:00 +01:00
|
|
|
const pct = progress.size > 0 ? Math.round((progress.downloaded / progress.size) * 100) : 0
|
|
|
|
|
const downloadedMB = (progress.downloaded / (1024 * 1024)).toFixed(1)
|
|
|
|
|
const totalMB = (progress.size / (1024 * 1024)).toFixed(1)
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
let message = 'Downloading…'
|
2026-04-07 22:05:08 +01:00
|
|
|
if (progress.size > 1024 && pct < 100) {
|
|
|
|
|
message = `Downloading: ${downloadedMB} / ${totalMB} MB (${pct}%)`
|
|
|
|
|
} else if (pct >= 100 || (progress.size > 0 && progress.downloaded >= progress.size)) {
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
message = 'Installing package…'
|
2026-04-07 22:05:08 +01:00
|
|
|
}
|
2026-04-07 19:44:00 +01:00
|
|
|
installingApps.value.set(appId, {
|
|
|
|
|
...current,
|
2026-04-07 22:05:08 +01:00
|
|
|
status: pct >= 100 ? 'installing' : 'downloading',
|
feat(install): phase-based progress bar replaces unparseable pull bytes
Podman emits zero parseable progress when stderr is piped (no TTY), so
the old byte-counter regex never matched in real installs. Users saw
0% for the whole pull, then a jump to 95%, then silence through
create-container, health-check, and post-install hooks.
Replace with 7 explicit lifecycle phases wired through install.rs and
update.rs: Preparing (5%), PullingImage (20%), CreatingContainer (70%),
StartingContainer (80%), WaitingHealthy (88%), PostInstall (95%),
Done (100%). Each maps to a fixed UI progress and status message.
Frontend PHASE_INFO mapper in stores/server.ts prioritizes phase when
present, falls back to byte-counter for legacy. A Math.max forward-only
guard ensures the bar never regresses. Deleted the duplicate watcher
in Discover.vue that was fighting the store's watcher with stale byte
logic. Added shimmer CSS on the fill (with prefers-reduced-motion
opt-out) so the bar looks alive during long phases.
2026-04-23 07:58:43 -04:00
|
|
|
progress: Math.max(current.progress, Math.min(pct, 95)),
|
2026-04-07 22:05:08 +01:00
|
|
|
message,
|
2026-04-07 19:44:00 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
} else if (installingApps.value.has(appId)) {
|
|
|
|
|
const state = pkg.state as string
|
|
|
|
|
// Only clear when app is fully running or definitively stopped — not during 'starting' transition
|
|
|
|
|
if (state === 'running' || state === 'stopped' || state === 'exited') {
|
|
|
|
|
installingApps.value.delete(appId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-07 22:05:08 +01:00
|
|
|
// Clear installingApps entries for apps that vanished from backend data
|
2026-04-08 09:20:18 +02:00
|
|
|
// Only clean up entries that have errored — active installs may take minutes to pull images
|
2026-04-07 22:05:08 +01:00
|
|
|
for (const [appId] of installingApps.value) {
|
|
|
|
|
if (packages && !(appId in packages)) {
|
|
|
|
|
const entry = installingApps.value.get(appId)
|
2026-04-08 09:20:18 +02:00
|
|
|
if (entry && entry.status === 'error') {
|
2026-04-07 22:05:08 +01:00
|
|
|
installingApps.value.delete(appId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-08 09:20:18 +02:00
|
|
|
// Clear uninstallingApps when the container disappears from backend data
|
|
|
|
|
for (const appId of uninstallingApps.value) {
|
|
|
|
|
if (packages && !(appId in packages)) {
|
|
|
|
|
uninstallingApps.value.delete(appId)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-07 19:44:00 +01:00
|
|
|
}, { deep: true })
|
|
|
|
|
|
2026-03-29 00:13:39 +00:00
|
|
|
function setInstallProgress(appId: string, progress: Partial<InstallProgress> & { id: string; title: string }) {
|
|
|
|
|
const existing = installingApps.value.get(appId)
|
|
|
|
|
installingApps.value.set(appId, {
|
|
|
|
|
status: 'downloading',
|
|
|
|
|
progress: 0,
|
|
|
|
|
message: 'Preparing...',
|
|
|
|
|
attempt: 0,
|
|
|
|
|
...existing,
|
|
|
|
|
...progress,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearInstallProgress(appId: string) {
|
|
|
|
|
installingApps.value.delete(appId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isInstalling(appId: string): boolean {
|
|
|
|
|
return installingApps.value.has(appId)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-22 03:30:21 +00:00
|
|
|
// Computed — derived from sync store's data
|
|
|
|
|
const serverName = computed(() => sync.serverInfo?.name || 'Archipelago')
|
|
|
|
|
const isRestarting = computed(() => sync.serverInfo?.['status-info']?.restarting || false)
|
|
|
|
|
const isShuttingDown = computed(() => sync.serverInfo?.['status-info']?.['shutting-down'] || false)
|
|
|
|
|
const isOffline = computed(() => !sync.isConnected || isRestarting.value || isShuttingDown.value)
|
|
|
|
|
|
2026-04-23 06:58:02 -04:00
|
|
|
// Package actions. install/uninstall/update are async on the backend:
|
|
|
|
|
// the RPC returns immediately with { status: 'installing'|'removing'|'updating',
|
|
|
|
|
// package_id } after flipping state, and the real work runs in a spawn.
|
|
|
|
|
// Progress is streamed via the WebSocket state push, not the RPC response.
|
|
|
|
|
async function installPackage(
|
|
|
|
|
id: string,
|
|
|
|
|
marketplaceUrl: string,
|
|
|
|
|
version: string,
|
|
|
|
|
): Promise<{ status: string; package_id: string }> {
|
2026-03-22 03:30:21 +00:00
|
|
|
return rpcClient.installPackage(id, marketplaceUrl, version)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 06:58:02 -04:00
|
|
|
async function uninstallPackage(id: string): Promise<{ status: string; package_id: string }> {
|
2026-03-22 03:30:21 +00:00
|
|
|
return rpcClient.uninstallPackage(id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function startPackage(id: string): Promise<void> {
|
|
|
|
|
return rpcClient.startPackage(id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function stopPackage(id: string): Promise<void> {
|
|
|
|
|
return rpcClient.stopPackage(id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function restartPackage(id: string): Promise<void> {
|
|
|
|
|
return rpcClient.restartPackage(id)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 06:58:02 -04:00
|
|
|
async function updatePackage(id: string): Promise<{ status: string; package_id: string }> {
|
2026-04-09 11:47:35 +02:00
|
|
|
return rpcClient.updatePackage(id)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-22 03:30:21 +00:00
|
|
|
// Server actions
|
|
|
|
|
async function updateServer(marketplaceUrl: string): Promise<'updating' | 'no-updates'> {
|
|
|
|
|
return rpcClient.updateServer(marketplaceUrl)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function restartServer(): Promise<void> {
|
|
|
|
|
return rpcClient.restartServer()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function shutdownServer(): Promise<void> {
|
|
|
|
|
return rpcClient.shutdownServer()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getMetrics(): Promise<Record<string, unknown>> {
|
|
|
|
|
return rpcClient.getMetrics()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Marketplace actions
|
|
|
|
|
async function getMarketplace(url: string): Promise<Record<string, unknown>> {
|
|
|
|
|
return rpcClient.getMarketplace(url)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateServerName(name: string) {
|
|
|
|
|
if (sync.data?.['server-info']) {
|
|
|
|
|
sync.data['server-info'].name = name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
// Computed
|
|
|
|
|
serverName,
|
|
|
|
|
isRestarting,
|
|
|
|
|
isShuttingDown,
|
|
|
|
|
isOffline,
|
|
|
|
|
|
2026-03-31 21:00:01 +01:00
|
|
|
// Install/uninstall tracking (global, persists across navigation)
|
2026-03-29 00:13:39 +00:00
|
|
|
installingApps,
|
|
|
|
|
setInstallProgress,
|
|
|
|
|
clearInstallProgress,
|
|
|
|
|
isInstalling,
|
2026-03-31 21:00:01 +01:00
|
|
|
uninstallingApps,
|
2026-03-29 00:13:39 +00:00
|
|
|
|
2026-03-22 03:30:21 +00:00
|
|
|
// Actions
|
|
|
|
|
installPackage,
|
|
|
|
|
uninstallPackage,
|
|
|
|
|
startPackage,
|
|
|
|
|
stopPackage,
|
|
|
|
|
restartPackage,
|
2026-04-09 11:47:35 +02:00
|
|
|
updatePackage,
|
2026-03-22 03:30:21 +00:00
|
|
|
updateServer,
|
|
|
|
|
restartServer,
|
|
|
|
|
shutdownServer,
|
|
|
|
|
getMetrics,
|
|
|
|
|
getMarketplace,
|
|
|
|
|
updateServerName,
|
|
|
|
|
}
|
|
|
|
|
})
|