server v3.3: 并行硬件采集修复
This commit is contained in:
parent
0600063e21
commit
53dd67a2ea
@ -11,7 +11,7 @@ const fs = require('fs');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
app.use(express.static(__dirname));
|
||||
|
||||
const DATA_DIR = '/opt/zhuyuan/data';
|
||||
try { fs.mkdirSync(DATA_DIR, { recursive: true }); } catch(e) {}
|
||||
@ -27,19 +27,12 @@ const SERVERS = [
|
||||
{ code: "AW-GZ-001", name: "企业主服务器 · 广州", ip: "43.139.251.175", key: "zy_gtw_cab20e8c1b957aaad7507bf542231521bba30665461aa540", port: 3910, spec: { cpu: 4, mem_gb: 16, disk_gb: 80 } },
|
||||
{ code: "AW-SH-002", name: "企业灾备 · 上海", ip: "124.222.54.198", key: "zy_gtw_242f1dd3b14c1260fb01196083abde3dcb85e53532d27666", port: 3910, spec: { cpu: 4, mem_gb: 4, disk_gb: 40 } },
|
||||
{ code: "AW-GZ-003", name: "企业灾备 · 广州", ip: "119.29.181.132", key: "zy_gtw_8c9a8b439af823690ae6ad57f52734771ff39493ab986032", port: 3910, spec: { cpu: 2, mem_gb: 8, disk_gb: 50 } },
|
||||
// 页页 · 硅谷(待接入)
|
||||
// { code: "YY-SV-001", name: "页页 · 硅谷", ip: "TBD", key: "TBD", port: 3910, spec: { cpu: 2, mem_gb: 4, disk_gb: 50 } },
|
||||
// 之之 · 待定
|
||||
// { code: "ZZ-XX-001", name: "之之 · 待定", ip: "TBD", key: "TBD", port: 3910, spec: { cpu: 2, mem_gb: 4, disk_gb: 50 } },
|
||||
];
|
||||
|
||||
const tempKeys = {};
|
||||
const authQueue = [];
|
||||
const hwCache = {};
|
||||
|
||||
// ── 硬件缓存 (60秒TTL) ──
|
||||
const hwCache = {}; // { code: { data, ts } }
|
||||
|
||||
// ── Gatekeeper exec 通用函数 ──
|
||||
async function gatekeeperExec(srv, cmd, timeoutMs) {
|
||||
timeoutMs = timeoutMs || 10000;
|
||||
const d = JSON.stringify({ cmd });
|
||||
@ -59,27 +52,21 @@ async function gatekeeperExec(srv, cmd, timeoutMs) {
|
||||
});
|
||||
}
|
||||
|
||||
// ── 硬件采集(通过 Gatekeeper exec) ──
|
||||
async function fetchHardware(srv) {
|
||||
const now = Date.now();
|
||||
const cached = hwCache[srv.code];
|
||||
if (cached && (now - cached.ts) < 60000) return cached.data; // 60s cache
|
||||
|
||||
if (cached && (now - cached.ts) < 60000) return cached.data;
|
||||
try {
|
||||
const cmd = "echo 'CPU_CORES:'$(nproc);echo 'UPTIME_S:'$(awk '{print int($1)}' /proc/uptime 2>/dev/null||echo 0);free -m|awk '/^Mem:/{printf \"MEM_TOTAL:%d\\nMEM_USED:%d\\nMEM_AVAIL:%d\\n\",$2,$3,$7}';cat /proc/loadavg 2>/dev/null|awk '{printf \"LOAD1:%.2f\\nLOAD5:%.2f\\nLOAD15:%.2f\\n\",$1,$2,$3}';df -h / 2>/dev/null|awk 'NR==2{printf \"DISK_TOTAL:%s\\nDISK_USED:%s\\nDISK_AVAIL:%s\\nDISK_PCT:%s\\n\",$2,$3,$4,$5}'";
|
||||
const r = await gatekeeperExec(srv, cmd, 8000);
|
||||
|
||||
if (!r.ok || !r.body || !r.body.stdout) {
|
||||
const fb = { cpu_cores: srv.spec.cpu || 2, uptime_h: 0, mem_total_mb: (srv.spec.mem_gb || 4) * 1024, mem_used_mb: 0, mem_avail_mb: (srv.spec.mem_gb || 4) * 1024, load_1min: 0, load_5min: 0, load_15min: 0, disk_total: (srv.spec.disk_gb || 50) + 'G', disk_used: '0G', disk_avail: (srv.spec.disk_gb || 50) + 'G', disk_pct: '0%', source: 'spec' };
|
||||
hwCache[srv.code] = { data: fb, ts: now };
|
||||
return fb;
|
||||
}
|
||||
|
||||
const stdout = r.body.stdout || '';
|
||||
const hw = { source: 'live' };
|
||||
|
||||
const m = (re) => { const match = stdout.match(re); return match ? match[1] : null; };
|
||||
|
||||
hw.cpu_cores = parseInt(m(/CPU_CORES:(\d+)/)) || srv.spec.cpu || 2;
|
||||
hw.uptime_h = Math.floor((parseInt(m(/UPTIME_S:(\d+)/)) || 0) / 3600);
|
||||
hw.mem_total_mb = parseInt(m(/MEM_TOTAL:(\d+)/)) || (srv.spec.mem_gb || 4) * 1024;
|
||||
@ -92,11 +79,9 @@ async function fetchHardware(srv) {
|
||||
hw.disk_used = m(/DISK_USED:(\S+)/) || '0G';
|
||||
hw.disk_avail = m(/DISK_AVAIL:(\S+)/) || hw.disk_total;
|
||||
hw.disk_pct = m(/DISK_PCT:(\S+)/) || '0%';
|
||||
|
||||
hw.mem_pct = hw.mem_total_mb > 0 ? Math.round(hw.mem_used_mb / hw.mem_total_mb * 100) : 0;
|
||||
hw.cpu_pct = hw.cpu_cores > 0 ? Math.min(Math.round(hw.load_1min / hw.cpu_cores * 100), 100) : 0;
|
||||
hw.disk_used_pct = parseInt(hw.disk_pct) || 0;
|
||||
|
||||
hwCache[srv.code] = { data: hw, ts: now };
|
||||
return hw;
|
||||
} catch (e) {
|
||||
@ -106,14 +91,13 @@ async function fetchHardware(srv) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 操作日志(滚动记录,最多100条) ──
|
||||
const opLogs = [];
|
||||
function addOpLog(code, name, action, detail, level) {
|
||||
opLogs.unshift({ time: new Date().toISOString(), code, name, action, detail, level: level || 'info' });
|
||||
if (opLogs.length > 100) opLogs.length = 100;
|
||||
}
|
||||
|
||||
// ========== 基础 API ==========
|
||||
// ========== API ==========
|
||||
|
||||
app.get('/api/servers', async (req, res) => {
|
||||
const withHw = req.query.hw !== '0';
|
||||
@ -134,12 +118,14 @@ app.get('/api/servers', async (req, res) => {
|
||||
});
|
||||
}));
|
||||
|
||||
for (const r of pingResults) {
|
||||
const hwTasks = pingResults.filter(r => r.alive && withHw).map(async (r) => {
|
||||
const srv = SERVERS.find(s => s.code === r.code);
|
||||
r.last_seen = new Date().toISOString();
|
||||
if (r.alive && withHw) {
|
||||
try { r.hardware = await fetchHardware(srv); } catch(e) { r.hardware = null; }
|
||||
}
|
||||
try { r.hardware = await fetchHardware(srv); } catch(e) { r.hardware = null; }
|
||||
});
|
||||
await Promise.all(hwTasks);
|
||||
for (const r of pingResults) {
|
||||
if (!r.last_seen) r.last_seen = new Date().toISOString();
|
||||
}
|
||||
|
||||
res.json({ ok: true, servers: pingResults, timestamp: new Date().toISOString(), total: SERVERS.length });
|
||||
@ -176,19 +162,12 @@ app.get('/api/summary', async (req, res) => {
|
||||
req.write(data); req.end();
|
||||
});
|
||||
}));
|
||||
|
||||
const aliveCount = pingResults.filter(r => r.alive).length;
|
||||
const totalCpu = SERVERS.reduce((sum, s) => sum + (s.spec.cpu || 2), 0);
|
||||
const totalMem = SERVERS.reduce((sum, s) => sum + (s.spec.mem_gb || 4), 0);
|
||||
const totalDisk = SERVERS.reduce((sum, s) => sum + (s.spec.disk_gb || 50), 0);
|
||||
const alertCount = opLogs.filter(l => l.level === 'error').length;
|
||||
|
||||
res.json({
|
||||
ok: true, total_servers: SERVERS.length, alive: aliveCount,
|
||||
offline: SERVERS.length - aliveCount, alerts: alertCount,
|
||||
total_cpu_cores: totalCpu, total_mem_gb: totalMem, total_disk_gb: totalDisk,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
res.json({ ok: true, total_servers: SERVERS.length, alive: aliveCount, offline: SERVERS.length - aliveCount, alerts: alertCount, total_cpu_cores: totalCpu, total_mem_gb: totalMem, total_disk_gb: totalDisk, timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.post('/api/knock', (req, res) => {
|
||||
@ -226,14 +205,11 @@ app.post('/api/exec', async (req, res) => {
|
||||
if (!temp_key || !target || !cmd) return res.status(400).json({ error: '缺少参数' });
|
||||
const ke = tempKeys[temp_key]; if (!ke || Date.now() > ke.expires_at) return res.status(401).json({ error: '密钥无效' });
|
||||
const s = SERVERS.find(s => s.code === target); if (!s) return res.status(404).json({ error: '未找到' });
|
||||
try {
|
||||
const r = await gatekeeperExec(s, cmd, 30000);
|
||||
addOpLog(s.code, s.name, 'EXEC', cmd.slice(0, 60), 'info');
|
||||
res.json({ ok: true, result: r });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
try { const r = await gatekeeperExec(s, cmd, 30000); addOpLog(s.code, s.name, 'EXEC', cmd.slice(0, 60), 'info'); res.json({ ok: true, result: r }); }
|
||||
catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
// ========== 节点池 API ==========
|
||||
// 节点池 API
|
||||
const POOL_REGISTRY_FILE = DATA_DIR + '/pool-registry.json';
|
||||
const JOIN_TOKENS_FILE = DATA_DIR + '/join-tokens.json';
|
||||
function loadPoolRegistry() { try { if (fs.existsSync(POOL_REGISTRY_FILE)) return JSON.parse(fs.readFileSync(POOL_REGISTRY_FILE, 'utf-8')); } catch(e) {} return { active: {}, pending: {}, history: [] }; }
|
||||
@ -284,11 +260,10 @@ app.post('/api/pool/report', (req, res) => {
|
||||
res.json({ ok: true, node_code });
|
||||
});
|
||||
|
||||
// ========== 启动 ==========
|
||||
const PORT = process.env.CONSOLE_PORT || 3920;
|
||||
app.listen(PORT, '127.0.0.1', () => {
|
||||
console.log('光湖主控台 v3.3 · ' + SERVERS.length + '台 | SG-001:' + PORT);
|
||||
console.log(' 硬件采集: 60s缓存 · 操作日志: 100条上限');
|
||||
console.log(' 硬件采集: 并行+60s缓存 · 操作日志: 100条上限');
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user