Skip to content

Commit 1a601b1

Browse files
committed
fix: include probe-* route/service modules excluded by overly-broad gitignore
The .gitignore debug rule 'probe-*.js' was unanchored and recursively matched src/routes/probe-*.routes.js and src/services/probe-*.service.js, so 13 required source files were never committed. The Docker/CI container crashed at startup with MODULE_NOT_FOUND (works on Windows due to its case-insensitive, non-git-aware local fs). Anchored debug rules to repo root and added the missing modules.
1 parent 350500c commit 1a601b1

14 files changed

Lines changed: 3567 additions & 5 deletions

.gitignore

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ test-results/
1515
__pycache__/
1616

1717
# Debug / 临时文件
18-
_debug*.txt
19-
_debug*.js
20-
_rescue_logs*.txt
18+
/_debug*.txt
19+
/_debug*.js
20+
/_rescue_logs*.txt
2121
*.tmp
22-
probe-*.js
23-
fix-claude-mcp.js
22+
/probe-*.js
23+
/fix-claude-mcp.js
2424

2525
# 部署脚本(含服务器凭据,仅本地使用)
2626
deploy.js

src/routes/probe-agent.routes.js

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
'use strict';
2+
3+
const express = require('express');
4+
const { PORT } = require('../config/env');
5+
6+
function resolveServerUrl(req) {
7+
const proto = req.headers['x-forwarded-proto'] || req.protocol || 'http';
8+
const host = req.headers['x-forwarded-host'] || req.headers.host || `localhost:${PORT}`;
9+
return `${proto}://${host}`.replace(/\/$/, '');
10+
}
11+
12+
function parseTimeQuery(value, fallback) {
13+
if (value === undefined || value === null || value === '') return fallback;
14+
const numeric = Number(value);
15+
if (Number.isFinite(numeric)) return numeric;
16+
return new Date(String(value)).getTime();
17+
}
18+
19+
function createProbeAgentPublicRouter({ probeAgentService }) {
20+
const router = express.Router();
21+
22+
router.post('/agent/probe/register', (req, res, next) => {
23+
try {
24+
const result = probeAgentService.registerAgent(req.body || {});
25+
res.json({ ok: true, ...result });
26+
} catch (error) {
27+
next(error);
28+
}
29+
});
30+
31+
router.post('/agent/probe/report', (req, res, next) => {
32+
try {
33+
const agent = probeAgentService.authenticateAgent(req.headers.authorization);
34+
if (!agent) return res.status(401).json({ ok: false, error: 'agent token 无效' });
35+
const result = probeAgentService.saveReport(agent, req.body || {});
36+
return res.json({ ok: true, ...result });
37+
} catch (error) {
38+
next(error);
39+
}
40+
});
41+
42+
router.get('/agent/probe/commands/next', async (req, res, next) => {
43+
try {
44+
const agent = probeAgentService.authenticateAgent(req.headers.authorization);
45+
if (!agent) return res.status(401).json({ ok: false, error: 'agent token 无效' });
46+
const command = await probeAgentService.waitForNextCommand(agent);
47+
if (!command) return res.status(204).end();
48+
return res.json({ ok: true, command });
49+
} catch (error) {
50+
next(error);
51+
}
52+
});
53+
54+
router.post('/agent/probe/commands/:commandId/result', (req, res, next) => {
55+
try {
56+
const agent = probeAgentService.authenticateAgent(req.headers.authorization);
57+
if (!agent) return res.status(401).json({ ok: false, error: 'agent token 无效' });
58+
const result = probeAgentService.completeCommand(agent, req.params.commandId, req.body || {});
59+
return res.json(result);
60+
} catch (error) {
61+
next(error);
62+
}
63+
});
64+
65+
return router;
66+
}
67+
68+
function createProbeAgentAdminRouter({ probeAgentService, probeAgentInstallerService, probeAggregatorService }) {
69+
const router = express.Router();
70+
71+
router.post('/probe-agents/:hostId/install-token', (req, res, next) => {
72+
try {
73+
const result = probeAgentService.generateInstallToken(req.params.hostId);
74+
const serverUrl = String(req.body?.serverUrl || resolveServerUrl(req)).replace(/\/$/, '');
75+
res.json({
76+
ok: true,
77+
...result,
78+
serverUrl,
79+
config: {
80+
hostId: result.hostId,
81+
installToken: result.installToken,
82+
serverUrl,
83+
registerUrl: `${serverUrl}/api/agent/probe/register`,
84+
reportUrl: `${serverUrl}/api/agent/probe/report`,
85+
},
86+
});
87+
} catch (error) {
88+
next(error);
89+
}
90+
});
91+
92+
router.post('/probe-agents/:hostId/install', async (req, res, next) => {
93+
try {
94+
if (!probeAgentInstallerService) return res.status(501).json({ ok: false, error: 'Agent 安装器未启用' });
95+
const serverUrl = String(req.body?.serverUrl || resolveServerUrl(req)).replace(/\/$/, '');
96+
const intervalSec = typeof req.body?.intervalSec === 'number' ? req.body.intervalSec : undefined;
97+
const relayUpstreamId = typeof req.body?.relayUpstreamId === 'string' ? req.body.relayUpstreamId : undefined;
98+
const install = await probeAgentInstallerService.install(req.params.hostId, {
99+
serverUrl,
100+
intervalSec,
101+
relayUpstreamId,
102+
clientIp: req.ip,
103+
});
104+
res.json({
105+
ok: true,
106+
hostId: install.hostId,
107+
expiresAt: install.expiresAt,
108+
serverUrl: install.serverUrl || serverUrl,
109+
relayUpstreamId: install.relayUpstreamId,
110+
result: install.result,
111+
});
112+
} catch (error) {
113+
next(error);
114+
}
115+
});
116+
117+
router.post('/probe-agents/:hostId/uninstall', async (req, res, next) => {
118+
try {
119+
if (!probeAgentInstallerService) return res.status(501).json({ ok: false, error: 'Agent 安装器未启用' });
120+
const result = await probeAgentInstallerService.uninstall(req.params.hostId, { clientIp: req.ip });
121+
res.json({ ok: true, ...result });
122+
} catch (error) {
123+
next(error);
124+
}
125+
});
126+
127+
router.post('/probe-agents/:hostId/revoke', (req, res, next) => {
128+
try {
129+
res.json(probeAgentService.revokeAgent(req.params.hostId));
130+
} catch (error) {
131+
next(error);
132+
}
133+
});
134+
135+
router.post('/probe-agents/:hostId/restart', async (req, res, next) => {
136+
try {
137+
if (!probeAgentInstallerService) return res.status(501).json({ ok: false, error: 'Agent 安装器未启用' });
138+
const result = await probeAgentInstallerService.restart(req.params.hostId, { clientIp: req.ip });
139+
res.json({ ok: true, ...result });
140+
} catch (error) {
141+
next(error);
142+
}
143+
});
144+
145+
router.get('/probe-agents/:hostId/logs', async (req, res, next) => {
146+
try {
147+
if (!probeAgentInstallerService) return res.status(501).json({ ok: false, error: 'Agent 安装器未启用' });
148+
const lines = parseInt(req.query.lines, 10) || 200;
149+
const result = await probeAgentInstallerService.fetchLogs(req.params.hostId, { clientIp: req.ip, lines });
150+
res.json({ ok: true, ...result });
151+
} catch (error) {
152+
next(error);
153+
}
154+
});
155+
156+
router.get('/probe-agents/:hostId/samples', (req, res, next) => {
157+
try {
158+
const minutes = parseInt(req.query.minutes, 10);
159+
const sinceMs = Number.isFinite(minutes) && minutes > 0
160+
? Math.min(minutes, 60 * 24) * 60 * 1000
161+
: 60 * 60 * 1000;
162+
const samples = probeAgentService.getSampleHistory(req.params.hostId, { sinceMs });
163+
res.json({ ok: true, hostId: req.params.hostId, sinceMs, samples });
164+
} catch (error) {
165+
next(error);
166+
}
167+
});
168+
169+
router.post('/probe-agents/samples-bulk', (req, res, next) => {
170+
try {
171+
const minutes = Number(req.body?.minutes);
172+
const sinceMs = Number.isFinite(minutes) && minutes > 0
173+
? Math.min(minutes, 60 * 24) * 60 * 1000
174+
: 60 * 60 * 1000;
175+
const hostIds = Array.isArray(req.body?.hostIds) ? req.body.hostIds.filter((id) => typeof id === 'string').slice(0, 200) : [];
176+
const result = {};
177+
for (const hostId of hostIds) {
178+
result[hostId] = probeAgentService.getSampleHistory(hostId, { sinceMs });
179+
}
180+
res.json({ ok: true, sinceMs, samples: result });
181+
} catch (error) {
182+
next(error);
183+
}
184+
});
185+
186+
router.get('/probe-agents/:hostId/timeseries', (req, res, next) => {
187+
try {
188+
if (!probeAggregatorService) return res.status(501).json({ ok: false, error: '聚合服务未启用' });
189+
const now = Date.now();
190+
const to = parseTimeQuery(req.query.to, now);
191+
const from = parseTimeQuery(req.query.from, to - 24 * 60 * 60 * 1000);
192+
if (!Number.isFinite(from) || !Number.isFinite(to) || from >= to) {
193+
return res.status(400).json({ ok: false, error: 'from/to 参数非法' });
194+
}
195+
const resolution = typeof req.query.resolution === 'string' ? req.query.resolution : 'auto';
196+
const result = probeAggregatorService.listTimeseries(req.params.hostId, {
197+
fromMs: from,
198+
toMs: to,
199+
resolution,
200+
});
201+
res.json({
202+
ok: true,
203+
hostId: req.params.hostId,
204+
fromMs: from,
205+
toMs: to,
206+
resolution: result.resolution,
207+
points: result.points,
208+
});
209+
} catch (error) {
210+
next(error);
211+
}
212+
});
213+
214+
return router;
215+
}
216+
217+
module.exports = {
218+
createProbeAgentAdminRouter,
219+
createProbeAgentPublicRouter,
220+
};

