@saltcorn/server 0.8.7-beta.5 → 0.8.7
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/load_plugins.js +14 -1
- package/locales/en.json +3 -1
- package/package.json +8 -8
- package/public/relationship_diagram_utils.js +32 -10
- package/public/saltcorn.css +3 -0
- package/public/saltcorn.js +13 -5
- package/routes/tables.js +38 -6
- package/routes/view.js +2 -0
- package/routes/viewedit.js +8 -0
- package/tests/plugins.test.js +103 -2
- package/public/vis-network.min.js +0 -49
package/load_plugins.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
const db = require("@saltcorn/data/db");
|
|
9
9
|
const { PluginManager } = require("live-plugin-manager");
|
|
10
|
-
const { getState } = require("@saltcorn/data/db/state");
|
|
10
|
+
const { getState, getRootState } = require("@saltcorn/data/db/state");
|
|
11
11
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
12
12
|
const fs = require("fs");
|
|
13
13
|
const proc = require("child_process");
|
|
@@ -179,10 +179,23 @@ const loadAllPlugins = async () => {
|
|
|
179
179
|
* @returns {Promise<void>}
|
|
180
180
|
*/
|
|
181
181
|
const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB) => {
|
|
182
|
+
const tenants_unsafe_plugins = getRootState().getConfig(
|
|
183
|
+
"tenants_unsafe_plugins",
|
|
184
|
+
false
|
|
185
|
+
);
|
|
186
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
187
|
+
if (!isRoot && !tenants_unsafe_plugins) {
|
|
188
|
+
if (plugin.source !== "npm") return;
|
|
189
|
+
//get allowed plugins
|
|
190
|
+
const instore = await Plugin.store_plugins_available();
|
|
191
|
+
const safes = instore.filter((p) => !p.unsafe).map((p) => p.location);
|
|
192
|
+
if (!safes.includes(plugin.location)) return;
|
|
193
|
+
}
|
|
182
194
|
const { version, plugin_module, location } = await requirePlugin(
|
|
183
195
|
plugin,
|
|
184
196
|
force
|
|
185
197
|
);
|
|
198
|
+
|
|
186
199
|
// install dependecies
|
|
187
200
|
for (const loc of plugin_module.dependencies || []) {
|
|
188
201
|
const existing = await Plugin.findOne({ location: loc });
|
package/locales/en.json
CHANGED
|
@@ -1191,5 +1191,7 @@
|
|
|
1191
1191
|
"Do not allow the following fields to have a value set from the query string or state": "Do not allow the following fields to have a value set from the query string or state",
|
|
1192
1192
|
"Action configuration saved": "Action configuration saved",
|
|
1193
1193
|
"Prevent any deletion of parent rows": "Prevent any deletion of parent rows",
|
|
1194
|
-
"If the parent row is deleted, set key fields on child rows to null": "If the parent row is deleted, set key fields on child rows to null"
|
|
1194
|
+
"If the parent row is deleted, set key fields on child rows to null": "If the parent row is deleted, set key fields on child rows to null",
|
|
1195
|
+
"Link out?": "Link out?",
|
|
1196
|
+
"Show a link to open popup contents in new tab": "Show a link to open popup contents in new tab"
|
|
1195
1197
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.7
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@saltcorn/base-plugin": "0.8.7
|
|
10
|
-
"@saltcorn/builder": "0.8.7
|
|
11
|
-
"@saltcorn/data": "0.8.7
|
|
12
|
-
"@saltcorn/admin-models": "0.8.7
|
|
13
|
-
"@saltcorn/filemanager": "0.8.7
|
|
14
|
-
"@saltcorn/markup": "0.8.7
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.7
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.7",
|
|
10
|
+
"@saltcorn/builder": "0.8.7",
|
|
11
|
+
"@saltcorn/data": "0.8.7",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.7",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.7",
|
|
14
|
+
"@saltcorn/markup": "0.8.7",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.7",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"adm-zip": "0.5.10",
|
|
@@ -39,7 +39,8 @@ var erHelper = (() => {
|
|
|
39
39
|
}px) scale(${scale || 1.0})`
|
|
40
40
|
);
|
|
41
41
|
};
|
|
42
|
-
|
|
42
|
+
let mouseDown = false;
|
|
43
|
+
let isMoving = false;
|
|
43
44
|
return {
|
|
44
45
|
translateY: (val) => {
|
|
45
46
|
const parsed = parseTransform();
|
|
@@ -54,22 +55,43 @@ var erHelper = (() => {
|
|
|
54
55
|
zoom: (val) => {
|
|
55
56
|
const parsed = parseTransform();
|
|
56
57
|
parsed.scale += val;
|
|
58
|
+
if (parsed.scale < 0.1) parsed.scale = 0.1;
|
|
59
|
+
else if (parsed.scale > 20) parsed.scale = 20;
|
|
57
60
|
buildTransform(parsed);
|
|
58
61
|
},
|
|
59
62
|
reset: () => {
|
|
60
63
|
buildTransform();
|
|
61
64
|
},
|
|
62
65
|
takePicture: () => {
|
|
63
|
-
// TODO as png, so that you can download it via right click
|
|
64
|
-
// right now, you can only print it to pdf
|
|
65
66
|
const svg = $("svg[aria-roledescription='er']")[0];
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const link = document.createElement("a");
|
|
68
|
+
link.href = `data:image/svg+xml;base64,${btoa(
|
|
69
|
+
new XMLSerializer().serializeToString(svg)
|
|
70
|
+
)}`;
|
|
71
|
+
link.download = "er-diagram.svg";
|
|
72
|
+
link.click();
|
|
73
|
+
},
|
|
74
|
+
onWheel: (event) => {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
erHelper.zoom(-0.001 * event.deltaY);
|
|
77
|
+
},
|
|
78
|
+
onMouseDown: () => {
|
|
79
|
+
mouseDown = true;
|
|
80
|
+
isMoving = false;
|
|
81
|
+
},
|
|
82
|
+
onMouseUp: () => {
|
|
83
|
+
mouseDown = false;
|
|
84
|
+
},
|
|
85
|
+
onMouseMove: (event) => {
|
|
86
|
+
if (mouseDown) {
|
|
87
|
+
isMoving = true;
|
|
88
|
+
document.getSelection().removeAllRanges();
|
|
89
|
+
erHelper.translateX(event.movementX);
|
|
90
|
+
erHelper.translateY(event.movementY);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
isTranslating: () => {
|
|
94
|
+
return isMoving;
|
|
73
95
|
},
|
|
74
96
|
};
|
|
75
97
|
})();
|
package/public/saltcorn.css
CHANGED
package/public/saltcorn.js
CHANGED
|
@@ -149,6 +149,8 @@ function pjax_to(href, e) {
|
|
|
149
149
|
success: function (res, textStatus, request) {
|
|
150
150
|
if (!inModal && !localizer.length)
|
|
151
151
|
window.history.pushState({ url: href }, "", href);
|
|
152
|
+
if (inModal && !localizer.length)
|
|
153
|
+
$(".sc-modal-linkout").attr("href", href);
|
|
152
154
|
setTimeout(() => {
|
|
153
155
|
loadPage = true;
|
|
154
156
|
}, 0);
|
|
@@ -258,11 +260,14 @@ function ensure_modal_exists_and_closed() {
|
|
|
258
260
|
<div class="modal-content">
|
|
259
261
|
<div class="modal-header">
|
|
260
262
|
<h5 class="modal-title">Modal title</h5>
|
|
261
|
-
<
|
|
262
|
-
<span class="sc-ajax-indicator
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
<div class="">
|
|
264
|
+
<span class="sc-ajax-indicator-wrapper">
|
|
265
|
+
<span class="sc-ajax-indicator ms-2" style="display: none;"><i class="fas fa-save"></i></span>
|
|
266
|
+
</span>
|
|
267
|
+
<a class="sc-modal-linkout ms-2" href="" target="_blank"><i class="fas fa-expand-alt"></i></a>
|
|
268
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
|
|
269
|
+
</button>
|
|
270
|
+
</div>
|
|
266
271
|
</div>
|
|
267
272
|
<div class="modal-body">
|
|
268
273
|
<p>Modal body text goes here.</p>
|
|
@@ -300,6 +305,9 @@ function ajax_modal(url, opts = {}) {
|
|
|
300
305
|
);
|
|
301
306
|
if (saveIndicate) $(".sc-ajax-indicator-wrapper").show();
|
|
302
307
|
else $(".sc-ajax-indicator-wrapper").hide();
|
|
308
|
+
var linkOut = !!request.getResponseHeader("SaltcornModalLinkOut");
|
|
309
|
+
if (linkOut) $(".sc-modal-linkout").show().attr("href", url);
|
|
310
|
+
else $(".sc-modal-linkout").hide();
|
|
303
311
|
if (width) $(".modal-dialog").css("max-width", width);
|
|
304
312
|
else $(".modal-dialog").css("max-width", "");
|
|
305
313
|
if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
|
package/routes/tables.js
CHANGED
|
@@ -463,7 +463,7 @@ const navigationPanel = () =>
|
|
|
463
463
|
button(
|
|
464
464
|
{
|
|
465
465
|
class: "btn btn-primary er-up",
|
|
466
|
-
onclick: "erHelper.translateY(
|
|
466
|
+
onclick: "erHelper.translateY(100)",
|
|
467
467
|
},
|
|
468
468
|
i({ class: "fas fa-chevron-up" })
|
|
469
469
|
),
|
|
@@ -477,7 +477,7 @@ const navigationPanel = () =>
|
|
|
477
477
|
button(
|
|
478
478
|
{
|
|
479
479
|
class: "btn btn-primary er-left",
|
|
480
|
-
onclick: "erHelper.translateX(
|
|
480
|
+
onclick: "erHelper.translateX(100)",
|
|
481
481
|
},
|
|
482
482
|
i({ class: "fas fa-chevron-left" })
|
|
483
483
|
),
|
|
@@ -488,14 +488,14 @@ const navigationPanel = () =>
|
|
|
488
488
|
button(
|
|
489
489
|
{
|
|
490
490
|
class: "btn btn-primary er-right",
|
|
491
|
-
onclick: "erHelper.translateX(100)",
|
|
491
|
+
onclick: "erHelper.translateX(-100)",
|
|
492
492
|
},
|
|
493
493
|
i({ class: "fas fa-chevron-right" })
|
|
494
494
|
),
|
|
495
495
|
button(
|
|
496
496
|
{
|
|
497
497
|
class: "btn btn-primary er-down",
|
|
498
|
-
onclick: "erHelper.translateY(100)",
|
|
498
|
+
onclick: "erHelper.translateY(-100)",
|
|
499
499
|
},
|
|
500
500
|
i({ class: "fas fa-chevron-down" })
|
|
501
501
|
),
|
|
@@ -542,11 +542,30 @@ router.get(
|
|
|
542
542
|
{
|
|
543
543
|
headerTag: `
|
|
544
544
|
<script type="module">
|
|
545
|
-
mermaid.initialize({
|
|
545
|
+
mermaid.initialize({
|
|
546
|
+
startOnLoad: false,
|
|
547
|
+
securityLevel: 'loose',
|
|
548
|
+
});
|
|
546
549
|
await mermaid.run({
|
|
547
550
|
querySelector: ".mermaid",
|
|
548
551
|
postRenderCallback: (id) => {
|
|
549
552
|
$("#" + id).css("height", "calc(100vh - 250px)");
|
|
553
|
+
$("#" + id + " > g").each(function(index) {
|
|
554
|
+
const jThis = $(this);
|
|
555
|
+
const id = jThis.attr("id");
|
|
556
|
+
if (id) {
|
|
557
|
+
const arr = /^entity-(.+)-(\\w+-\\w+-\\w+-\\w+-\\w+$)/.exec(id);
|
|
558
|
+
if (arr?.length === 3) {
|
|
559
|
+
const textEnt = $("#text-entity-" + arr[1] + "-" + arr[2]);
|
|
560
|
+
textEnt.css("cursor", "pointer");
|
|
561
|
+
textEnt.on("click", function () {
|
|
562
|
+
if (!erHelper.isTranslating()) {
|
|
563
|
+
window.open("/table/" + encodeURIComponent(this.innerHTML));
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
});
|
|
550
569
|
}
|
|
551
570
|
});
|
|
552
571
|
</script>`,
|
|
@@ -573,17 +592,30 @@ router.get(
|
|
|
573
592
|
contents: [
|
|
574
593
|
div(
|
|
575
594
|
{
|
|
595
|
+
id: "erd-wrapper",
|
|
576
596
|
style: "height: calc(100vh - 250px);",
|
|
577
597
|
class: "overflow-scroll position-relative",
|
|
578
598
|
},
|
|
579
599
|
screenshotPanel(),
|
|
580
600
|
pre(
|
|
581
|
-
{
|
|
601
|
+
{
|
|
602
|
+
class: "mermaid",
|
|
603
|
+
style: "height: calc(100vh - 250px); color: transparent;",
|
|
604
|
+
},
|
|
582
605
|
buildMermaidMarkup(tables)
|
|
583
606
|
),
|
|
584
607
|
navigationPanel()
|
|
585
608
|
),
|
|
586
609
|
script({ src: "/relationship_diagram_utils.js" }),
|
|
610
|
+
script(
|
|
611
|
+
domReady(`
|
|
612
|
+
const erdWrapper = $("#erd-wrapper")[0];
|
|
613
|
+
erdWrapper.onwheel = erHelper.onWheel;
|
|
614
|
+
erdWrapper.onmousedown = erHelper.onMouseDown;
|
|
615
|
+
erdWrapper.onmouseup = erHelper.onMouseUp;
|
|
616
|
+
window.addEventListener("mousemove", erHelper.onMouseMove);
|
|
617
|
+
`)
|
|
618
|
+
),
|
|
587
619
|
],
|
|
588
620
|
},
|
|
589
621
|
],
|
package/routes/view.js
CHANGED
|
@@ -83,6 +83,8 @@ router.get(
|
|
|
83
83
|
);
|
|
84
84
|
if (isModal && view.attributes?.popup_save_indicator)
|
|
85
85
|
res.set("SaltcornModalSaveIndicator", `true`);
|
|
86
|
+
if (isModal && view.attributes?.popup_link_out)
|
|
87
|
+
res.set("SaltcornModalLinkOut", `true`);
|
|
86
88
|
if (tic) {
|
|
87
89
|
const tock = new Date();
|
|
88
90
|
const ms = tock.getTime() - tic.getTime();
|
package/routes/viewedit.js
CHANGED
|
@@ -250,6 +250,14 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
250
250
|
parent_field: "attributes",
|
|
251
251
|
tab: "Popup settings",
|
|
252
252
|
},
|
|
253
|
+
{
|
|
254
|
+
name: "popup_link_out",
|
|
255
|
+
label: req.__("Link out?"),
|
|
256
|
+
sublabel: req.__("Show a link to open popup contents in new tab"),
|
|
257
|
+
type: "Bool",
|
|
258
|
+
parent_field: "attributes",
|
|
259
|
+
tab: "Popup settings",
|
|
260
|
+
},
|
|
253
261
|
...(isEdit
|
|
254
262
|
? [
|
|
255
263
|
new Field({
|
package/tests/plugins.test.js
CHANGED
|
@@ -2,8 +2,13 @@ const request = require("supertest");
|
|
|
2
2
|
const getApp = require("../app");
|
|
3
3
|
const Table = require("@saltcorn/data/models/table");
|
|
4
4
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
5
|
-
const { getState } = require("@saltcorn/data/db/state");
|
|
6
|
-
|
|
5
|
+
const { getState, add_tenant } = require("@saltcorn/data/db/state");
|
|
6
|
+
const { install_pack } = require("@saltcorn/admin-models/models/pack");
|
|
7
|
+
const {
|
|
8
|
+
switchToTenant,
|
|
9
|
+
insertTenant,
|
|
10
|
+
create_tenant,
|
|
11
|
+
} = require("@saltcorn/admin-models/models/tenant");
|
|
7
12
|
const {
|
|
8
13
|
getAdminLoginCookie,
|
|
9
14
|
itShouldRedirectUnauthToLogin,
|
|
@@ -16,6 +21,7 @@ const db = require("@saltcorn/data/db");
|
|
|
16
21
|
const load_plugins = require("../load_plugins");
|
|
17
22
|
|
|
18
23
|
beforeAll(async () => {
|
|
24
|
+
if (!db.isSQLite) await db.query(`drop schema if exists test101 CASCADE `);
|
|
19
25
|
await resetToFixtures();
|
|
20
26
|
});
|
|
21
27
|
afterAll(db.close);
|
|
@@ -325,3 +331,98 @@ describe("config endpoints", () => {
|
|
|
325
331
|
.expect(toInclude(">FooSiteName<"));
|
|
326
332
|
});
|
|
327
333
|
});
|
|
334
|
+
|
|
335
|
+
const plugin_pack = (plugin) => ({
|
|
336
|
+
tables: [],
|
|
337
|
+
views: [],
|
|
338
|
+
plugins: [
|
|
339
|
+
{
|
|
340
|
+
...plugin,
|
|
341
|
+
configuration: null,
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
pages: [],
|
|
345
|
+
roles: [],
|
|
346
|
+
library: [],
|
|
347
|
+
triggers: [],
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe("Tenant cannot install unsafe plugins", () => {
|
|
351
|
+
if (!db.isSQLite) {
|
|
352
|
+
it("creates a new tenant", async () => {
|
|
353
|
+
db.enable_multi_tenant();
|
|
354
|
+
const loadAndSaveNewPlugin = load_plugins.loadAndSaveNewPlugin;
|
|
355
|
+
|
|
356
|
+
await getState().setConfig("base_url", "http://example.com/");
|
|
357
|
+
|
|
358
|
+
add_tenant("test101");
|
|
359
|
+
|
|
360
|
+
await switchToTenant(
|
|
361
|
+
await insertTenant("test101", "foo@foo.com", ""),
|
|
362
|
+
"http://test101.example.com/"
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
await create_tenant({
|
|
366
|
+
t: "test101",
|
|
367
|
+
loadAndSaveNewPlugin,
|
|
368
|
+
plugin_loader() {},
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
it("can install safe plugins on tenant", async () => {
|
|
372
|
+
await db.runWithTenant("test101", async () => {
|
|
373
|
+
const loadAndSaveNewPlugin = load_plugins.loadAndSaveNewPlugin;
|
|
374
|
+
|
|
375
|
+
await install_pack(
|
|
376
|
+
plugin_pack({
|
|
377
|
+
name: "html",
|
|
378
|
+
source: "npm",
|
|
379
|
+
location: "@saltcorn/html",
|
|
380
|
+
}),
|
|
381
|
+
"Todo list",
|
|
382
|
+
loadAndSaveNewPlugin
|
|
383
|
+
);
|
|
384
|
+
const dbPlugin = await Plugin.findOne({ name: "html" });
|
|
385
|
+
expect(dbPlugin).not.toBe(null);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
it("cannot install unsafe plugins on tenant", async () => {
|
|
389
|
+
await db.runWithTenant("test101", async () => {
|
|
390
|
+
const loadAndSaveNewPlugin = load_plugins.loadAndSaveNewPlugin;
|
|
391
|
+
|
|
392
|
+
await install_pack(
|
|
393
|
+
plugin_pack({
|
|
394
|
+
name: "sql-list",
|
|
395
|
+
source: "npm",
|
|
396
|
+
location: "@saltcorn/sql-list",
|
|
397
|
+
}),
|
|
398
|
+
"Todo list",
|
|
399
|
+
loadAndSaveNewPlugin
|
|
400
|
+
);
|
|
401
|
+
const dbPlugin = await Plugin.findOne({ name: "sql-list" });
|
|
402
|
+
expect(dbPlugin).toBe(null);
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
it("can install unsafe plugins on tenant when permitted", async () => {
|
|
406
|
+
await getState().setConfig("tenants_unsafe_plugins", true);
|
|
407
|
+
await db.runWithTenant("test101", async () => {
|
|
408
|
+
const loadAndSaveNewPlugin = load_plugins.loadAndSaveNewPlugin;
|
|
409
|
+
|
|
410
|
+
await install_pack(
|
|
411
|
+
plugin_pack({
|
|
412
|
+
name: "sql-list",
|
|
413
|
+
source: "npm",
|
|
414
|
+
location: "@saltcorn/sql-list",
|
|
415
|
+
}),
|
|
416
|
+
"Todo list",
|
|
417
|
+
loadAndSaveNewPlugin
|
|
418
|
+
);
|
|
419
|
+
const dbPlugin = await Plugin.findOne({ name: "sql-list" });
|
|
420
|
+
expect(dbPlugin).not.toBe(null);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
} else {
|
|
424
|
+
it("does not support tenants on SQLite", async () => {
|
|
425
|
+
expect(db.isSQLite).toBe(true);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
});
|