// # 光湖服务器主控台 API
// # Guanghu Server Console v3.1 — with GPU & Agent API
// # 部署: guanghulab.com/console/
// # 版权: 国作登字-2026-A-00037559 · TCS-0002∞

const express = require('express');
const http = require('http');
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');

const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

// 数据存储目录
const DATA_DIR = '/opt/zhuyuan/data';

// 确保数据目录存在
try { fs.mkdirSync(DATA_DIR, { recursive: true }); } catch(e) {}

// 六台服务器配置（从 gatekeeper-deployment.json 读取）
const SERVERS = [
  { code: "BS-GZ-006", name: "广州 · 代码仓库", ip: "43.139.217.141", key: "zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23", port: 3910 },
  { code: "BS-SG-001", name: "新加坡 · 铸渊大脑", ip: "43.156.237.110", key: "zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9", port: 3911 },
  { code: "BS-SG-002", name: "新加坡 · 铸渊面孔", ip: "43.134.16.246", key: "zy_gtw_d1f6d2b8cb4ea44292bd036e4dd03a70745ea502d4a3ae40", port: 3910 },
  { code: "BS-SG-003", name: "新加坡 · BS-SVR-SG-001", ip: "43.153.193.169", key: "zy_gtw_d7c033d8ae992ffc9dd3e0dd5fe332dace0b644c0ae7c847", port: 3910 },
  { code: "ZY-SG-006", name: "新加坡 · ZY-SVR-006", ip: "43.153.203.105", key: "zy_gtw_fff861a2fe40cbdc366ee21f218023be8b1c182f66c852d3", port: 3910 },
  { code: "BS-SH-005", name: "上海", ip: "124.223.10.33", key: "zy_gtw_b1045de5ddfd7832e3c53349d9c896f054b85a4a9ece22f9", port: 3910 },
];

// 临时密钥缓存
const tempKeys = {};
const authQueue = [];

// ========== 心跳采集 ==========

async function pingServer(srv) {
  const start = Date.now();
  try {
    const data = JSON.stringify({});
    const opts = {
      hostname: srv.ip, port: srv.port,
      path: '/health', method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + srv.key,
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(data)
      },
      timeout: 5000
    };
    const result = await new Promise((resolve) => {
      const req = http.request(opts, (res) => {
        let body = '';
        res.on('data', c => body += c);
        res.on('end', () => resolve({ ok: true, status: res.statusCode, body }));
      });
      req.on('error', (e) => resolve({ ok: false, error: e.message }));
      req.on('timeout', () => { req.destroy(); resolve({ ok: false, error: 'timeout' }); });
      req.write(data);
      req.end();
    });
    return {
      code: srv.code, name: srv.name, ip: srv.ip,
      alive: result.ok && result.status === 200,
      latency: Date.now() - start,
      error: result.error || null,
      last_seen: new Date().toISOString()
    };
  } catch (e) {
    return { code: srv.code, name: srv.name, ip: srv.ip, alive: false, latency: 0, error: e.message, last_seen: new Date().toISOString() };
  }
}

// ========== Agent认证 ==========

function checkAgentAuth(req) {
  const auth = req.headers.authorization || '';
  const token = auth.replace('Bearer ', '');
  if (!token) return false;
  try {
    const keysData = fs.readFileSync('/tmp/zhuyuan-keys.json', 'utf-8');
    const keys = JSON.parse(keysData || '[]');
    return keys.some(k => k.value === token);
  } catch(e) {
    return false;
  }
}

// ========== API 路由 ==========

// 1. 获取所有服务器状态
app.get('/api/servers', async (req, res) => {
  const results = await Promise.all(SERVERS.map(s => pingServer(s)));
  res.json({ ok: true, servers: results, timestamp: new Date().toISOString() });
});

// 2. 铸渊发起操作请求
app.post('/api/knock', (req, res) => {
  const { target, reason } = req.body;
  if (!target) return res.status(400).json({ error: '缺少 target 参数' });
  const srv = SERVERS.find(s => s.code === target || s.name.includes(target));
  if (!srv) return res.status(404).json({ error: '未找到服务器: ' + target });
  
  const requestId = 'req_' + crypto.randomBytes(8).toString('hex');
  const entry = {
    id: requestId,
    target: srv.code,
    target_name: srv.name,
    reason: reason || '未说明',
    status: 'pending',
    created_at: new Date().toISOString(),
    temp_key: null
  };
  authQueue.push(entry);
  res.json({ ok: true, request_id: requestId, message: '敲门已发送，等待冰朔确认' });
});