src/routes/probe-alert.routes.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
3+
const express = require('express');
4+
5+
function createProbeAlertRouter({ alertService }) {
6+
const router = express.Router();
7+
8+
router.get('/probe-alerts/rules', (req, res, next) => {
9+
try {
10+
res.json({ ok: true, rules: alertService.listRules() });
11+
} catch (error) { next(error); }
12+
});
13+
14+
router.post('/probe-alerts/rules', (req, res, next) => {
15+
try {
16+
const rule = alertService.createRule(req.body || {});
17+
res.status(201).json({ ok: true, rule });
18+
} catch (error) { next(error); }
19+
});
20+
21+
router.put('/probe-alerts/rules/:id', (req, res, next) => {
22+
try {
23+
const rule = alertService.updateRule(req.params.id, req.body || {});
24+
res.json({ ok: true, rule });
25+
} catch (error) { next(error); }
26+
});
27+
28+
router.delete('/probe-alerts/rules/:id', (req, res, next) => {
29+
try {
30+
alertService.deleteRule(req.params.id);
31+
res.json({ ok: true });
32+
} catch (error) { next(error); }
33+
});
34+
35+
router.get('/probe-alerts/events', (req, res, next) => {
36+
try {
37+
const status = String(req.query.status || 'open');
38+
const limit = parseInt(req.query.limit, 10);
39+
const offset = parseInt(req.query.offset, 10);
40+
const events = alertService.listEvents({ status, limit, offset });
41+
res.json({ ok: true, events, openCount: alertService.countOpenEvents() });
42+
} catch (error) { next(error); }
43+
});
44+
45+
router.post('/probe-alerts/events/ack-all', (req, res, next) => {
46+
try {
47+
const result = alertService.ackOpenEvents();
48+
res.json({ ok: true, ...result });
49+
} catch (error) { next(error); }
50+
});
51+
52+
router.post('/probe-alerts/events/:id/ack', (req, res, next) => {
53+
try {
54+
const result = alertService.ackEvent(req.params.id);
55+
res.json({ ok: true, ...result });
56+
} catch (error) { next(error); }
57+
});
58+
59+
return router;
60+
}
61+
62+
module.exports = {
63+
createProbeAlertRouter,
64+
};

