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:
Dorian
2026-04-07 19:44:00 +01:00
parent b0e5e8c00e
commit 4fc6c103ba
9 changed files with 319 additions and 297 deletions

View File

@@ -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, {