feat: cloud native file browser, settings Claude auth, deploy hardening

- Add native Cloud file browser with FileBrowser API integration
- Add cloud store, filebrowser-client, useAudioPlayer, useFileType composables
- Add Cloud components: FileGrid, FileCard, FileCardGrid, CloudToolbar
- Add Claude authentication section to Settings with OAuth status check
- Harden deploy script to preserve /aiui/ and claude-login.html
- Add nginx proxies for btcpay, homeassistant, filebrowser (HTTPS block)
- Add app configs for filebrowser, searxng, penpot in package.rs
- Update goal progress tracking with app aliases
- Improve mobile back button composable with ResizeObserver
- Update various views with cloud integration and UI refinements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-04 23:05:01 +00:00
co-authored by Claude Opus 4.6
parent 173bf8fc0f
commit d7ff678e9d
26 changed files with 2053 additions and 265 deletions
+41 -2
View File
@@ -6,6 +6,19 @@ import { useAppStore } from './app'
const STORAGE_KEY = 'archipelago-goal-progress'
/** App ID aliases — goal definitions use canonical IDs but the backend may register under variant names */
const APP_ALIASES: Record<string, string[]> = {
immich: ['immich-server', 'immich-app', 'immich_server'],
nextcloud: ['nextcloud-aio', 'nextcloud-server'],
'bitcoin-knots': ['bitcoin', 'bitcoin-core'],
}
function matchesAppId(pkgId: string, appId: string): boolean {
if (pkgId === appId) return true
const aliases = APP_ALIASES[appId]
return aliases ? aliases.includes(pkgId) : false
}
export const useGoalStore = defineStore('goals', () => {
const progress = ref<Record<string, GoalProgress>>({})
@@ -34,15 +47,41 @@ export const useGoalStore = defineStore('goals', () => {
const appStore = useAppStore()
const packages = appStore.packages
// Auto-sync install step completion from actual package state
// This ensures steps tick when apps are installed outside the wizard
let didSync = false
for (const step of goal.steps) {
if (step.appId && step.action === 'install') {
const isInstalled = Object.keys(packages).some((pkgId) => matchesAppId(pkgId, step.appId!))
if (isInstalled) {
if (!progress.value[goalId]) {
progress.value[goalId] = {
goalId,
status: 'in-progress',
currentStepIndex: 0,
completedSteps: [],
startedAt: Date.now(),
}
didSync = true
}
if (!progress.value[goalId].completedSteps.includes(step.id)) {
progress.value[goalId].completedSteps.push(step.id)
didSync = true
}
}
}
}
if (didSync) save()
const allRunning = goal.requiredApps.every((appId) =>
Object.entries(packages).some(
([pkgId, pkg]) => pkgId === appId && pkg.state === 'running',
([pkgId, pkg]) => matchesAppId(pkgId, appId) && pkg.state === 'running',
),
)
if (allRunning) return 'completed'
const anyInstalled = goal.requiredApps.some((appId) =>
Object.keys(packages).some((pkgId) => pkgId === appId),
Object.keys(packages).some((pkgId) => matchesAppId(pkgId, appId)),
)
if (anyInstalled || progress.value[goalId]) return 'in-progress'