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:
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}...`)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
'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': {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user