// 3. 获取待处理的授权请求
app.get('/api/pending', (req, res) => {
  res.json({ ok: true, requests: authQueue.filter(r => r.status === 'pending') });
});

// 4. 冰朔确认授权
app.post('/api/authorize', (req, res) => {
  const { request_id } = req.body;
  if (!request_id) return res.status(400).json({ error: '缺少 request_id' });
  const entry = authQueue.find(r => r.id === request_id && r.status === 'pending');
  if (!entry) return res.status(404).json({ error: '未找到该请求或已处理' });
  
  const tempKey = 'tmp_' + crypto.randomBytes(24).toString('hex');
  entry.status = 'approved';
  entry.temp_key = tempKey;
  entry.approved_at = new Date().toISOString();
  
  tempKeys[tempKey] = {
    server: entry.target,
    expires_at: Date.now() + 5 * 60 * 1000,
    request_id: request_id
  };
  
  res.json({ ok: true, temp_key: tempKey, expires_in: '5分钟' });
});

// 5. 拒绝授权
app.post('/api/reject', (req, res) => {
  const { request_id } = req.body;
  const entry = authQueue.find(r => r.id === request_id && r.status === 'pending');
  if (entry) entry.status = 'rejected';
  res.json({ ok: true });
});

// 6. 验证临时密钥是否有效
app.get('/api/verify-key/:key', (req, res) => {
  const entry = tempKeys[req.params.key];
  if (!entry) return res.json({ ok: false, valid: false, reason: '密钥不存在或已过期' });
  if (Date.now() > entry.expires_at) {
    delete tempKeys[req.params.key];
    return res.json({ ok: false, valid: false, reason: '密钥已过期' });
  }
  res.json({ ok: true, valid: true, server: entry.server });
});

// 7. 通过临时密钥执行命令
app.post('/api/exec', async (req, res) => {
  const { temp_key, target, cmd } = req.body;
  if (!temp_key || !target || !cmd) return res.status(400).json({ error: '缺少参数' });
  
  const keyEntry = tempKeys[temp_key];
  if (!keyEntry || Date.now() > keyEntry.expires_at) {
    return res.status(401).json({ error: '密钥无效或已过期' });
  }
  
  const srv = SERVERS.find(s => s.code === target);
  if (!srv) return res.status(404).json({ error: '未找到服务器' });
  
  try {
    const data = JSON.stringify({ cmd });
    const opts = {
      hostname: srv.ip, port: srv.port,
      path: '/exec', method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + srv.key,
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(data)
      },
      timeout: 30000
    };
    const result = await new Promise((resolve) => {
      const req = http.request(opts, (r) => {
        let body = '';
        r.on('data', c => body += c);
        r.on('end', () => {
          try { resolve(JSON.parse(body)); }
          catch(e) { resolve({ error: body }); }
        });
      });
      req.on('error', (e) => resolve({ error: e.message }));
      req.write(data);
      req.end();
    });
    res.json({ ok: true, result });
  } catch (e) {
    res.status(500).json({ error: e.message });
  }
});

// ========== MCP 终端会话 (v3.0) ==========

const terminalSessions = {};

// 8. 打开终端会话（自动敲门）
app.post('/api/terminal/open', (req, res) => {
  const { target, reason } = req.body;
  if (!target) return res.status(400).json({ error: '缺少 target 参数' });
  
  const srv = SERVERS.find(s => s.code === target || s.name.includes(target));
  if (!srv) return res.status(404).json({ error: '未找到服务器: ' + target });
  
  const sessionId = 'term_' + crypto.randomBytes(8).toString('hex');
  const requestId = 'req_' + crypto.randomBytes(8).toString('hex');
  
  const entry = {
    id: requestId,
    target: srv.code,
    target_name: srv.name,
    reason: reason || 'MCP终端会话',
    status: 'pending',
    created_at: new Date().toISOString(),
    temp_key: null
  };
  authQueue.push(entry);
  
  terminalSessions[sessionId] = {
    server: srv.code,
    server_name: srv.name,
    request_id: requestId,
    status: 'awaiting_auth',
    temp_key: null,
    created_at: new Date().toISOString(),
    operation_stage: '敲门中',
    human_readable: '铸渊正在连接 ' + srv.name + '，请求操作权限...'
  };
  
  res.json({
    ok: true,
    session_id: sessionId,
    request_id: requestId,
    server: srv.code,
    server_name: srv.name,
    status: 'awaiting_auth'
  });
});

