状态灯双重核对: docker只读接口查runner真实进程, 强停不再假显示运行中(新增'已停止'态); 手动停止单列
This commit is contained in:
@@ -10,6 +10,8 @@ services:
|
|||||||
- /opt/hermes/data/recruitment-data:/src
|
- /opt/hermes/data/recruitment-data:/src
|
||||||
# 任务机状态文件(只读, 任务状态灯用)
|
# 任务机状态文件(只读, 任务状态灯用)
|
||||||
- /opt/hermes/data/logs/recruitment/state:/state:ro
|
- /opt/hermes/data/logs/recruitment/state:/state:ro
|
||||||
|
# docker只读接口(任务状态灯核对runner进程是否真实存活)
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
# 自身状态(users.db/secret/快照)持久化
|
# 自身状态(users.db/secret/快照)持久化
|
||||||
- ./data:/data/admin
|
- ./data:/data/admin
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
+3
-2
@@ -292,8 +292,9 @@ async function refreshTaskStatus(){
|
|||||||
const s=await api('/api/task-status');
|
const s=await api('/api/task-status');
|
||||||
$('taskStatus').innerHTML=['hr1','hr2'].map(a=>{
|
$('taskStatus').innerHTML=['hr1','hr2'].map(a=>{
|
||||||
const t=s[a]||{state:'idle',text:'今日未运行'};
|
const t=s[a]||{state:'idle',text:'今日未运行'};
|
||||||
const extra=t.chatLeft!=null?`·剩${t.chatLeft}次`:'';
|
const extra=t.chatLeft!=null&&t.state==='running'?`·剩${t.chatLeft}次`:'';
|
||||||
return `<span class="tchip ${t.state}" title="${a}"><span class="dot"></span>${a} ${t.text}${extra}</span>`;
|
const tip=t.detail?`(${t.detail})`:'';
|
||||||
|
return `<span class="tchip ${t.state}" title="${a}${tip}"><span class="dot"></span>${a} ${t.text}${extra}</span>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}catch(_){}
|
}catch(_){}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -286,22 +286,38 @@ app.post('/api/users/:id/resetpwd', requireAuth, requireSuper, (req, res) => {
|
|||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------- 筛选任务状态(所有登录用户, 读runner状态文件) ----------
|
// ---------- 筛选任务状态(所有登录用户) ----------
|
||||||
app.get('/api/task-status', requireAuth, (req, res) => {
|
// 状态文件 + 进程存活双重核对: runner被强停时不写状态文件,仅信文件会假"运行中"
|
||||||
|
const http = require('http');
|
||||||
|
function hermesRunnerProcs() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const req = http.request(
|
||||||
|
{ socketPath: '/var/run/docker.sock', path: '/containers/hermes/top?ps_args=-eo pid,args', method: 'GET', timeout: 3000 },
|
||||||
|
(r) => { let d = ''; r.on('data', (c) => d += c); r.on('end', () => { try { resolve(JSON.parse(d).Processes || []); } catch (_) { resolve([]); } }); }
|
||||||
|
);
|
||||||
|
req.on('error', () => resolve([]));
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
app.get('/api/task-status', requireAuth, async (req, res) => {
|
||||||
const stateDir = process.env.STATE_DIR || '/state';
|
const stateDir = process.env.STATE_DIR || '/state';
|
||||||
const bjToday = new Date(Date.now() + 8 * 3600e3).toISOString().slice(0, 10);
|
const bjToday = new Date(Date.now() + 8 * 3600e3).toISOString().slice(0, 10);
|
||||||
const doneSet = new Set(['time_cutoff', 'chat_exhausted', 'done']);
|
const doneSet = new Set(['time_cutoff', 'chat_exhausted', 'done']);
|
||||||
const errSet = new Set(['login_expired', 'alert', 'tunnel_failed', 'platform_blocked', 'qr_expired', 'error', 'stopped_by_user']);
|
const errSet = new Set(['login_expired', 'alert', 'tunnel_failed', 'platform_blocked', 'qr_expired', 'error']);
|
||||||
|
const procs = await hermesRunnerProcs();
|
||||||
|
const alive = (acct) => procs.some((p) => /daily-runner(\.js)?\s/.test(String(p)) && String(p).includes(acct));
|
||||||
const out = {};
|
const out = {};
|
||||||
for (const acct of ['hr1', 'hr2']) {
|
for (const acct of ['hr1', 'hr2']) {
|
||||||
try {
|
let s = null;
|
||||||
const s = JSON.parse(fs.readFileSync(path.join(stateDir, `state-${acct}.json`), 'utf8'));
|
try { s = JSON.parse(fs.readFileSync(path.join(stateDir, `state-${acct}.json`), 'utf8')); } catch (_) {}
|
||||||
if (s.date !== bjToday) { out[acct] = { state: 'idle', text: '今日未运行' }; continue; }
|
const isAlive = alive(acct);
|
||||||
if (s.status === 'running') out[acct] = { state: 'running', text: '运行中', chatLeft: s.chatLeft };
|
if (isAlive) { out[acct] = { state: 'running', text: '运行中', chatLeft: s && s.chatLeft }; continue; }
|
||||||
else if (doneSet.has(s.status)) out[acct] = { state: 'done', text: '已收工', chatLeft: s.chatLeft };
|
if (!s || s.date !== bjToday) { out[acct] = { state: 'idle', text: '今日未运行' }; continue; }
|
||||||
else if (errSet.has(s.status)) out[acct] = { state: 'error', text: '异常停止', detail: s.status };
|
if (s.status === 'running') out[acct] = { state: 'stopped', text: '已停止', detail: '状态未更新(进程已退出)' };
|
||||||
else out[acct] = { state: 'done', text: '已收工', chatLeft: s.chatLeft };
|
else if (s.status === 'stopped_by_user') out[acct] = { state: 'stopped', text: '手动停止' };
|
||||||
} catch (_) { out[acct] = { state: 'idle', text: '今日未运行' }; }
|
else if (doneSet.has(s.status)) out[acct] = { state: 'done', text: '已收工', chatLeft: s.chatLeft };
|
||||||
|
else if (errSet.has(s.status)) out[acct] = { state: 'error', text: '异常停止', detail: s.status };
|
||||||
|
else out[acct] = { state: 'done', text: '已收工', chatLeft: s.chatLeft };
|
||||||
}
|
}
|
||||||
res.json(out);
|
res.json(out);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user