Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ui.js 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. var inited = false;
  2. module.exports = function(RED) {
  3. if (!inited) {
  4. inited = true;
  5. init(RED.server, RED.httpNode || RED.httpAdmin, RED.log, RED.settings);
  6. }
  7. return {
  8. add: add,
  9. addLink: addLink,
  10. addBaseConfig: addBaseConfig,
  11. emit: emit,
  12. emitSocket: emitSocket,
  13. toNumber: toNumber.bind(null, false),
  14. toFloat: toNumber.bind(null, true),
  15. updateUi: updateUi,
  16. ev: ev,
  17. getTheme: getTheme,
  18. getSizes: getSizes,
  19. isDark: isDark
  20. };
  21. };
  22. var fs = require('fs');
  23. var path = require('path');
  24. var events = require('events');
  25. var process = require('process');
  26. var socketio = require('socket.io');
  27. var serveStatic = require('serve-static');
  28. var compression = require('compression');
  29. var dashboardVersion = require('./package.json').version;
  30. var baseConfiguration = {};
  31. var io;
  32. var menu = [];
  33. var globals = [];
  34. var settings = {};
  35. var updateValueEventName = 'update-value';
  36. var currentValues = {};
  37. var replayMessages = {};
  38. var removeStateTimers = {};
  39. var removeStateTimeout = 1000;
  40. var ev = new events.EventEmitter();
  41. var params = {};
  42. ev.setMaxListeners(0);
  43. // default manifest.json to be returned as required.
  44. var mani = {
  45. "name": "Node-RED Dashboard",
  46. "short_name": "Dashboard",
  47. "description": "A dashboard for Node-RED",
  48. "start_url": "./#/0",
  49. "background_color": "#910000",
  50. "theme_color": "#910000",
  51. "display": "standalone",
  52. "icons": [
  53. {"src":"icon192x192.png", "sizes":"192x192", "type":"image/png"},
  54. {"src":"icon120x120.png", "sizes":"120x120", "type":"image/png"},
  55. {"src":"icon64x64.png", "sizes":"64x64", "type":"image/png"}
  56. ]
  57. }
  58. function toNumber(keepDecimals, config, input, old, m, s) {
  59. if (input === undefined || input === null) { return; }
  60. if (typeof input !== "number") {
  61. var inputString = input.toString();
  62. input = keepDecimals ? parseFloat(inputString) : parseInt(inputString);
  63. }
  64. if (s) { input = Math.round(Math.round(input/s)*s*10000)/10000; }
  65. return isNaN(input) ? config.min : input;
  66. }
  67. function emit(event, data) {
  68. io.emit(event, data);
  69. }
  70. function emitSocket(event, data) {
  71. if (data.hasOwnProperty("msg") && data.msg.hasOwnProperty("socketid") && (data.msg.socketid !== undefined)) {
  72. io.to(data.msg.socketid).emit(event, data);
  73. }
  74. else if (data.hasOwnProperty("socketid") && (data.socketid !== undefined)) {
  75. io.to(data.socketid).emit(event, data);
  76. }
  77. else {
  78. io.emit(event, data);
  79. }
  80. }
  81. function noConvert(value) {
  82. return value;
  83. }
  84. function beforeEmit(msg, value) {
  85. return { value:value };
  86. }
  87. function beforeSend(msg) {
  88. //do nothing
  89. }
  90. /* This is the handler for inbound msg from previous nodes...
  91. options:
  92. node - the node that represents the control on a flow
  93. control - the control to be added
  94. tab - tab config node that this control belongs to
  95. group - group name
  96. [emitOnlyNewValues] - boolean (default true).
  97. If true, it checks if the payload changed before sending it
  98. to the front-end. If the payload is the same no message is sent.
  99. [forwardInputMessages] - boolean (default true).
  100. If true, forwards input messages to the output
  101. [storeFrontEndInputAsState] - boolean (default true).
  102. If true, any message received from front-end is stored as state
  103. [persistantFrontEndValue] - boolean (default true).
  104. If true, last received message is send again when front end reconnect.
  105. [convert] - callback to convert the value before sending it to the front-end
  106. [beforeEmit] - callback to prepare the message that is emitted to the front-end
  107. [convertBack] - callback to convert the message from front-end before sending it to the next connected node
  108. [beforeSend] - callback to prepare the message that is sent to the output,
  109. if the returned msg has a property _dontSend, then it won't get sent.
  110. */
  111. function add(opt) {
  112. clearTimeout(removeStateTimers[opt.node.id]);
  113. delete removeStateTimers[opt.node.id];
  114. if (typeof opt.emitOnlyNewValues === 'undefined') {
  115. opt.emitOnlyNewValues = true;
  116. }
  117. if (typeof opt.forwardInputMessages === 'undefined') {
  118. opt.forwardInputMessages = true;
  119. }
  120. if (typeof opt.storeFrontEndInputAsState === 'undefined') {
  121. opt.storeFrontEndInputAsState = true;
  122. }
  123. if (typeof opt.persistantFrontEndValue === 'undefined') {
  124. opt.persistantFrontEndValue = true;
  125. }
  126. opt.convert = opt.convert || noConvert;
  127. opt.beforeEmit = opt.beforeEmit || beforeEmit;
  128. opt.convertBack = opt.convertBack || noConvert;
  129. opt.beforeSend = opt.beforeSend || beforeSend;
  130. opt.control.id = opt.node.id;
  131. var remove = addControl(opt.tab, opt.group, opt.control);
  132. opt.node.on("input", function(msg) {
  133. if (typeof msg.enabled === 'boolean') {
  134. var state = replayMessages[opt.node.id];
  135. if (!state) { replayMessages[opt.node.id] = state = {id: opt.node.id}; }
  136. state.disabled = !msg.enabled;
  137. io.emit(updateValueEventName, state); // dcj mu
  138. }
  139. // remove res and req as they are often circular
  140. if (msg.hasOwnProperty("res")) { delete msg.res; }
  141. if (msg.hasOwnProperty("req")) { delete msg.req; }
  142. // Retrieve the dataset for this node
  143. var oldValue = currentValues[opt.node.id];
  144. // let any arriving msg.ui_control message mess with control parameters
  145. if (msg.ui_control && (typeof msg.ui_control === "object") && (!Array.isArray(msg.ui_control)) && (!Buffer.isBuffer(msg.ui_control) )) {
  146. var changed = {};
  147. for (var property in msg.ui_control) {
  148. if (msg.ui_control.hasOwnProperty(property) && opt.control.hasOwnProperty(property)) {
  149. if ((property !== "id")&&(property !== "type")&&(property !== "order")&&(property !== "name")&&(property !== "value")&&(property !== "width")&&(property !== "height")) {
  150. opt.control[property] = msg.ui_control[property];
  151. changed[property] = msg.ui_control[property];
  152. }
  153. }
  154. }
  155. if (Object.keys(changed).length !== 0) {
  156. io.emit('ui-control', {control:changed, id:opt.node.id});
  157. }
  158. if (!msg.hasOwnProperty("payload")) { return; }
  159. }
  160. // Call the convert function in the node to get the new value
  161. // as well as the full dataset.
  162. var conversion = opt.convert(msg.payload, oldValue, msg, opt.control.step);
  163. // If the update flag is set, emit the newPoint, and store the full dataset
  164. var fullDataset;
  165. var newPoint;
  166. if ((typeof(conversion) === 'object') && (conversion !== null) && (conversion.update !== undefined)) {
  167. newPoint = conversion.newPoint;
  168. fullDataset = conversion.updatedValues;
  169. }
  170. else if (conversion === undefined) {
  171. fullDataset = oldValue;
  172. newPoint = true;
  173. }
  174. else {
  175. // If no update flag is set, this means the conversion contains
  176. // the full dataset or the new value (e.g. gauges)
  177. fullDataset = conversion;
  178. }
  179. // If we have something new to emit
  180. if (newPoint !== undefined || !opt.emitOnlyNewValues || oldValue != fullDataset) {
  181. currentValues[opt.node.id] = fullDataset;
  182. // Determine what to emit over the websocket
  183. // (the new point or the full dataset).
  184. // Always store the full dataset.
  185. var toStore = opt.beforeEmit(msg, fullDataset);
  186. var toEmit;
  187. if ((newPoint !== undefined) && (typeof newPoint !== "boolean")) { toEmit = opt.beforeEmit(msg, newPoint); }
  188. else { toEmit = toStore; }
  189. var addField = function(m) {
  190. if (opt.control.hasOwnProperty(m) && opt.control[m] && opt.control[m].indexOf("{{") !== -1) {
  191. var a = opt.control[m].split("{{");
  192. a.shift();
  193. for (var i = 0; i < a.length; i++) {
  194. var b = a[i].split("}}")[0].trim();
  195. b.replace(/\"/g,'').replace(/\'/g,'');
  196. if (b.indexOf("|") !== -1) { b = b.split("|")[0]; }
  197. if (b.indexOf(" ") !== -1) { b = b.split(" ")[0]; }
  198. if (b.indexOf("?") !== -1) { b = b.split("?")[0]; }
  199. b.replace(/\(/g,'').replace(/\)/g,'');
  200. if (b.indexOf("msg.") >= 0) {
  201. b = b.split("msg.")[1];
  202. if (b.indexOf(".") !== -1) { b = b.split(".")[0]; }
  203. if (b.indexOf("[") !== -1) { b = b.split("[")[0]; }
  204. if (!toEmit.hasOwnProperty("msg")) { toEmit.msg = {}; }
  205. if (!toEmit.msg.hasOwnProperty(b) && msg.hasOwnProperty(b) && (msg[b] !== undefined)) {
  206. if (Buffer.isBuffer(msg[b])) { toEmit.msg[b] = msg[b].toString("binary"); }
  207. else { toEmit.msg[b] = JSON.parse(JSON.stringify(msg[b])); }
  208. }
  209. //if (Object.keys(toEmit.msg).length === 0) { delete toEmit.msg; }
  210. }
  211. else {
  212. if (b.indexOf(".") !== -1) { b = b.split(".")[0]; }
  213. if (b.indexOf("[") !== -1) { b = b.split("[")[0]; }
  214. if (!toEmit.hasOwnProperty(b) && msg.hasOwnProperty(b)) {
  215. if (Buffer.isBuffer(msg[b])) { toEmit[b] = msg[b].toString("binary"); }
  216. else { toEmit[b] = JSON.parse(JSON.stringify(msg[b])); }
  217. }
  218. }
  219. }
  220. }
  221. }
  222. // if label, format, color, units, tooltip or icon fields are set to a msg property, emit that as well
  223. addField("className");
  224. addField("label");
  225. addField("format");
  226. addField("color");
  227. addField("units");
  228. addField("tooltip");
  229. addField("icon");
  230. if (msg.hasOwnProperty("enabled")) { toEmit.disabled = !msg.enabled; }
  231. if (msg.hasOwnProperty("className")) { toEmit.className = msg.className; }
  232. toEmit.id = toStore.id = opt.node.id;
  233. //toEmit.socketid = msg.socketid; // dcj mu
  234. // Emit and Store the data
  235. //if (settings.verbose) { console.log("UI-EMIT",JSON.stringify(toEmit)); }
  236. emitSocket(updateValueEventName, toEmit);
  237. if (opt.persistantFrontEndValue === true) {
  238. replayMessages[opt.node.id] = toStore;
  239. }
  240. // Handle the node output
  241. if (opt.forwardInputMessages && opt.node._wireCount && fullDataset !== undefined) {
  242. msg.payload = opt.convertBack(fullDataset);
  243. msg = opt.beforeSend(msg) || msg;
  244. //if (settings.verbose) { console.log("UI-SEND",JSON.stringify(msg)); }
  245. if (!msg._dontSend) { opt.node.send(msg); }
  246. }
  247. }
  248. });
  249. // This is the handler for messages coming back from the UI
  250. var handler = function (msg) {
  251. if (msg.id !== opt.node.id) { return; } // ignore if not us
  252. if (settings.readOnly === true) { return; } // don't accept input if we are in read only mode
  253. var converted = opt.convertBack(msg.value);
  254. if (opt.storeFrontEndInputAsState === true) {
  255. currentValues[msg.id] = converted;
  256. if (opt.persistantFrontEndValue === true) {
  257. replayMessages[msg.id] = msg;
  258. }
  259. }
  260. var toSend = {payload:converted};
  261. toSend = opt.beforeSend(toSend, msg) || toSend;
  262. if (toSend !== undefined) {
  263. toSend.socketid = toSend.socketid || msg.socketid;
  264. if (msg.hasOwnProperty("meta")) { toSend.meta = msg.meta; }
  265. if (toSend.hasOwnProperty("topic") && (toSend.topic === undefined)) { delete toSend.topic; }
  266. // send to following nodes
  267. if (!msg.hasOwnProperty("_dontSend")) { opt.node.send(toSend); }
  268. }
  269. if (opt.storeFrontEndInputAsState === true) {
  270. //fwd to all UI clients
  271. io.emit(updateValueEventName, msg);
  272. }
  273. };
  274. ev.on(updateValueEventName, handler);
  275. return function() {
  276. ev.removeListener(updateValueEventName, handler);
  277. remove();
  278. removeStateTimers[opt.node.id] = setTimeout(function() {
  279. delete currentValues[opt.node.id];
  280. delete replayMessages[opt.node.id];
  281. }, removeStateTimeout);
  282. };
  283. }
  284. //from: https://stackoverflow.com/a/28592528/3016654
  285. function join() {
  286. var trimRegex = new RegExp('^\\/|\\/$','g');
  287. var paths = Array.prototype.slice.call(arguments);
  288. return '/'+paths.map(function(e) {
  289. if (e) { return e.replace(trimRegex,""); }
  290. }).filter(function(e) {return e;}).join('/');
  291. }
  292. function init(server, app, log, redSettings) {
  293. var uiSettings = redSettings.ui || {};
  294. if ((uiSettings.hasOwnProperty("path")) && (typeof uiSettings.path === "string")) {
  295. settings.path = uiSettings.path;
  296. }
  297. else { settings.path = 'ui'; }
  298. if ((uiSettings.hasOwnProperty("readOnly")) && (typeof uiSettings.readOnly === "boolean")) {
  299. settings.readOnly = uiSettings.readOnly;
  300. }
  301. else { settings.readOnly = false; }
  302. settings.defaultGroupHeader = uiSettings.defaultGroup || 'Default';
  303. settings.verbose = redSettings.verbose || false;
  304. var fullPath = join(redSettings.httpNodeRoot, settings.path);
  305. var socketIoPath = join(fullPath, 'socket.io');
  306. io = socketio(server, {path: socketIoPath});
  307. var dashboardMiddleware = function(req, res, next) { next(); }
  308. if (uiSettings.middleware) {
  309. if (typeof uiSettings.middleware === "function" || Array.isArray(uiSettings.middleware)) {
  310. dashboardMiddleware = uiSettings.middleware;
  311. }
  312. }
  313. fs.stat(path.join(__dirname, 'dist/index.html'), function(err, stat) {
  314. app.use(compression());
  315. if (!err) {
  316. app.use( join(settings.path, "manifest.json"), function(req, res) { res.send(mani); });
  317. app.use( join(settings.path), dashboardMiddleware, serveStatic(path.join(__dirname, "dist")) );
  318. }
  319. else {
  320. log.info("[Dashboard] Dashboard using development folder");
  321. app.use(join(settings.path), dashboardMiddleware, serveStatic(path.join(__dirname, "src")));
  322. var vendor_packages = [
  323. 'angular', 'angular-sanitize', 'angular-animate', 'angular-aria', 'angular-material', 'angular-touch',
  324. 'angular-material-icons', 'svg-morpheus', 'font-awesome', 'weather-icons-lite',
  325. 'sprintf-js', 'jquery', 'jquery-ui', 'd3', 'raphael', 'justgage', 'angular-chart.js', 'chart.js',
  326. 'moment', 'angularjs-color-picker', 'tinycolor2', 'less'
  327. ];
  328. vendor_packages.forEach(function (packageName) {
  329. app.use(join(settings.path, 'vendor', packageName), serveStatic(path.join(__dirname, 'node_modules', packageName)));
  330. });
  331. }
  332. });
  333. if ( process.versions.node.split('.')[0] < 12 ) {
  334. log.error("Dashboard version "+dashboardVersion+" requires Nodejs 12 or more recent");
  335. }
  336. else {
  337. log.info("Dashboard version " + dashboardVersion + " started at " + fullPath);
  338. }
  339. if (typeof uiSettings.ioMiddleware === "function") {
  340. io.use(uiSettings.ioMiddleware);
  341. } else if (Array.isArray(uiSettings.ioMiddleware)) {
  342. uiSettings.ioMiddleware.forEach(function (ioMiddleware) {
  343. io.use(ioMiddleware);
  344. });
  345. } else {
  346. io.use(function (socket, next) {
  347. if (socket.client.conn.request.url.indexOf("transport=websocket") !== -1) {
  348. // Reject direct websocket requests
  349. socket.client.conn.close();
  350. return;
  351. }
  352. if (socket.handshake.xdomain === false) {
  353. return next();
  354. } else {
  355. socket.disconnect(true);
  356. }
  357. });
  358. }
  359. io.on('connection', function(socket) {
  360. ev.emit("newsocket", socket.id, socket.request.headers['x-real-ip'] || socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress);
  361. updateUi(socket);
  362. socket.on(updateValueEventName, ev.emit.bind(ev, updateValueEventName));
  363. socket.on('ui-replay-state', function() {
  364. var ids = Object.getOwnPropertyNames(replayMessages);
  365. setTimeout(function() {
  366. ids.forEach(function (id) {
  367. socket.emit(updateValueEventName, replayMessages[id]);
  368. });
  369. }, 50);
  370. socket.emit('ui-replay-done');
  371. });
  372. socket.on('ui-change', function(index) {
  373. var name = "";
  374. if ((index != null) && !isNaN(index) && (menu.length > 0) && (index < menu.length) && menu[index]) {
  375. name = (menu[index].hasOwnProperty("header") && typeof menu[index].header !== 'undefined') ? menu[index].header : menu[index].name;
  376. ev.emit("changetab", index, name, socket.id, socket.request.headers['x-real-ip'] || socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress, params);
  377. }
  378. });
  379. socket.on('ui-collapse', function(d) {
  380. ev.emit("collapse", d.group, d.state, socket.id, socket.request.headers['x-real-ip'] || socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress);
  381. });
  382. socket.on('ui-refresh', function() {
  383. updateUi();
  384. });
  385. socket.on('disconnect', function() {
  386. ev.emit("endsocket", socket.id, socket.request.headers['x-real-ip'] || socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress);
  387. });
  388. socket.on('ui-audio', function(audioStatus) {
  389. ev.emit("audiostatus", audioStatus, socket.id, socket.request.headers['x-real-ip'] || socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress);
  390. });
  391. socket.on('ui-params', function(p) {
  392. delete p.socketid;
  393. params = p;
  394. });
  395. });
  396. }
  397. var updateUiPending = false;
  398. function updateUi(to) {
  399. if (!to) {
  400. if (updateUiPending) { return; }
  401. updateUiPending = true;
  402. to = io;
  403. }
  404. process.nextTick(function() {
  405. menu.forEach(function(o) {
  406. o.theme = baseConfiguration.theme;
  407. });
  408. to.emit('ui-controls', {
  409. site: baseConfiguration.site,
  410. theme: baseConfiguration.theme,
  411. menu: menu,
  412. globals: globals
  413. });
  414. updateUiPending = false;
  415. });
  416. }
  417. function find(array, predicate) {
  418. for (var i=0; i<array.length; i++) {
  419. if (predicate(array[i])) {
  420. return array[i];
  421. }
  422. }
  423. }
  424. function itemSorter(item1, item2) {
  425. if (item1.order === 0 && item2.order !== 0) {
  426. return 1;
  427. }
  428. else if (item1.order !== 0 && item2.order === 0) {
  429. return -1;
  430. }
  431. return item1.order - item2.order;
  432. }
  433. function addControl(tab, groupHeader, control) {
  434. if (typeof control.type !== 'string') { return function() {}; }
  435. // global template?
  436. if (control.type === 'template' && control.templateScope === 'global') {
  437. // add content to globals
  438. globals.push(control);
  439. updateUi();
  440. // return remove function
  441. return function() {
  442. var index = globals.indexOf(control);
  443. if (index >= 0) {
  444. globals.splice(index, 1);
  445. updateUi();
  446. }
  447. }
  448. }
  449. else {
  450. groupHeader = groupHeader || settings.defaultGroupHeader;
  451. control.order = parseFloat(control.order);
  452. var foundTab = find(menu, function (t) {
  453. if (tab && tab.hasOwnProperty("id")) { return t.id === tab.id }
  454. });
  455. if (!foundTab) {
  456. if (tab === null) { return; }
  457. foundTab = {
  458. id: tab.id,
  459. header: tab.config.name,
  460. order: parseFloat(tab.config.order),
  461. icon: tab.config.icon,
  462. //icon: tab.config.hidden ? "fa-ban" : tab.config.icon,
  463. disabled: tab.config.disabled,
  464. hidden: tab.config.hidden,
  465. items: []
  466. };
  467. menu.push(foundTab);
  468. menu.sort(itemSorter);
  469. }
  470. var foundGroup = find(foundTab.items, function (g) {return g.header === groupHeader;});
  471. if (!foundGroup) {
  472. foundGroup = {
  473. header: groupHeader,
  474. items: []
  475. };
  476. foundTab.items.push(foundGroup);
  477. }
  478. foundGroup.items.push(control);
  479. foundGroup.items.sort(itemSorter);
  480. foundGroup.order = groupHeader.config.order;
  481. foundTab.items.sort(itemSorter);
  482. updateUi();
  483. // Return the remove function for this control
  484. return function() {
  485. var index = foundGroup.items.indexOf(control);
  486. if (index >= 0) {
  487. // Remove the item from the group
  488. foundGroup.items.splice(index, 1);
  489. // If the group is now empty, remove it from the tab
  490. if (foundGroup.items.length === 0) {
  491. index = foundTab.items.indexOf(foundGroup);
  492. if (index >= 0) {
  493. foundTab.items.splice(index, 1);
  494. // If the tab is now empty, remove it as well
  495. if (foundTab.items.length === 0) {
  496. index = menu.indexOf(foundTab);
  497. if (index >= 0) {
  498. menu.splice(index, 1);
  499. }
  500. }
  501. }
  502. }
  503. updateUi();
  504. }
  505. }
  506. }
  507. }
  508. function addLink(name, link, icon, order, target, className) {
  509. var newLink = {
  510. name: name,
  511. link: link,
  512. icon: icon,
  513. order: order || 1,
  514. target: target,
  515. className: className
  516. };
  517. menu.push(newLink);
  518. menu.sort(itemSorter);
  519. updateUi();
  520. return function() {
  521. var index = menu.indexOf(newLink);
  522. if (index < 0) { return; }
  523. menu.splice(index, 1);
  524. updateUi();
  525. }
  526. }
  527. function addBaseConfig(config) {
  528. if (config) { baseConfiguration = config; }
  529. mani.name = config.site ? config.site.name : "Node-RED Dashboard";
  530. mani.short_name = mani.name.replace("Node-RED","").trim();
  531. mani.background_color = config.theme.themeState["page-titlebar-backgroundColor"].value;
  532. mani.theme_color = config.theme.themeState["page-titlebar-backgroundColor"].value;
  533. updateUi();
  534. }
  535. function angularColorToHex(color) {
  536. var angColorValues = { red: "#F44336", pink: "#E91E63", purple: "#9C27B0", deeppurple: "#673AB7",
  537. indigo: "#3F51B5", blue: "#2196F3", lightblue: "#03A9F4", cyan: "#00BCD4", teal: "#009688",
  538. green: "#4CAF50", lightgreen: "#8BC34A", lime: "#CDDC39", yellow: "#FFEB3B", amber: "#FFC107",
  539. orange: "#FF9800", deeporange: "#FF5722", brown: "#795548", grey: "#9E9E9E", bluegrey: "#607D8B"};
  540. return angColorValues[color.replace("-","").toLowerCase()];
  541. }
  542. function getTheme() {
  543. if (baseConfiguration && baseConfiguration.site && baseConfiguration.site.allowTempTheme && baseConfiguration.site.allowTempTheme === "none") {
  544. baseConfiguration.theme.name = "theme-custom";
  545. baseConfiguration.theme.themeState["widget-backgroundColor"].value = angularColorToHex(baseConfiguration.theme.angularTheme.primary);
  546. baseConfiguration.theme.themeState["widget-textColor"].value = (baseConfiguration.theme.angularTheme.palette === "dark") ? "#fff" : "#000";
  547. return baseConfiguration.theme.themeState;
  548. }
  549. else if (baseConfiguration && baseConfiguration.hasOwnProperty("theme") && (typeof baseConfiguration.theme !== "undefined") ) {
  550. return baseConfiguration.theme.themeState;
  551. }
  552. else {
  553. return undefined;
  554. }
  555. }
  556. function getSizes() {
  557. if (baseConfiguration && baseConfiguration.hasOwnProperty("site") && (typeof baseConfiguration.site !== "undefined") && baseConfiguration.site.hasOwnProperty("sizes")) {
  558. return baseConfiguration.site.sizes;
  559. }
  560. else {
  561. return { sx:48, sy:48, gx:6, gy:6, cx:6, cy:6, px:0, py:0 };
  562. }
  563. }
  564. function isDark() {
  565. if (baseConfiguration && baseConfiguration.site && baseConfiguration.site.allowTempTheme && baseConfiguration.site.allowTempTheme === "none") {
  566. if (baseConfiguration && baseConfiguration.hasOwnProperty("theme") && baseConfiguration.theme.hasOwnProperty("angularTheme")) {
  567. if (baseConfiguration.theme.angularTheme && baseConfiguration.theme.angularTheme.palette && baseConfiguration.theme.angularTheme.palette === "dark") { return true;}
  568. }
  569. return false;
  570. }
  571. else if (baseConfiguration && baseConfiguration.hasOwnProperty("theme") && baseConfiguration.theme.hasOwnProperty("themeState")) {
  572. var rgb = parseInt(baseConfiguration.theme.themeState["page-backgroundColor"].value.substring(1), 16);
  573. var luma = 0.2126 * ((rgb >> 16) & 0xff) + 0.7152 * ((rgb >> 8) & 0xff) + 0.0722 * ((rgb >> 0) & 0xff); // per ITU-R BT.709
  574. if (luma > 128) { return false; }
  575. else { return true; }
  576. }
  577. else { return false; } // if in doubt - let's say it's light.
  578. }