// 9. 轮询终端会话状态
app.get('/api/terminal/status/:session_id', (req, res) => {
  const session = terminalSessions[req.params.session_id];
  if (!session) return res.status(404).json({ error: '会话不存在' });
  
  if (session.status === 'awaiting_auth' && session.request_id) {
    const entry = authQueue.find(r => r.id === session.request_id);
    if (entry) {
      if (entry.status === 'approved') {
        session.status = 'ready';
        session.temp_key = entry.temp_key;
        session.operation_stage = '已授权';
        session.human_readable = '冰朔已确认信任，铸渊获得操作权限，准备执行操作...';
      } else if (entry.status === 'rejected') {
        session.status = 'rejected';
        session.operation_stage = '已拒绝';
        session.human_readable = '冰朔未确认此操作，铸渊等待新的指令。';
      }
    }
  }
  
  res.json({
    ok: true,
    session_id: req.params.session_id,
    status: session.status,
    server: session.server,
    server_name: session.server_name,
    operation_stage: session.operation_stage,
    human_readable: session.human_readable,
    created_at: session.created_at
  });
});

// 10. 在终端会话中执行命令
app.post('/api/terminal/exec', async (req, res) => {
  const { session_id, cmd } = req.body;
  if (!session_id || !cmd) return res.status(400).json({ error: '缺少参数' });
  
  const session = terminalSessions[session_id];
  if (!session) return res.status(404).json({ error: '会话不存在' });
  if (session.status !== 'ready') return res.status(400).json({ error: '会话尚未就绪，等待冰朔授权中' });
  
  session.operation_stage = '执行中';
  session.human_readable = '铸渊正在 ' + session.server_name + ' 上执行操作...';
  
  const srv = SERVERS.find(s => s.code === session.server);
  if (!srv) return res.status(404).json({ error: '未找到服务器' });
  
  const startTime = Date.now();
  
  try {
    const data = JSON.stringify({ cmd });
    const opts = {
      hostname: srv.ip, port: srv.port,
      path: '/exec', method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + srv.key,
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(data)
      },
      timeout: 60000
    };
    const result = await new Promise((resolve) => {
      const req = http.request(opts, (r) => {
        let body = '';
        r.on('data', c => body += c);
        r.on('end', () => {
          try { resolve(JSON.parse(body)); }
          catch(e) { resolve({ output: body }); }
        });
      });
      req.on('error', (e) => resolve({ error: e.message }));
      req.on('timeout', () => { req.destroy(); resolve({ error: '执行超时(60s)' }); });
      req.write(data);
      req.end();
    });
    
    const elapsed = Date.now() - startTime;
    session.operation_stage = '完成';
    session.human_readable = '操作已完成，铸渊已返回结果到对话。耗时 ' + elapsed + 'ms。';
    res.json({
      ok: true,
      cmd: cmd,
      result: result,
      elapsed_ms: elapsed,
      server: srv.code
    });
  } catch (e) {
    res.status(500).json({ error: e.message });
  }
});

// 11. 更新终端会话操作状态（铸渊从对话中推送）
app.post('/api/terminal/update-status/:session_id', (req, res) => {
  const session = terminalSessions[req.params.session_id];
  if (!session) return res.status(404).json({ error: '会话不存在' });
  const { operation_stage, human_readable } = req.body;
  if (operation_stage) session.operation_stage = operation_stage;
  if (human_readable) session.human_readable = human_readable;
  res.json({ ok: true, operation_stage: session.operation_stage, human_readable: session.human_readable });
});

// 12. 关闭终端会话
app.post('/api/terminal/close/:session_id', (req, res) => {
  const session = terminalSessions[req.params.session_id];
  if (session) delete terminalSessions[req.params.session_id];
  res.json({ ok: true });
});

