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:
2026-02-25 14:07:11 -08:00
parent df02542c26
commit 0910c966a5
37 changed files with 1884 additions and 645 deletions

View File

@@ -1,76 +1,31 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { homedir } from 'os';
import { PrismaClient } from '@prisma/client';
import { HostConfig } from './types';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const CONFIG_FILE = path.join(__dirname, 'hosts.json');
function parseEnvHosts(): HostConfig[] {
const hostsEnv = process.env.SSH_HOSTS;
if (!hostsEnv) return [];
const hosts: HostConfig[] = [];
const entries = hostsEnv.split(',').map(h => h.trim()).filter(Boolean);
for (const entry of entries) {
const [name, ip] = entry.split(':');
if (name && ip) {
hosts.push({
name: name.trim(),
ip: ip.trim(),
sshUser: process.env.SSH_USER || 'bear',
sshKeyPath: process.env.SSH_KEY,
sshPort: process.env.SSH_PORT ? parseInt(process.env.SSH_PORT, 10) : 22,
});
}
}
return hosts;
}
function parseJsonConfig(): HostConfig[] {
if (!fs.existsSync(CONFIG_FILE)) {
console.error('Config file not found:', CONFIG_FILE);
return [];
}
const prisma = new PrismaClient();
export async function getHostConfigs(): Promise<HostConfig[]> {
try {
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
const data = JSON.parse(content);
if (!data.hosts || !Array.isArray(data.hosts)) {
console.error('No hosts array in config');
return [];
}
const hosts = data.hosts.map((h: Partial<HostConfig>) => ({
name: h.name || '',
ip: h.ip || '',
const dbConfigs = await prisma.hostConfig.findMany();
return dbConfigs.map(h => ({
name: h.name,
ip: h.ip,
sshUser: h.sshUser || 'bear',
sshKeyPath: h.sshKeyPath?.replace(/^~/, homedir()),
sshPort: h.sshPort || 22,
})).filter((h: HostConfig) => h.name && h.ip);
console.error('Loaded hosts:', JSON.stringify(hosts));
return hosts;
} catch (e: any) {
console.error('Config parse error:', e.message);
hostType: h.hostType,
}));
} catch (error) {
console.error('Error fetching host configs from DB:', error);
return [];
}
}
export function getHostConfigs(): HostConfig[] {
const envHosts = parseEnvHosts();
if (envHosts.length > 0) {
return envHosts;
export async function hasConfig(): Promise<boolean> {
try {
const count = await prisma.hostConfig.count();
return count > 0;
} catch (error) {
return false;
}
return parseJsonConfig();
}
export function hasConfig(): boolean {
return getHostConfigs().length > 0;
}