Rename to hkt.sh
This commit is contained in:
129
projects/status-panel/server.js
Normal file
129
projects/status-panel/server.js
Normal file
@@ -0,0 +1,129 @@
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const wss = new WebSocket.Server({ server });
|
||||
|
||||
const db = new Database('status.db');
|
||||
|
||||
// 初始化数据库
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
secret TEXT NOT NULL,
|
||||
last_seen INTEGER,
|
||||
created_at INTEGER DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS stats (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
node_id INTEGER,
|
||||
cpu REAL,
|
||||
mem_used INTEGER,
|
||||
mem_total INTEGER,
|
||||
disk_used INTEGER,
|
||||
disk_total INTEGER,
|
||||
net_in INTEGER,
|
||||
net_out INTEGER,
|
||||
uptime INTEGER,
|
||||
load1 REAL,
|
||||
load5 REAL,
|
||||
load15 REAL,
|
||||
ts INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
FOREIGN KEY (node_id) REFERENCES nodes(id)
|
||||
);
|
||||
`);
|
||||
|
||||
// 生成密钥
|
||||
function genSecret() {
|
||||
return Math.random().toString(36).substr(2, 16);
|
||||
}
|
||||
|
||||
// API: 获取所有节点状态
|
||||
app.get('/api/nodes', (req, res) => {
|
||||
const nodes = db.prepare(`
|
||||
SELECT n.*, s.*
|
||||
FROM nodes n
|
||||
LEFT JOIN stats s ON n.id = s.node_id
|
||||
AND s.ts = (SELECT MAX(ts) FROM stats WHERE node_id = n.id)
|
||||
`).all();
|
||||
res.json(nodes);
|
||||
});
|
||||
|
||||
// API: 添加节点
|
||||
app.post('/api/nodes', express.json(), (req, res) => {
|
||||
const { name } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'name required' });
|
||||
|
||||
const secret = genSecret();
|
||||
try {
|
||||
const result = db.prepare('INSERT INTO nodes (name, secret) VALUES (?, ?)').run(name, secret);
|
||||
res.json({ id: result.lastInsertRowid, name, secret });
|
||||
} catch (e) {
|
||||
res.status(400).json({ error: 'name exists' });
|
||||
}
|
||||
});
|
||||
|
||||
// API: 删除节点
|
||||
app.delete('/api/nodes/:id', (req, res) => {
|
||||
db.prepare('DELETE FROM nodes WHERE id = ?').run(req.params.id);
|
||||
db.prepare('DELETE FROM stats WHERE node_id = ?').run(req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// Agent 上报接口
|
||||
app.post('/report', express.json(), (req, res) => {
|
||||
const { secret, cpu, mem_used, mem_total, disk_used, disk_total, net_in, net_out, uptime, load1, load5, load15 } = req.body;
|
||||
|
||||
const node = db.prepare('SELECT * FROM nodes WHERE secret = ?').get(secret);
|
||||
if (!node) return res.status(403).json({ error: 'invalid secret' });
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
db.prepare('UPDATE nodes SET last_seen = ? WHERE id = ?').run(now, node.id);
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO stats (node_id, cpu, mem_used, mem_total, disk_used, disk_total, net_in, net_out, uptime, load1, load5, load15)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(node.id, cpu, mem_used, mem_total, disk_used, disk_total, net_in, net_out, uptime, load1, load5, load15);
|
||||
|
||||
// 清理7天前的数据
|
||||
db.prepare('DELETE FROM stats WHERE ts < ?').run(now - 86400 * 7);
|
||||
|
||||
// WebSocket 广播
|
||||
broadcast({ type: 'update', node_id: node.id, data: req.body });
|
||||
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// WebSocket 广播
|
||||
function broadcast(data) {
|
||||
const msg = JSON.stringify(data);
|
||||
wss.clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Agent 脚本下载
|
||||
app.get('/agent.sh', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'agent.sh'));
|
||||
});
|
||||
|
||||
// 静态文件
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// 首页
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3800;
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Status panel running on http://0.0.0.0:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user