Add LoraBell app support and enhance package management in mock backend

- Introduced LoraBell as a static demo app in the mock backend, preventing its uninstallation.
- Merged static dev apps with Docker container data for improved package management.
- Updated app details and URLs for LoraBell in the Apps and AppDetails views.
- Enhanced the dummyApps utility to include LoraBell's configuration for consistent app representation.
This commit is contained in:
Dorian
2026-02-18 08:30:12 +00:00
parent 2472af790b
commit 3da0d53952
8 changed files with 190 additions and 8 deletions

View File

@@ -484,6 +484,9 @@ async function uninstallPackage(id) {
console.log(`[Package] 🗑️ Uninstalling ${id}...`)
try {
if (staticDevApps[id]) {
throw new Error(`${id} is a demo app and cannot be uninstalled`)
}
if (!mockData['package-data'][id]) {
throw new Error(`Package ${id} is not installed`)
}
@@ -566,20 +569,74 @@ const mockData = {
},
}
// Static dev apps (always shown in My Apps when using mock backend)
const staticDevApps = {
'lorabell': {
title: 'LoraBell',
version: '1.0.0',
status: 'running',
state: 'running',
'static-files': {
license: 'MIT',
instructions: 'A LoRa based doorbell',
icon: '/assets/img/app-icons/lorabell.png'
},
manifest: {
id: 'lorabell',
title: 'LoraBell',
version: '1.0.0',
description: {
short: 'A LoRa based doorbell',
long: 'A LoRa based doorbell - receive doorbell notifications over LoRa radio.'
},
'release-notes': 'Initial release',
license: 'MIT',
'wrapper-repo': '#',
'upstream-repo': '#',
'support-site': '#',
'marketing-site': '#',
'donation-url': null,
interfaces: {
main: {
name: 'Web Interface',
description: 'LoraBell web interface',
ui: true
}
}
},
installed: {
'current-dependents': {},
'current-dependencies': {},
'last-backup': null,
'interface-addresses': {
main: {
'tor-address': 'lorabell.onion',
'lan-address': '/lorabell-info.html'
}
},
status: 'running'
}
}
}
function mergePackageData(dockerApps) {
return { ...dockerApps, ...staticDevApps }
}
// Initialize package data from Docker on startup
async function initializePackageData() {
console.log('[Docker] Querying running containers...')
const dockerApps = await getDockerContainers()
mockData['package-data'] = dockerApps
mockData['package-data'] = mergePackageData(dockerApps)
const appCount = Object.keys(dockerApps).length
const runningCount = Object.values(dockerApps).filter(app => app.state === 'running').length
const appCount = Object.keys(mockData['package-data']).length
const runningCount = Object.values(mockData['package-data']).filter(app => app.state === 'running').length
console.log(`[Docker] Found ${appCount} containers (${runningCount} running)`)
if (appCount > 0) {
console.log('[Docker] Apps detected:')
Object.entries(dockerApps).forEach(([id, app]) => {
Object.entries(mockData['package-data']).forEach(([id, app]) => {
const port = app.installed?.['interface-addresses']?.main?.['lan-address']
console.log(` - ${app.title} (${app.state})${port ? `${port}` : ''}`)
})
@@ -884,17 +941,17 @@ server.listen(PORT, '0.0.0.0', async () => {
`)
console.log('Mock backend is running. Press Ctrl+C to stop.\n')
// Periodically update package data from Docker
// Periodically update package data from Docker (merge with static dev apps)
setInterval(async () => {
const dockerApps = await getDockerContainers()
mockData['package-data'] = dockerApps
mockData['package-data'] = mergePackageData(dockerApps)
// Broadcast update to connected clients
broadcastUpdate([
{
op: 'replace',
path: '/package-data',
value: dockerApps
value: mockData['package-data']
}
])
}, 5000) // Update every 5 seconds

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LoraBell</title>
<style>
body { background: #000; color: #fff; font-family: system-ui, sans-serif; padding: 2rem; max-width: 32rem; margin: 0 auto; }
h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem; }
p { color: rgba(255,255,255,0.8); margin-bottom: 0.5rem; }
.muted { color: rgba(255,255,255,0.6); font-size: 0.875rem; }
</style>
</head>
<body>
<h1>LoraBell</h1>
<p>A LoRa based doorbell</p>
<p class="muted">This device has no web interface. It operates over LoRa radio and sends doorbell notifications to your node.</p>
</body>
</html>

View File

@@ -114,6 +114,42 @@ export const dummyApps: Record<string, PackageDataEntry> = {
status: ServiceStatus.Running
}
},
'lorabell': {
state: PackageState.Running,
'static-files': {
license: 'MIT',
instructions: 'A LoRa based doorbell',
icon: '/assets/img/app-icons/lorabell.png'
},
manifest: {
id: 'lorabell',
title: 'LoraBell',
version: '1.0.0',
description: {
short: 'A LoRa based doorbell',
long: 'A LoRa based doorbell - receive doorbell notifications over LoRa radio.'
},
'release-notes': 'Initial release',
license: 'MIT',
'wrapper-repo': '#',
'upstream-repo': '#',
'support-site': '#',
'marketing-site': '#',
'donation-url': null
},
installed: {
'current-dependents': {},
'current-dependencies': {},
'last-backup': null,
'interface-addresses': {
main: {
'tor-address': 'lorabell.onion',
'lan-address': '/lorabell-info.html'
}
},
status: ServiceStatus.Running
}
},
'grafana': {
state: PackageState.Running,
'static-files': {

View File

@@ -613,6 +613,10 @@ function launchApp() {
// Special handling for apps with Docker containers
// TODO: Replace dummy app URLs with real URLs when apps are packaged
const appUrls: Record<string, { dev: string, prod: string }> = {
'lorabell': {
dev: '/lorabell-info.html',
prod: '/lorabell-info.html'
},
'atob': {
dev: 'http://localhost:8102',
prod: 'https://app.atobitcoin.io'

View File

@@ -244,8 +244,12 @@ function launchApp(id: string) {
return
}
// Fallback: Special handling for apps with Docker containers
// Fallback: Special handling for apps with Docker containers / no-web-interface devices
const appUrls: Record<string, { dev: string, prod: string }> = {
'lorabell': {
dev: '/lorabell-info.html',
prod: '/lorabell-info.html'
},
'atob': {
dev: 'http://localhost:8102',
prod: 'https://app.atobitcoin.io'