feat: VPN peer QR code UI, consolidate CI workflows
- Add vpn.create-peer, vpn.list-peers, vpn.remove-peer RPC methods - Generate WireGuard config + QR code (SVG) for mobile device connection - Add "Add Device" modal on Network page with QR scanner support - Remove old build-iso.yml (replaced by build-iso-dev.yml) - Remove container-tests.yml (tests run in dev workflow) - Remove container orchestration tests from dev workflow (redundant) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// Server store — computed server state and RPC action proxies
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { rpcClient } from '../api/rpc-client'
|
||||
import { useSyncStore } from './sync'
|
||||
import type { InstallProgress } from '../views/marketplace/marketplaceData'
|
||||
@@ -13,6 +13,45 @@ export const useServerStore = defineStore('server', () => {
|
||||
const installingApps = ref<Map<string, InstallProgress>>(new Map())
|
||||
const uninstallingApps = ref<Set<string>>(new Set())
|
||||
|
||||
// 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)) {
|
||||
if ((pkg.state as string) === 'installing') {
|
||||
// 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,
|
||||
message: 'Installing...',
|
||||
attempt: 0,
|
||||
})
|
||||
}
|
||||
const progress = pkg['install-progress']
|
||||
if (progress) {
|
||||
const current = installingApps.value.get(appId)!
|
||||
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)
|
||||
installingApps.value.set(appId, {
|
||||
...current,
|
||||
status: 'downloading',
|
||||
progress: Math.min(pct, 95),
|
||||
message: progress.size > 0 ? `Downloading: ${downloadedMB} / ${totalMB} MB (${pct}%)` : 'Downloading...',
|
||||
})
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
function setInstallProgress(appId: string, progress: Partial<InstallProgress> & { id: string; title: string }) {
|
||||
const existing = installingApps.value.get(appId)
|
||||
installingApps.value.set(appId, {
|
||||
|
||||
Reference in New Issue
Block a user