@frangoteam/fuxa-min 1.2.6 → 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +1 -1
  2. package/api/apikeys/index.js +107 -0
  3. package/api/apikeys/verify-api-or-token.js +47 -0
  4. package/api/index.js +37 -12
  5. package/api/jwt-helper.js +46 -0
  6. package/api/resources/index.js +162 -11
  7. package/api/scheduler/index.js +184 -0
  8. package/dist/3rdpartylicenses.txt +2 -378
  9. package/dist/assets/i18n/de.json +1 -1
  10. package/dist/assets/i18n/en.json +197 -18
  11. package/dist/assets/i18n/fr.json +836 -349
  12. package/dist/assets/i18n/ja.json +1751 -0
  13. package/dist/assets/i18n/ru.json +1 -1
  14. package/dist/assets/i18n/sv.json +6 -1
  15. package/dist/assets/i18n/zh-cn.json +525 -68
  16. package/dist/assets/i18n/zh-tw.json +1718 -0
  17. package/dist/assets/images/nodered-icon.svg +19 -0
  18. package/dist/assets/lib/svgeditor/extensions/ext-bundle.min.js +1 -1
  19. package/dist/assets/lib/svgeditor/fuxa-editor.min.js +2 -2
  20. package/dist/index.html +2 -2
  21. package/dist/main.a17204bdf90c1b7a.js +329 -0
  22. package/dist/{polyfills.8df7953530aa56e1.js → polyfills.c8e7db9850a3ad8b.js} +1 -1
  23. package/dist/{runtime.9136a61a9b98f987.js → runtime.8ef63094e52a66ba.js} +1 -1
  24. package/dist/styles.7afa4817cbd46a9c.css +1 -0
  25. package/docs/openapi.yaml +1045 -0
  26. package/integrations/node-red/index.js +219 -0
  27. package/integrations/node-red/node-red-contrib-fuxa/index.js +1 -0
  28. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-ack-alarm.html +49 -0
  29. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-ack-alarm.js +27 -0
  30. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-emit-event.html +31 -0
  31. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-emit-event.js +17 -0
  32. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-enable-device.html +49 -0
  33. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-enable-device.js +25 -0
  34. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-execute-script.html +50 -0
  35. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-execute-script.js +49 -0
  36. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-alarms.html +27 -0
  37. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-alarms.js +19 -0
  38. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.html +100 -0
  39. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.js +25 -0
  40. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-device-property.html +48 -0
  41. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-device-property.js +25 -0
  42. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-device.html +49 -0
  43. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-device.js +25 -0
  44. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-historical-tags.html +152 -0
  45. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-historical-tags.js +76 -0
  46. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-history-alarms.html +88 -0
  47. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-history-alarms.js +28 -0
  48. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.html +53 -0
  49. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.js +78 -0
  50. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.html +45 -0
  51. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.js +28 -0
  52. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-id.html +45 -0
  53. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-id.js +23 -0
  54. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.html +44 -0
  55. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.js +25 -0
  56. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-open-card.html +44 -0
  57. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-open-card.js +23 -0
  58. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-send-message.html +43 -0
  59. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-send-message.js +24 -0
  60. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-device-property.html +53 -0
  61. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-device-property.js +27 -0
  62. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.html +61 -0
  63. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.js +39 -0
  64. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.html +43 -0
  65. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.js +23 -0
  66. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-view.html +44 -0
  67. package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-view.js +23 -0
  68. package/integrations/node-red/node-red-contrib-fuxa/package.json +41 -0
  69. package/main.js +146 -42
  70. package/package.json +13 -7
  71. package/runtime/apikeys/apiKeysStorage.js +125 -0
  72. package/runtime/apikeys/index.js +123 -0
  73. package/runtime/devices/device-utils.js +23 -3
  74. package/runtime/devices/device.js +23 -5
  75. package/runtime/devices/fuxaserver/index.js +22 -2
  76. package/runtime/devices/melsec/index.js +362 -0
  77. package/runtime/devices/modbus/datatypes.js +35 -5
  78. package/runtime/devices/modbus/index.js +76 -41
  79. package/runtime/devices/opcua/index.js +19 -6
  80. package/runtime/devices/webcam/index.js +292 -0
  81. package/runtime/events.js +3 -0
  82. package/runtime/index.js +26 -5
  83. package/runtime/jobs/cleaner.js +34 -1
  84. package/runtime/notificator/index.js +46 -18
  85. package/runtime/plugins/index.js +7 -0
  86. package/runtime/scheduler/scheduler-service.js +1608 -0
  87. package/runtime/scheduler/scheduler-storage.js +184 -0
  88. package/runtime/scripts/index.js +9 -7
  89. package/runtime/storage/influxdb/index.js +1 -1
  90. package/runtime/utils.js +23 -0
  91. package/settings.default.js +20 -2
  92. package/dist/main.744ebf9b135efd34.js +0 -329
  93. package/dist/styles.466e813c4ba26c88.css +0 -1
package/README.md CHANGED
@@ -9,7 +9,7 @@ FUXA is a web-based Process Visualization (SCADA/HMI/Dashboard) software. With F
9
9
  ![fuxa action](/screenshot/feature-action-move.gif)
10
10
 
11
11
  ## Features
12
- - Devices connectivity with Modbus RTU/TCP, Siemens S7 Protocol, OPC-UA, BACnet IP, MQTT, WebAPI, Ethernet/IP (Allen Bradley), ODBC
12
+ - Devices connectivity with Modbus RTU/TCP, Siemens S7 Protocol, OPC-UA, BACnet IP, MQTT, WebAPI, Ethernet/IP (Allen Bradley), ADSclient, Gpio (Raspberry), WebCam, MELSEC, ODBC, Redis
13
13
  - SCADA/HMI Web-Editor - Engineering and Design completely web-based
14
14
  - Cross-Platform Full-Stack - Backend with NodeJs and Frontend with Web technologies (HTML5, CSS, Javascript, Angular, SVG)
15
15
 
@@ -0,0 +1,107 @@
1
+ /**
2
+ * 'api/apikeys': ApiKeys API to GET/POST/DELETE apikeys
3
+ */
4
+
5
+ var express = require("express");
6
+ const authJwt = require('../jwt-helper');
7
+ var runtime;
8
+ var secureFnc;
9
+ var checkGroupsFnc;
10
+
11
+ module.exports = {
12
+ init: function (_runtime, _secureFnc, _checkGroupsFnc) {
13
+ runtime = _runtime;
14
+ secureFnc = _secureFnc;
15
+ checkGroupsFnc = _checkGroupsFnc;
16
+ },
17
+ app: function() {
18
+ var apiKeysApp = express();
19
+ apiKeysApp.use(function(req,res,next) {
20
+ if (!runtime.project) {
21
+ res.status(404).end();
22
+ } else {
23
+ next();
24
+ }
25
+ });
26
+
27
+ /**
28
+ * GET ApiKeys
29
+ * Take from ApiKeys storage and reply
30
+ */
31
+ apiKeysApp.get("/api/apikeys", secureFnc, function(req, res) {
32
+ const permission = checkGroupsFnc(req);
33
+ if (res.statusCode === 403) {
34
+ runtime.logger.error("api get apikeys: Tocken Expired");
35
+ } else if (!authJwt.haveAdminPermission(permission)) {
36
+ res.status(401).json({error:"unauthorized_error", message: "Unauthorized!"});
37
+ runtime.logger.error("api get apikeys: Unauthorized!");
38
+ } else {
39
+ runtime.apiKeys.getApiKeys(req.query).then(result => {
40
+ if (result) {
41
+ res.json(result);
42
+ } else {
43
+ res.end();
44
+ }
45
+ }).catch(function(err) {
46
+ if (err.code) {
47
+ res.status(400).json({error:err.code, message: err.message});
48
+ } else {
49
+ res.status(400).json({error:"unexpected_error", message:err.toString()});
50
+ }
51
+ runtime.logger.error("api get apikeys: " + err.message);
52
+ });
53
+ }
54
+ });
55
+
56
+ /**
57
+ * POST ApiKeys
58
+ * Set apikeys storage
59
+ */
60
+ apiKeysApp.post("/api/apikeys", secureFnc, function(req, res, next) {
61
+ const permission = checkGroupsFnc(req);
62
+ if (res.statusCode === 403) {
63
+ runtime.logger.error("api post apikeys: Tocken Expired");
64
+ } else if (!authJwt.haveAdminPermission(permission)) {
65
+ res.status(401).json({error:"unauthorized_error", message: "Unauthorized!"});
66
+ runtime.logger.error("api post apikeys: Unauthorized");
67
+ } else {
68
+ runtime.apiKeys.setApiKeys(req.body.params).then(function(data) {
69
+ res.end();
70
+ }).catch(function(err) {
71
+ if (err.code) {
72
+ res.status(400).json({error:err.code, message: err.message});
73
+ } else {
74
+ res.status(400).json({error:"unexpected_error", message:err.toString()});
75
+ }
76
+ runtime.logger.error("api post apikeys: " + err.message);
77
+ });
78
+ }
79
+ });
80
+
81
+ /**
82
+ * DELETE Roles
83
+ * Delete apikeys from storage
84
+ */
85
+ apiKeysApp.delete("/api/apikeys", secureFnc, function(req, res, next) {
86
+ const permission = checkGroupsFnc(req);
87
+ if (res.statusCode === 403) {
88
+ runtime.logger.error("api delete apikeys: Tocken Expired");
89
+ } else if (!authJwt.haveAdminPermission(permission)) {
90
+ res.status(401).json({error:"unauthorized_error", message: "Unauthorized!"});
91
+ runtime.logger.error("api delete apikeys: Unauthorized");
92
+ } else {
93
+ runtime.apiKeys.removeApiKeys(JSON.parse(req.query.apikeys)).then(function(data) {
94
+ res.end();
95
+ }).catch(function(err) {
96
+ if (err.code) {
97
+ res.status(400).json({error:err.code, message: err.message});
98
+ } else {
99
+ res.status(400).json({error:"unexpected_error", message:err.toString()});
100
+ }
101
+ runtime.logger.error("api delete apikeys: " + err.message);
102
+ });
103
+ }
104
+ });
105
+ return apiKeysApp;
106
+ }
107
+ }
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const authJwt = require('../jwt-helper');
4
+
5
+ /**
6
+ * Middleware that accepts either JWT (x-access-token) or API key (x-api-key).
7
+ * It is active only when security is enabled.
8
+ */
9
+ module.exports = function verifyApiOrToken(runtime) {
10
+ return async function (req, res, next) {
11
+ if (!runtime?.settings?.secureEnabled) {
12
+ return next();
13
+ }
14
+
15
+ const apiKey = req.headers['x-api-key'];
16
+ if (apiKey) {
17
+ try {
18
+ const stored = await runtime.apiKeys.getApiKeys();
19
+ const now = Date.now();
20
+ const validKey = stored.find(k => {
21
+ if (!k || k.key !== apiKey || k.enabled === false) {
22
+ return false;
23
+ }
24
+ if (!k.expires) {
25
+ return true;
26
+ }
27
+ const expiresAt = new Date(k.expires).getTime();
28
+ return !isNaN(expiresAt) && expiresAt > now;
29
+ });
30
+
31
+ if (validKey) {
32
+ req.apiKey = validKey;
33
+ // Grant admin group to match existing authorization checks.
34
+ req.userId = validKey.id || `apikey:${apiKey}`;
35
+ req.userGroups = authJwt.adminGroups[0];
36
+ return next();
37
+ }
38
+ } catch (err) {
39
+ runtime.logger.error(`api-key validation failed: ${err}`);
40
+ return res.status(500).json({ error: 'unexpected_error', message: 'ApiKey validation failed' });
41
+ }
42
+ return res.status(401).json({ error: 'unauthorized_error', message: 'Invalid API Key' });
43
+ }
44
+
45
+ return authJwt.verifyToken(req, res, next);
46
+ };
47
+ };
package/api/index.js CHANGED
@@ -12,15 +12,20 @@ const rateLimit = require("express-rate-limit");
12
12
  var prjApi = require('./projects');
