Add server.js
This commit is contained in:
parent
a69e7abff8
commit
ccde43ce37
1 changed files with 166 additions and 0 deletions
166
server.js
Normal file
166
server.js
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
const express = require('express');
|
||||||
|
const cors = require('cors');
|
||||||
|
const axios = require('axios');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
|
// Proxy endpoint for getting token
|
||||||
|
app.post('/api/token', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { apiKey, baseUrl } = req.body;
|
||||||
|
|
||||||
|
const response = await axios.post(
|
||||||
|
`${baseUrl}/identity/connect/token`,
|
||||||
|
'grant_type=client_credentials&scope=platform',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Basic ${apiKey}`,
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
timeout: 10000
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
res.json(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Token error:', error.message);
|
||||||
|
res.status(error.response?.status || 500).json({
|
||||||
|
error: error.message,
|
||||||
|
details: error.response?.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Proxy endpoint for fetching RUNNING recorders
|
||||||
|
// Strategy: use events as the primary source (they reflect what's actually running),
|
||||||
|
// then enrich with channel data for editing (source:text, destinationId:int)
|
||||||
|
app.get('/api/channels', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { token, baseUrl } = req.query;
|
||||||
|
|
||||||
|
if (!token || !baseUrl) {
|
||||||
|
return res.status(400).json({ error: 'Missing token or baseUrl' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = { 'Authorization': `Bearer ${token}` };
|
||||||
|
|
||||||
|
// Fetch channels and events in parallel
|
||||||
|
const [channelsRes, eventsRes] = await Promise.allSettled([
|
||||||
|
axios.get(`${baseUrl}/api/store/channel/v1/channels`, { headers, timeout: 10000 }),
|
||||||
|
axios.get(`${baseUrl}/api/v2/store/schedule/events`, { headers, timeout: 10000 })
|
||||||
|
]);
|
||||||
|
|
||||||
|
const channelsData = channelsRes.status === 'fulfilled'
|
||||||
|
? (Array.isArray(channelsRes.value.data) ? channelsRes.value.data : [])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const eventsData = eventsRes.status === 'fulfilled'
|
||||||
|
? (Array.isArray(eventsRes.value.data) ? eventsRes.value.data : [])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
console.log(`Fetched ${channelsData.length} channels, ${eventsData.length} events`);
|
||||||
|
if (eventsData.length > 0) {
|
||||||
|
console.log('Sample event keys:', Object.keys(eventsData[0]));
|
||||||
|
console.log('Sample event:', JSON.stringify(eventsData[0], null, 2).substring(0, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a channel lookup map by URI
|
||||||
|
const channelByUri = {};
|
||||||
|
channelsData.forEach(ch => {
|
||||||
|
if (ch['uri:text']) channelByUri[ch['uri:text']] = ch;
|
||||||
|
if (ch.uri) channelByUri[ch.uri] = ch;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter events to only those currently running
|
||||||
|
const runningEvents = eventsData.filter(event => {
|
||||||
|
const status = (event['status:enum'] || '').toLowerCase();
|
||||||
|
return status === 'running';
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Running events: ${runningEvents.length}`);
|
||||||
|
|
||||||
|
// Enrich each running event with its channel data
|
||||||
|
const result = runningEvents.map(event => {
|
||||||
|
const channelUri = event['source']?.['channel:text'];
|
||||||
|
const channel = channelUri ? channelByUri[channelUri] : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Clean up name - remove leading "undefined" or "null" prefixes
|
||||||
|
const rawName = event['name:text'] || channel?.['name:text'] || 'Unknown';
|
||||||
|
const cleanName = rawName.replace(/^(undefined|null)\s*/i, '').trim() || rawName;
|
||||||
|
|
||||||
|
// Event fields
|
||||||
|
_eventId: event['scheduleEvent:id'] || event['id'],
|
||||||
|
_health: event['health:enum'],
|
||||||
|
_status: event['status:enum'],
|
||||||
|
_healthCondition: event['healthCondition:enum'],
|
||||||
|
_channelUri: channelUri,
|
||||||
|
|
||||||
|
// Channel fields (for editing)
|
||||||
|
'channel:id': channel?.['channel:id'] || null,
|
||||||
|
'name:text': cleanName,
|
||||||
|
'source:text': channel?.['source:text'] || null,
|
||||||
|
'destinationId:int': channel?.['destinationId:int'] || null,
|
||||||
|
'elasticRecorder': channel?.['elasticRecorder'] || null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Channels error:', error.message);
|
||||||
|
res.status(error.response?.status || 500).json({
|
||||||
|
error: error.message,
|
||||||
|
details: error.response?.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Proxy endpoint for updating channel
|
||||||
|
app.patch('/api/channels/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { token, baseUrl } = req.query;
|
||||||
|
const { id } = req.params;
|
||||||
|
const data = req.body;
|
||||||
|
|
||||||
|
if (!token || !baseUrl) {
|
||||||
|
return res.status(400).json({ error: 'Missing token or baseUrl' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.patch(
|
||||||
|
`${baseUrl}/api/store/channel/v1/channels/${id}`,
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` },
|
||||||
|
timeout: 10000
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
res.json(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Update error:', error.message);
|
||||||
|
res.status(error.response?.status || 500).json({
|
||||||
|
error: error.message,
|
||||||
|
details: error.response?.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Health check
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.json({ status: 'ok' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Serve index
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`✅ Dashboard running on http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue