Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/0mnitx_openclaw-dashboard.json
T

1 line
10 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "0mnitx/openclaw-dashboard", "files": {"src/server.js": {"a471c8c64b9809463ccb2a51917797f90e3ac4f8": {"t": 1785674017.726663, "n": 9579, "h": "b2a136c4a4b79559", "c": "const express = require('express');\nconst { exec } = require('child_process');\nconst os = require('os');\nconst axios = require('axios');\n\nconst app = express();\nconst PORT = 3000;\n\napp.use(express.static('public'));\n\n// MiniMax Token Plan 配置\n// API 文档: https://platform.minimaxi.com/docs/token-plan/intro\nconst TOKENPLAN_CONFIG = {\n enabled: true,\n apiUrl: 'https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains',\n apiKey: 'sk-cp-H58PybR65Oz-Rv3wNgLjcXtRb_YZTjP-WbejPSsUMKsIDtolbjQb-172gEc9Bk6Tnl7xpuu24s-j4TKlBzlRozDcSe6E2WbuMfxBgnVFAFDFFpA0Y8Kgn0M'\n};\n\n// 获取 OpenClaw 状态\nfunction getOpenClawStatus() {\n return new Promise((resolve) => {\n exec('openclaw status 2>/dev/null', { maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {\n if (err) {\n resolve({ error: err.message, stdout: stdout, stderr: stderr });\n } else {\n resolve({ data: stdout, error: null });\n }\n });\n });\n}\n\n// 获取系统资源\nfunction getSystemResources() {\n const cpu = os.cpus();\n const totalMem = os.totalmem();\n const freeMem = os.freemem();\n const usedMem = totalMem - freeMem;\n \n let totalIdle = 0, totalTick = 0;\n cpu.forEach(c => {\n for (const type in c.times) {\n totalTick += c.times[type];\n }\n totalIdle += c.times.idle;\n });\n const cpuUsage = ((1 - totalIdle / totalTick) * 100).toFixed(1);\n\n return {\n cpu: cpuUsage,\n cpuCores: cpu.length,\n memory: {\n total: formatBytes(totalMem),\n used: formatBytes(usedMem),\n free: formatBytes(freeMem),\n usedPercent: ((usedMem / totalMem) * 100).toFixed(1)\n },\n loadAvg: os.loadavg(),\n uptime: formatUptime(os.uptime()),\n hostname: os.hostname(),\n platform: os.platform() + ' ' + os.release()\n };\n}\n\nfunction formatBytes(bytes) {\n const gb = bytes / (1024 * 1024 * 1024);\n return gb.toFixed(1) + ' GB';\n}\n\nfunction formatUptime(seconds) {\n const d = Math.floor(seconds / 86400);\n const h = Math.floor((seconds % 86400) / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n return d > 0 ? `${d}d ${h}h ${m}m` : `${h}h ${m}m`;\n}\n\n// 解析 Sessions 表格\nfunction parseSessions(text) {\n const sessions = [];\n if (!text) return sessions;\n \n const lines = text.split('\\n');\n let state = 'search';\n let prevLine = '';\n \n for (const line of lines) {\n if (state === 'search') {\n if (line.trim() === 'Sessions') {\n state = 'found';\n }\n continue;\n }\n \n if (state === 'found') {\n if (line.includes('├')) {\n // Check if the PREVIOUS line was the header row (│ Key │ Kind │...)\n if (prevLine.includes('│') && prevLine.includes('Key')) {\n state = 'data';\n }\n // If not, keep searching\n }\n prevLine = line;\n continue;\n }\n \n if (state !== 'data') continue;\n \n if (line.includes('└')) {\n break;\n }\n \n if (!line.includes('│') || line.trim() === '') continue;\n \n const cleaned = line.replace(/[┌┬┤┘┴└├┼─│]/g, ' ');\n const cells = cleaned.split(/\\s{2,}/).filter(c => c.trim());\n \n if (cells.length >= 4) {\n const key = cells[0].trim();\n const kind = cells[1].trim();\n const age = cells[2].trim();\n const model = cells[3].trim();\n const tokens = cells[4] ? cells[4].trim() : '';\n \n if (!key || key === 'Key') continue;\n \n const sessionId = key.replace('agent:', '').replace(/:/g, ' › ');\n \n // age 可能被拆成两个 cell,需要合并\n // 情况1: \"1m\" + \"ago\" = \"1m ago\"(分钟前)\n // 情况2: \"just\" + \"now\" = \"just now\"(活跃)\n let fullAge = age;\n if (cells[3] === 'ago' && (age.endsWith('m') || age.match(/^\\d+h$/))) {\n fullAge = age + ' ' + cells[3];\n } else if (cells[3] === 'now' && age === 'just') {\n fullAge = 'just now';\n }\n \n const finalAgeDisplay = fullAge === 'just now' ? '刚刚' : \n fullAge.endsWith('m ago') ? fullAge.replace('m ago', '分钟前') :\n fullAge.endsWith('h ago') ? fullAge.replace('h ago', '小时前') :\n fullAge.endsWith('d ago') ? fullAge.replace('d ago', '天前') : fullAge;\n \n const tokenMatch = tokens.match(/(\\d+)k\\/(\\d+)k\\s*\\((\\d+)%\\)/);\n const tokenPercent = tokenMatch ? parseInt(tokenMatch[3]) : 0;\n \n const isReallyActive = fullAge === 'just now';\n \n sessions.push({\n id: sessionId,\n kind: kind,\n age: finalAgeDisplay,\n model: model,\n tokens: tokens,\n tokenPercent: tokenPercent,\n isActive: isReallyActive\n });\n }\n }\n \n return sessions;\n}\n\n// API: 系统资源\napp.get('/api/system', (req, res) => {\n const resources = getSystemResources();\n exec('df -h / | tail -1 | awk \\'{print $2,$3,$5}\\'', (err, stdout) => {\n if (!err && stdout) {\n const parts = stdout.trim().split(/\\s+/);\n resources.disk = {\n total: parts[0],\n used: parts[1],\n percent: parts[2]\n };\n }\n res.json(resources);\n });\n});\n\n// API: OpenClaw 状态\napp.get('/api/openclaw', async (req, res) => {\n const status = await getOpenClawStatus();\n const sessions = parseSessions(status.data);\n \n let gatewayStatus = { running: false, detail: '' };\n if (status.data) {\n const lines = status.data.split('\\n');\n lines.forEach(line => {\n if (line.includes('Gateway') && line.includes('running')) {\n gatewayStatus = { running: true, detail: '运行中' };\n }\n });\n }\n \n const activeCount = sessions.filter(s => s.isActive).length;\n const totalSessions = sessions.length;\n \n res.json({\n error: status.error,\n data: status.data,\n sessions: sessions,\n gateway: gatewayStatus,\n summary: {\n totalSessions: totalSessions,\n activeSessions: activeCount,\n activeModels: [...new Set(sessions.map(s => s.model))]\n }\n });\n});\n\n// API: Token 用量(MiniMax Token Plan)\napp.get('/api/token', async (req, res) => {\n if (!TOKENPLAN_CONFIG.enabled || !TOKENPLAN_CONFIG.apiUrl) {\n res.json({\n configured: false,\n message: '请在 src/server.js 中配置 TOKENPLAN_CONFIG'\n });\n return;\n }\n \n try {\n const response = await axios.get(TOKENPLAN_CONFIG.apiUrl, {\n headers: {\n 'Authorization': `Bearer ${TOKENPLAN_CONFIG.apiKey}`,\n 'Accept': 'application/json'\n },\n timeout: 10000\n });\n \n const rawData = response.data;\n const modelRemains = rawData.model_remains || [];\n \n // 提取主要模型\n const mainModel = modelRemains.find(m => m.model_name && m.model_name.includes('MiniMax-M'));\n const speechModel = modelRemains.find(m => m.model_name && m.model_name.includes('speech'));\n const imageModel = modelRemains.find(m => m.model_name && m.model_name.includes('image'));\n \n // 计算5小时窗口重置时间\n let resetIn = '未知';\n if (mainModel && mainModel.end_time) {\n const now = Date.now();\n const remainingMs = Math.max(0, mainModel.end_time - now);\n const hours = Math.floor(remainingMs / (1000 * 60 * 60));\n const minutes = Math.floor((remainingMs % (1000 * 60 * 60)) / (1000 * 60));\n resetIn = hours > 0 ? `${hours}小时${minutes}分钟` : `${minutes}分钟`;\n }\n \n // 计算周额度重置时间\n let weeklyResetIn = '未知';\n if (mainModel && mainModel.weekly_end_time) {\n const now = Date.now();\n const remainingMs = Math.max(0, mainModel.weekly_end_time - now);\n const days = Math.floor(remainingMs / (1000 * 60 * 60 * 24));\n const hours = Math.floor((remainingMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));\n weeklyResetIn = `${days}天${hours}小时`;\n }\n \n res.json({\n configured: true,\n error: null,\n models: {\n main: mainModel ? {\n name: 'MiniMax-M*',\n used: mainModel.current_interval_total_count - mainModel.current_interval_usage_count,\n total: mainModel.current_interval_total_count,\n remaining: mainModel.current_interval_usage_count,\n percent: Math.round(((mainModel.current_interval_total_count - mainModel.current_interval_usage_count) / mainModel.current_interval_total_count) * 100),\n resetIn: resetIn\n } : null,\n speech: speechModel && speechModel.current_interval_total_count > 0 ? {\n name: 'Speech-HD',\n used: speechModel.current_interval_total_count - speechModel.current_interval_usage_count,\n total: speechModel.current_interval_total_count,\n remaining: speechModel.current_interval_usage_count,\n percent: Math.round(((speechModel.current_interval_total_count - speechModel.current_interval_usage_count) / speechModel.current_interval_total_count) * 100)\n } : null,\n image: imageModel && imageModel.current_interval_total_count > 0 ? {\n name: 'Image-01',\n used: imageModel.current_interval_total_count - imageModel.current_interval_usage_count,\n total: imageModel.current_interval_total_count,\n remaining: imageModel.current_interval_usage_count,\n percent: Math.round(((imageModel.current_interval_total_count - imageModel.current_interval_usage_count) / imageModel.current_interval_total_count) * 100)\n } : null\n },\n weekly: mainModel ? {\n used: mainModel.current_weekly_total_count - mainModel.current_weekly_usage_count,\n total: mainModel.current_weekly_total_count,\n remaining: mainModel.current_weekly_usage_count,\n resetIn: weeklyResetIn\n } : null\n });\n } catch (e) {\n res.json({\n configured: true,\n error: e.message\n });\n }\n});\n\napp.listen(PORT, '0.0.0.0', () => {\n console.log(`🚀 OpenClaw Dashboard 运行中: http://0.0.0.0:${PORT}`);\n});"}}}}