// 13. 获取所有活跃终端会话
app.get('/api/terminal/sessions', (req, res) => {
  const sessions = Object.entries(terminalSessions).map(([id, s]) => ({
    session_id: id,
    server: s.server,
    server_name: s.server_name,
    status: s.status,
    operation_stage: s.operation_stage,
    human_readable: s.human_readable,
    created_at: s.created_at,
    age_seconds: Math.floor((Date.now() - new Date(s.created_at).getTime()) / 1000)
  }));
  res.json({ ok: true, sessions });
});

// ========== 密钥投递 API ==========

// 密钥存储
const KEYS_FILE = '/tmp/zhuyuan-keys.json';
let keyStore = [];

function loadKeys() {
  try {
    if (fs.existsSync(KEYS_FILE)) {
      const data = fs.readFileSync(KEYS_FILE, 'utf-8');
      keyStore = JSON.parse(data || '[]');
    }
  } catch(e) { keyStore = []; }
}
function saveKeys() {
  fs.writeFileSync(KEYS_FILE, JSON.stringify(keyStore, null, 2));
}
loadKeys();

// 14. 投递密钥
app.post('/api/keys/submit', (req, res) => {
  const { value, label } = req.body;
  if (!value) return res.status(400).json({ error: '缺少密钥内容' });
  const id = 'key_' + crypto.randomBytes(6).toString('hex');
  keyStore.push({ id, value, label: label || '未命名', created_at: new Date().toISOString() });
  saveKeys();
  res.json({ ok: true, id });
});

// 15. 列出所有密钥（不包含值）
app.get('/api/keys', (req, res) => {
  const list = keyStore.map(k => ({ id: k.id, label: k.label, created_at: k.created_at }));
  res.json({ ok: true, keys: list });
});

// 16. 获取单个密钥值
app.get('/api/keys/retrieve/:id', (req, res) => {
  const key = keyStore.find(k => k.id === req.params.id);
  if (!key) return res.status(404).json({ error: '密钥不存在' });
  res.json({ ok: true, value: key.value, label: key.label });
});

// 17. 删除密钥
app.delete('/api/keys/:id', (req, res) => {
  keyStore = keyStore.filter(k => k.id !== req.params.id);
  saveKeys();
  res.json({ ok: true });
});

// ========== GPU & 训练 & Agent API (v3.1 新增) ==========

// 18. GPU状态 - Agent推送
app.post('/api/gpu/status', (req, res) => {
  if (!checkAgentAuth(req)) return res.status(401).json({ error: '未授权，请检查API Key' });
  const data = {
    timestamp: new Date().toISOString(),
    hostname: req.body.hostname || '3090-server',
    gpus: req.body.gpus || []
  };
  try {
    fs.writeFileSync(DATA_DIR + '/gpu-status.json', JSON.stringify(data, null, 2));
    res.json({ ok: true });
  } catch(e) {
    res.status(500).json({ error: '存储失败: ' + e.message });
  }
});

// 19. GPU状态 - 仪表盘读取
app.get('/api/gpu/status', (req, res) => {
  try {
    const data = JSON.parse(fs.readFileSync(DATA_DIR + '/gpu-status.json', 'utf-8'));
    const age = Date.now() - new Date(data.timestamp).getTime();
    res.json({ ok: true, ...data, online: age < 120000, age_seconds: Math.floor(age / 1000) });
  } catch(e) {
    res.json({ ok: true, gpus: [], timestamp: null, online: false });
  }
});

// 20. 训练状态 - Agent推送
app.post('/api/training/status', (req, res) => {
  if (!checkAgentAuth(req)) return res.status(401).json({ error: '未授权' });
  const data = {
    timestamp: new Date().toISOString(),
    job_id: req.body.job_id || 'hldp-3b-test',
    status: req.body.status || 'idle',
    step: req.body.step || 0,
    total_steps: req.body.total_steps || 0,
    loss: req.body.loss || null,
    loss_history: req.body.loss_history || [],
    eta_seconds: req.body.eta_seconds || null,
    learning_rate: req.body.learning_rate || null,
    elapsed_seconds: req.body.elapsed_seconds || 0,
    model_name: req.body.model_name || '',
    message: req.body.message || ''
  };
  try {
    fs.writeFileSync(DATA_DIR + '/training-status.json', JSON.stringify(data, null, 2));
    res.json({ ok: true });
  } catch(e) {
    res.status(500).json({ error: '存储失败' });
  }
});