src/routes/probe-diag.routes.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
3+
const express = require('express');
4+
5+
function createProbeDiagRouter({ diagService }) {
6+
const router = express.Router();
7+
8+
function wrap(fn) {
9+
return async (req, res, next) => {
10+
try {
11+
const result = await fn(req);
12+
res.json({ ok: true, ...result });
13+
} catch (error) {
14+
next(error);
15+
}
16+
};
17+
}
18+
19+
router.post('/probe-diag/:hostId/ping', wrap((req) => diagService.ping(req.params.hostId, {
20+
target: req.body?.target,
21+
count: req.body?.count,
22+
timeoutSec: req.body?.timeoutSec,
23+
clientIp: req.ip,
24+
})));
25+
26+
router.post('/probe-diag/:hostId/http', wrap((req) => diagService.http(req.params.hostId, {
27+
url: req.body?.url,
28+
method: req.body?.method,
29+
timeoutSec: req.body?.timeoutSec,
30+
clientIp: req.ip,
31+
})));
32+
33+
router.post('/probe-diag/:hostId/dns', wrap((req) => diagService.dns(req.params.hostId, {
34+
name: req.body?.name,
35+
clientIp: req.ip,
36+
})));
37+
38+
return router;
39+
}
40+
41+
module.exports = {
42+
createProbeDiagRouter,
43+
};

0 commit comments

Comments
 (0)