boomack 0.8.2 → 0.9.2
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.
- package/client/public/js/client.js +41 -20
- package/client/themes/blue-night/properties.json +1 -0
- package/client/themes/dark/properties.json +1 -0
- package/client/themes/default/properties.json +1 -0
- package/client/themes/green-night/properties.json +1 -0
- package/client/themes/iron/properties.json +1 -0
- package/client/themes/red-night/properties.json +1 -0
- package/client/themes/science/properties.json +1 -0
- package/client/views/panel.ejs +3 -1
- package/client/views/parts/client-resources.ejs +6 -6
- package/client/views/parts/document-layout.ejs +2 -0
- package/client/views/parts/grid-layout.ejs +2 -0
- package/client/views/parts/panel-head.ejs +2 -0
- package/client/views/parts/slot.ejs +22 -4
- package/client/views/slot-panel.ejs +2 -1
- package/default-config.yaml +7 -2
- package/package.json +6 -5
- package/server-build/cli.js +0 -0
- package/server-build/index.js +47 -21
- package/server-build/pipeline/render.js +0 -1
- package/server-build/pipeline/render.test.js +5 -5
- package/server-build/pipeline/transform.test.js +1 -1
- package/server-build/pipeline.js +4 -3
- package/server-build/plugins/csv.js +3 -6
- package/server-build/plugins/generic.js +1 -1
- package/server-build/plugins/highlight.js +1 -1
- package/server-build/plugins/markdown.js +2 -2
- package/server-build/plugins/media.js +3 -3
- package/server-build/plugins/text.js +3 -3
- package/server-build/routes/eval-request.js +134 -0
- package/server-build/routes/eval-requests.js +152 -0
- package/server-build/routes/export.js +25 -10
- package/server-build/routes/panels.js +25 -28
- package/server-build/routes/web-client.js +80 -7
- package/server-build/service/client-resources.js +1 -1
- package/server-build/service/panels.js +5 -0
- package/server-build/service/plugins.js +74 -57
- package/server-build/service/plugins.test.js +14 -7
- package/server-build/service/render.js +7 -1
- package/server-build/service/render.test.js +11 -11
- package/server-build/service/transform.js +4 -2
- package/server-build/service/transform.test.js +4 -4
- package/server-build/typedefs.js +14 -5
|
@@ -44,7 +44,7 @@ exports.textTransformations = {
|
|
|
44
44
|
'commonmark': textTransformationCommonmark,
|
|
45
45
|
'markdown': textTransformationMarkdownIt,
|
|
46
46
|
};
|
|
47
|
-
function textTransformationCommonmark(
|
|
47
|
+
function textTransformationCommonmark(text) {
|
|
48
48
|
const parser = new Parser({
|
|
49
49
|
smart: true,
|
|
50
50
|
});
|
|
@@ -54,7 +54,7 @@ function textTransformationCommonmark({ text }) {
|
|
|
54
54
|
const dom = parser.parse(text);
|
|
55
55
|
return renderer.render(dom);
|
|
56
56
|
}
|
|
57
|
-
function textTransformationMarkdownIt(
|
|
57
|
+
function textTransformationMarkdownIt(text, { options }) {
|
|
58
58
|
// configure Markdown it! with options
|
|
59
59
|
const md = new MarkdownIt({
|
|
60
60
|
html: _.isBoolean(options.allowHtml) ? options.allowHtml : true,
|
|
@@ -47,10 +47,10 @@ exports.renderers = {
|
|
|
47
47
|
'audio': renderAudio,
|
|
48
48
|
'video': renderVideo,
|
|
49
49
|
};
|
|
50
|
-
function renderImage(url
|
|
50
|
+
function renderImage(url) {
|
|
51
51
|
return `<img src="${url}"/>`;
|
|
52
52
|
}
|
|
53
|
-
function renderAudio(url, type, options) {
|
|
53
|
+
function renderAudio(url, { type, options }) {
|
|
54
54
|
let switchAttributes = '';
|
|
55
55
|
if (enforceBoolean(options.mediaControls, true))
|
|
56
56
|
switchAttributes += ' controls';
|
|
@@ -65,7 +65,7 @@ function renderAudio(url, type, options) {
|
|
|
65
65
|
`</a>` +
|
|
66
66
|
`</audio>`;
|
|
67
67
|
}
|
|
68
|
-
function renderVideo(url, type, options) {
|
|
68
|
+
function renderVideo(url, { type, options }) {
|
|
69
69
|
let switchAttributes = '';
|
|
70
70
|
if (enforceBoolean(options.mediaControls, true))
|
|
71
71
|
switchAttributes += ' controls';
|
|
@@ -26,13 +26,13 @@ exports.textTransformations = {
|
|
|
26
26
|
'uppercase': textTransformationUpperCase,
|
|
27
27
|
'lowercase': textTransformationLowerCase,
|
|
28
28
|
};
|
|
29
|
-
function textTransformationPlain(
|
|
29
|
+
function textTransformationPlain(text) {
|
|
30
30
|
return html.textLines(text);
|
|
31
31
|
}
|
|
32
|
-
function textTransformationUpperCase(
|
|
32
|
+
function textTransformationUpperCase(text) {
|
|
33
33
|
return html.textLines(_.upperCase(text));
|
|
34
34
|
}
|
|
35
|
-
function textTransformationLowerCase(
|
|
35
|
+
function textTransformationLowerCase(text) {
|
|
36
36
|
return html.textLines(_.lowerCase(text));
|
|
37
37
|
}
|
|
38
38
|
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setup = void 0;
|
|
4
|
+
const _ = require('lodash');
|
|
5
|
+
const YAML = require('yaml');
|
|
6
|
+
const async = require('async');
|
|
7
|
+
const express = require('express');
|
|
8
|
+
const { requestStream } = require('../utils');
|
|
9
|
+
const { info, warning, error } = require('../service/response-messages');
|
|
10
|
+
const cfg = require('../service/config');
|
|
11
|
+
const { log } = require('../service/logging');
|
|
12
|
+
const { checkPanelAccess } = require('../service/rbac');
|
|
13
|
+
const panels = require('../service/panels');
|
|
14
|
+
const themes = require('../service/themes');
|
|
15
|
+
const { apiAuth, forbiddenMessage } = require('../middleware/token-auth');
|
|
16
|
+
const { dataBody } = require('../middleware/data-parser');
|
|
17
|
+
const pipeline = require('../pipeline');
|
|
18
|
+
function setup(app, io) {
|
|
19
|
+
if (!cfg.getBoolean('api.enable.evaluation')) {
|
|
20
|
+
log.info('Deactivated routes for evaluation requests');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const contentNS = io.of('/boomack-content');
|
|
24
|
+
function checkAuthorization(authorization, request, cb) {
|
|
25
|
+
if (!authorization) {
|
|
26
|
+
cb(null, request);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const panelId = request.panel || 'default';
|
|
30
|
+
if (!checkPanelAccess(authorization, panelId)) {
|
|
31
|
+
cb({ message: 'Forbidden', forbidden: true });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
cb(null, request);
|
|
35
|
+
}
|
|
36
|
+
function checkRequest(request, cb) {
|
|
37
|
+
cb(null, {
|
|
38
|
+
panel: request.panel || 'default',
|
|
39
|
+
script: typeof request.script === 'string' ? request.script : null,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function handleEvaluationRequest(requests, auth, res) {
|
|
43
|
+
log.verbose(`Request to evaluate: ${_.size(requests)} JS code blocks`);
|
|
44
|
+
const checkPermissions = (r, cb) => checkAuthorization(auth, r, cb);
|
|
45
|
+
async.waterfall([
|
|
46
|
+
cb => async.map(requests, checkPermissions, cb),
|
|
47
|
+
(requests, cb) => async.map(requests, checkRequest, cb),
|
|
48
|
+
(commands, cb) => async.filter(commands, (cmd, cb) => cb(null, !!cmd.script), cb),
|
|
49
|
+
(commands, cb) => {
|
|
50
|
+
log.verbose(`Sending ${_.size(commands)} evaluation commands`);
|
|
51
|
+
// distribute commands on different panels
|
|
52
|
+
const evalCommands = _.groupBy(commands, c => c.panel);
|
|
53
|
+
_.forEach(evalCommands, (cmds, id) => {
|
|
54
|
+
contentNS.to(id).emit('eval', cmds);
|
|
55
|
+
});
|
|
56
|
+
cb(null);
|
|
57
|
+
},
|
|
58
|
+
], err => {
|
|
59
|
+
if (err) {
|
|
60
|
+
if (err.forbidden) {
|
|
61
|
+
error(res, forbiddenMessage);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.error('ERROR', err.message);
|
|
65
|
+
console.error(err.stack);
|
|
66
|
+
error(res, {
|
|
67
|
+
title: 'Sending evaluation commands failed',
|
|
68
|
+
message: err.message,
|
|
69
|
+
details: err.stack,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
res.status(204).send();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
app.post('/v1/eval', [apiAuth('content.evaluate'), dataBody], (req, res) => {
|
|
79
|
+
if (!_.isObject(req.body) && !_.isArray(req.body)) {
|
|
80
|
+
const hint = cfg.getBoolean('api.request.yaml') ?
|
|
81
|
+
'Hint: JSON and YAML is supported. Make sure to use the right Content-Type header.' :
|
|
82
|
+
'Hint: Only JSON is allowed. Make sure to use the right Content-Type header.';
|
|
83
|
+
if (req.parsingError) {
|
|
84
|
+
error(res, {
|
|
85
|
+
status: 400,
|
|
86
|
+
title: 'Malformed Request Body',
|
|
87
|
+
message: req.parsingError,
|
|
88
|
+
details: hint,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
error(res, {
|
|
93
|
+
status: 400,
|
|
94
|
+
title: 'Malformed Request Body',
|
|
95
|
+
message: 'Empty body or unknown error during body parsing.',
|
|
96
|
+
detail: hint,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const requests = _.isArray(req.body) ? req.body : [req.body];
|
|
102
|
+
handleEvaluationRequest(requests, req.authorization, res);
|
|
103
|
+
});
|
|
104
|
+
app.post('/v1/panels/:panelId/eval', [
|
|
105
|
+
apiAuth('content.evaluate'),
|
|
106
|
+
express.text({ limit: '16MB', type: 'application/javascript' })
|
|
107
|
+
], (req, res) => {
|
|
108
|
+
const panelId = req.params.panelId;
|
|
109
|
+
const panel = panels.lookup(panelId);
|
|
110
|
+
if (!panel) {
|
|
111
|
+
error(res, {
|
|
112
|
+
status: 404,
|
|
113
|
+
title: 'Target Not Found',
|
|
114
|
+
message: `The target panel "${panelId}" does not exist.`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const type = req.get('Content-Type');
|
|
118
|
+
if (type !== 'application/javascript') {
|
|
119
|
+
error(res, {
|
|
120
|
+
status: 400,
|
|
121
|
+
title: 'Unsupported Content-Type',
|
|
122
|
+
message: 'The MIME type of the evaluation request must be application/javascript.',
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const evalRequest = {
|
|
127
|
+
panel: panelId,
|
|
128
|
+
stream: req.body,
|
|
129
|
+
};
|
|
130
|
+
handleEvaluationRequest([evalRequest], req.authorization, res);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
exports.setup = setup;
|
|
134
|
+
//# sourceMappingURL=eval-request.js.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setup = void 0;
|
|
4
|
+
const _ = require('lodash');
|
|
5
|
+
const YAML = require('yaml');
|
|
6
|
+
const async = require('async');
|
|
7
|
+
const express = require('express');
|
|
8
|
+
const { requestStream } = require('../utils');
|
|
9
|
+
const { info, warning, error } = require('../service/response-messages');
|
|
10
|
+
const cfg = require('../service/config');
|
|
11
|
+
const { log } = require('../service/logging');
|
|
12
|
+
const { checkPanelAccess } = require('../service/rbac');
|
|
13
|
+
const panels = require('../service/panels');
|
|
14
|
+
const themes = require('../service/themes');
|
|
15
|
+
const { apiAuth, forbiddenMessage } = require('../middleware/token-auth');
|
|
16
|
+
const { dataBody } = require('../middleware/data-parser');
|
|
17
|
+
const pipeline = require('../pipeline');
|
|
18
|
+
function setup(app, io) {
|
|
19
|
+
if (!cfg.getBoolean('api.enable.evaluation')) {
|
|
20
|
+
log.info('Deactivated routes for evaluation requests');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const contentNS = io.of('/boomack-content');
|
|
24
|
+
function checkAuthorization(authorization, request, cb) {
|
|
25
|
+
if (!authorization) {
|
|
26
|
+
cb(null, request);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const panelId = request.panel || 'default';
|
|
30
|
+
if (!checkPanelAccess(authorization, panelId)) {
|
|
31
|
+
cb({ message: 'Forbidden', forbidden: true });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
cb(null, request);
|
|
35
|
+
}
|
|
36
|
+
function checkRequest(request, cb) {
|
|
37
|
+
const panelId = request.panel;
|
|
38
|
+
const panel = panels.lookup(panelId);
|
|
39
|
+
if (!panel) {
|
|
40
|
+
cb({
|
|
41
|
+
notFound: true,
|
|
42
|
+
message: `The target panel "${panelId}" does not exist.`,
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
cb(null, {
|
|
47
|
+
panel: request.panel || 'default',
|
|
48
|
+
script: typeof request.script === 'string' ? request.script : null,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function handleEvaluationRequest(requests, auth, res) {
|
|
52
|
+
log.verbose(`Request to evaluate: ${_.size(requests)} JS code blocks`);
|
|
53
|
+
const checkPermissions = (r, cb) => checkAuthorization(auth, r, cb);
|
|
54
|
+
async.waterfall([
|
|
55
|
+
cb => async.map(requests, checkPermissions, cb),
|
|
56
|
+
(requests, cb) => async.map(requests, checkRequest, cb),
|
|
57
|
+
(commands, cb) => async.filter(commands, (cmd, cb) => cb(null, !!cmd.script), cb),
|
|
58
|
+
(commands, cb) => {
|
|
59
|
+
if (_.size(commands) > 0) {
|
|
60
|
+
log.verbose(`Sending ${_.size(commands)} evaluation commands`);
|
|
61
|
+
// distribute commands on different panels
|
|
62
|
+
const evalCommands = _.groupBy(commands, c => c.panel);
|
|
63
|
+
_.forEach(evalCommands, (cmds, id) => {
|
|
64
|
+
contentNS.to(id).emit('eval', cmds);
|
|
65
|
+
});
|
|
66
|
+
cb(null);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
], err => {
|
|
70
|
+
if (err) {
|
|
71
|
+
if (err.forbidden) {
|
|
72
|
+
error(res, forbiddenMessage);
|
|
73
|
+
}
|
|
74
|
+
else if (err.notFound) {
|
|
75
|
+
error(res, {
|
|
76
|
+
status: 400,
|
|
77
|
+
title: 'Panel not found',
|
|
78
|
+
message: err.message,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.error('ERROR', err.message);
|
|
83
|
+
console.error(err.stack);
|
|
84
|
+
error(res, {
|
|
85
|
+
title: 'Sending evaluation commands failed',
|
|
86
|
+
message: err.message,
|
|
87
|
+
details: err.stack,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
res.status(204).send();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
app.post('/v1/eval', [apiAuth('content.evaluate'), dataBody], (req, res) => {
|
|
97
|
+
if (!_.isObject(req.body) && !_.isArray(req.body)) {
|
|
98
|
+
const hint = cfg.getBoolean('api.request.yaml') ?
|
|
99
|
+
'Hint: JSON and YAML is supported. Make sure to use the right Content-Type header.' :
|
|
100
|
+
'Hint: Only JSON is allowed. Make sure to use the right Content-Type header.';
|
|
101
|
+
if (req.parsingError) {
|
|
102
|
+
error(res, {
|
|
103
|
+
status: 400,
|
|
104
|
+
title: 'Malformed Request Body',
|
|
105
|
+
message: req.parsingError,
|
|
106
|
+
details: hint,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
error(res, {
|
|
111
|
+
status: 400,
|
|
112
|
+
title: 'Malformed Request Body',
|
|
113
|
+
message: 'Empty body or unknown error during body parsing.',
|
|
114
|
+
detail: hint,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const requests = _.isArray(req.body) ? req.body : [req.body];
|
|
120
|
+
handleEvaluationRequest(requests, req.authorization, res);
|
|
121
|
+
});
|
|
122
|
+
app.post('/v1/panels/:panelId/eval', [
|
|
123
|
+
apiAuth('content.evaluate'),
|
|
124
|
+
express.text({ limit: '16MB', type: 'application/javascript' })
|
|
125
|
+
], (req, res) => {
|
|
126
|
+
const panelId = req.params.panelId;
|
|
127
|
+
const panel = panels.lookup(panelId);
|
|
128
|
+
if (!panel) {
|
|
129
|
+
error(res, {
|
|
130
|
+
status: 404,
|
|
131
|
+
title: 'Target Not Found',
|
|
132
|
+
message: `The target panel "${panelId}" does not exist.`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const type = req.get('Content-Type');
|
|
136
|
+
if (type !== 'application/javascript') {
|
|
137
|
+
error(res, {
|
|
138
|
+
status: 400,
|
|
139
|
+
title: 'Unsupported Content-Type',
|
|
140
|
+
message: 'The MIME type of the evaluation request must be application/javascript.',
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const evalRequest = {
|
|
145
|
+
panel: panelId,
|
|
146
|
+
script: req.body,
|
|
147
|
+
};
|
|
148
|
+
handleEvaluationRequest([evalRequest], req.authorization, res);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
exports.setup = setup;
|
|
152
|
+
//# sourceMappingURL=eval-requests.js.map
|
|
@@ -16,6 +16,7 @@ const clientRes = require('../service/client-resources');
|
|
|
16
16
|
const plugins = require('../service/plugins');
|
|
17
17
|
const resources = require('../service/resources');
|
|
18
18
|
const uiPaths = require('../service/web-ui-paths');
|
|
19
|
+
const { error } = require('../service/response-messages');
|
|
19
20
|
const { webAuthBasic } = require('../middleware/basic-auth');
|
|
20
21
|
const { webAuthStandard } = require('../middleware/standard-auth');
|
|
21
22
|
const { apiAuth, forbiddenMessage } = require('../middleware/token-auth');
|
|
@@ -161,9 +162,13 @@ exports.setup = function (app) {
|
|
|
161
162
|
const clientResGroupIds = clientRes.getResourceGroupsForPanel(panel);
|
|
162
163
|
// plugin resources
|
|
163
164
|
_.forEach(clientResGroupIds, groupId => {
|
|
164
|
-
const
|
|
165
|
-
_.forEach(
|
|
166
|
-
zip.file(
|
|
165
|
+
const stylePaths = plugins.getStylePathsOfClientResourceGroup(groupId);
|
|
166
|
+
_.forEach(stylePaths, (resourcePath, i) => {
|
|
167
|
+
zip.file(resourcePath, { name: `plugins/styles/${groupId}/${i}` });
|
|
168
|
+
});
|
|
169
|
+
const scriptPaths = plugins.getScriptPathsOfClientResourceGroup(groupId);
|
|
170
|
+
_.forEach(scriptPaths, (resourcePath, i) => {
|
|
171
|
+
zip.file(resourcePath, { name: `plugins/scripts/${groupId}/${i}` });
|
|
167
172
|
});
|
|
168
173
|
});
|
|
169
174
|
// resources
|
|
@@ -258,7 +263,8 @@ exports.setup = function (app) {
|
|
|
258
263
|
const subject = slot ?
|
|
259
264
|
`slot ${panel.id}/${slot.id}` :
|
|
260
265
|
`panel ${panel.id}`;
|
|
261
|
-
const
|
|
266
|
+
const pluginStylesDir = path.join(targetDir, 'plugins', 'styles');
|
|
267
|
+
const pluginScriptsDir = path.join(targetDir, 'plugins', 'scripts');
|
|
262
268
|
const resourcesSubPath = 'resources/' + name;
|
|
263
269
|
render(panel, slot, options, (err, html) => {
|
|
264
270
|
if (err) {
|
|
@@ -269,7 +275,11 @@ exports.setup = function (app) {
|
|
|
269
275
|
const displayCommands = getDisplayCommands(panel, slot);
|
|
270
276
|
const referencedResources = findReferencedResources(displayCommands);
|
|
271
277
|
html = replaceResourceUrls(html, referencedResources, basePath + resourcesSubPath);
|
|
272
|
-
const clientResGroups = _.map(clientRes.getResourceGroupsForPanel(panel, slot), groupId => ({
|
|
278
|
+
const clientResGroups = _.map(clientRes.getResourceGroupsForPanel(panel, slot), groupId => ({
|
|
279
|
+
id: groupId,
|
|
280
|
+
stylePaths: plugins.getStylePathsOfClientResourceGroup(groupId),
|
|
281
|
+
scriptPaths: plugins.getScriptPathsOfClientResourceGroup(groupId),
|
|
282
|
+
}));
|
|
273
283
|
const maxResourceSize = parseDataAmount(cfg.get('export.local.resourceSizeLimit'));
|
|
274
284
|
const includedResources = [];
|
|
275
285
|
let resourceSize = 0;
|
|
@@ -330,12 +340,17 @@ exports.setup = function (app) {
|
|
|
330
340
|
}
|
|
331
341
|
},
|
|
332
342
|
// plugin resources
|
|
333
|
-
cb => async.each(clientResGroups, ({ id,
|
|
334
|
-
const
|
|
343
|
+
cb => async.each(clientResGroups, ({ id, stylePaths, scriptPaths }, cb) => {
|
|
344
|
+
const groupStylesDir = path.join(pluginStylesDir, id);
|
|
345
|
+
const groupScriptsDir = path.join(pluginScriptsDir, id);
|
|
335
346
|
async.waterfall([
|
|
336
|
-
cb => mkdir(path.join(
|
|
337
|
-
cb => async.eachOf(
|
|
338
|
-
fs.copyFile(
|
|
347
|
+
cb => mkdir(path.join(groupStylesDir), cb),
|
|
348
|
+
cb => async.eachOf(stylePaths, (p, i, cb) => {
|
|
349
|
+
fs.copyFile(p, path.join(groupStylesDir, i.toString()), cb);
|
|
350
|
+
}, cb),
|
|
351
|
+
cb => mkdir(path.join(groupScriptsDir), cb),
|
|
352
|
+
cb => async.eachOf(scriptPaths, (p, i, cb) => {
|
|
353
|
+
fs.copyFile(p, path.join(groupScriptsDir, i.toString()), cb);
|
|
339
354
|
}, cb),
|
|
340
355
|
], cb);
|
|
341
356
|
}, cb),
|
|
@@ -73,17 +73,30 @@ exports.setup = function (app, io) {
|
|
|
73
73
|
}
|
|
74
74
|
const panel = panels.lookup(panelId);
|
|
75
75
|
if (panel && (panel.layout.grid || panel.layout.document)) {
|
|
76
|
+
const theme = themes.safeTheme(panel.layout.theme);
|
|
76
77
|
res.format({
|
|
77
|
-
html: () =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
html: () => themes.getThemeProperties(theme, (err, themeProperties) => {
|
|
79
|
+
if (err) {
|
|
80
|
+
warning(res, {
|
|
81
|
+
status: 500,
|
|
82
|
+
title: 'Theme Error',
|
|
83
|
+
message: 'Failed to load the properties of the selected theme.',
|
|
84
|
+
details: err,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
res.render(panel.layout.type === 'document' ? 'parts/document-layout.ejs' : 'parts/grid-layout.ejs', {
|
|
89
|
+
_,
|
|
90
|
+
uiPaths,
|
|
91
|
+
query: req.query,
|
|
92
|
+
basePath: cfg.serverBasePath(),
|
|
93
|
+
theme,
|
|
94
|
+
themeOverride: false,
|
|
95
|
+
themeProperties,
|
|
96
|
+
panel,
|
|
97
|
+
offline: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
87
100
|
}),
|
|
88
101
|
json: () => res.json(panel.layout),
|
|
89
102
|
text: () => res.type('text').send(YAML.stringify(panel.layout)),
|
|
@@ -143,6 +156,7 @@ exports.setup = function (app, io) {
|
|
|
143
156
|
panel.layout.theme != newLayout.theme ||
|
|
144
157
|
panel.layout.style != newLayout.style ||
|
|
145
158
|
panel.layout.script != newLayout.script ||
|
|
159
|
+
panel.layout.type != newLayout.type ||
|
|
146
160
|
!_.isEqual(panel.layout.grid, newLayout.grid) ||
|
|
147
161
|
!_.isEqual(panel.layout.document, newLayout.document));
|
|
148
162
|
action(panelId, newLayout, (err, panel2) => {
|
|
@@ -158,24 +172,7 @@ exports.setup = function (app, io) {
|
|
|
158
172
|
contentNS.to(panelId).emit('reload', {});
|
|
159
173
|
}
|
|
160
174
|
else {
|
|
161
|
-
|
|
162
|
-
_,
|
|
163
|
-
uiPaths,
|
|
164
|
-
query: {},
|
|
165
|
-
basePath: cfg.serverBasePath(),
|
|
166
|
-
themes: themes.getThemes(),
|
|
167
|
-
theme: themes.safeTheme(panel2.layout.theme),
|
|
168
|
-
themeOverride: false,
|
|
169
|
-
panel: panel2,
|
|
170
|
-
offline: false,
|
|
171
|
-
}, (err, html) => {
|
|
172
|
-
if (err) {
|
|
173
|
-
log.error('Rendering layout failed: ' + err.message);
|
|
174
|
-
log.error(err.stack);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
contentNS.to(panelId).emit('layout', html);
|
|
178
|
-
});
|
|
175
|
+
contentNS.to(panelId).emit('reload-layout', {});
|
|
179
176
|
}
|
|
180
177
|
if (panel) {
|
|
181
178
|
log.verbose(`Layout updated for '${panelId}': ` +
|
|
@@ -173,6 +173,22 @@ exports.setup = function (app, io) {
|
|
|
173
173
|
},
|
|
174
174
|
})]);
|
|
175
175
|
});
|
|
176
|
+
_.forEach(plugins.getPlugInIds(), plugInId => {
|
|
177
|
+
const plugin = plugins.getPlugIn(plugInId);
|
|
178
|
+
const routes = _.keys(plugin.static);
|
|
179
|
+
_.forEach(routes, route => {
|
|
180
|
+
if (route.indexOf(' ') >= 0) {
|
|
181
|
+
log.warn('Invalid route for static resource in plugin ' + plugInId + ': ' + route);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const resourcePath = _.get(plugin, ['static', route]);
|
|
185
|
+
if (!path.isAbsolute(resourcePath)) {
|
|
186
|
+
log.warn('Path to static resource ' + route + ' in plugin ' + plugInId + ' is not absolute');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
app.use('/plugins/' + plugInId + '/static/' + _.trimStart(route, '/'), express.static(resourcePath, staticOptions));
|
|
190
|
+
});
|
|
191
|
+
});
|
|
176
192
|
function visiblePanelsForRequest(req) {
|
|
177
193
|
return _.chain(panels.getPanelIds())
|
|
178
194
|
.filter(panelId => isAccessToPanelAllowed(req, panelId))
|
|
@@ -231,6 +247,42 @@ exports.setup = function (app, io) {
|
|
|
231
247
|
log.verbose('A home client disconnected from Socket.IO');
|
|
232
248
|
});
|
|
233
249
|
});
|
|
250
|
+
app.get('/panels/:panelId/layout', mw, (req, res) => {
|
|
251
|
+
const panelId = req.params.panelId;
|
|
252
|
+
if (!isAccessToPanelAllowed(req, panelId)) {
|
|
253
|
+
res.status(403).end();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const panel = panels.lookup(panelId);
|
|
257
|
+
if (panel) {
|
|
258
|
+
const theme = themes.safeTheme(req.query.theme || panel.layout.theme);
|
|
259
|
+
themes.getThemeProperties(theme, (err, themeProperties) => {
|
|
260
|
+
if (err) {
|
|
261
|
+
res.status(500).end();
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
res.render(panel.layout.type === 'document' ? 'parts/document-layout.ejs' : 'parts/grid-layout.ejs', {
|
|
265
|
+
_,
|
|
266
|
+
uiPaths,
|
|
267
|
+
query: req.query,
|
|
268
|
+
basePath: cfg.serverBasePath(),
|
|
269
|
+
theme,
|
|
270
|
+
themeProperties,
|
|
271
|
+
themeOverride: !!req.query.theme,
|
|
272
|
+
panel,
|
|
273
|
+
offline: false,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
warning(res, {
|
|
280
|
+
status: 404,
|
|
281
|
+
title: 'Panel Not Found',
|
|
282
|
+
message: 'Go to the home page to select an existing panel.',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
});
|
|
234
286
|
app.get('/panels/:panelId', mw, (req, res) => {
|
|
235
287
|
const panelId = req.params.panelId;
|
|
236
288
|
if (!isAccessToPanelAllowed(req, panelId)) {
|
|
@@ -417,22 +469,43 @@ exports.setup = function (app, io) {
|
|
|
417
469
|
});
|
|
418
470
|
}
|
|
419
471
|
});
|
|
420
|
-
app.get('/plugins/
|
|
472
|
+
app.get('/plugins/scripts/:groupId/:index', mw, (req, res) => {
|
|
473
|
+
const groupId = req.params.groupId;
|
|
474
|
+
const index = _.toNumber(req.params.index);
|
|
475
|
+
const paths = plugins.getScriptPathsOfClientResourceGroup(groupId);
|
|
476
|
+
const resourcePath = _.get(paths, index);
|
|
477
|
+
const maxAgeVal = cfg.get('web.cache.static');
|
|
478
|
+
const maxAge = _.isString(maxAgeVal) ? Math.floor(ms(maxAgeVal) / 1000) : maxAgeVal;
|
|
479
|
+
if (resourcePath) {
|
|
480
|
+
res.sendFile(resourcePath, {
|
|
481
|
+
headers: {
|
|
482
|
+
'Content-Type': 'application/javascript',
|
|
483
|
+
'Cache-Control': `public, max-age=${maxAge}`,
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
log.info(`Requested unknown plugin script resource: ${groupId}, ${index}`);
|
|
489
|
+
res.status(404).end();
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
app.get('/plugins/styles/:groupId/:index', mw, (req, res) => {
|
|
421
493
|
const groupId = req.params.groupId;
|
|
422
494
|
const index = _.toNumber(req.params.index);
|
|
423
|
-
const
|
|
424
|
-
const
|
|
495
|
+
const paths = plugins.getStylePathsOfClientResourceGroup(groupId);
|
|
496
|
+
const resourcePath = _.get(paths, index);
|
|
425
497
|
const maxAgeVal = cfg.get('web.cache.static');
|
|
426
498
|
const maxAge = _.isString(maxAgeVal) ? Math.floor(ms(maxAgeVal) / 1000) : maxAgeVal;
|
|
427
|
-
if (
|
|
428
|
-
res.sendFile(
|
|
499
|
+
if (resourcePath) {
|
|
500
|
+
res.sendFile(resourcePath, {
|
|
429
501
|
headers: {
|
|
430
|
-
'
|
|
502
|
+
'Content-Type': 'text/css',
|
|
503
|
+
'Cache-Control': `public, max-age=${maxAge}`,
|
|
431
504
|
},
|
|
432
505
|
});
|
|
433
506
|
}
|
|
434
507
|
else {
|
|
435
|
-
log.info(`Requested unknown
|
|
508
|
+
log.info(`Requested unknown plugin style resource: ${groupId}, ${index}`);
|
|
436
509
|
res.status(404).end();
|
|
437
510
|
}
|
|
438
511
|
});
|
|
@@ -24,7 +24,7 @@ function getResourceGroupsForPanel(panel, slot) {
|
|
|
24
24
|
resourceGroupIds.add(groupId);
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
|
-
return
|
|
27
|
+
return Array.from(resourceGroupIds);
|
|
28
28
|
}
|
|
29
29
|
exports.getResourceGroupsForPanel = getResourceGroupsForPanel;
|
|
30
30
|
//# sourceMappingURL=client-resources.js.map
|
|
@@ -215,6 +215,7 @@ exports.pushDisplayCommand = function (displayCommand, cb) {
|
|
|
215
215
|
// if display command replaces current content
|
|
216
216
|
// update current content
|
|
217
217
|
displayCommand.no = layoutSlot.history ? currentSequenceNo + 1 : 1;
|
|
218
|
+
displayCommand.version = 0;
|
|
218
219
|
panel.content.set(slotId, displayCommand);
|
|
219
220
|
// proceed with history
|
|
220
221
|
}
|
|
@@ -222,10 +223,12 @@ exports.pushDisplayCommand = function (displayCommand, cb) {
|
|
|
222
223
|
// if display command extends current content at the end
|
|
223
224
|
currentContent.title = displayCommand.title;
|
|
224
225
|
currentContent.content = currentContent.content + CONTENT_SEPARATOR + displayCommand.content;
|
|
226
|
+
currentContent.version = currentContent.version + 1;
|
|
225
227
|
currentContent.extensions = currentContent.extensions + 1;
|
|
226
228
|
limitExtensions(currentContent, layoutSlot.extensions, displayCommand.extend);
|
|
227
229
|
// return display command, but do not store it
|
|
228
230
|
displayCommand.no = currentSequenceNo;
|
|
231
|
+
displayCommand.version = currentContent.version;
|
|
229
232
|
cb(null, displayCommand);
|
|
230
233
|
return;
|
|
231
234
|
}
|
|
@@ -233,10 +236,12 @@ exports.pushDisplayCommand = function (displayCommand, cb) {
|
|
|
233
236
|
// if display command extends current content at the beginning
|
|
234
237
|
currentContent.title = displayCommand.title;
|
|
235
238
|
currentContent.content = displayCommand.content + CONTENT_SEPARATOR + currentContent.content;
|
|
239
|
+
currentContent.version = currentContent.version + 1;
|
|
236
240
|
currentContent.extensions = currentContent.extensions + 1;
|
|
237
241
|
limitExtensions(currentContent, layoutSlot.extensions, displayCommand.extend);
|
|
238
242
|
// return display command, but do not store it
|
|
239
243
|
displayCommand.no = currentSequenceNo;
|
|
244
|
+
displayCommand.version = currentContent.version;
|
|
240
245
|
cb(null, displayCommand);
|
|
241
246
|
return;
|
|
242
247
|
}
|