feat: migrate from static config to database and add authentication
- Replaced static hosts.json and staticConfig.ts with SQLite database (Prisma) - Implemented JWT authentication and Login UI - Added dynamic API routes for hosts, topology, and settings - Updated UI components to fetch and manage state dynamically - Added Settings interface for managing hosts and topology nodes
This commit is contained in:
@@ -7,10 +7,13 @@
|
||||
import { Router } from 'express';
|
||||
import { execSync } from 'child_process';
|
||||
import { homedir } from 'os';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { getHostConfigs } from '../config';
|
||||
import { DiscoveryResponse } from '../types';
|
||||
import { io } from '../index';
|
||||
|
||||
const router = Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
interface HostDiscoveryResult {
|
||||
name: string;
|
||||
@@ -35,11 +38,11 @@ async function discoverHost(
|
||||
console.error(`DEBUG: ${name} keyPath=${keyPath}, user=${sshUser}`);
|
||||
const keyArg = `-i ${keyPath}`;
|
||||
const portArg = sshPort && sshPort !== 22 ? `-p ${sshPort}` : '';
|
||||
|
||||
|
||||
const dockerCmd = `ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no ${keyArg} ${portArg} ${sshUser}@${ip} "docker ps --format '{{.Names}}'" 2>/dev/null`;
|
||||
const dockerOutput = execSync(dockerCmd, { encoding: 'utf-8', timeout: 15000 });
|
||||
const containers = dockerOutput.trim().split('\n').filter(c => c.trim());
|
||||
|
||||
|
||||
let services: string[] = [];
|
||||
if (hostType !== 'proxmox') {
|
||||
try {
|
||||
@@ -50,7 +53,7 @@ async function discoverHost(
|
||||
console.error(`DEBUG: ${name} systemd discovery failed`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let vms: Array<{ id: string; name: string; status: string; type: 'lxc' | 'qemu' }> = [];
|
||||
if (hostType === 'proxmox' || name === 'proxmox') {
|
||||
try {
|
||||
@@ -63,7 +66,7 @@ async function discoverHost(
|
||||
vms.push({ id: parts[0], name: parts[1], status: parts[2] || 'unknown', type: 'lxc' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const vmCmd = `ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no ${keyArg} ${portArg} ${sshUser}@${ip} "qm list" 2>/dev/null`;
|
||||
const vmOutput = execSync(vmCmd, { encoding: 'utf-8', timeout: 10000 });
|
||||
const vmLines = vmOutput.trim().split('\n').slice(1);
|
||||
@@ -77,7 +80,7 @@ async function discoverHost(
|
||||
console.error(`DEBUG: ${name} Proxmox discovery failed`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
name,
|
||||
ip,
|
||||
@@ -97,18 +100,53 @@ async function discoverHost(
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/discover - Discover all hosts via SSH
|
||||
// POST /api/discover - Trigger discovery but return cached immediately
|
||||
router.post('/discover', async (req, res) => {
|
||||
try {
|
||||
const hosts = getHostConfigs();
|
||||
|
||||
// 1. Fetch from cache immediately for fast loading
|
||||
const cachedSetting = await prisma.settings.findUnique({ where: { key: 'last_discovery' } });
|
||||
|
||||
let previousState: DiscoveryResponse = {
|
||||
hosts: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
errors: [],
|
||||
};
|
||||
|
||||
if (cachedSetting && cachedSetting.value) {
|
||||
try {
|
||||
previousState = JSON.parse(cachedSetting.value);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse cached discovery state');
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Return cached response to unblock the UI request
|
||||
res.json(previousState);
|
||||
|
||||
// 3. Kick off background discovery
|
||||
runBackgroundDiscovery();
|
||||
|
||||
} catch (error: any) {
|
||||
res.status(500).json({
|
||||
hosts: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
errors: [error.message || 'Cache retrieval failed'],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function runBackgroundDiscovery() {
|
||||
try {
|
||||
const hosts = await getHostConfigs();
|
||||
|
||||
if (hosts.length === 0) {
|
||||
const response: DiscoveryResponse = {
|
||||
hosts: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
errors: ['No hosts configured'],
|
||||
};
|
||||
return res.json(response);
|
||||
await cacheAndEmit(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const results: HostDiscoveryResult[] = [];
|
||||
@@ -134,7 +172,7 @@ router.post('/discover', async (req, res) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const errors: string[] = [];
|
||||
results.forEach((result: HostDiscoveryResult) => {
|
||||
if (!result.online && result.error) {
|
||||
@@ -155,15 +193,26 @@ router.post('/discover', async (req, res) => {
|
||||
errors,
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
await cacheAndEmit(response);
|
||||
} catch (error: any) {
|
||||
const response: DiscoveryResponse = {
|
||||
hosts: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
errors: [error.message || 'Discovery failed'],
|
||||
};
|
||||
res.status(500).json(response);
|
||||
console.error('Background discovery failed:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function cacheAndEmit(response: DiscoveryResponse) {
|
||||
try {
|
||||
// Cache the standard response to the DB
|
||||
await prisma.settings.upsert({
|
||||
where: { key: 'last_discovery' },
|
||||
update: { value: JSON.stringify(response) },
|
||||
create: { key: 'last_discovery', value: JSON.stringify(response) },
|
||||
});
|
||||
|
||||
// Broadcast the full update via Socket.IO
|
||||
io.emit('topology:update', response);
|
||||
} catch (err) {
|
||||
console.error('Failed to cache and emit discovery results:', err);
|
||||
}
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user