// 21. 训练状态 - 仪表盘读取
app.get('/api/training/status', (req, res) => {
  try {
    const data = JSON.parse(fs.readFileSync(DATA_DIR + '/training-status.json', 'utf-8'));
    const age = Date.now() - new Date(data.timestamp).getTime();
    res.json({ ok: true, ...data, online: age < 120000 });
  } catch(e) {
    res.json({ ok: true, status: 'offline', message: '铸渊Agent未连接' });
  }
});

// 22. Agent操作日志 - Agent推送
app.post('/api/agent/log', (req, res) => {
  if (!checkAgentAuth(req)) return res.status(401).json({ error: '未授权' });
  const entry = {
    timestamp: new Date().toISOString(),
    level: req.body.level || 'info',
    category: req.body.category || 'agent',
    message: req.body.message || ''
  };
  try {
    const logPath = DATA_DIR + '/agent-log.jsonl';
    fs.appendFileSync(logPath, JSON.stringify(entry) + '\n');
    const lines = fs.readFileSync(logPath, 'utf-8').trim().split('\n').filter(Boolean);
    if (lines.length > 500) {
      fs.writeFileSync(logPath, lines.slice(-500).join('\n') + '\n');
    }
    res.json({ ok: true });
  } catch(e) {
    res.status(500).json({ error: '存储失败' });
  }
});

// 23. Agent操作日志 - 仪表盘读取
app.get('/api/agent/log', (req, res) => {
  try {
    const logPath = DATA_DIR + '/agent-log.jsonl';
    if (!fs.existsSync(logPath)) return res.json({ ok: true, entries: [], total: 0 });
    const lines = fs.readFileSync(logPath, 'utf-8').trim().split('\n').filter(Boolean);
    const limit = parseInt(req.query.limit) || 50;
    const entries = lines.slice(-limit).map(l => {
      try { return JSON.parse(l); } catch(e) { return { level: 'info', message: l }; }
    });
    res.json({ ok: true, entries, total: lines.length });
  } catch(e) {
    res.json({ ok: true, entries: [], total: 0 });
  }
});

// 24. Agent日记 - Agent推送
app.post('/api/agent/diary', (req, res) => {
  if (!checkAgentAuth(req)) return res.status(401).json({ error: '未授权' });
  const entry = {
    timestamp: new Date().toISOString(),
    type: req.body.type || 'info',
    title: req.body.title || '',
    description: req.body.description || ''
  };
  try {
    const diaryPath = DATA_DIR + '/agent-diary.jsonl';
    fs.appendFileSync(diaryPath, JSON.stringify(entry) + '\n');
    const lines = fs.readFileSync(diaryPath, 'utf-8').trim().split('\n').filter(Boolean);
    if (lines.length > 200) {
      fs.writeFileSync(diaryPath, lines.slice(-200).join('\n') + '\n');
    }
    res.json({ ok: true });
  } catch(e) {
    res.status(500).json({ error: '存储失败' });
  }
});

// 25. Agent日记 - 仪表盘读取
app.get('/api/agent/diary', (req, res) => {
  try {
    const diaryPath = DATA_DIR + '/agent-diary.jsonl';
    if (!fs.existsSync(diaryPath)) return res.json({ ok: true, entries: [], total: 0 });
    const lines = fs.readFileSync(diaryPath, 'utf-8').trim().split('\n').filter(Boolean);
    const entries = lines.map(l => {
      try { return JSON.parse(l); } catch(e) { return { type: 'info', title: l }; }
    });
    res.json({ ok: true, entries: entries.reverse().slice(0, 20), total: entries.length });
  } catch(e) {
    res.json({ ok: true, entries: [], total: 0 });
  }
});

