cluster route: fallback IP from request + /devices/blackmagic endpoint
Heartbeat handler now overrides 172.x docker bridge IPs with the request's source address when the request itself came from a real LAN. Adds GET /devices/blackmagic that flattens every node's capabilities so the recorder UI can show a card picker spanning the whole cluster.
This commit is contained in:
parent
485af25d4a
commit
0efef0d81b
1 changed files with 47 additions and 1 deletions
|
|
@ -5,6 +5,18 @@ import { requireAuth } from '../middleware/auth.js';
|
|||
const router = express.Router();
|
||||
router.use(requireAuth);
|
||||
|
||||
// If the agent reported a Docker-bridge IP (172.16/12) but the request
|
||||
// itself came from a real LAN address, prefer the request's source — the
|
||||
// agent likely runs in bridge mode without NODE_IP set.
|
||||
function pickIp(reportedIp, reqIp) {
|
||||
const clean = (s) => (s || '').replace(/^::ffff:/, '');
|
||||
const isBridge = (ip) => /^172\.(1[6-9]|2\d|3[01])\./.test(ip || '');
|
||||
const r = clean(reqIp);
|
||||
if (!reportedIp) return r || null;
|
||||
if (isBridge(reportedIp) && r && !isBridge(r)) return r;
|
||||
return reportedIp;
|
||||
}
|
||||
|
||||
// GET / – list all registered cluster nodes with online status
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
|
|
@ -33,6 +45,8 @@ router.post('/heartbeat', async (req, res, next) => {
|
|||
|
||||
if (!hostname) return res.status(400).json({ error: 'hostname is required' });
|
||||
|
||||
const effectiveIp = pickIp(ip_address, req.ip || req.socket?.remoteAddress);
|
||||
|
||||
const r = await pool.query(
|
||||
`INSERT INTO cluster_nodes
|
||||
(hostname, ip_address, role, version, api_url,
|
||||
|
|
@ -52,7 +66,7 @@ router.post('/heartbeat', async (req, res, next) => {
|
|||
RETURNING *`,
|
||||
[
|
||||
hostname,
|
||||
ip_address || null,
|
||||
effectiveIp,
|
||||
role,
|
||||
version || null,
|
||||
api_url || null,
|
||||
|
|
@ -67,6 +81,38 @@ router.post('/heartbeat', async (req, res, next) => {
|
|||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
// GET /devices/blackmagic – flatten every node's DeckLink cards for the
|
||||
// recorder picker. Returns one entry per device with the host node info.
|
||||
router.get('/devices/blackmagic', async (req, res, next) => {
|
||||
try {
|
||||
const r = await pool.query(
|
||||
`SELECT id, hostname, ip_address, role, capabilities,
|
||||
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
||||
FROM cluster_nodes
|
||||
WHERE capabilities IS NOT NULL`
|
||||
);
|
||||
const out = [];
|
||||
for (const row of r.rows) {
|
||||
const online = Number(row.stale_seconds) < 120;
|
||||
const bm = (row.capabilities && row.capabilities.blackmagic) || [];
|
||||
const model = (row.capabilities && row.capabilities.blackmagic_model) || null;
|
||||
bm.forEach((d, idx) => {
|
||||
out.push({
|
||||
node_id: row.id,
|
||||
hostname: row.hostname,
|
||||
ip_address: row.ip_address,
|
||||
role: row.role,
|
||||
online,
|
||||
model,
|
||||
index: d.index !== undefined ? d.index : idx,
|
||||
device: d.device,
|
||||
});
|
||||
});
|
||||
}
|
||||
res.json(out);
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
// GET /:id/ping – probe the node's api_url/health endpoint directly
|
||||
router.get('/:id/ping', async (req, res, next) => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue