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:
parent
2472af790b
commit
3da0d53952
36
DEMO-DEPLOY.md
Normal file
36
DEMO-DEPLOY.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Demo Deployment via Portainer
|
||||||
|
|
||||||
|
Deploy Archipelago with the **mock backend** for demos. No real node required.
|
||||||
|
|
||||||
|
## Quick Deploy (Portainer)
|
||||||
|
|
||||||
|
1. In Portainer: **Stacks** → **Add stack**
|
||||||
|
2. Name: `archy-demo`
|
||||||
|
3. **Web editor** → paste contents of `docker-compose.demo.yml`
|
||||||
|
4. Or **Build from repository**: use this repo URL and set Compose path to `docker-compose.demo.yml`
|
||||||
|
5. Deploy
|
||||||
|
|
||||||
|
**Access:** http://your-host:4848
|
||||||
|
|
||||||
|
## Mock Backend
|
||||||
|
|
||||||
|
- Uses the Node.js mock backend (not the Rust backend)
|
||||||
|
- Pre-loaded apps, fake data, simulated install/start/stop
|
||||||
|
- **Login password:** `password123`
|
||||||
|
|
||||||
|
## Port
|
||||||
|
|
||||||
|
Default: **4848**. To change, edit the ports mapping in `docker-compose.demo.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ports:
|
||||||
|
- "YOUR_PORT:80"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dev Mode
|
||||||
|
|
||||||
|
`VITE_DEV_MODE=existing` skips setup/onboarding and goes straight to login. For other flows:
|
||||||
|
|
||||||
|
- `setup` – Password setup screen first
|
||||||
|
- `onboarding` – Experimental onboarding flow
|
||||||
|
- `existing` – Login only (default for demo)
|
||||||
26
docker-compose.demo.yml
Normal file
26
docker-compose.demo.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Archipelago Demo Stack - Mock backend + Vue UI
|
||||||
|
# Deploy via Portainer: Web editor → paste this, or deploy from repo
|
||||||
|
# Access at http://localhost:4848 (or your host:4848)
|
||||||
|
|
||||||
|
services:
|
||||||
|
neode-backend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: neode-ui/Dockerfile.backend
|
||||||
|
container_name: archy-demo-backend
|
||||||
|
environment:
|
||||||
|
VITE_DEV_MODE: "existing" # Skip setup/onboarding, go straight to login
|
||||||
|
expose:
|
||||||
|
- "5959"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
neode-web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: neode-ui/Dockerfile.web
|
||||||
|
container_name: archy-demo-web
|
||||||
|
ports:
|
||||||
|
- "4848:80"
|
||||||
|
depends_on:
|
||||||
|
- neode-backend
|
||||||
|
restart: unless-stopped
|
||||||
@ -484,6 +484,9 @@ async function uninstallPackage(id) {
|
|||||||
console.log(`[Package] 🗑️ Uninstalling ${id}...`)
|
console.log(`[Package] 🗑️ Uninstalling ${id}...`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (staticDevApps[id]) {
|
||||||
|
throw new Error(`${id} is a demo app and cannot be uninstalled`)
|
||||||
|
}
|
||||||
if (!mockData['package-data'][id]) {
|
if (!mockData['package-data'][id]) {
|
||||||
throw new Error(`Package ${id} is not installed`)
|
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
|
// Initialize package data from Docker on startup
|
||||||
async function initializePackageData() {
|
async function initializePackageData() {
|
||||||
console.log('[Docker] Querying running containers...')
|
console.log('[Docker] Querying running containers...')
|
||||||
const dockerApps = await getDockerContainers()
|
const dockerApps = await getDockerContainers()
|
||||||
mockData['package-data'] = dockerApps
|
mockData['package-data'] = mergePackageData(dockerApps)
|
||||||
|
|
||||||
const appCount = Object.keys(dockerApps).length
|
const appCount = Object.keys(mockData['package-data']).length
|
||||||
const runningCount = Object.values(dockerApps).filter(app => app.state === 'running').length
|
const runningCount = Object.values(mockData['package-data']).filter(app => app.state === 'running').length
|
||||||
|
|
||||||
console.log(`[Docker] Found ${appCount} containers (${runningCount} running)`)
|
console.log(`[Docker] Found ${appCount} containers (${runningCount} running)`)
|
||||||
|
|
||||||
if (appCount > 0) {
|
if (appCount > 0) {
|
||||||
console.log('[Docker] Apps detected:')
|
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']
|
const port = app.installed?.['interface-addresses']?.main?.['lan-address']
|
||||||
console.log(` - ${app.title} (${app.state})${port ? ` → ${port}` : ''}`)
|
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')
|
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 () => {
|
setInterval(async () => {
|
||||||
const dockerApps = await getDockerContainers()
|
const dockerApps = await getDockerContainers()
|
||||||
mockData['package-data'] = dockerApps
|
mockData['package-data'] = mergePackageData(dockerApps)
|
||||||
|
|
||||||
// Broadcast update to connected clients
|
// Broadcast update to connected clients
|
||||||
broadcastUpdate([
|
broadcastUpdate([
|
||||||
{
|
{
|
||||||
op: 'replace',
|
op: 'replace',
|
||||||
path: '/package-data',
|
path: '/package-data',
|
||||||
value: dockerApps
|
value: mockData['package-data']
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}, 5000) // Update every 5 seconds
|
}, 5000) // Update every 5 seconds
|
||||||
|
|||||||
BIN
neode-ui/public/assets/img/app-icons/lorabell.png
Normal file
BIN
neode-ui/public/assets/img/app-icons/lorabell.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
19
neode-ui/public/lorabell-info.html
Normal file
19
neode-ui/public/lorabell-info.html
Normal 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>
|
||||||
@ -114,6 +114,42 @@ export const dummyApps: Record<string, PackageDataEntry> = {
|
|||||||
status: ServiceStatus.Running
|
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': {
|
'grafana': {
|
||||||
state: PackageState.Running,
|
state: PackageState.Running,
|
||||||
'static-files': {
|
'static-files': {
|
||||||
|
|||||||
@ -613,6 +613,10 @@ function launchApp() {
|
|||||||
// Special handling for apps with Docker containers
|
// Special handling for apps with Docker containers
|
||||||
// TODO: Replace dummy app URLs with real URLs when apps are packaged
|
// TODO: Replace dummy app URLs with real URLs when apps are packaged
|
||||||
const appUrls: Record<string, { dev: string, prod: string }> = {
|
const appUrls: Record<string, { dev: string, prod: string }> = {
|
||||||
|
'lorabell': {
|
||||||
|
dev: '/lorabell-info.html',
|
||||||
|
prod: '/lorabell-info.html'
|
||||||
|
},
|
||||||
'atob': {
|
'atob': {
|
||||||
dev: 'http://localhost:8102',
|
dev: 'http://localhost:8102',
|
||||||
prod: 'https://app.atobitcoin.io'
|
prod: 'https://app.atobitcoin.io'
|
||||||
|
|||||||
@ -244,8 +244,12 @@ function launchApp(id: string) {
|
|||||||
return
|
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 }> = {
|
const appUrls: Record<string, { dev: string, prod: string }> = {
|
||||||
|
'lorabell': {
|
||||||
|
dev: '/lorabell-info.html',
|
||||||
|
prod: '/lorabell-info.html'
|
||||||
|
},
|
||||||
'atob': {
|
'atob': {
|
||||||
dev: 'http://localhost:8102',
|
dev: 'http://localhost:8102',
|
||||||
prod: 'https://app.atobitcoin.io'
|
prod: 'https://app.atobitcoin.io'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user