fix: auth, container resilience, ISO build, gamepad polish
- fix: login disconnect — verify session before WebSocket connect - fix: 403 on app install — distinguish CSRF vs RBAC errors, only retry CSRF - fix: health monitor now watches ALL containers (removed skip list for backend services like nbxplorer, databases, UI containers) - fix: server.get-state added to CSRF-exempt list (read-only) - fix: ISO build includes container-specs.sh and lib/common.sh in rootfs so reconcile actually works on fresh installs - fix: gamepad nav — improved Server tab zone nav, focus styles, autofocus - chore: move L484 web-only apps to Services tab - chore: install store for cross-view install tracking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
68f2a9c5bf
commit
fdd69ce1b5
@@ -32,7 +32,15 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
// Initialize data structure immediately so dashboard can render
|
||||
await sync.initializeData()
|
||||
|
||||
// Connect WebSocket in background - don't block login flow
|
||||
// Verify session cookies are established before WebSocket connect.
|
||||
// Without this, the WS upgrade can race ahead of cookie processing → 401.
|
||||
try {
|
||||
await rpcClient.call({ method: 'server.echo', params: { message: 'session-ready' } })
|
||||
} catch {
|
||||
// Non-fatal: WS reconnect logic will handle it
|
||||
}
|
||||
|
||||
// Connect WebSocket in background
|
||||
sync.connectWebSocket().catch((err) => {
|
||||
if (import.meta.env.DEV) console.warn('[Store] WebSocket connection failed after login, will retry:', err)
|
||||
})
|
||||
@@ -52,6 +60,14 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
|
||||
const sync = useSyncStore()
|
||||
await sync.initializeData()
|
||||
|
||||
// Verify session cookies are established before WebSocket connect
|
||||
try {
|
||||
await rpcClient.call({ method: 'server.echo', params: { message: 'session-ready' } })
|
||||
} catch {
|
||||
// Non-fatal: WS reconnect logic will handle it
|
||||
}
|
||||
|
||||
sync.connectWebSocket().catch((err) => {
|
||||
if (import.meta.env.DEV) console.warn('[Store] WebSocket connection failed after TOTP login, will retry:', err)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Install store — tracks in-progress app installations across navigation.
|
||||
// Marketplace.vue writes here; Apps.vue reads to show "Installing..." cards.
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { reactive, computed } from 'vue'
|
||||
|
||||
export interface InstallEntry {
|
||||
id: string
|
||||
title: string
|
||||
status: 'downloading' | 'installing' | 'starting' | 'complete' | 'error'
|
||||
progress: number
|
||||
message: string
|
||||
}
|
||||
|
||||
export const useInstallStore = defineStore('install', () => {
|
||||
// Reactive map: appId -> InstallEntry
|
||||
const entries = reactive(new Map<string, InstallEntry>())
|
||||
|
||||
/** All app IDs currently installing */
|
||||
const installingIds = computed(() => new Set(entries.keys()))
|
||||
|
||||
/** Start tracking an install */
|
||||
function trackInstall(id: string, title: string) {
|
||||
entries.set(id, {
|
||||
id,
|
||||
title,
|
||||
status: 'downloading',
|
||||
progress: 0,
|
||||
message: 'Preparing installation...',
|
||||
})
|
||||
}
|
||||
|
||||
/** Update progress for an in-flight install */
|
||||
function updateProgress(id: string, update: Partial<Omit<InstallEntry, 'id'>>) {
|
||||
const current = entries.get(id)
|
||||
if (!current) return
|
||||
entries.set(id, { ...current, ...update })
|
||||
}
|
||||
|
||||
/** Mark install complete and auto-clear after delay */
|
||||
function completeInstall(id: string) {
|
||||
const current = entries.get(id)
|
||||
if (!current) return
|
||||
entries.set(id, { ...current, status: 'complete', progress: 100, message: 'Installation complete!' })
|
||||
setTimeout(() => entries.delete(id), 2000)
|
||||
}
|
||||
|
||||
/** Mark install as failed and auto-clear after delay */
|
||||
function failInstall(id: string, message: string) {
|
||||
const current = entries.get(id)
|
||||
if (!current) return
|
||||
entries.set(id, { ...current, status: 'error', progress: 0, message })
|
||||
setTimeout(() => entries.delete(id), 5000)
|
||||
}
|
||||
|
||||
/** Remove tracking (e.g. when backend reports the app is installed) */
|
||||
function clearInstall(id: string) {
|
||||
entries.delete(id)
|
||||
}
|
||||
|
||||
/** Check if an app is currently installing */
|
||||
function isInstalling(id: string): boolean {
|
||||
return entries.has(id)
|
||||
}
|
||||
|
||||
/** Get progress for an app, or undefined */
|
||||
function getProgress(id: string): InstallEntry | undefined {
|
||||
return entries.get(id)
|
||||
}
|
||||
|
||||
return {
|
||||
entries,
|
||||
installingIds,
|
||||
trackInstall,
|
||||
updateProgress,
|
||||
completeInstall,
|
||||
failInstall,
|
||||
clearInstall,
|
||||
isInstalling,
|
||||
getProgress,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user