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();
|
const router = express.Router();
|
||||||
router.use(requireAuth);
|
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
|
// GET / – list all registered cluster nodes with online status
|
||||||
router.get('/', async (req, res, next) => {
|
router.get('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -33,6 +45,8 @@ router.post('/heartbeat', async (req, res, next) => {
|
||||||
|
|
||||||
if (!hostname) return res.status(400).json({ error: 'hostname is required' });
|
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(
|
const r = await pool.query(
|
||||||
`INSERT INTO cluster_nodes
|
`INSERT INTO cluster_nodes
|
||||||
(hostname, ip_address, role, version, api_url,
|
(hostname, ip_address, role, version, api_url,
|
||||||
|
|
@ -52,7 +66,7 @@ router.post('/heartbeat', async (req, res, next) => {
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
hostname,
|
hostname,
|
||||||
ip_address || null,
|
effectiveIp,
|
||||||
role,
|
role,
|
||||||
version || null,
|
version || null,
|
||||||
api_url || null,
|
api_url || null,
|
||||||
|
|
@ -67,6 +81,38 @@ router.post('/heartbeat', async (req, res, next) => {
|
||||||
} catch (err) { next(err); }
|
} 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
|
// GET /:id/ping – probe the node's api_url/health endpoint directly
|
||||||
router.get('/:id/ping', async (req, res, next) => {
|
router.get('/:id/ping', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue