fix: startup order, health endpoint, real metrics, settings warning, chart history
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -39,10 +39,14 @@ app.use('/api', apiRoutes);
|
|||||||
app.use(notFound);
|
app.use(notFound);
|
||||||
app.use(errorHandler);
|
app.use(errorHandler);
|
||||||
|
|
||||||
app.listen(port, async () => {
|
async function start() {
|
||||||
await runMigrations();
|
await runMigrations();
|
||||||
console.log(`⚡️ Server is running on port ${port}`);
|
app.listen(port, () => {
|
||||||
console.log(`🌍 Environment: ${process.env.NODE_ENV || 'development'}`);
|
console.log(`⚡️ Server is running on port ${port}`);
|
||||||
});
|
console.log(`🌍 Environment: ${process.env.NODE_ENV || 'development'}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start().catch(console.error);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import metricsRouter from './metrics';
|
|||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
router.get('/health', (req, res) => {
|
||||||
|
res.json({ status: 'ok', timestamp: new Date().toISOString(), uptime: process.uptime() });
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
message: 'Server Manager API',
|
message: 'Server Manager API',
|
||||||
|
|||||||
@@ -1,17 +1,29 @@
|
|||||||
import { Router, Request, Response } from 'express';
|
import { Router, Request, Response } from 'express';
|
||||||
|
import os from 'os';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// GET /api/metrics - Get system metrics
|
// GET /api/metrics - Get system metrics
|
||||||
router.get('/', async (req: Request, res: Response) => {
|
router.get('/', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
// TODO: Implement system metrics (CPU, memory, disk, network)
|
const cpus = os.cpus();
|
||||||
|
const loadAvg = os.loadavg()[0];
|
||||||
|
const cpuUsage = Math.min(Math.round((loadAvg / cpus.length) * 100), 100);
|
||||||
|
|
||||||
|
const totalMem = os.totalmem();
|
||||||
|
const freeMem = os.freemem();
|
||||||
|
const usedMem = totalMem - freeMem;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
cpu: { usage: 0, cores: 0 },
|
cpu: { usage: cpuUsage, cores: cpus.length },
|
||||||
memory: { used: 0, total: 0, percentage: 0 },
|
memory: {
|
||||||
|
used: Math.round(usedMem / 1024 / 1024),
|
||||||
|
total: Math.round(totalMem / 1024 / 1024),
|
||||||
|
percentage: Math.round((usedMem / totalMem) * 100),
|
||||||
|
},
|
||||||
disk: { used: 0, total: 0, percentage: 0 },
|
disk: { used: 0, total: 0, percentage: 0 },
|
||||||
network: { rx: 0, tx: 0 },
|
network: { rx: 0, tx: 0 },
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: 'Failed to fetch metrics' });
|
res.status(500).json({ error: 'Failed to fetch metrics' });
|
||||||
@@ -21,11 +33,7 @@ router.get('/', async (req: Request, res: Response) => {
|
|||||||
// GET /api/metrics/:serverId - Get metrics for specific server
|
// GET /api/metrics/:serverId - Get metrics for specific server
|
||||||
router.get('/:serverId', async (req: Request, res: Response) => {
|
router.get('/:serverId', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
// TODO: Implement per-server metrics
|
res.json({ serverId: req.params.serverId, metrics: {} });
|
||||||
res.json({
|
|
||||||
serverId: req.params.serverId,
|
|
||||||
metrics: {}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: 'Failed to fetch server metrics' });
|
res.status(500).json({ error: 'Failed to fetch server metrics' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
import { api } from '@/lib/api'
|
import { api } from '@/lib/api'
|
||||||
import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
|
import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
|
||||||
import { Cpu, HardDrive, MemoryStick, Network } from 'lucide-react'
|
import { Cpu, HardDrive, MemoryStick, Network } from 'lucide-react'
|
||||||
@@ -28,21 +29,31 @@ function MetricCard({ label, value, max, unit, icon: Icon, color }: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Metrics() {
|
export default function Metrics() {
|
||||||
|
const [history, setHistory] = useState<Array<{ t: number; cpu: number; mem: number }>>([]);
|
||||||
|
|
||||||
const { data } = useQuery({
|
const { data } = useQuery({
|
||||||
queryKey: ['metrics'],
|
queryKey: ['metrics'],
|
||||||
queryFn: () => api.get('/metrics').then(r => r.data),
|
queryFn: () => api.get('/metrics').then(r => r.data),
|
||||||
refetchInterval: 10000,
|
refetchInterval: 10000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) return
|
||||||
|
setHistory(prev => {
|
||||||
|
const next = [...prev, {
|
||||||
|
t: Date.now(),
|
||||||
|
cpu: data.cpu?.usage ?? 0,
|
||||||
|
mem: data.memory?.percentage ?? 0,
|
||||||
|
}]
|
||||||
|
return next.slice(-20)
|
||||||
|
})
|
||||||
|
}, [data])
|
||||||
|
|
||||||
const cpu = data?.cpu ?? { usage: 0, cores: 2 }
|
const cpu = data?.cpu ?? { usage: 0, cores: 2 }
|
||||||
const mem = data?.memory ?? { used: 0, total: 3800 }
|
const mem = data?.memory ?? { used: 0, total: 3800 }
|
||||||
const disk = data?.disk ?? { used: 0, total: 116000 }
|
const disk = data?.disk ?? { used: 0, total: 116000 }
|
||||||
|
|
||||||
const chartData = Array.from({ length: 20 }, (_, i) => ({
|
const chartData = history.length > 1 ? history : [{ t: 0, cpu: 0, mem: 0 }, { t: 1, cpu: 0, mem: 0 }]
|
||||||
t: i,
|
|
||||||
cpu: Math.random() * 30 + 5,
|
|
||||||
mem: Math.random() * 20 + 40,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export default function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs text-[hsl(215_20.2%_65.1%)] mb-1">Refresh Interval</label>
|
<label className="block text-xs text-[hsl(215_20.2%_65.1%)] mb-1">Refresh Interval</label>
|
||||||
<select className="w-full px-3 py-2 rounded-lg bg-[hsl(217.2_32.6%_10%)] border border-[hsl(217.2_32.6%_17.5%)] text-white text-sm focus:outline-none focus:border-blue-500">
|
<select defaultValue="30" className="w-full px-3 py-2 rounded-lg bg-[hsl(217.2_32.6%_10%)] border border-[hsl(217.2_32.6%_17.5%)] text-white text-sm focus:outline-none focus:border-blue-500">
|
||||||
<option value="10">10 seconds</option>
|
<option value="10">10 seconds</option>
|
||||||
<option value="30" selected>30 seconds</option>
|
<option value="30">30 seconds</option>
|
||||||
<option value="60">1 minute</option>
|
<option value="60">1 minute</option>
|
||||||
<option value="300">5 minutes</option>
|
<option value="300">5 minutes</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
Reference in New Issue
Block a user