Implement bundled app management in RPC and UI
- Added new RPC methods for starting and stopping bundled apps, allowing management of pre-loaded container images. - Enhanced container listing logic to include a fallback to Podman for bundled apps. - Updated the UI to display bundled apps with their respective statuses, including start and stop functionality. - Introduced a new Pinia store structure to manage loading states and app statuses for bundled applications. - Refactored existing components to improve user experience and streamline app management.
This commit is contained in:
@@ -1,13 +1,59 @@
|
||||
// Pinia store for container management
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { containerClient, type ContainerStatus, type ContainerAppInfo } from '@/api/container-client'
|
||||
import { containerClient, type ContainerStatus } from '@/api/container-client'
|
||||
|
||||
// Bundled apps that come pre-loaded with Archipelago
|
||||
export interface BundledApp {
|
||||
id: string
|
||||
name: string
|
||||
image: string
|
||||
description: string
|
||||
icon: string
|
||||
ports: { host: number; container: number }[]
|
||||
volumes: { host: string; container: string }[]
|
||||
category: 'bitcoin' | 'lightning' | 'home' | 'other'
|
||||
}
|
||||
|
||||
export const BUNDLED_APPS: BundledApp[] = [
|
||||
{
|
||||
id: 'bitcoin-knots',
|
||||
name: 'Bitcoin Knots',
|
||||
image: 'localhost/bitcoinknots/bitcoin:29',
|
||||
description: 'Full Bitcoin node with additional features',
|
||||
icon: '₿',
|
||||
ports: [{ host: 8332, container: 8332 }, { host: 8333, container: 8333 }],
|
||||
volumes: [{ host: '/var/lib/archipelago/bitcoin', container: '/data' }],
|
||||
category: 'bitcoin',
|
||||
},
|
||||
{
|
||||
id: 'lnd',
|
||||
name: 'Lightning (LND)',
|
||||
image: 'docker.io/lightninglabs/lnd:v0.18.4-beta',
|
||||
description: 'Lightning Network Daemon for fast Bitcoin payments',
|
||||
icon: '⚡',
|
||||
ports: [{ host: 9735, container: 9735 }, { host: 10009, container: 10009 }],
|
||||
volumes: [{ host: '/var/lib/archipelago/lnd', container: '/root/.lnd' }],
|
||||
category: 'lightning',
|
||||
},
|
||||
{
|
||||
id: 'homeassistant',
|
||||
name: 'Home Assistant',
|
||||
image: 'ghcr.io/home-assistant/home-assistant:stable',
|
||||
description: 'Open source home automation platform',
|
||||
icon: '🏠',
|
||||
ports: [{ host: 8123, container: 8123 }],
|
||||
volumes: [{ host: '/var/lib/archipelago/homeassistant', container: '/config' }],
|
||||
category: 'home',
|
||||
},
|
||||
]
|
||||
|
||||
export const useContainerStore = defineStore('container', () => {
|
||||
// State
|
||||
const containers = ref<ContainerStatus[]>([])
|
||||
const healthStatus = ref<Record<string, string>>({})
|
||||
const loading = ref(false)
|
||||
const loadingApps = ref<Set<string>>(new Set()) // Track loading state per app
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// Getters
|
||||
@@ -27,6 +73,28 @@ export const useContainerStore = defineStore('container', () => {
|
||||
healthStatus.value[appId] || 'unknown'
|
||||
)
|
||||
|
||||
// Get container for a bundled app (matches by name)
|
||||
const getContainerForApp = computed(() => (appId: string) => {
|
||||
return containers.value.find(c =>
|
||||
c.name === appId ||
|
||||
c.name.includes(appId) ||
|
||||
c.name === `archipelago-${appId}` ||
|
||||
c.name === `archipelago-${appId}-dev`
|
||||
)
|
||||
})
|
||||
|
||||
// Check if an app is currently loading (starting/stopping)
|
||||
const isAppLoading = computed(() => (appId: string) =>
|
||||
loadingApps.value.has(appId)
|
||||
)
|
||||
|
||||
// Get app state: 'running', 'stopped', 'not-installed'
|
||||
const getAppState = computed(() => (appId: string) => {
|
||||
const container = getContainerForApp.value(appId)
|
||||
if (!container) return 'not-installed'
|
||||
return container.state
|
||||
})
|
||||
|
||||
// Actions
|
||||
async function fetchContainers() {
|
||||
loading.value = true
|
||||
@@ -65,7 +133,7 @@ export const useContainerStore = defineStore('container', () => {
|
||||
}
|
||||
|
||||
async function startContainer(appId: string) {
|
||||
loading.value = true
|
||||
loadingApps.value.add(appId)
|
||||
error.value = null
|
||||
try {
|
||||
await containerClient.startContainer(appId)
|
||||
@@ -75,12 +143,12 @@ export const useContainerStore = defineStore('container', () => {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to start container'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
loadingApps.value.delete(appId)
|
||||
}
|
||||
}
|
||||
|
||||
async function stopContainer(appId: string) {
|
||||
loading.value = true
|
||||
loadingApps.value.add(appId)
|
||||
error.value = null
|
||||
try {
|
||||
await containerClient.stopContainer(appId)
|
||||
@@ -89,7 +157,38 @@ export const useContainerStore = defineStore('container', () => {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to stop container'
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
loadingApps.value.delete(appId)
|
||||
}
|
||||
}
|
||||
|
||||
// Start a bundled app (creates and starts container)
|
||||
async function startBundledApp(app: BundledApp) {
|
||||
loadingApps.value.add(app.id)
|
||||
error.value = null
|
||||
try {
|
||||
await containerClient.startBundledApp(app)
|
||||
await fetchContainers()
|
||||
await fetchHealthStatus()
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to start app'
|
||||
throw e
|
||||
} finally {
|
||||
loadingApps.value.delete(app.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop a bundled app
|
||||
async function stopBundledApp(appId: string) {
|
||||
loadingApps.value.add(appId)
|
||||
error.value = null
|
||||
try {
|
||||
await containerClient.stopBundledApp(appId)
|
||||
await fetchContainers()
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to stop app'
|
||||
throw e
|
||||
} finally {
|
||||
loadingApps.value.delete(appId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,17 +215,30 @@ export const useContainerStore = defineStore('container', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function getContainerStatus(appId: string) {
|
||||
try {
|
||||
return await containerClient.getContainerStatus(appId)
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : 'Failed to get status'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
containers,
|
||||
healthStatus,
|
||||
loading,
|
||||
loadingApps,
|
||||
error,
|
||||
// Getters
|
||||
runningContainers,
|
||||
stoppedContainers,
|
||||
getContainerById,
|
||||
getHealthStatus,
|
||||
getContainerForApp,
|
||||
isAppLoading,
|
||||
getAppState,
|
||||
// Actions
|
||||
fetchContainers,
|
||||
fetchHealthStatus,
|
||||
@@ -135,5 +247,8 @@ export const useContainerStore = defineStore('container', () => {
|
||||
stopContainer,
|
||||
removeContainer,
|
||||
getContainerLogs,
|
||||
getContainerStatus,
|
||||
startBundledApp,
|
||||
stopBundledApp,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user