// 26. 综合状态 - 一次性拉取所有Agent数据
app.get('/api/agent/combined', (req, res) => {
  const result = { gpu: null, training: null, logs: [], diary: [], online: false, last_seen: null };
  try {
    if (fs.existsSync(DATA_DIR + '/gpu-status.json')) {
      result.gpu = JSON.parse(fs.readFileSync(DATA_DIR + '/gpu-status.json', 'utf-8'));
      result.last_seen = result.gpu.timestamp;
      result.online = result.gpu.timestamp && (Date.now() - new Date(result.gpu.timestamp).getTime() < 120000);
    }
  } catch(e) {}
  try {
    if (fs.existsSync(DATA_DIR + '/training-status.json')) {
      result.training = JSON.parse(fs.readFileSync(DATA_DIR + '/training-status.json', 'utf-8'));
    }
  } catch(e) {}
  try {
    const logPath = DATA_DIR + '/agent-log.jsonl';
    if (fs.existsSync(logPath)) {
      const logLines = fs.readFileSync(logPath, 'utf-8').trim().split('\n').filter(Boolean);
      result.logs = logLines.slice(-20).map(l => { try { return JSON.parse(l); } catch(e) { return { message: l }; } });
    }
  } catch(e) {}
  try {
    const diaryPath = DATA_DIR + '/agent-diary.jsonl';
    if (fs.existsSync(diaryPath)) {
      const diaryLines = fs.readFileSync(diaryPath, 'utf-8').trim().split('\n').filter(Boolean);
      result.diary = diaryLines.map(l => { try { return JSON.parse(l); } catch(e) { return { type: 'info', title: l }; } }).reverse().slice(0, 10);
    }
  } catch(e) {}
  res.json(result);
});

// ========== 算力池 API ==========

let cachedPoolStatus = {};

// 27. 接收gatekeeper上报的节点状态
app.post('/api/pool/report', (req, res) => {
  const { node_code, node_name, cpu_load, mem_total_mb, mem_avail_mb, disk_used_pct, uptime_hours, tasks, spec } = req.body;
  if (!node_code) return res.status(400).json({ error: '缺少 node_code' });
  cachedPoolStatus[node_code] = {
    name: node_name || node_code,
    online: true,
    cpu_load: cpu_load || 0,
    mem_total_mb: mem_total_mb || 0,
    mem_avail_mb: mem_avail_mb || 0,
    disk_used_pct: disk_used_pct || 0,
    uptime_hours: uptime_hours || 0,
    tasks: tasks || 0,
    spec: spec || { cpu: 2, mem_gb: 4 },
    last_seen: new Date().toISOString()
  };
  res.json({ ok: true });
});

// 28. 获取算力池汇总状态
app.get('/api/pool/status', (req, res) => {
  const nodes = {};
  let totalCpu = 0, usedCpu = 0, totalMem = 0, usedMem = 0, onlineNodes = 0;
  let totalSpec = { cpu: 0, mem_gb: 0 };
  
  for (const srv of SERVERS) {
    const cached = cachedPoolStatus[srv.code];
    if (cached) {
      nodes[srv.code] = cached;
      const spec = cached.spec || { cpu: 2, mem_gb: 4 };
      totalCpu += spec.cpu;
      usedCpu += cached.cpu_load;
      totalMem += spec.mem_gb;
      const memAvailGb = cached.mem_avail_mb / 1024;
      usedMem += Math.max(0, spec.mem_gb - memAvailGb);
      onlineNodes++;
      totalSpec.cpu += spec.cpu;
      totalSpec.mem_gb += spec.mem_gb;
    } else {
      nodes[srv.code] = {
        name: srv.name,
        online: false,
        cpu_load: 0, mem_total_mb: 0, mem_avail_mb: 0,
        disk_used_pct: 0, uptime_hours: 0, tasks: 0,
        spec: { cpu: 2, mem_gb: 4 },
        last_seen: null
      };
      totalSpec.cpu += 2;
      totalSpec.mem_gb += 4;
    }
  }
  
  const pool = {
    updated_at: new Date().toISOString(),
    cpu_total: totalSpec.cpu,
    cpu_used: usedCpu,
    cpu_available: Math.max(0, totalSpec.cpu - usedCpu),
    mem_total_gb: totalSpec.mem_gb,
    mem_used_gb: usedMem,
    mem_available_gb: Math.max(0, totalSpec.mem_gb - usedMem),
    total_nodes: SERVERS.length,
    online_nodes: onlineNodes
  };
  
  res.json({
    ok: true,
    pool: { pool, nodes, total_spec: totalSpec, active_tasks: 0, updated_at: pool.updated_at }
  });
});

const PORT = process.env.CONSOLE_PORT || 3920;
app.listen(PORT, '127.0.0.1', () => {
  console.log('光湖主控台 API v3.1 — GPU+Agent训练台');
  console.log('   监听: 127.0.0.1:' + PORT);
  console.log('   服务器: ' + SERVERS.length + ' 台 | GPU训练台: 已启用');
  console.log('   数据目录: ' + DATA_DIR);
});