13
13
  var authApi = require('./auth');
14
14
  var usersApi = require('./users');
15
+ var apiKeysApi = require('./apikeys');
15
16
  var alarmsApi = require('./alarms');
16
17
  var pluginsApi = require('./plugins');
17
18
  var diagnoseApi = require('./diagnose');
18
19
  var scriptsApi = require('./scripts');
19
20
  var resourcesApi = require('./resources');
20
21
  var daqApi = require('./daq');
22
+ var schedulerApi = require('./scheduler');
21
23
  var commandApi = require('./command');
22
24
  const reports = require('../dist/reports.service');
23
25
  const reportsApi = new reports.ReportsApiService();
26
+ const verifyApiOrToken = require('./apikeys/verify-api-or-token');
27
+
28
+ const version = '1.0.0';
24
29
 
25
30
  var apiApp;
26
31
  var server;
@@ -29,6 +34,7 @@ var runtime;
29
34
  function init(_server, _runtime) {
30
35
  server = _server;
31
36
  runtime = _runtime;
37
+
32
38
  return new Promise(function (resolve, reject) {
33
39
  if (runtime.settings.disableServer !== false) {
34
40
  apiApp = express();
@@ -39,28 +45,33 @@ function init(_server, _runtime) {
39
45
  apiApp.use(bodyParser.json({limit:maxApiRequestSize}));
40
46
  apiApp.use(bodyParser.urlencoded({limit:maxApiRequestSize, extended: true}));
41
47
  authJwt.init(runtime.settings.secureEnabled, runtime.settings.secretCode, runtime.settings.tokenExpiresIn);
42
- prjApi.init(runtime, authJwt.verifyToken, verifyGroups);
48
+ const authMiddleware = verifyApiOrToken(runtime);
49
+ prjApi.init(runtime, authMiddleware, verifyGroups);
43
50
  apiApp.use(prjApi.app());
44
- usersApi.init(runtime, authJwt.verifyToken, verifyGroups);
51
+ usersApi.init(runtime, authMiddleware, verifyGroups);
45
52
  apiApp.use(usersApi.app());
46
- alarmsApi.init(runtime, authJwt.verifyToken, verifyGroups);
53
+ alarmsApi.init(runtime, authMiddleware, verifyGroups);
47
54
  apiApp.use(alarmsApi.app());
48
55
  authApi.init(runtime, authJwt.secretCode, authJwt.tokenExpiresIn);
49
56
  apiApp.use(authApi.app());
50
- pluginsApi.init(runtime, authJwt.verifyToken, verifyGroups);
57
+ pluginsApi.init(runtime, authMiddleware, verifyGroups);
51
58
  apiApp.use(pluginsApi.app());
52
- diagnoseApi.init(runtime, authJwt.verifyToken, verifyGroups);
59
+ diagnoseApi.init(runtime, authMiddleware, verifyGroups);
53
60
  apiApp.use(diagnoseApi.app());
54
- daqApi.init(runtime, authJwt.verifyToken, verifyGroups);
61
+ daqApi.init(runtime, authMiddleware, verifyGroups);
55
62
  apiApp.use(daqApi.app());
56
- scriptsApi.init(runtime, authJwt.verifyToken, verifyGroups);
63
+ schedulerApi.init(runtime, authMiddleware, verifyGroups);
64
+ apiApp.use(schedulerApi.app());
65
+ scriptsApi.init(runtime, authMiddleware, verifyGroups);
57
66
  apiApp.use(scriptsApi.app());
58
- resourcesApi.init(runtime, authJwt.verifyToken, verifyGroups);
67
+ resourcesApi.init(runtime, authMiddleware, verifyGroups);
59
68
  apiApp.use(resourcesApi.app());
60
- commandApi.init(runtime, authJwt.verifyToken, verifyGroups);
69
+ commandApi.init(runtime, authMiddleware, verifyGroups);
61
70
  apiApp.use(commandApi.app());
62
- reportsApi.init(runtime, authJwt.verifyToken, verifyGroups);
71
+ reportsApi.init(runtime, authMiddleware, verifyGroups);
63
72
  apiApp.use(reportsApi.app());
73
+ apiKeysApi.init(runtime, authMiddleware, verifyGroups);
74
+ apiApp.use(apiKeysApi.app());
64
75
 
65
76
  const limiter = rateLimit({
66
77
  windowMs: 5 * 60 * 1000, // 5 minutes
@@ -79,6 +90,13 @@ function init(_server, _runtime) {
79
90
  next(err);
80
91
  });
81
92
 
93
+ /**
94
+ * GET Server setting data
95
+ */
96
+ apiApp.get('/api/version', function (req, res) {
97
+ res.json(version);
98
+ });
99
+
82
100
  /**
83
101
  * GET Server setting data
84
102
  */
@@ -101,7 +119,7 @@ function init(_server, _runtime) {
101
119
  /**
102
120
  * POST Server user settings
103
121
  */
104
- apiApp.post("/api/settings", authJwt.verifyToken, function(req, res, next) {
122
+ apiApp.post("/api/settings", authMiddleware, function(req, res, next) {
105
123
  const permission = verifyGroups(req);
106
124
  if (res.statusCode === 403) {
107
125
  runtime.logger.error("api post settings: Tocken Expired");
@@ -128,7 +146,7 @@ function init(_server, _runtime) {
128
146
  /**
129
147
  * GET Heartbeat to check token
130
148
  */
131
- apiApp.post('/api/heartbeat', authJwt.verifyToken, function (req, res) {
149
+ apiApp.post('/api/heartbeat', authMiddleware, function (req, res) {
132
150
  if (!runtime.settings.secureEnabled) {
133
151
  res.end();
134
152
  } else if (res.statusCode === 403) {
@@ -152,6 +170,7 @@ function init(_server, _runtime) {
152
170
  res.end();
153
171
  }
154
172
  });
173
+
155
174
  runtime.logger.info('api: init successful!', true);
156
175
  } else {
157
176
  }
@@ -179,10 +198,16 @@ function mergeUserSettings(settings) {
179
198
  if (settings.alarms) {
180
199
  runtime.settings.alarms = settings.alarms;
181
200
  }
201
+ if (settings.logs) {
202
+ runtime.settings.logs = settings.logs;
203
+ }
182
204
  }
183
205
 
184
206
  function verifyGroups(req) {
185
207
  if (runtime.settings && runtime.settings.secureEnabled) {
208
+ if (req.apiKey) {
209
+ return authJwt.adminGroups[0];
210
+ }
186
211
  if (req.tokenExpired) {
187
212
  return (runtime.settings.userRole) ? null : 0;
188
213
  }
package/api/jwt-helper.js CHANGED
@@ -76,6 +76,51 @@ function verifyToken (req, res, next) {
76
76
  }
77
77
  }
78
78
 
79
+ function requireAuth (req, res, next) {
80
+ // Allow requests from FUXA interface (iframe embedding)
81
+ // Check for common FUXA referer patterns
82
+ const referer = req.headers.referer;
83
+ if (referer) {
84
+ // Allow if referer is from the same host (to support IP access without specific paths)
85
+ const requestHost = req.headers.host;
86
+ if (referer.startsWith(`http://${requestHost}`) || referer.startsWith(`https://${requestHost}`)) {
87
+ return next();
88
+ }
89
+ // Allow if referer contains common FUXA paths or is from the same server
90
+ const fuxaPatterns = [
91
+ '/fuxa', '/editor', '/viewer', '/lab', '/home',
92
+ 'localhost:', '127.0.0.1:', '0.0.0.0:'
93
+ ];
94
+ const hasFuxaReferer = fuxaPatterns.some(pattern => referer.includes(pattern));
95
+ if (hasFuxaReferer) {
96
+ return next();
97
+ }
98
+ }
99
+
100
+ // For direct access, require authentication
101
+ let token = req.headers['x-access-token'];
102
+
103
+ if (!token) {
104
+ return res.status(401).json({ error: "unauthorized_error", message: "Authentication required!" });
105
+ }
106
+
107
+ jwt.verify(token, secretCode, (err, decoded) => {
108
+ if (err) {
109
+ return res.status(401).json({ error: "unauthorized_error", message: "Invalid token!" });
110
+ } else {
111
+ req.userId = decoded.id;
112
+ req.userGroups = decoded.groups;
113
+ if (req.headers['x-auth-user']) {
114
+ let user = JSON.parse(req.headers['x-auth-user']);
115
+ if (user && user.groups != req.userGroups) {
116
+ return res.status(403).json({ error: "unauthorized_error", message: "User Profile Corrupted!" });
117
+ }
118
+ }
119
+ next();
120
+ }
121
+ });
122
+ }
123
+
79
124
  function getNewToken(headers) {
80
125
  const authUser = (headers['x-auth-user']) ? JSON.parse(headers['x-auth-user']) : null;
81
126
  if (authUser) {
@@ -119,6 +164,7 @@ module.exports = {
119
164
  init: init,
120
165
  verify: verify,
121
166
  verifyToken: verifyToken,
167
+ requireAuth: requireAuth,
122
168
  getNewToken: getNewToken,
123
169
  getGuestToken: getGuestToken,
124
170
  get secretCode() { return secretCode },
@@ -7,6 +7,7 @@ const path = require('path');
7
7
  var express = require("express");
8
8
  const authJwt = require('../jwt-helper');
9
9
  const Report = require('../../runtime/jobs/report');
10
+ const fontkit = require('fontkit');
10
11
  const os = require('os');
11
12
 
12
13
  var runtime;
@@ -41,16 +42,16 @@ module.exports = {
41
42
  runtime.logger.error("api get resources/images: Unauthorized!");
42
43
  } else {
43
44
  try {
44
- var result = {...req.query, ...{ groups: [] }};
45
+ var result = { ...req.query, ...{ groups: [] } };
45
46
  var resourcesDirs = getDirectories(runtime.settings.imagesFileDir);
46
47
  for (var i = 0; i < resourcesDirs.length; i++) {
47
48
  var group = { name: resourcesDirs[i], items: [] };
48
49
  var dirPath = path.resolve(runtime.settings.imagesFileDir, resourcesDirs[i]);
49
- var wwwSubDir = path.join('_images', resourcesDirs[i]);
50
- var files = getFiles(dirPath, ['.jpg','.jpeg', '.png', '.gif', '.svg']);
50
+ var wwwSubDir = path.join('_images', resourcesDirs[i]);
51
+ var files = getFiles(dirPath, ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.mp4', '.webm', '.ogg', '.ogv']);
51
52
  for (var x = 0; x < files.length; x++) {
52
53
  var filename = files[x].replace(/\.[^\/.]+$/, '');
53
- group.items.push({ path: path.join(wwwSubDir, files[x]).split(path.sep).join(path.posix.sep), name: filename });
54
+ group.items.push({ path: path.join(wwwSubDir, files[x]).split(path.sep).join(path.posix.sep), name: filename });
54
55
  }
55
56
  result.groups.push(group);
56
57
  }
@@ -66,6 +67,74 @@ module.exports = {
66
67
  }
67
68
  });
68
69
 
70
+ /**
71
+ * GET Server resources folder content
72
+ */
73
+ resourcesApp.get('/api/resources/resources', secureFnc, function (req, res) {
74
+ try {
75
+ const resourcesFilter = { fonts: ['ttf'] };
76
+ const wwwSubDir = '_resources';
77
+ const result = { ...req.query, ...{ groups: [] } };
78
+ const group = { name: wwwSubDir, items: [] };
79
+ var files = getFiles(runtime.settings.resourcesFileDir, ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.pdf', '.ttf', '.mp4', '.webm', '.ogg', '.ogv']);
80
+ for (var x = 0; x < files.length; x++) {
81
+ const fileName = files[x];
82
+ const filePath = path.join(wwwSubDir, files[x]).split(path.sep).join(path.posix.sep);
83
+ var fileLabel;
84
+ if (resourcesFilter.fonts.some(suffix => fileName.endsWith(suffix))) {
85
+ const font = fontkit.openSync(filePath);
86
+ fileLabel = font.fullName;
87
+ }
88
+
89
+ group.items.push({
90
+ path: filePath,
91
+ name: fileName,
92
+ label: fileLabel
93
+ });
94
+ }
95
+ result.groups.push(group);
96
+ res.json(result);
97
+ } catch (err) {
98
+ if (err.code) {
99
+ res.status(400).json({ error: err.code, message: err.message });
100
+ } else {
101
+ res.status(400).json({ error: "unexpected_error", message: err.toString() });
102
+ }
103
+ runtime.logger.error("api get resources/resources: " + err.message);
104
+ }
105
+ });
106
+
107
+ /**
108
+ * POST remove resource file
109
+ */
110
+ resourcesApp.post('/api/resources/remove', secureFnc, function (req, res) {
111
+ const permission = checkGroupsFnc(req);
112
+ if (res.statusCode === 403) {
113
+ runtime.logger.error("api post device: Tocken Expired");
114
+ } else if (!authJwt.haveAdminPermission(permission)) {
115
+ res.status(401).json({ error: "unauthorized_error", message: "Unauthorized!" });
116
+ runtime.logger.error("api post remove resource: Unauthorized");
117
+ } else {
118
+ try {
119
+ let fileName = req.body.file.replace(new RegExp('../', 'g'), '');
120
+ const filePath = path.join(runtime.settings.resourcesFileDir, fileName);
121
+ if (fs.existsSync(filePath)) {
122
+ fs.unlinkSync(filePath);
123
+ }
124
+ runtime.logger.info(`resources '${filePath}' deleted!`, true);
125
+ res.end();
126
+ } catch (err) {
127
+ if (err && err.code) {
128
+ res.status(400).json({ error: err.code, message: err.message });
129
+ runtime.logger.error("api remove resource: " + err.message);
130
+ } else {
131
+ res.status(400).json({ error: "unexpected_error", message: err });
132
+ runtime.logger.error("api remove resource: " + err);
133
+ }
134
+ }
135
+ }
136
+ });
137
+
69
138
  /**
70
139
  * GET svg/canvas rendered and converted to image
71
140
  */
@@ -102,8 +171,90 @@ module.exports = {
102
171
  });
103
172
 
104
173
  /**
105
- * GET Server widgets folder content
174
+ * GET Templates
175
+ * Take from resources storage and reply
106
176
  */
177
+ resourcesApp.get("/api/resources/templates", secureFnc, function (req, res) {
178
+ const permission = checkGroupsFnc(req);
179
+ if (res.statusCode === 403) {
180
+ runtime.logger.error("api get templates: Tocken Expired");
181
+ } else if (!authJwt.haveAdminPermission(permission)) {
182
+ res.status(401).json({ error: "unauthorized_error", message: "Unauthorized!" });
183
+ runtime.logger.error("api get templates: Unauthorized!");
184
+ } else {
185
+ runtime.resourcesMgr.getTemplates(req.query).then(result => {
186
+ // res.header("Access-Control-Allow-Origin", "*");
187
+ // res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
188
+ if (result) {
189
+ result.forEach(template => {
190
+ template.content = JSON.parse(template.content);
191
+ });
192
+ res.json(result);
193
+ } else {
194
+ res.end();
195
+ }
196
+ }).catch(function (err) {
197
+ if (err.code) {
198
+ res.status(400).json({ error: err.code, message: err.message });
199
+ } else {
200
+ res.status(400).json({ error: "unexpected_error", message: err.toString() });
201
+ }
202
+ runtime.logger.error("api get templates: " + err.message);
203
+ });
204
+ }
205
+ });
206
+
207
+ /**
208
+ * POST template
209
+ */
210
+ resourcesApp.post('/api/resources/template', secureFnc, function (req, res) {
211
+ const permission = checkGroupsFnc(req);
212
+ if (res.statusCode === 403) {
213
+ runtime.logger.error("api post device: Tocken Expired");
214
+ } else if (!authJwt.haveAdminPermission(permission)) {
215
+ res.status(401).json({ error: "unauthorized_error", message: "Unauthorized!" });
216
+ runtime.logger.error("api post template: Unauthorized");
217
+ } else {
218
+ runtime.resourcesMgr.setTemplate(req.body.template).then((data) => {
219
+ res.end();
220
+ }).catch(function (err) {
221
+ if (err.code) {
222
+ res.status(400).json({ error: err.code, message: err.message });
223
+ } else {
224
+ res.status(400).json({ error: "unexpected_error", message: err.toString() });
225
+ }
226
+ runtime.logger.error("api post template: " + err.message);
227
+ });
228
+ }
229
+ });
230
+
231
+ /**
232
+ * DELETE template
233
+ */
234
+ resourcesApp.delete("/api/resources/templates", secureFnc, function (req, res, next) {
235
+ const permission = checkGroupsFnc(req);
236
+ if (res.statusCode === 403) {
237
+ runtime.logger.error("api delete templates: Tocken Expired");
238
+ } else if (!authJwt.haveAdminPermission(permission)) {
239
+ res.status(401).json({ error: "unauthorized_error", message: "Unauthorized!" });
240
+ runtime.logger.error("api delete templates: Unauthorized");
241
+ } else {
242
+ runtime.resourcesMgr.removeTemplates(req.query.templates).then((data) => {
243
+ res.end();
244
+ }).catch(function (err) {
245
+ if (err.code) {
246
+ res.status(400).json({ error: err.code, message: err.message });
247
+ } else {
248
+ res.status(400).json({ error: "unexpected_error", message: err.toString() });
249
+ }
250
+ runtime.logger.error("api delete templates: " + err.message);
251
+ });
252
+ }
253
+ });
254
+
255
+ /**
256
+ * GET Server widgets folder content
257
+ */
107
258
  resourcesApp.get('/api/resources/widgets', secureFnc, function (req, res) {
108
259
  const permission = checkGroupsFnc(req);
109
260
  if (res.statusCode === 403) {
@@ -113,16 +264,16 @@ module.exports = {
113
264
  runtime.logger.error("api get resources/widgets: Unauthorized!");
114
265
  } else {
115
266
  try {
116
- var result = {...req.query, ...{ groups: [] }};
267
+ var result = { ...req.query, ...{ groups: [] } };
117
268
  var resourcesDirs = getDirectories(runtime.settings.widgetsFileDir);
118
269
  for (var i = 0; i < resourcesDirs.length; i++) {
119
270
  var group = { name: resourcesDirs[i], items: [] };
120
271
  var dirPath = path.resolve(runtime.settings.widgetsFileDir, resourcesDirs[i]);
121
- var wwwSubDir = path.join('_widgets', resourcesDirs[i]);
122
- var files = getFiles(dirPath, ['.svg']);
272
+ var wwwSubDir = path.join('_widgets', resourcesDirs[i]);
273
+ var files = getFiles(dirPath, ['.svg']);
123
274
  for (var x = 0; x < files.length; x++) {
124
275
  var filename = files[x];
125
- group.items.push({ path: path.join(wwwSubDir, files[x]).split(path.sep).join(path.posix.sep), name: filename });
276
+ group.items.push({ path: path.join(wwwSubDir, files[x]).split(path.sep).join(path.posix.sep), name: filename });
126
277
  }
127
278
  result.groups.push(group);
128
279
  }
@@ -191,14 +342,14 @@ module.exports = {
191
342
  }
192
343
  }
193
344
 
194
- function getDirectories (pathDir) {
345
+ function getDirectories(pathDir) {
195
346
  const directoriesInDIrectory = fs.readdirSync(pathDir, { withFileTypes: true })
196
347
  .filter((item) => item.isDirectory())
197
348
  .map((item) => item.name);
198
349
  return directoriesInDIrectory;
199
350
  }
200
351
 
201
- function getFiles (pathDir, extensions) {
352
+ function getFiles(pathDir, extensions) {
202
353
  const filesInDIrectory = fs.readdirSync(pathDir)
203
354
  .filter((item) => extensions.indexOf(path.extname(item).toLowerCase()) !== -1);
204
355
  return filesInDIrectory;