Init WebCG
This commit is contained in:
@@ -0,0 +1,786 @@
|
||||
|
||||
/* ------------------------------------------
|
||||
|
||||
Public API routes for external controllers
|
||||
such as Stream Deck or similar.
|
||||
|
||||
/api/v1/
|
||||
|
||||
Home route is a list of available commands.
|
||||
|
||||
--------------------------------------------- */
|
||||
|
||||
var express = require("express");
|
||||
const router = express.Router();
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const moment = require('moment');
|
||||
const directoryPath = path.normalize(config.general.dataroot);
|
||||
const logger = require('../utils/logger');
|
||||
logger.debug('API-v1 route loading...');
|
||||
const spx = require('../utils/spx_server_functions.js');
|
||||
const xlsx = require('node-xlsx').default;
|
||||
const axios = require('axios')
|
||||
const PlayoutCCG = require('../utils/playout_casparCG.js');
|
||||
const { query } = require("../utils/logger");
|
||||
const spxAuth = require('../utils/spx_auth.js');
|
||||
let apiCache = [] // used to cache [undocumented] API calls
|
||||
let ack = 'Sent request to SPX server. Acknowledgement is not to be expected.'
|
||||
let ack2 = 'Sent request to SPX Controller. Acknowledgement is not to be expected.'
|
||||
const apiHandler = require('../utils/api-handlers.js');
|
||||
|
||||
const notInSolo = {
|
||||
status: 501,
|
||||
message: 'Not Implemented',
|
||||
info: 'This API endpoint is not available in WebCG.',
|
||||
more: 'For advanced API features, please see WebCG Production or Broadcast'
|
||||
}
|
||||
|
||||
// ROUTES -------------------------------------------------------------------------------------------
|
||||
router.get('/', function (req, res) {
|
||||
let functionsDoc = {
|
||||
"sections": [
|
||||
|
||||
{
|
||||
"section": "Common API",
|
||||
"info": "Generic API endpoints and utilities available in all WebCG versions without a license.",
|
||||
"commands": [
|
||||
{
|
||||
"vers": "v1.1.2",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/version",
|
||||
"info": "Returns SPX version info and current host-id"
|
||||
},
|
||||
{
|
||||
"vers": "v1.1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/panic",
|
||||
"info": "Force clear to all output layers without out-animations. (Note, this does NOT save on-air state of rundown items to false, so when UI is reloaded the items will show the state before panic was triggered.) This is to be used for emergency situations only and not as a normal STOP command substitute."
|
||||
},
|
||||
{
|
||||
"vers": "v1.0.14",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/feedproxy?url=https://feeds.bbci.co.uk/news/rss.xml&format=xml",
|
||||
"info": "A proxy endpoint for passing feed data from CORS protected datasources. (If you need to pass url parameters use <code>%26</code> instead of <code>&</code> to separate them)."
|
||||
},
|
||||
{
|
||||
"vers": "v1.3.0",
|
||||
"method": "POST",
|
||||
"param": "/api/v1/feedproxy",
|
||||
"info": "A POST version of the feedproxy endpoint. This endpoint is a helper for outgoing GET or POST requests requiring custom headers, such as <code>Authorization</code> or similar. Data is passed to the helper in the <code>body</code> of the POST request, in which <code>url</code> is the actual URL of the external endpoint. If the body contains a <code>postBody</code> -object, it will be passed to the outgoing API request as <code>body</code>. See principle in the example below, or search SPX Knowledge Base for more info with keyword <code>feedproxy</code>.",
|
||||
"code": { url: "https://api.endpoint.com/requiring/customheaders/", headers: { "key1": "my first value", "key2": "second value" }, postBody: { info: "If postBody is found, the outgoing request will be done using POST method, otherwise as GET." } }
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"section": "Controller API",
|
||||
"restriction": "Some endpoints require WebCG Production license",
|
||||
"info": "API endpoints for controlling the WebCG Graphics Controller rundown. Please note some endpoints will require a valid WebCG Production license.",
|
||||
"commands": [
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/rundown/focusPrevious",
|
||||
"info": "Move focus up to previous item, will not circle back to bottom when top is reached."
|
||||
},
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/rundown/focusNext",
|
||||
"info": "Move focus down to next item, will not circle back to top when end is reached."
|
||||
},
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/rundown/stopAllLayers",
|
||||
"info": "Animate all layers (used by the current rundown) out, but does not clear layers."
|
||||
},
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/item/play",
|
||||
"info": "Start focused item."
|
||||
},
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/item/continue",
|
||||
"info": "Issue continue command to selected item. Notice this needs support from the template itself and does not work as play or stop."
|
||||
},
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/item/stop",
|
||||
"info": "Stop focused item."
|
||||
},
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/rundown/load?file=MyFirstProject/MyFirstRundown",
|
||||
"info": "Open rundown from project / file."
|
||||
},
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/rundown/focusFirst",
|
||||
"info": "Move focus to the first item on the rundown.",
|
||||
},
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/rundown/focusLast",
|
||||
"info": "Move focus to the last item on the rundown.",
|
||||
},
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/rundown/focusByID/1234567890",
|
||||
"info": "Move focus by ID on the rundown.",
|
||||
},
|
||||
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/item/play/1234567890",
|
||||
"info": "Start item by ID on the active rundown.",
|
||||
"active": false,
|
||||
},
|
||||
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/item/continue/1234567890",
|
||||
"info": "Continue to item by ID on the active rundown. Notice this needs support from the template itself and does not work as play or stop.",
|
||||
"active": false,
|
||||
},
|
||||
|
||||
{
|
||||
"vers": "1.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/item/stop/1234567890",
|
||||
"info": "Stop item by ID on the active rundown.",
|
||||
"active": false,
|
||||
},
|
||||
{
|
||||
"vers": "v1.0.12",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/invokeTemplateFunction?playserver=OVERLAY&playchannel=1&playlayer=19&webplayout=19&function=myCustomTemplateFunction¶ms=Hello%20World",
|
||||
"info": "Uses an invoke handler to call a function in a template. See required parameters in the example call above. JSON objects can be passed as params by urlEncoding stringified JSON. Search SPX Knowledge Base for more info with keyword <code>invoke</code>.",
|
||||
"active": false,
|
||||
},
|
||||
{
|
||||
"vers": "v1.3.0",
|
||||
"method": "GET",
|
||||
"param": "/api/v1/invokeExtensionFunction?function=sendCmd¶ms=incrementNumber",
|
||||
"info": "Uses SPX's messaging system to call a function in an extension. JSON objects can be passed as params by urlEncoding stringified JSON. The extension will need to implement SPX's messaging system, search SPX Knowledge Base for more info with keyword <code>invokeExtensionFunction</code>.",
|
||||
"active": false,
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"section": "Server API",
|
||||
"restriction": "SPX Broadcast license required",
|
||||
"info": "API endpoints targeting the SPX server directly without going through the rundown controller. These endpoints are for advanced uses and require a SPX Broadcast license.",
|
||||
"commands": [
|
||||
{
|
||||
"vers": "v1.0.12, v.1.3.2",
|
||||
"method": "POST",
|
||||
"active": false,
|
||||
"param": "/api/v1/directplayout",
|
||||
"info": "Populate template and execute a play/continue/stop -command to it. Please note the optional <code>updateRundownItem</code> property. <code>updateRundownItemitemID</code> is an optional object for forcing UI updates and persisting to defined rundown file. <b>Please note: special charaters in values does not work at the moment!</b> Post request body example as JSON:",
|
||||
"code": { casparServer: "OVERLAY", casparChannel: "1", casparLayer: "20", webplayoutLayer: "20", relativeTemplatePath: "/vendor/pack/template.html", out: "manual", DataFields: [{ field: "f0", value: "Firstname" }, { field: "f1", value: "Lastname" }], command: "play", updateRundownItem: { updateUI: true, itemID: "myItemID", persist: true, "project": "myFirstProject", "rundown": "myFirstRundown" } }
|
||||
},
|
||||
{
|
||||
"vers": "v1.1.0",
|
||||
"active": false,
|
||||
"method": "GET",
|
||||
"param": "/api/v1/controlRundownItemByID?file=MyProject/FirstRundown&item=1234567890&command=play",
|
||||
"info": "Play / stop an item from a known rundown. (Remember you can rename rundown items from WebCG GUI)"
|
||||
},
|
||||
{
|
||||
"vers": "v1.1.1",
|
||||
"method": "GET",
|
||||
"active": false,
|
||||
"param": "/api/v1/getprojects",
|
||||
"info": "Returns projects as an array of strings."
|
||||
},
|
||||
{
|
||||
"vers": "v1.1.1",
|
||||
"method": "GET",
|
||||
"active": false,
|
||||
"param": "/api/v1/getrundowns?project=MyProject",
|
||||
"info": "Returns rundown names of a given project as an array of strings."
|
||||
},
|
||||
{
|
||||
"vers": "v1.3.0",
|
||||
"method": "GET",
|
||||
"active": false,
|
||||
"param": "/api/v1/allrundowns",
|
||||
"info": "Returns all projects and rundowns"
|
||||
},
|
||||
{
|
||||
"vers": "v1.1.1",
|
||||
"method": "GET",
|
||||
"active": false,
|
||||
"param": "/api/v1/rundown/get",
|
||||
"info": "Returns current rundown as json. "
|
||||
},
|
||||
{
|
||||
"vers": "v1.3.0",
|
||||
"method": "GET",
|
||||
"active": false,
|
||||
"param": "/api/v1/rundown/json?project=MyProject&rundown=FirstRundown",
|
||||
"info": "Returns content of a specific rundown as json data."
|
||||
},
|
||||
{
|
||||
"vers": "v1.3.0",
|
||||
"method": "POST",
|
||||
"active": false,
|
||||
"param": "/api/v1/rundown/json",
|
||||
"info": "Creates or updates a rundown file. This can be used for example with application extensions. POST <code>body:content</code> must contain valid rundown JSON data, otherwise SPX controller may not be able to read it. For more info search SPX Knowledge Base with keyword <code>api rundown/json</code>",
|
||||
"code": { project: "myProjectName", file: "newRundown.json", content: { comment: "Playlist generated by MyApp", templates: [{ "description": "First template", "playserver": "OVERLAY", "etc": "..." }, { "description": "Second template", "playserver": "OVERLAY", "etc": "..." }] } }
|
||||
},
|
||||
{
|
||||
"vers": "v1.1.1",
|
||||
"method": "GET",
|
||||
"active": false,
|
||||
"param": "/api/v1/getlayerstate",
|
||||
"info": "Returns current memory state of web-playout layers of the server (not UI). Please note, if API commands are used to load templates, this may not return them as expected!"
|
||||
},
|
||||
|
||||
{
|
||||
"vers": "v1.1.3",
|
||||
"method": "GET",
|
||||
"active": false,
|
||||
"param": "/api/v1/gettemplates?project=MyProject",
|
||||
"info": "Returns templates and their settings from a given project."
|
||||
},
|
||||
{
|
||||
"vers": "v1.3.0",
|
||||
"method": "GET",
|
||||
"active": false,
|
||||
"param": "/api/v1/executeScript?file=win-open-calculator.bat",
|
||||
"info": "Execute a shell script/batch file in <code>ASSETS/scripts</code> folder using a shell associated with a given file extension."
|
||||
},
|
||||
|
||||
{
|
||||
"vers": "v1.3.0",
|
||||
"method": "GET",
|
||||
"active": false,
|
||||
"param": "/api/v1/getFileList?assetsfolder=excel",
|
||||
"info": "Returns an array of filenames fround in a given subfolder of ASSETS, such as <code>excel</code>."
|
||||
},
|
||||
{
|
||||
"vers": "v1.3.0",
|
||||
"method": "POST",
|
||||
"active": false,
|
||||
"param": "/api/v1/saveCustomJSON",
|
||||
"info": "Creates or updates a JSON file in ASSETS/json folder. This can be used for persisting arbitrary data to a JSON file. The <code>content</code> property of the below example gets saved to <code>ASSETS/json/todoApp/myTodo.json</code>. Note the subfolder property is optional.",
|
||||
"code": { subfolder: "todoApp", filename: "myData.json", content: { note: "Get these done by the end of month", items: [{ "task": "Grow a beard", "done": false }, { "task": "Get a haircut", "done": true }] } }
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
res.render('view-api-v1', {
|
||||
layout: false,
|
||||
functionList: functionsDoc,
|
||||
version: global.vers
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// COMMON API ----------------------------------------------------------
|
||||
|
||||
router.get('/version', async (req, res) => {
|
||||
// let data = {};
|
||||
// data.vendor = "SPX Graphics";
|
||||
// data.product = "SPX Solo";
|
||||
// data.version = global.vers;
|
||||
// data.id = global.pmac;
|
||||
// data.os = process.platform;
|
||||
// return res.status(200).json(data)
|
||||
let data = {};
|
||||
data.vendor = 'SPX Graphics';
|
||||
data.product = 'SPX Solo';
|
||||
data.version = global.vers;
|
||||
data.id = global.pmac;
|
||||
data.hostname = config.general.hostname || '';
|
||||
data.os = process.platform;
|
||||
data.rootFolder = spx.getStartUpFolder();
|
||||
data.uptime = {};
|
||||
let uptime = spx.getUpTime();
|
||||
data.uptime.seconds = uptime[0];
|
||||
data.uptime.text = uptime[1];
|
||||
|
||||
// await spx.checkLicense();
|
||||
// if (global.licensed) {
|
||||
// data.license.type = global.license.productBrand;
|
||||
// data.license.expiration = global.license.expiration;
|
||||
// data.license.days = spx.getLicenseDaysRemaining();
|
||||
// }
|
||||
|
||||
if (global.env && global.env.vendor) {
|
||||
data.env = {};
|
||||
data.env.vendor = global.env.vendor;
|
||||
data.env.product = global.env.product;
|
||||
data.env.version = global.env.version;
|
||||
}
|
||||
return res.status(200).json(data);
|
||||
}); // end version
|
||||
|
||||
|
||||
router.get('/panic', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
io.emit('SPXMessage2Client', { spxcmd: 'clearAllLayers' }); // clear webrenderers
|
||||
io.emit('SPXMessage2Controller', { APIcmd: 'RundownAllStatesToStopped' }); // stop UI and save stopped values to rundown
|
||||
if (spx.CCGServersConfigured) {
|
||||
PlayoutCCG.clearChannelsFromGCServer(req.body.server) // server is optional
|
||||
}
|
||||
return res.status(200).json({ status: 200, message: 'OK', info: 'Panic executed. Layers cleared forcefully.' })
|
||||
}); // end panic
|
||||
|
||||
|
||||
router.get('/feedproxy', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
if (!req.query.url) {
|
||||
logger.error('URL missing from feedproxy parameters');
|
||||
return res.status(500).json({ type: 'error', message: 'URL missing from parameters' })
|
||||
}
|
||||
|
||||
let URL = req.query.url
|
||||
URL = URL.replace(/meta-data/g, '');
|
||||
URL = URL.replace(/&path=/g, '###');
|
||||
axios.get(URL)
|
||||
.then(function (response) {
|
||||
res.header('Access-Control-Allow-Origin', '*')
|
||||
switch (req.query.format) {
|
||||
case 'xml':
|
||||
res.set('Content-Type', 'application/rss+xml')
|
||||
break;
|
||||
|
||||
default:
|
||||
res.set('Content-Type', 'application/json')
|
||||
break
|
||||
}
|
||||
res.send(response.data)
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
return res.status(500).json({ type: 'error', message: error.message })
|
||||
});
|
||||
}); // end feedproxy
|
||||
|
||||
|
||||
router.post('/feedproxy', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
try {
|
||||
if (!req.body.url) {
|
||||
let errMsg = "url missing from feedproxy request. Make sure to have the correct JSON in your request.";
|
||||
throw { status: 404, message: errMsg };
|
||||
}
|
||||
|
||||
if (req.body.postBody) {
|
||||
executePOSTRequest(req, res); // res handled in the function
|
||||
} else {
|
||||
executeGETRequest(req, res); // res handled in the function
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(error.status || 500).json({
|
||||
status: error.status || 500,
|
||||
error: error.message,
|
||||
info: "Search SPX Knowledge Base for more using keyword 'feedproxy'."
|
||||
});
|
||||
}
|
||||
}); // end feedproxy post handler
|
||||
|
||||
|
||||
async function executePOSTRequest(req, res) {
|
||||
try {
|
||||
const { url, headers, postBody } = req.body;
|
||||
|
||||
if (!url || !headers || !postBody) {
|
||||
return res.status(400).json({ error: 'Missing required parameters in request body' });
|
||||
}
|
||||
|
||||
let requestBody;
|
||||
try {
|
||||
requestBody = JSON.parse(postBody);
|
||||
} catch (error) {
|
||||
return res.status(400).json({ error: 'Invalid postBody format' });
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(requestBody),
|
||||
headers: headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return res.status(response.status).json({ error: `Target API returned an error: ${response.status} ${response.statusText}` });
|
||||
}
|
||||
const data = await response.json();
|
||||
res.status(response.status).json(data);
|
||||
} catch (error) {
|
||||
console.error('Error in executePOSTRequest:', error);
|
||||
res.status(500).json({ error: 'Failed to forward POST request' });
|
||||
}
|
||||
}
|
||||
|
||||
function executeGETRequest(req, res) {
|
||||
// This handles response to the client
|
||||
// console.log('Using GET method', req.body);
|
||||
axios.
|
||||
get(req.body.url, {
|
||||
headers: req.body.headers
|
||||
})
|
||||
|
||||
.then((response) => {
|
||||
res.header('Access-Control-Allow-Origin', '*')
|
||||
// console.log('Response status:', response.statusText, response.status, response.data);
|
||||
logger.verbose('executeGETRequest response: ' + response.status);
|
||||
res.status(response.status || 200).send(response.data);
|
||||
})
|
||||
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
// console.log('data......', error.response.data);
|
||||
// console.log('status....', error.response.status);
|
||||
// console.log('headers...', error.response.headers);
|
||||
res.status(error.response.status || 500).send(error.response.data);
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
// console.log('request...', error.request);
|
||||
res.status(500).send(error.request);
|
||||
} else {
|
||||
// Something happened in setting up the request
|
||||
// console.log('Error msg...', error.message);
|
||||
res.status(500).send(error.message);
|
||||
}
|
||||
});
|
||||
} // end executeGETRequest
|
||||
|
||||
|
||||
router.get('/rundown/load/', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
|
||||
// Improved in 1.3.1 to sanitize input
|
||||
let file = await spx.strip(req.query.file);
|
||||
console.log('Loading rundown: ' + file);
|
||||
|
||||
// Added in 1.3.1 check if it exists
|
||||
let project = file.split('/')[0];
|
||||
let rundown = file.split('/')[1];
|
||||
let fullPath = path.resolve(spx.getDatarootFolder(), project, 'data', rundown + '.json');
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
let errMsg = 'Rundown [' + file + '] not found, cannot load it.';
|
||||
logger.error(errMsg);
|
||||
return res.status(404).json({ status: 404, message: errMsg });
|
||||
}
|
||||
|
||||
let dataOut = {};
|
||||
dataOut.info = ack2
|
||||
dataOut.APIcmd = 'RundownLoad';
|
||||
dataOut.file = file;
|
||||
dataOut.apikey = req.query.apikey || '';
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).json(dataOut);
|
||||
}); // end load
|
||||
|
||||
|
||||
router.get('/rundown/stopAllLayers', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
let dataOut = {};
|
||||
dataOut.info = ack2
|
||||
dataOut.APIcmd = 'RundownStopAll';
|
||||
dataOut.apikey = req.query.apikey || '';
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).json(dataOut);
|
||||
}); // end stopAllLayers
|
||||
|
||||
|
||||
router.get('/changeItemID', async (req, res) => {
|
||||
// Added in 1.0.15 - undocumented intentionally
|
||||
// ID button in SPX controller uses this API endpoint:
|
||||
// /api/v1/changeItemID?rundownfile=C:/SPX/DATAROOT/PROJECT/data/list.json&ID=0000001&newID=0000002
|
||||
|
||||
try {
|
||||
let file = req.query.rundownfile || '';
|
||||
let oldI = req.query.ID || '';
|
||||
let newI = req.query.newID || '';
|
||||
if (!file || !oldI || !newI) {
|
||||
throw 'Missing data: file [' + file + '], old [' + oldI + '], new [' + newI + ']';
|
||||
}
|
||||
|
||||
let datafile = path.normalize(file);
|
||||
let RundownData = await spx.GetJsonData(datafile);
|
||||
|
||||
// First check for conflicts
|
||||
RundownData.templates.forEach((item, index) => {
|
||||
if (item.itemID === newI) {
|
||||
throw 'ID-conflict'
|
||||
}
|
||||
});
|
||||
|
||||
RundownData.templates.forEach((item, index) => {
|
||||
if (item.itemID === oldI) {
|
||||
item.itemID = newI
|
||||
}
|
||||
});
|
||||
|
||||
RundownData.updated = new Date().toISOString();
|
||||
|
||||
RundownData = await spx.appendProjectFile(RundownData, datafile, "from changeItemID");
|
||||
global.rundownData = RundownData;
|
||||
await spx.writeFile(datafile, RundownData);
|
||||
logger.verbose('Changed item ID to ' + newI);
|
||||
return res.status(200).send('ID changed to ' + newI);
|
||||
} catch (error) {
|
||||
switch (error) {
|
||||
case 'ID-conflict':
|
||||
msg = 'ID conflict, ID was not changed.'
|
||||
lvl = 'warn'
|
||||
break;
|
||||
|
||||
default:
|
||||
msg = 'Error in changeItemID: ' + error
|
||||
lvl = 'error'
|
||||
}
|
||||
logger[lvl](msg);
|
||||
return res.status(409).send('ID not changed');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
router.get('/changeItemData', async (req, res) => {
|
||||
// Added in 1.1.1
|
||||
// ID button in SPX controller uses this API endpoint:
|
||||
// /api/v1/changeItemData?rundownfile=C:/SPX/DATAROOT/PROJECT/data/list.json & ID=1234567890 & key=out & newValue=manual
|
||||
|
||||
try {
|
||||
let file = req.query.rundownfile || '';
|
||||
let epoc = req.query.ID || '';
|
||||
let prop = req.query.key || '';
|
||||
let valu = req.query.newValue || '';
|
||||
if (!file || !epoc || !prop || !valu) {
|
||||
logger.warn('Missing data from changeItemData: file: [' + file + '], ID: [' + epoc + '], key: [' + prop + '], value: [' + valu + ']')
|
||||
throw 'Missing data, see log.';
|
||||
}
|
||||
let datafile = path.normalize(file);
|
||||
let RundownData = await spx.GetJsonData(datafile);
|
||||
RundownData.templates.forEach((item, index) => {
|
||||
if (item.itemID === epoc) {
|
||||
item[prop] = valu
|
||||
}
|
||||
});
|
||||
|
||||
RundownData.updated = new Date().toISOString();
|
||||
RundownData = await spx.appendProjectFile(RundownData, datafile, "from changeItemData");
|
||||
global.rundownData = RundownData; // push to memory also for next take
|
||||
await spx.writeFile(datafile, RundownData);
|
||||
logger.verbose('ChangeItemData: file: [' + file + '], ID: [' + epoc + '], key: [' + prop + '], value: [' + valu + ']')
|
||||
return res.status(200).send(prop + ' changed to ' + valu);
|
||||
} catch (error) {
|
||||
console.log('Error', error);
|
||||
logger.error('changeItemData error', error);
|
||||
return res.status(409).send('Failed to change ' + prop + ' to ' + valu + '!');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// CONTROLLER API -----------------------------------------------------------------------------
|
||||
|
||||
router.get('/item/play', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
let dataOut = {};
|
||||
dataOut.info = ack2
|
||||
dataOut.status = 200;
|
||||
dataOut.message = 'OK';
|
||||
dataOut.APIcmd = 'ItemPlay';
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).json(dataOut);
|
||||
});
|
||||
|
||||
|
||||
router.get('/item/continue', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
let dataOut = {};
|
||||
dataOut.status = 200;
|
||||
dataOut.message = 'OK';
|
||||
dataOut.info = ack2
|
||||
dataOut.APIcmd = 'ItemContinue';
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).json(dataOut);
|
||||
}); // end item continue
|
||||
|
||||
|
||||
router.get('/item/stop', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
let dataOut = {};
|
||||
dataOut.status = 200;
|
||||
dataOut.message = 'OK';
|
||||
dataOut.info = ack2
|
||||
dataOut.APIcmd = 'ItemStop';
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).json(dataOut);
|
||||
}); // end item stop
|
||||
|
||||
|
||||
router.get('/rundown/focusNext/', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
let dataOut = {};
|
||||
dataOut.info = ack2
|
||||
dataOut.APIcmd = 'RundownFocusNext';
|
||||
dataOut.apikey = req.query.apikey || '';
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).json(dataOut);
|
||||
}); // end focusNext
|
||||
|
||||
|
||||
router.get('/rundown/focusPrevious/', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
let dataOut = {};
|
||||
dataOut.info = ack2
|
||||
dataOut.APIcmd = 'RundownFocusPrevious';
|
||||
dataOut.apikey = req.query.apikey || '';
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).json(dataOut);
|
||||
}); // end focusPrevious
|
||||
|
||||
|
||||
router.get('/rundown/focusFirst/', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
let dataOut = {};
|
||||
dataOut.info = ack2
|
||||
dataOut.APIcmd = 'RundownFocusFirst';
|
||||
dataOut.apikey = req.query.apikey || '';
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).json(dataOut);
|
||||
}); // end focusFirst
|
||||
|
||||
router.get('/rundown/focusLast/', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
let dataOut = {};
|
||||
dataOut.info = ack2
|
||||
dataOut.APIcmd = 'RundownFocusLast';
|
||||
dataOut.apikey = req.query.apikey || '';
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).json(dataOut);
|
||||
}); // end focusLast
|
||||
|
||||
router.get('/rundown/focusByID/:id', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
let dataOut = {};
|
||||
dataOut.info = ack2;
|
||||
dataOut.APIcmd = 'RundownFocusByID';
|
||||
dataOut.apikey = req.query.apikey || '';
|
||||
dataOut.itemID = req.params.id;
|
||||
io.emit('SPXMessage2Controller', dataOut);
|
||||
res.status(200).send('Sent request to controller: ' + JSON.stringify(dataOut));
|
||||
}); // end focusByID
|
||||
|
||||
|
||||
// router.get('/rundown/focusFirst/', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
// res.status(501).json(notInSolo);
|
||||
// }); // end focusFirst
|
||||
|
||||
|
||||
// router.get('/rundown/focusLast/', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
// res.status(501).json(notInSolo);
|
||||
// }); // end focusLast
|
||||
|
||||
|
||||
// router.get('/rundown/focusByID/:id', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
// res.status(501).json(notInSolo);
|
||||
// }); // end focusByID
|
||||
|
||||
|
||||
router.get('/item/play/:id', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end item play by ID
|
||||
|
||||
|
||||
router.get('/item/continue/:id', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end item continue by ID
|
||||
|
||||
|
||||
router.get('/item/stop/:id', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end item stop by ID
|
||||
|
||||
|
||||
router.get('/invokeTemplateFunction/', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end invokeTemplateFunction
|
||||
|
||||
|
||||
router.get('/invokeExtensionFunction/', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end invokeExtensionFunction
|
||||
|
||||
|
||||
// SERVER API ----------------------------------------------------------------------------------
|
||||
|
||||
router.post('/directplayout', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end directplayout
|
||||
|
||||
|
||||
router.get('/directplayout', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end directplayout
|
||||
|
||||
|
||||
router.get('/getprojects', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end getprojects
|
||||
|
||||
|
||||
router.get('/allrundowns', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end allrundowns
|
||||
|
||||
|
||||
router.get('/getrundowns', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end getrundowns
|
||||
|
||||
|
||||
router.get('/gettemplates', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end gettemplates
|
||||
|
||||
|
||||
router.get('/getlayerstate', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end getlayerstate
|
||||
|
||||
|
||||
router.get('/executeScript', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end executeScript file
|
||||
|
||||
|
||||
router.get('/getFileList', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end getFileList
|
||||
|
||||
|
||||
router.post('/saveCustomJSON', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end saveCustomJSON
|
||||
|
||||
|
||||
router.get('/controlRundownItemByID', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end controlRundownItemByID
|
||||
|
||||
|
||||
router.get('/rundown/get', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end get current rundown as JSON
|
||||
|
||||
|
||||
router.get('/rundown/json', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end get specific rundown as JSON
|
||||
|
||||
|
||||
router.post('/rundown/json', spxAuth.CheckAPIKey, async (req, res) => {
|
||||
res.status(501).json(notInSolo);
|
||||
}); // end create or update rundown as JSON
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,440 @@
|
||||
|
||||
// -----------------------------------------
|
||||
// Handle Express server routes for the API (at "/api/")
|
||||
// -----------------------------------------
|
||||
var express = require("express");
|
||||
const router = express.Router();
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const moment = require('moment');
|
||||
const directoryPath = path.normalize(config.general.dataroot);
|
||||
const logger = require('../utils/logger');
|
||||
logger.debug('API-route loading...');
|
||||
const spx = require('../utils/spx_server_functions.js');
|
||||
const xlsx = require('node-xlsx').default;
|
||||
|
||||
// --- WATCHOUT!!!! v1.3.3 disabled --------
|
||||
// const { now } = require("moment");
|
||||
// const { constants } = require("buffer");
|
||||
|
||||
// ROUTES -------------------------------------------------------------------------------------------
|
||||
router.get('/', function (req, res) {
|
||||
res.send('Looking for this <a href="/api/v1/">api/v1</a>?');
|
||||
});
|
||||
|
||||
|
||||
router.get('/files', async (req, res) => {
|
||||
const fileListAsJSON = await GetDataFiles();
|
||||
res.send(fileListAsJSON);
|
||||
}); // file
|
||||
|
||||
|
||||
router.get('/openFileFolder/', async (req, res) => {
|
||||
// Added in 1.3.1. And fixed in 1.3.2...
|
||||
// Added optional parameter forceFolder to open a different folder than templates.
|
||||
if (config.general.disableOpenFolderCommand == true) {
|
||||
let msg = 'openFileFolder -command disabled in config.';
|
||||
logger.warn(msg);
|
||||
return res.status(403).send(msg);
|
||||
}
|
||||
let dirpath, folder = null;
|
||||
if (req.query.openFolderOnly) {
|
||||
folder = path.join(spx.getStartUpFolder(), 'ASSETS', req.query.openFolderOnly);
|
||||
// folder = path.dirname(dirpath);
|
||||
} else {
|
||||
let relpath = req.query.file || '';
|
||||
if (!req.query.file) {
|
||||
let msg = 'openFileFolder -command requires a file parameter.';
|
||||
logger.warn(msg);
|
||||
return res.status(403).send(msg);
|
||||
}
|
||||
filepath = path.join(spx.getStartUpFolder(), 'ASSETS', 'templates', relpath);
|
||||
folder = path.dirname(filepath);
|
||||
}
|
||||
|
||||
// Added in 1.3.1 for security: if not found nothing is done.
|
||||
if (!fs.existsSync) {
|
||||
logger.error('Folder ' + folder + ' does not exist.');
|
||||
return res.status(404).send('Folder ' + folder + ' does not exist.');
|
||||
}
|
||||
|
||||
// open folder in each operating system
|
||||
if (process.platform === 'darwin') {
|
||||
require('child_process').exec('open "' + folder + '"');
|
||||
} else if (process.platform === 'win32') {
|
||||
require('child_process').exec('explorer "' + folder + '"');
|
||||
} else if (process.platform === 'linux') {
|
||||
require('child_process').exec('xdg-open "' + folder + '"');
|
||||
} else {
|
||||
logger.error('Unknown operating system: ' + process.platform);
|
||||
}
|
||||
res.sendStatus(200)
|
||||
}); // openFileFolder of a template for editing
|
||||
|
||||
|
||||
router.get('/licBasic/', async (req, res) => {
|
||||
// added in 1.1.1 - license check. Minor tweaks in 1.1.3
|
||||
// Format:
|
||||
// 4x?rot(pmac)10x? | AA-AA-33-UQ-GZ-ZX-BB-BB-BB-BB
|
||||
// Not safe because it is not encrypted.
|
||||
let stripd = req.query.str.replace(/-/g, ''); // strip dashes
|
||||
let rotlic = stripd.substring(4,12); // get 8 chars
|
||||
// console.log('rotlic // from: ' + rotlic + ' to ' + spx.rot(rotlic, true) + ' vs ' + global.pmac.toUpperCase());
|
||||
if (spx.rot(rotlic, true) === global.pmac.toUpperCase()) {
|
||||
res.status(200).send('{result:ok}');
|
||||
} else {
|
||||
res.status(403).send('{result:invalid}');
|
||||
}
|
||||
}); // GET licBasic check ended
|
||||
|
||||
|
||||
router.get('/rotBasic/', async (req, res) => {
|
||||
// Require host-id, returns key.
|
||||
let rotlic = spx.rot(req.query.id);
|
||||
let soclic = spx.dashify(spx.salt(4) + rotlic + spx.salt(8));
|
||||
let revlic = spx.rot(rotlic, true);
|
||||
let json = "{\"pmac\":\"" + global.pmac + "\",\"rot\":\"" + rotlic + "\",\"soclic\":\"" + soclic + "\",\"chk\":\"" + revlic + "\"}";
|
||||
res.send(json);
|
||||
}); // GET rotBasic/?id=12345678
|
||||
|
||||
|
||||
router.get('/logger/', async (req, res) => {
|
||||
// Minimalistic GET logger for template messages
|
||||
let message = req.query.message
|
||||
let source = req.query.source
|
||||
let level = req.query.level.toLowerCase()
|
||||
let channel = req.query.channel
|
||||
let msg = '(api/logger, ' + channel + ', ' + source + '): ' + message
|
||||
// console.log(msg);
|
||||
eval('logger.' + level + '(msg)'); // nasty, eh?
|
||||
res.sendStatus(200)
|
||||
}); // GET logger route ended
|
||||
|
||||
|
||||
router.post('/logger/', async (req, res) => {
|
||||
// Minimalistic POST logger for template messages
|
||||
let message = req.body.message
|
||||
let source = req.body.source
|
||||
let level = req.body.level.toLowerCase()
|
||||
let channel = req.body.channel
|
||||
let msg = '(api/logger, ' + channel + ', ' + source + '): ' + message
|
||||
// console.log(msg);
|
||||
eval('logger.' + level + '(msg)'); // nasty, eh?
|
||||
res.sendStatus(200)
|
||||
}); // POST logger route ended
|
||||
|
||||
|
||||
router.post('/browseFiles/', async (req, res) => {
|
||||
// This is axios ajax handler for file browser on dbl click on a folder
|
||||
// REQUEST: current folder and next folder name
|
||||
// RETURNS: json data with folder and file arrays
|
||||
// 1.1.0 - refactored navigation process to use '..' for parent folder.
|
||||
let curFolder = req.body.curFolder || ".";
|
||||
let tgtFolder = req.body.tgtFolder || "";
|
||||
let extension = req.body.extension || "HTM";
|
||||
let rootFolder = req.body.rootFolder || path.join(spx.getStartUpFolder(), 'ASSETS');
|
||||
let BrowseFolder = path.join(curFolder, tgtFolder);
|
||||
|
||||
let osRootPath = path.resolve(rootFolder)
|
||||
let osTargPath = path.resolve(BrowseFolder)
|
||||
let navigateTo = osTargPath;
|
||||
let feedbackMs = '';
|
||||
|
||||
if ( osTargPath.length <= osRootPath.length ) {
|
||||
logger.verbose('Targeting beyond limits, sending root-identifier. Path: ' + osTargPath);
|
||||
navigateTo = osRootPath;
|
||||
feedbackMs = 'root';
|
||||
} else {
|
||||
feedbackMs = 'ok';
|
||||
}
|
||||
const fileListAsJSON = await spx.GetFilesAndFolders(navigateTo, extension);
|
||||
fileListAsJSON.message=feedbackMs; // force feedback message to UI at RenderFolder()
|
||||
res.send(fileListAsJSON);
|
||||
}); // POST browseFiles API route ended
|
||||
|
||||
|
||||
router.post('/heartbeat/', async (req, res) => {
|
||||
// REQUEST: data = a heartbeat string
|
||||
// RETURNS: none
|
||||
// 1.1.0 submit anonymous usage stats
|
||||
try {
|
||||
|
||||
if (global.config.general.allowstats===false || global.config.general.allowstats=='false') {
|
||||
logger.verbose('Heartbeat / stats disabled.');
|
||||
return
|
||||
} // Stats disabled by config. Added in 1.1.1.
|
||||
|
||||
let d = req.body.data;
|
||||
let h = 'smartpx.fi';
|
||||
spx.collectSPXInfo('hello from api/heartbeat endpoint')
|
||||
.then(function(si) {
|
||||
let u = 'http://' + h + '/gc/messageservice2/?'+ si + '&d=' + d;
|
||||
spx.httpGet(u);
|
||||
return si
|
||||
})
|
||||
.then(function(si) {
|
||||
logger.verbose('Stats ' + si + ' AND ' + d);
|
||||
res.status(200).send('{all:good}'); // ok 200 AJAX RESPONSE
|
||||
return;
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error in api/heartbeat: ' + error);
|
||||
res.status(500).send(error);
|
||||
};
|
||||
|
||||
}); // POST heartbeat
|
||||
|
||||
|
||||
router.post('/readExcelData', async (req, res) => {
|
||||
// Function can be called from a template to get all data from
|
||||
// an Excel file in the ASSETS/excel -folder.
|
||||
// Data parsing / logic must be implemented in the template
|
||||
// this just dumps data out as-is.
|
||||
// var excelFile = path.join(__dirname, '..', 'ASSETS', req.body.filename); // fails when packaged
|
||||
// Improved in 1.1.1 - Return cached data also if fileref is empty (for some reason).
|
||||
try {
|
||||
var excelFile = path.join(spx.getStartUpFolder(), 'ASSETS', req.body.filename); // v.1.0.15: getStartUpFolder()
|
||||
var workSheetsData;
|
||||
let timenow = Date.now();
|
||||
// console.log('Excel cache age ' + (timenow - excel.readtime) + ' ms');
|
||||
|
||||
if ( excel.data && excel.filename == req.body.filename && (timenow - excel.readtime) <= 1000 || !req.body.filename ) { /* milliseconds */
|
||||
// sama data requested less than a second ago, return data from memory
|
||||
logger.verbose('Returning cached Excel data from memory')
|
||||
workSheetsData = excel.data;
|
||||
// console.log('Returning CACHED Excel data.\n');
|
||||
} else {
|
||||
// get it from Excel file
|
||||
logger.verbose('Returning Excel data from FILE and saving to cache.')
|
||||
workSheetsData = xlsx.parse(excelFile);
|
||||
// console.log('Returning Excel FILE data and caching it.\n');
|
||||
|
||||
// cache excel data to a global variable
|
||||
global.excel.readtime = Date.now();
|
||||
global.excel.filename = req.body.filename;
|
||||
global.excel.data = workSheetsData;
|
||||
}
|
||||
|
||||
|
||||
logger.verbose('OK API read Excel data from ' + excelFile);
|
||||
res.status(200).send(workSheetsData); // ok 200 AJAX RESPONSE
|
||||
return;
|
||||
} catch (error) {
|
||||
logger.error('Error in api/readExcelData while reading Excel ' + excelFile + ": " + error);
|
||||
res.status(500).send(error); }; // Server error
|
||||
return;
|
||||
}); // POST readExcelData to get Excel data from file
|
||||
|
||||
|
||||
router.post('/savefile/:filebasename', async (req, res) => {
|
||||
spx.talk('Saving file ' + req.params.filebasename);
|
||||
try {
|
||||
if (!req.params.filebasename) {
|
||||
throw new Error("Filename missing, cannot save file.");
|
||||
}
|
||||
let datafile = path.join(directoryPath, req.params.filebasename) + '.json';
|
||||
logger.debug('Saving file ' + datafile + '...');
|
||||
let data = req.body;
|
||||
await spx.writeFile(datafile,data);
|
||||
res.status(200).send('OK, created file ' + datafile); // ok 200 AJAX RESPONSE
|
||||
} catch (error) {
|
||||
logger.error('Error while saving ' + datafile + ': ' + err);
|
||||
res.status(500).send(error);
|
||||
}; //file written
|
||||
}); // POST savefile API route ended
|
||||
|
||||
router.post('/saverundownfile/:projectName/:rundownName', async (req, res) => {
|
||||
// Added in 1.3.0
|
||||
// Used by extensions that will modify rundowns and save them back to the server.
|
||||
// This will also send a message to the UI to request a reload.
|
||||
console.log('Saving rundown file ' + req.params.rundownName + ' in project ' + req.params.projectName + '...');
|
||||
try {
|
||||
if (!req.params.projectName || !req.params.rundownName) {
|
||||
throw new Error("Project or filename missing from request, cannot save file.");
|
||||
}
|
||||
let datafile = path.join(directoryPath, req.params.projectName, 'data', req.params.rundownName) + '.json';
|
||||
logger.debug('Saving rundown file ' + datafile + '...');
|
||||
let data = req.body;
|
||||
await spx.writeFile(datafile,data);
|
||||
io.emit('SPXMessage2Client', {
|
||||
spxcmd: "showMessageSlider",
|
||||
msg: "⛔ Rundown data was modified by API. Reload view!",
|
||||
type: "warn",
|
||||
persist: true
|
||||
});
|
||||
res.status(200).send('OK, created file ' + datafile); // ok 200 AJAX RESPONSE
|
||||
} catch (error) {
|
||||
logger.error('Error in api/saverundownfile' + error);
|
||||
res.status(500).send(error);
|
||||
}; //file written
|
||||
}); // POST savefile API route ended
|
||||
|
||||
|
||||
router.post('/exportCSVfile', async (req, res) => {
|
||||
// console.log('Exporting CSV...');
|
||||
try {
|
||||
let showFolder = req.body.foldername || "";
|
||||
let datafile = req.body.datafile || "";
|
||||
let dataJSONfile= path.join(spx.getDatarootFolder(), showFolder, 'data', datafile + '.json'); // Changed in 1.3.1
|
||||
let rundownData = await spx.GetJsonData(dataJSONfile);
|
||||
let CSVdata = ''
|
||||
|
||||
let item_description,
|
||||
item_playserver,
|
||||
item_playchannel,
|
||||
item_playlayer,
|
||||
item_webplayout,
|
||||
item_out,
|
||||
item_uicolor,
|
||||
item_dataformat,
|
||||
item_relpath,
|
||||
item_graphicPath,
|
||||
item_version,
|
||||
item_id
|
||||
|
||||
rundownData.templates.forEach((item,index) => {
|
||||
// console.log('Iterating template index ' + index);
|
||||
if (item.itemID == req.body.itemID) {
|
||||
// This is the template to process.
|
||||
// console.log('Exporting template ' + item.itemID);
|
||||
|
||||
item_description = item.description || '';
|
||||
item_playserver = item.playserver || '';
|
||||
item_playchannel = item.playchannel || '1';
|
||||
item_playlayer = item.playlayer || '10';
|
||||
item_webplayout = item.webplayout || '10';
|
||||
item_out = item.out || 'manual';
|
||||
item_uicolor = item.uicolor || '0';
|
||||
item_dataformat = item.dataformat || 'json';
|
||||
item_relpath = item.relpath || '';
|
||||
// if OGraf, we need to add a few more properties:
|
||||
if (item_relpath.toLowerCase().endsWith(".ograf.json")) {
|
||||
item_graphicPath = item.ografProps.graphicPath || '';
|
||||
item_version = item.ografProps.version || '';
|
||||
item_id = item.ografProps.id || '';
|
||||
}
|
||||
|
||||
CSVdata = '\r\n# SPX Rundown item CSV export. (More info: https://docs.spxgraphics.com/Guides/Tutorials/how+to+use+csv+files)\r\n\r\n'
|
||||
CSVdata += '# description #;' + item_description + '\r\n'
|
||||
CSVdata += '# playserver #;' + item_playserver + '\r\n'
|
||||
CSVdata += '# playchannel #;' + item_playchannel + '\r\n'
|
||||
CSVdata += '# playlayer #;' + item_playlayer + '\r\n'
|
||||
CSVdata += '# webplayout #;' + item_webplayout + '\r\n'
|
||||
CSVdata += '# out #;' + item_out + '\r\n'
|
||||
CSVdata += '# uicolor #;' + item_uicolor + '\r\n'
|
||||
CSVdata += '# dataformat #;' + item_dataformat + '\r\n'
|
||||
CSVdata += '# relpath #;' + item_relpath + '\r\n'
|
||||
// if OGraf, add graphicPath, version and id
|
||||
if (item_relpath.toLowerCase().endsWith(".ograf.json")) {
|
||||
CSVdata += '# graphicPath #;' + item_graphicPath + '\r\n';
|
||||
CSVdata += '# version #;' + item_version + '\r\n';
|
||||
CSVdata += '# id #;' + item_id + '\r\n';
|
||||
}
|
||||
CSVdata += '# onair #;false\r\n'
|
||||
CSVdata += '# project #;' + showFolder + '\r\n'
|
||||
CSVdata += '# rundown #;' + datafile + '\r\n'
|
||||
CSVdata += '\r\n'
|
||||
|
||||
// print field ID's
|
||||
CSVdata += '# FieldUUIDs #;'
|
||||
item.DataFields.forEach((field,findex) => {
|
||||
if (field.field && field.field!='' ) {
|
||||
CSVdata += field.field + ';'
|
||||
}
|
||||
});
|
||||
CSVdata += '\r\n';
|
||||
|
||||
// print field types
|
||||
CSVdata += '# FieldTypes #;'
|
||||
item.DataFields.forEach((field,findex) => {
|
||||
if (field.field && field.field!='' ) {
|
||||
CSVdata += field.ftype + ';'
|
||||
}
|
||||
});
|
||||
CSVdata += '\r\n'
|
||||
|
||||
// print field titles
|
||||
CSVdata += '# FieldTitls #;'
|
||||
item.DataFields.forEach((field,findex) => {
|
||||
if (field.field && field.field!='' ) {
|
||||
CSVdata += field.title + ';'
|
||||
}
|
||||
});
|
||||
CSVdata += '\r\n'
|
||||
|
||||
// print field fcalls if button
|
||||
CSVdata += '# Fieldfcalls #;'
|
||||
item.DataFields.forEach((field,findex) => {
|
||||
if (field.field && field.field!='' && field.fcall) {
|
||||
CSVdata += field.fcall + ";";
|
||||
}
|
||||
|
||||
});
|
||||
CSVdata += '\r\n'
|
||||
|
||||
// print field values
|
||||
CSVdata += '\r\n'
|
||||
let itemData = '# ID:auto;'
|
||||
item.DataFields.forEach((field,findex) => {
|
||||
if (field.field && field.field!='' && field.value) {
|
||||
let dataToSave = field.value.replace(/\n/g,'<BR>') || ''; // replace all newlines with <BR>
|
||||
itemData += dataToSave + ";";
|
||||
}
|
||||
});
|
||||
CSVdata += itemData + '\r\n'
|
||||
}
|
||||
});
|
||||
|
||||
let timestamp = spx.prettifyDate(new Date(), 'YYYY-MM-DD-HHMMSS');
|
||||
let filenameref = item_relpath.split('.')[0].replace('\\', '/').split('/').slice(-1)[0];
|
||||
|
||||
// generate CSV folder if not there
|
||||
let CSVfolder = path.join(spx.getStartUpFolder(), 'ASSETS', 'csv')
|
||||
fs.existsSync(CSVfolder) || fs.mkdirSync(CSVfolder)
|
||||
let CSVfileRef = path.join(CSVfolder, filenameref + '_' + timestamp + '.csv');
|
||||
await spx.writeTextFile(CSVfileRef,CSVdata);
|
||||
// console.log(' Created CSV file ' + CSVfileRef);
|
||||
logger.verbose('Created ' + CSVfileRef + ' from itemID ' + req.body.itemID + ' on ' + dataJSONfile + '. ');
|
||||
res.status(200).send('Generated file ' + CSVfileRef);
|
||||
} catch (error) {
|
||||
logger.error('API error in exportCSVfile(): ', error);
|
||||
}; //file written
|
||||
}); // POST exportCSVfile end
|
||||
|
||||
|
||||
// FUNCTIONS -------------------------------------------------------------------------------------------
|
||||
async function GetDataFiles() {
|
||||
// Get files
|
||||
// const directoryPath = path.normalize("X:/01_Projects/Yle/CG/DEV/DATA_FOLDER/");
|
||||
const directoryPath = path.normalize(config.general.dataroot);
|
||||
let jsonData = {};
|
||||
var key = 'files';
|
||||
jsonData.folder = directoryPath;
|
||||
jsonData[key] = [];
|
||||
let id = 0;
|
||||
|
||||
try {
|
||||
fs.readdirSync(directoryPath).forEach(file => {
|
||||
let ext = path.extname(file).toUpperCase();
|
||||
if (ext == ".JSON") {
|
||||
var stats = fs.statSync(path.join(directoryPath, file));
|
||||
var datem = moment(stats.mtime, 'DD.MM.YYYY').format();
|
||||
var filedata = {
|
||||
id: id,
|
||||
name: file,
|
||||
date: datem
|
||||
};
|
||||
id++;
|
||||
jsonData[key].push(filedata);
|
||||
}
|
||||
});
|
||||
return jsonData;
|
||||
}
|
||||
catch (error) {
|
||||
logger.error('Error while reading files from ' + directoryPath + ': ' + err);
|
||||
return (error);
|
||||
}
|
||||
} // GetDataFiles ended
|
||||
|
||||
|
||||
module.exports = router;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,352 @@
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Handle Express server routes for the CasparCG commands.
|
||||
// -------------------------------------------------------
|
||||
var express = require("express");
|
||||
const router = express.Router();
|
||||
const path = require('path');
|
||||
|
||||
const logger = require('../utils/logger');
|
||||
logger.debug('Caspar-route loading...');
|
||||
const spx = require('../utils/spx_server_functions.js');
|
||||
|
||||
const ip = require('ip')
|
||||
const ipad = ip.address(); // my ip address
|
||||
const port = config.general.port || 5656;
|
||||
|
||||
const PlayoutCCG = require('../utils/playout_casparCG.js');
|
||||
|
||||
|
||||
// ROUTES CCG -----------------------------------------------------------------------------------
|
||||
router.get('/', function (req, res) {
|
||||
res.send('Nothing here. Go away!');
|
||||
});
|
||||
|
||||
router.get('/system', function (req, res) {
|
||||
res.send('Nothing here. Get lost.');
|
||||
});
|
||||
|
||||
router.get('/requestInfo', async (req, res) => {
|
||||
var SERVER = req.query.server; // ?server=OVERLAY
|
||||
var COMMAND = req.query.command; // &command=INFO
|
||||
// console.log('requestInfo from ' + SERVER + ', cmd: ' + COMMAND);
|
||||
const CCGHost = global.CCGSockets[PlayoutCCG.getSockIndex(SERVER)].spxhost;
|
||||
const CCGPort = global.CCGSockets[PlayoutCCG.getSockIndex(SERVER)].spxport;
|
||||
const net = require('net')
|
||||
CasparRequest = new net.Socket();
|
||||
CasparRequest.connect(CCGPort, CCGHost, function () {
|
||||
CasparRequest.write(COMMAND + '\r\n');
|
||||
CasparRequest.on('data', function (data) {
|
||||
res.set('Content-Type', 'text/plain'); // format
|
||||
res.status(200).send(data);
|
||||
CasparRequest.destroy();
|
||||
});
|
||||
|
||||
CasparRequest.on('error', function (error) {
|
||||
return res.status(500).send(error);
|
||||
CasparRequest.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/system/:data', (req, res) => {
|
||||
// same principle as with /control/:data
|
||||
// do OpSys level stuff, open folders etc..
|
||||
//
|
||||
// NOTE: This endpoint is in the WRONG PLACE
|
||||
// it should be moved to a more generic place
|
||||
//
|
||||
// console.log('System utilities / ' + req.params.data);
|
||||
data = JSON.parse(req.params.data);
|
||||
let directoryPath = "";
|
||||
logger.verbose('System utilities / ' + JSON.stringify(data));
|
||||
res.sendStatus(200);
|
||||
switch (data.command) {
|
||||
|
||||
case 'DATAFOLDER':
|
||||
directoryPath = path.normalize(config.general.dataroot);
|
||||
require('child_process').exec('start "" ' + directoryPath);
|
||||
break;
|
||||
|
||||
case 'TEMPLATEFOLDER':
|
||||
// directoryPath = path.normalize(config.general.templatefolder);
|
||||
directoryPath = path.join(spx.getStartUpFolder(), 'ASSETS', 'templates');
|
||||
require('child_process').exec('start "" ' + directoryPath);
|
||||
break;
|
||||
|
||||
case 'CHECKCONNECTIONS':
|
||||
// console.log('CCG: Checking server connections...');
|
||||
spx.checkServerConnections();
|
||||
break;
|
||||
|
||||
case 'RESTARTSERVER':
|
||||
//
|
||||
logger.error('RESTART SERVER REQUEST RECEIVED. Will try to kill and restart using process manager.');
|
||||
process.exit(2);
|
||||
break;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/control/:data', (req, res) => {
|
||||
// Principle:
|
||||
// We get data object which has
|
||||
// - data.command (ADD | STOP | UPDATE)
|
||||
// - data.fields [{ id: 'f0', value: 'Eka' }, { id: 'f1', value: 'Toka' }];
|
||||
// - data.element headline1
|
||||
// - data.profile News
|
||||
//
|
||||
// Then we read profiles-file and search for the required element
|
||||
// such as 'headline1' and get needed data from it:
|
||||
// - templatefile
|
||||
// - server
|
||||
// - channel
|
||||
// - layer
|
||||
data = JSON.parse(req.params.data);
|
||||
logger.info('CCG/Control/ ' + JSON.stringify(data));
|
||||
res.sendStatus(200);
|
||||
|
||||
if (!spx.CCGServersConfigured){ return } // exit early, no need to do any CasparCG work
|
||||
|
||||
var DataStr = "";
|
||||
const directoryPath = path.normalize(config.general.dataroot);
|
||||
let profilefile = path.join(directoryPath, 'config', 'profiles.json');
|
||||
const profileDataAsJSON = spx.GetJsonData(profilefile);
|
||||
var arr = profileDataAsJSON.profiles;
|
||||
let GFX_Teml = "";
|
||||
let GFX_Serv = "";
|
||||
let GFX_Chan = "";
|
||||
let GFX_Laye = "";
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var obj = arr[i];
|
||||
if (obj.name == data.profile) {
|
||||
GFX_Teml = eval("obj.templates." + data.element + ".templatefile");
|
||||
GFX_Serv = eval("obj.templates." + data.element + ".server");
|
||||
GFX_Chan = eval("obj.templates." + data.element + ".channel");
|
||||
GFX_Laye = eval("obj.templates." + data.element + ".layer");
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug('CCG/Control - Profile ' + data.profile + "', Template: '" + GFX_Teml, "', CasparCG: " + GFX_Serv + ", " + GFX_Chan + ", " + GFX_Laye);
|
||||
|
||||
if (data.command == "ADD" || data.command == "UPDATE") {
|
||||
let TEMPLATEDATA = "";
|
||||
if (data.fields) {
|
||||
data.fields.forEach(item => {
|
||||
logger.verbose('Processing field for CCG: ' + item.id + ' with value: ' + item.value);
|
||||
TEMPLATEDATA += spx.CGComponentFactory(item.id, item.value)
|
||||
});
|
||||
}
|
||||
DataSta = "<templateData>";
|
||||
DataEnd = "</templateData>";
|
||||
DataStr = DataSta + TEMPLATEDATA + DataEnd;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (data.command) {
|
||||
case 'ADD':
|
||||
global.CCGSockets[spx.getSockIndex(spx.getChannel(data))].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' ADD 1 "' + GFX_Teml + '" 1 "' + DataStr + '"\r\n');
|
||||
break;
|
||||
|
||||
case 'UPDATE':
|
||||
console.log('TODO: This is probably not used to send update to CasparCG... See playout_casparCG.js instead! ** FIXME:**');
|
||||
global.CCGSockets[spx.getSockIndex(spx.getChannel(data))].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' UPDATE 1 "' + DataStr + '"\r\n');
|
||||
break;
|
||||
|
||||
case 'NEXT':
|
||||
global.CCGSockets[spx.getSockIndex(spx.getChannel(data))].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' NEXT 0\r\n');
|
||||
break;
|
||||
|
||||
case 'STOP':
|
||||
global.CCGSockets[spx.getSockIndex(spx.getChannel(data))].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' STOP 1\r\n');
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.warn('CCG/Control - Unknown command: ' + data.command);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ERROR in /control/:data, unable to send CCG command. Server up? ' + error)
|
||||
}
|
||||
|
||||
|
||||
}); // control ended
|
||||
|
||||
router.get('/controljson/:data', (req, res) => {
|
||||
|
||||
data = JSON.parse(req.params.data);
|
||||
logger.info('CCG/controljson ' + JSON.stringify(data));
|
||||
res.sendStatus(200);
|
||||
if (!spx.CCGServersConfigured){ return } // exit early, no need to do any CasparCG work
|
||||
const directoryPath = path.normalize(config.general.dataroot);
|
||||
let profilefile = path.join(directoryPath, 'config', 'profiles.json');
|
||||
const profileDataAsJSON = spx.GetJsonData(profilefile);
|
||||
var arr = profileDataAsJSON.profiles;
|
||||
let GFX_Teml = "";
|
||||
let GFX_Serv = "";
|
||||
let GFX_Chan = "";
|
||||
let GFX_Laye = "";
|
||||
let GFX_JSON = ""
|
||||
let stng = ""
|
||||
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var obj = arr[i];
|
||||
if (obj.name == data.profile) {
|
||||
GFX_Teml = eval("obj.templates." + data.element + ".templatefile");
|
||||
GFX_Serv = eval("obj.templates." + data.element + ".server");
|
||||
GFX_Chan = eval("obj.templates." + data.element + ".channel");
|
||||
GFX_Laye = eval("obj.templates." + data.element + ".layer");
|
||||
GFX_JSON = JSON.stringify(data.jsonData);
|
||||
}
|
||||
}
|
||||
|
||||
logger.verbose("CCG/ControlJSON Profile: '" + data.profile + "', Template: '" + GFX_Teml, "', CasparCG: " + GFX_Serv + ", " + GFX_Chan + ", " + GFX_Laye)
|
||||
logger.debug('json' + JSON.stringify(GFX_JSON));
|
||||
stng = GFX_JSON;
|
||||
stng = stng.split('§backslash§').join('\'); // replace §backslash§ with html corresponding entity
|
||||
stng = stng.split('"').join('\\"'); // replace " with \"
|
||||
|
||||
try {
|
||||
switch (data.command) {
|
||||
case 'ADD':
|
||||
global.CCGSockets[spx.getSockIndex(spx.getChannel(data))].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' ADD 1 "' + GFX_Teml + '" 1 "' + stng + '"\r\n');
|
||||
break;
|
||||
|
||||
case 'UPDATE':
|
||||
global.CCGSockets[spx.getSockIndex(spx.getChannel(data))].write('CG ' + GFX_Chan + '-' + GFX_Laye + ' UPDATE 1 "' + stng + '"\r\n');
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.warn('CCG/ControlJSON - Unknown command: ' + data.command);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ERROR in /controljson/:data: ' + error)
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/disable', async (req, res) => {
|
||||
// Added in 1.1.0. Set "disabled" state of the given CasparCG server
|
||||
// This only affects playback commands. Initialization works normally.
|
||||
logger.verbose('CasparCG server ' + req.body.server + ' disabled to ' + req.body.disabled);
|
||||
try {
|
||||
var ConfigFile = global.configfileref;
|
||||
var ConfigData = await spx.GetJsonData(ConfigFile);
|
||||
if (ConfigData.casparcg.servers) {
|
||||
ConfigData.casparcg.servers.forEach((serverItem,index) => {
|
||||
if (serverItem.name == req.body.server) {
|
||||
serverItem.disabled = req.body.disabled
|
||||
}
|
||||
});
|
||||
}
|
||||
global.config = ConfigData; // update mem version also
|
||||
await spx.writeFile(ConfigFile,ConfigData);
|
||||
let response = ['Config changed']
|
||||
res.status(200).send(response); // ok 200 AJAX RESPONSE
|
||||
} catch (error) {
|
||||
console.error('ERROR', error);
|
||||
let errmsg = 'Server error in CCG disable [' + error + ']';
|
||||
logger.error(errmsg);
|
||||
res.status(500).send(errmsg) // error 500 AJAX RESPONSE
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Create all required Socket Connections for CasparCG servers specified in config.json
|
||||
const net = require('net')
|
||||
let ServerDataForLogger = [];
|
||||
|
||||
if (config.casparcg) {
|
||||
config.casparcg.servers.forEach((element,index) => {
|
||||
const CurName = element.name;
|
||||
const CurHost = element.host;
|
||||
const CurPort = element.port;
|
||||
|
||||
ServerDataForLogger.push({ name: CurName, host: CurHost, port: CurPort });
|
||||
|
||||
// next two lines creates a dynamic variable for this loop iteration
|
||||
// Changed in 1.3.0, from:
|
||||
// var CurCCG = CurName + "= undefined";
|
||||
// eval(CurCCG);
|
||||
|
||||
// ..to:
|
||||
var CurCCG = new net.Socket();
|
||||
// end of change
|
||||
|
||||
global.CCGSockets.push(CurCCG); // --> PUSH Socket object to a global array for later use
|
||||
CurCCG.spxname = CurName; // save each entry a name for later searching!
|
||||
CurCCG.spxhost = CurHost; // save each entry a host for later searching! (v.1.0.14)
|
||||
CurCCG.spxport = CurPort; // save each entry a port for later searching! (v.1.0.14)
|
||||
|
||||
CurCCG.connect(CurPort, CurHost, function () {
|
||||
ServerDataForLogger.push({ name: CurName, host: CurHost, port: CurPort });
|
||||
data = { spxcmd: 'updateServerIndicator', indicator: 'indicator' + index, color: '#00CC00' };
|
||||
io.emit('SPXMessage2Client', data);
|
||||
data = { spxcmd: 'updateStatusText', status: 'Communication established with ' + CurName + '.' };
|
||||
io.emit('SPXMessage2Client', data);
|
||||
logger.verbose('SPX connected to CasparCG as \'' + CurName + '\' at ' + CurHost + ":" + CurPort + '.');
|
||||
});
|
||||
|
||||
CurCCG.on('data', function (data) {
|
||||
logger.verbose('SPX received data from CasparCG ' + CurName + ': ' + data);
|
||||
|
||||
// we must parse the data so we can evaluate it...
|
||||
let CCG_RETURN_TEXT = String(data).replace('\r','').replace('\n',''); // convert return object to string, strip \r\n
|
||||
let CCG_RETURN_CODE = CCG_RETURN_TEXT.substring(0, 2); // first two chars
|
||||
switch (CCG_RETURN_CODE) {
|
||||
case "20":
|
||||
logger.verbose('Comms good with ' + CurName + ": " + CCG_RETURN_TEXT);
|
||||
break;
|
||||
|
||||
case "40":
|
||||
logger.error(CurName + ' CasparCG response: ' + CCG_RETURN_TEXT );
|
||||
logger.debug('Verify CasparCG\'s (' + CurName + ') access to templates on SPX server at ' + spx.getTemplateSourcePath());
|
||||
data = { spxcmd: 'updateStatusText', status: 'Error in comms with ' + CurName + '.' };
|
||||
io.emit('SPXMessage2Client', data);
|
||||
break;
|
||||
|
||||
case "50":
|
||||
logger.error('Failed ' + CurName + ": " + CCG_RETURN_TEXT);
|
||||
data = { spxcmd: 'updateStatusText', status: CurName + ' failed.' };
|
||||
io.emit('SPXMessage2Client', data);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.error('Unknown status value ' + CurName + ' - ' + CCG_RETURN_CODE + ' - ' + CCG_RETURN_TEXT);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// SocketIO call to client
|
||||
data = { spxcmd: 'updateServerIndicator', indicator: 'indicator' + index, color: '#00CC00' };
|
||||
io.emit('SPXMessage2Client', data);
|
||||
if (data.toString().endsWith('exit')) {
|
||||
CCGclient.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
CurCCG.on('close', function () {
|
||||
// SocketIO call to client
|
||||
data = { spxcmd: 'updateServerIndicator', indicator: 'indicator' + index, color: '#CC0000' };
|
||||
io.emit('SPXMessage2Client', data);
|
||||
data = { spxcmd: 'updateStatusText', status: 'Connection to ' + CurName + ' was closed.' };
|
||||
io.emit('SPXMessage2Client', data);
|
||||
logger.verbose('SPX connection to CasparCG "' + CurName + '" closed (' + CurHost + ':' + CurPort + ').');
|
||||
});
|
||||
|
||||
CurCCG.on('error', function (err) {
|
||||
// console.log('Still 4?', CurCCG.connecting)
|
||||
data = { spxcmd: 'updateServerIndicator', indicator: 'indicator' + index, color: '#CC0000' };
|
||||
io.emit('SPXMessage2Client', data);
|
||||
|
||||
data = { spxcmd: 'updateStatusText', status: 'Communication error with ' + CurName + '.' };
|
||||
io.emit('SPXMessage2Client', data);
|
||||
|
||||
logger.warn('Unable to connect CasparCG "' + CurName + '" (' + CurCCG.spxhost + ':' + CurCCG.spxport + '). Is it running?');
|
||||
// console.log('Sockets: ', global.CCGSockets);
|
||||
});
|
||||
|
||||
// console.log('Still 3?', CurCCG.connecting)
|
||||
});
|
||||
}; // end if
|
||||
|
||||
logger.debug('ServerDataForLogger during init: ' + JSON.stringify(ServerDataForLogger));
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
// var express = require("express");
|
||||
// const router = express.Router();
|
||||
// const path = require('path');
|
||||
// const fs = require('fs');
|
||||
// const moment = require('moment');
|
||||
// const directoryPath = path.normalize(config.general.dataroot);
|
||||
// const logger = require('../utils/logger');
|
||||
// logger.debug('WebPlayer-route loading...');
|
||||
// const spx = require('../utils/spx_server_functions.js');
|
||||
|
||||
// // ROUTES -------------------------------------------------------------------------------------------
|
||||
// router.get('/', function (reg, res) {
|
||||
// let htmlFile = path.join(__dirname+'/../http/renderer/index.html');
|
||||
// // res.sendFile(htmlFile);
|
||||
// res.send(htmlFile);
|
||||
// });
|
||||
|
||||
// module.exports = router;
|
||||
Reference in New Issue
Block a user