feat: settings routes for hardware inventory, GPU transcoding, capture service URL
This commit is contained in:
parent
dd3c2894f6
commit
1725ec1de9
1 changed files with 104 additions and 28 deletions
|
|
@ -6,7 +6,8 @@ import { getAmppConfig } from '../ampp/client.js';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
|
|
||||||
// GET /api/v1/settings/ampp — Return current AMPP config (token value masked)
|
// ── AMPP integration ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
router.get('/ampp', async (req, res, next) => {
|
router.get('/ampp', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
|
|
@ -15,7 +16,7 @@ router.get('/ampp', async (req, res, next) => {
|
||||||
const out = {};
|
const out = {};
|
||||||
for (const row of result.rows) {
|
for (const row of result.rows) {
|
||||||
if (row.key === 'ampp_token') {
|
if (row.key === 'ampp_token') {
|
||||||
out.ampp_token_exists = true; // Never return the raw token
|
out.ampp_token_exists = true;
|
||||||
} else {
|
} else {
|
||||||
out[row.key] = row.value;
|
out[row.key] = row.value;
|
||||||
}
|
}
|
||||||
|
|
@ -26,62 +27,137 @@ router.get('/ampp', async (req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// PUT /api/v1/settings/ampp — Save AMPP credentials
|
|
||||||
router.put('/ampp', async (req, res, next) => {
|
router.put('/ampp', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { ampp_base_url, ampp_token } = req.body;
|
const { ampp_base_url, ampp_token } = req.body;
|
||||||
if (!ampp_base_url) {
|
if (!ampp_base_url) return res.status(400).json({ error: 'ampp_base_url is required' });
|
||||||
return res.status(400).json({ error: 'ampp_base_url is required' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = ampp_base_url.trim().replace(/\/$/, '');
|
const baseUrl = ampp_base_url.trim().replace(/\/$/, '');
|
||||||
|
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO settings (key, value, updated_at)
|
`INSERT INTO settings (key, value, updated_at) VALUES ('ampp_base_url', $1, NOW())
|
||||||
VALUES ('ampp_base_url', $1, NOW())
|
|
||||||
ON CONFLICT (key) DO UPDATE SET value = $1, updated_at = NOW()`,
|
ON CONFLICT (key) DO UPDATE SET value = $1, updated_at = NOW()`,
|
||||||
[baseUrl]
|
[baseUrl]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ampp_token) {
|
if (ampp_token) {
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO settings (key, value, updated_at)
|
`INSERT INTO settings (key, value, updated_at) VALUES ('ampp_token', $1, NOW())
|
||||||
VALUES ('ampp_token', $1, NOW())
|
|
||||||
ON CONFLICT (key) DO UPDATE SET value = $1, updated_at = NOW()`,
|
ON CONFLICT (key) DO UPDATE SET value = $1, updated_at = NOW()`,
|
||||||
[ampp_token.trim()]
|
[ampp_token.trim()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ message: 'AMPP settings saved' });
|
res.json({ message: 'AMPP settings saved' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /api/v1/settings/ampp/test — Verify AMPP connectivity
|
|
||||||
router.post('/ampp/test', async (req, res, next) => {
|
router.post('/ampp/test', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const config = await getAmppConfig();
|
const config = await getAmppConfig();
|
||||||
if (!config) {
|
if (!config) return res.status(400).json({ error: 'AMPP credentials not configured' });
|
||||||
return res.status(400).json({ error: 'AMPP credentials not configured' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const testUrl = `${config.ampp_base_url}/api/v1/store/folder/folders?limit=1`;
|
const testUrl = `${config.ampp_base_url}/api/v1/store/folder/folders?limit=1`;
|
||||||
const testRes = await fetch(testUrl, {
|
const testRes = await fetch(testUrl, {
|
||||||
headers: {
|
headers: { Authorization: `Bearer ${config.ampp_token}`, 'Content-Type': 'application/json' },
|
||||||
Authorization: `Bearer ${config.ampp_token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
if (!testRes.ok) return res.status(400).json({ error: `AMPP returned HTTP ${testRes.status}` });
|
||||||
if (!testRes.ok) {
|
|
||||||
return res.status(400).json({ error: `AMPP returned HTTP ${testRes.status}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ message: 'AMPP connection successful' });
|
res.json({ message: 'AMPP connection successful' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(400).json({ error: `Connection failed: ${err.message}` });
|
res.status(400).json({ error: `Connection failed: ${err.message}` });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Hardware inventory ─────────────────────────────────────────────────────
|
||||||
|
// GET /api/v1/settings/hardware — aggregate GPU/BMD capabilities from all cluster nodes
|
||||||
|
|
||||||
|
router.get('/hardware', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const result = await pool.query(
|
||||||
|
`SELECT id, hostname, role, ip_address, capabilities,
|
||||||
|
EXTRACT(EPOCH FROM (NOW() - last_seen))::int AS stale_seconds
|
||||||
|
FROM cluster_nodes
|
||||||
|
ORDER BY role, hostname`
|
||||||
|
);
|
||||||
|
const nodes = result.rows.map(n => ({
|
||||||
|
id: n.id,
|
||||||
|
hostname: n.hostname,
|
||||||
|
role: n.role,
|
||||||
|
ip_address: n.ip_address,
|
||||||
|
online: n.stale_seconds < 120,
|
||||||
|
capabilities: n.capabilities || {},
|
||||||
|
}));
|
||||||
|
res.json({ nodes });
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── GPU/Transcoding settings ───────────────────────────────────────────────
|
||||||
|
// Keys: gpu_transcode_enabled, gpu_codec, gpu_preset, gpu_bitrate_mbps, gpu_node
|
||||||
|
|
||||||
|
router.get('/transcoding', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const result = await pool.query(
|
||||||
|
`SELECT key, value FROM settings
|
||||||
|
WHERE key IN ('gpu_transcode_enabled','gpu_codec','gpu_preset','gpu_bitrate_mbps','gpu_node')`
|
||||||
|
);
|
||||||
|
const out = {
|
||||||
|
gpu_transcode_enabled: 'false',
|
||||||
|
gpu_codec: 'h264_nvenc',
|
||||||
|
gpu_preset: 'p4',
|
||||||
|
gpu_bitrate_mbps: '8',
|
||||||
|
gpu_node: '',
|
||||||
|
};
|
||||||
|
for (const { key, value } of result.rows) out[key] = value;
|
||||||
|
res.json(out);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put('/transcoding', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const allowed = ['gpu_transcode_enabled', 'gpu_codec', 'gpu_preset', 'gpu_bitrate_mbps', 'gpu_node'];
|
||||||
|
for (const key of allowed) {
|
||||||
|
if (req.body[key] !== undefined) {
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO settings (key, value, updated_at) VALUES ($1, $2, NOW())
|
||||||
|
ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()`,
|
||||||
|
[key, String(req.body[key])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.json({ message: 'Transcoding settings saved' });
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Capture service routing ────────────────────────────────────────────────
|
||||||
|
// capture_service_url — points to the remote capture node's API
|
||||||
|
// When set, the local capture proxy routes all requests to this URL instead of the sidecar
|
||||||
|
|
||||||
|
router.get('/capture-service', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const result = await pool.query(
|
||||||
|
"SELECT value FROM settings WHERE key = 'capture_service_url'"
|
||||||
|
);
|
||||||
|
res.json({ capture_service_url: result.rows[0]?.value || '' });
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put('/capture-service', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const url = (req.body.capture_service_url || '').trim().replace(/\/$/, '');
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO settings (key, value, updated_at) VALUES ('capture_service_url', $1, NOW())
|
||||||
|
ON CONFLICT (key) DO UPDATE SET value = $1, updated_at = NOW()`,
|
||||||
|
[url]
|
||||||
|
);
|
||||||
|
res.json({ message: 'Capture service URL saved' });
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue