feat(ui): add type filter toggles
This commit is contained in:
23
server/config.example.json
Normal file
23
server/config.example.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"name": "ubuntu",
|
||||
"ip": "192.168.50.61",
|
||||
"sshUser": "bear",
|
||||
"sshKeyPath": "~/.ssh/id_ed25519",
|
||||
"sshPort": 22
|
||||
},
|
||||
{
|
||||
"name": "grizzley",
|
||||
"ip": "192.168.50.84",
|
||||
"sshUser": "bear",
|
||||
"sshKeyPath": "~/.ssh/id_ed25519"
|
||||
},
|
||||
{
|
||||
"name": "truenas",
|
||||
"ip": "192.168.50.12",
|
||||
"sshUser": "root",
|
||||
"sshKeyPath": "~/.ssh/id_ed25519"
|
||||
}
|
||||
]
|
||||
}
|
||||
46
server/config.json
Normal file
46
server/config.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"hosts": [
|
||||
{
|
||||
"name": "ubuntu",
|
||||
"ip": "192.168.50.61",
|
||||
"sshUser": "bear",
|
||||
"sshKeyPath": "~/.ssh/id_ed25519",
|
||||
"sshPort": 22
|
||||
},
|
||||
{
|
||||
"name": "grizzley",
|
||||
"ip": "192.168.50.84",
|
||||
"sshUser": "bear",
|
||||
"sshKeyPath": "~/.ssh/id_ed25519",
|
||||
"sshPort": 22
|
||||
},
|
||||
{
|
||||
"name": "truenas",
|
||||
"ip": "192.168.50.12",
|
||||
"sshUser": "root",
|
||||
"sshKeyPath": "~/.ssh/id_ed25519",
|
||||
"sshPort": 22
|
||||
},
|
||||
{
|
||||
"name": "proxmox",
|
||||
"ip": "192.168.50.11",
|
||||
"sshUser": "root",
|
||||
"sshKeyPath": "~/.ssh/id_ed25519",
|
||||
"sshPort": 22
|
||||
},
|
||||
{
|
||||
"name": "ice",
|
||||
"ip": "192.168.50.197",
|
||||
"sshUser": "bear",
|
||||
"sshKeyPath": "~/.ssh/id_ed25519",
|
||||
"sshPort": 22
|
||||
},
|
||||
{
|
||||
"name": "panda",
|
||||
"ip": "192.168.50.196",
|
||||
"sshUser": "bear",
|
||||
"sshKeyPath": "~/.ssh/id_ed25519",
|
||||
"sshPort": 22
|
||||
}
|
||||
]
|
||||
}
|
||||
64
server/config.ts
Normal file
64
server/config.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { HostConfig } from './types';
|
||||
|
||||
const CONFIG_FILE = path.join(__dirname, 'config.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)) return [];
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
||||
const data = JSON.parse(content);
|
||||
|
||||
if (!data.hosts || !Array.isArray(data.hosts)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.hosts.map((h: Partial<HostConfig>) => ({
|
||||
name: h.name || '',
|
||||
ip: h.ip || '',
|
||||
sshUser: h.sshUser || 'bear',
|
||||
sshKeyPath: h.sshKeyPath,
|
||||
sshPort: h.sshPort || 22,
|
||||
})).filter((h: HostConfig) => h.name && h.ip);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getHostConfigs(): HostConfig[] {
|
||||
const envHosts = parseEnvHosts();
|
||||
if (envHosts.length > 0) {
|
||||
return envHosts;
|
||||
}
|
||||
|
||||
return parseJsonConfig();
|
||||
}
|
||||
|
||||
export function hasConfig(): boolean {
|
||||
return getHostConfigs().length > 0;
|
||||
}
|
||||
20
server/index.ts
Normal file
20
server/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
|
||||
const app = express();
|
||||
const PORT = 3001;
|
||||
|
||||
// CORS middleware for frontend communication
|
||||
app.use(cors({
|
||||
origin: 'http://localhost:3000',
|
||||
credentials: true,
|
||||
}));
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
78
server/types.ts
Normal file
78
server/types.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Backend Types for Homelab Topology Visualizer
|
||||
*
|
||||
* Type definitions for server-side operations including:
|
||||
* - Host configuration for SSH connections
|
||||
* - API response types for discovery, config, files, and stats
|
||||
*/
|
||||
|
||||
export interface HostConfig {
|
||||
/** Hostname for display in topology */
|
||||
name: string;
|
||||
/** IP address for SSH connection */
|
||||
ip: string;
|
||||
/** SSH username */
|
||||
sshUser: string;
|
||||
/** Optional path to SSH private key file */
|
||||
sshKeyPath?: string;
|
||||
/** Optional SSH port (defaults to 22) */
|
||||
sshPort?: number;
|
||||
}
|
||||
|
||||
export interface DiscoveryResponse {
|
||||
/** Array of discovered hosts with their status */
|
||||
hosts: Array<{
|
||||
name: string;
|
||||
ip: string;
|
||||
online: boolean;
|
||||
containers?: string[];
|
||||
error?: string;
|
||||
}>;
|
||||
/** Timestamp of discovery run */
|
||||
timestamp: string;
|
||||
/** Any errors encountered during discovery */
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export interface ConfigResponse {
|
||||
/** Raw YAML configuration content */
|
||||
yaml: string;
|
||||
/** Path to the config file */
|
||||
path: string;
|
||||
/** Error message if config retrieval failed */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface VolumeMount {
|
||||
/** Source path on host */
|
||||
source: string;
|
||||
/** Destination path in container */
|
||||
destination: string;
|
||||
/** Mount mode (e.g., 'rw', 'ro') */
|
||||
mode: string;
|
||||
}
|
||||
|
||||
export interface FilesResponse {
|
||||
/** Array of volume mounts */
|
||||
volumes: VolumeMount[];
|
||||
/** Error message if file retrieval failed */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface NetworkStats {
|
||||
/** Bytes received */
|
||||
rx: number;
|
||||
/** Bytes transmitted */
|
||||
tx: number;
|
||||
}
|
||||
|
||||
export interface StatsResponse {
|
||||
/** CPU usage percentage */
|
||||
cpu: number;
|
||||
/** Memory usage percentage */
|
||||
memory: number;
|
||||
/** Network I/O statistics */
|
||||
network: NetworkStats;
|
||||
/** Error message if stats retrieval failed */
|
||||
error?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user