@saltcorn/server 0.8.7-beta.6 → 0.8.8-beta.0
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/auth/admin.js +20 -16
- package/auth/routes.js +12 -8
- package/load_plugins.js +14 -1
- package/locales/en.json +29 -1
- package/locales/si.json +1197 -0
- package/package.json +8 -8
- package/public/diagram_utils.js +21 -1
- package/public/relationship_diagram_utils.js +32 -10
- package/public/saltcorn-common.js +40 -19
- package/public/saltcorn.js +11 -5
- package/routes/admin.js +2 -0
- package/routes/common_lists.js +34 -19
- package/routes/diagram.js +214 -199
- package/routes/fields.js +113 -6
- package/routes/index.js +2 -0
- package/routes/models.js +492 -0
- package/routes/page.js +10 -6
- package/routes/tables.js +105 -47
- package/routes/view.js +10 -6
- package/routes/viewedit.js +27 -5
- package/tests/plugins.test.js +103 -2
- package/tests/view.test.js +217 -0
- package/public/vis-network.min.js +0 -49
package/routes/tables.js
CHANGED
|
@@ -11,6 +11,7 @@ const Table = require("@saltcorn/data/models/table");
|
|
|
11
11
|
const File = require("@saltcorn/data/models/file");
|
|
12
12
|
const View = require("@saltcorn/data/models/view");
|
|
13
13
|
const User = require("@saltcorn/data/models/user");
|
|
14
|
+
const Model = require("@saltcorn/data/models/model");
|
|
14
15
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
15
16
|
const {
|
|
16
17
|
mkTable,
|
|
@@ -54,7 +55,7 @@ const {
|
|
|
54
55
|
} = require("@saltcorn/data/models/discovery");
|
|
55
56
|
const { getState } = require("@saltcorn/data/db/state");
|
|
56
57
|
const { cardHeaderTabs } = require("@saltcorn/markup/layout_utils");
|
|
57
|
-
const { tablesList } = require("./common_lists");
|
|
58
|
+
const { tablesList, viewsList } = require("./common_lists");
|
|
58
59
|
const {
|
|
59
60
|
InvalidConfiguration,
|
|
60
61
|
removeAllWhiteSpace,
|
|
@@ -463,7 +464,7 @@ const navigationPanel = () =>
|
|
|
463
464
|
button(
|
|
464
465
|
{
|
|
465
466
|
class: "btn btn-primary er-up",
|
|
466
|
-
onclick: "erHelper.translateY(
|
|
467
|
+
onclick: "erHelper.translateY(100)",
|
|
467
468
|
},
|
|
468
469
|
i({ class: "fas fa-chevron-up" })
|
|
469
470
|
),
|
|
@@ -477,7 +478,7 @@ const navigationPanel = () =>
|
|
|
477
478
|
button(
|
|
478
479
|
{
|
|
479
480
|
class: "btn btn-primary er-left",
|
|
480
|
-
onclick: "erHelper.translateX(
|
|
481
|
+
onclick: "erHelper.translateX(100)",
|
|
481
482
|
},
|
|
482
483
|
i({ class: "fas fa-chevron-left" })
|
|
483
484
|
),
|
|
@@ -488,14 +489,14 @@ const navigationPanel = () =>
|
|
|
488
489
|
button(
|
|
489
490
|
{
|
|
490
491
|
class: "btn btn-primary er-right",
|
|
491
|
-
onclick: "erHelper.translateX(100)",
|
|
492
|
+
onclick: "erHelper.translateX(-100)",
|
|
492
493
|
},
|
|
493
494
|
i({ class: "fas fa-chevron-right" })
|
|
494
495
|
),
|
|
495
496
|
button(
|
|
496
497
|
{
|
|
497
498
|
class: "btn btn-primary er-down",
|
|
498
|
-
onclick: "erHelper.translateY(100)",
|
|
499
|
+
onclick: "erHelper.translateY(-100)",
|
|
499
500
|
},
|
|
500
501
|
i({ class: "fas fa-chevron-down" })
|
|
501
502
|
),
|
|
@@ -542,11 +543,30 @@ router.get(
|
|
|
542
543
|
{
|
|
543
544
|
headerTag: `
|
|
544
545
|
<script type="module">
|
|
545
|
-
mermaid.initialize({
|
|
546
|
+
mermaid.initialize({
|
|
547
|
+
startOnLoad: false,
|
|
548
|
+
securityLevel: 'loose',
|
|
549
|
+
});
|
|
546
550
|
await mermaid.run({
|
|
547
551
|
querySelector: ".mermaid",
|
|
548
552
|
postRenderCallback: (id) => {
|
|
549
553
|
$("#" + id).css("height", "calc(100vh - 250px)");
|
|
554
|
+
$("#" + id + " > g").each(function(index) {
|
|
555
|
+
const jThis = $(this);
|
|
556
|
+
const id = jThis.attr("id");
|
|
557
|
+
if (id) {
|
|
558
|
+
const arr = /^entity-(.+)-(\\w+-\\w+-\\w+-\\w+-\\w+$)/.exec(id);
|
|
559
|
+
if (arr?.length === 3) {
|
|
560
|
+
const textEnt = $("#text-entity-" + arr[1] + "-" + arr[2]);
|
|
561
|
+
textEnt.css("cursor", "pointer");
|
|
562
|
+
textEnt.on("click", function () {
|
|
563
|
+
if (!erHelper.isTranslating()) {
|
|
564
|
+
window.open("/table/" + encodeURIComponent(this.innerHTML));
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
});
|
|
550
570
|
}
|
|
551
571
|
});
|
|
552
572
|
</script>`,
|
|
@@ -573,17 +593,30 @@ router.get(
|
|
|
573
593
|
contents: [
|
|
574
594
|
div(
|
|
575
595
|
{
|
|
596
|
+
id: "erd-wrapper",
|
|
576
597
|
style: "height: calc(100vh - 250px);",
|
|
577
598
|
class: "overflow-scroll position-relative",
|
|
578
599
|
},
|
|
579
600
|
screenshotPanel(),
|
|
580
601
|
pre(
|
|
581
|
-
{
|
|
602
|
+
{
|
|
603
|
+
class: "mermaid",
|
|
604
|
+
style: "height: calc(100vh - 250px); color: transparent;",
|
|
605
|
+
},
|
|
582
606
|
buildMermaidMarkup(tables)
|
|
583
607
|
),
|
|
584
608
|
navigationPanel()
|
|
585
609
|
),
|
|
586
610
|
script({ src: "/relationship_diagram_utils.js" }),
|
|
611
|
+
script(
|
|
612
|
+
domReady(`
|
|
613
|
+
const erdWrapper = $("#erd-wrapper")[0];
|
|
614
|
+
erdWrapper.onwheel = erHelper.onWheel;
|
|
615
|
+
erdWrapper.onmousedown = erHelper.onMouseDown;
|
|
616
|
+
erdWrapper.onmouseup = erHelper.onMouseUp;
|
|
617
|
+
window.addEventListener("mousemove", erHelper.onMouseMove);
|
|
618
|
+
`)
|
|
619
|
+
),
|
|
587
620
|
],
|
|
588
621
|
},
|
|
589
622
|
],
|
|
@@ -622,7 +655,14 @@ const attribBadges = (f) => {
|
|
|
622
655
|
let s = "";
|
|
623
656
|
if (f.attributes) {
|
|
624
657
|
Object.entries(f.attributes).forEach(([k, v]) => {
|
|
625
|
-
if (
|
|
658
|
+
if (
|
|
659
|
+
[
|
|
660
|
+
"summary_field",
|
|
661
|
+
"on_delete_cascade",
|
|
662
|
+
"on_delete",
|
|
663
|
+
"unique_error_msg",
|
|
664
|
+
].includes(k)
|
|
665
|
+
)
|
|
626
666
|
return;
|
|
627
667
|
if (v || v === 0) s += badge("secondary", k);
|
|
628
668
|
});
|
|
@@ -753,37 +793,10 @@ router.get(
|
|
|
753
793
|
);
|
|
754
794
|
var viewCardContents;
|
|
755
795
|
if (views.length > 0) {
|
|
756
|
-
viewCardContents =
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
key: (r) => link(`/view/${encodeURIComponent(r.name)}`, r.name),
|
|
761
|
-
},
|
|
762
|
-
{ label: req.__("Pattern"), key: "viewtemplate" },
|
|
763
|
-
{
|
|
764
|
-
label: req.__("Configure"),
|
|
765
|
-
key: (r) =>
|
|
766
|
-
link(
|
|
767
|
-
`/viewedit/config/${encodeURIComponent(
|
|
768
|
-
r.name
|
|
769
|
-
)}?on_done_redirect=${encodeURIComponent(
|
|
770
|
-
`table/${table.name}`
|
|
771
|
-
)}`,
|
|
772
|
-
req.__("Configure")
|
|
773
|
-
),
|
|
774
|
-
},
|
|
775
|
-
{
|
|
776
|
-
label: req.__("Delete"),
|
|
777
|
-
key: (r) =>
|
|
778
|
-
post_delete_btn(
|
|
779
|
-
`/viewedit/delete/${encodeURIComponent(r.id)}`,
|
|
780
|
-
req
|
|
781
|
-
),
|
|
782
|
-
},
|
|
783
|
-
],
|
|
784
|
-
views,
|
|
785
|
-
{ hover: true }
|
|
786
|
-
);
|
|
796
|
+
viewCardContents = await viewsList(views, req, {
|
|
797
|
+
on_done_redirect: encodeURIComponent(`table/${table.name}`),
|
|
798
|
+
notable: true,
|
|
799
|
+
});
|
|
787
800
|
} else {
|
|
788
801
|
viewCardContents = div(
|
|
789
802
|
h4(req.__("No views defined")),
|
|
@@ -806,6 +819,33 @@ router.get(
|
|
|
806
819
|
),
|
|
807
820
|
};
|
|
808
821
|
}
|
|
822
|
+
const models = await Model.find({ table_id: table.id });
|
|
823
|
+
const modelCard = div(
|
|
824
|
+
mkTable(
|
|
825
|
+
[
|
|
826
|
+
{
|
|
827
|
+
label: req.__("Name"),
|
|
828
|
+
key: (r) => link(`/models/show/${r.id}`, r.name),
|
|
829
|
+
},
|
|
830
|
+
{ label: req.__("Pattern"), key: "modelpattern" },
|
|
831
|
+
{
|
|
832
|
+
label: req.__("Delete"),
|
|
833
|
+
key: (r) =>
|
|
834
|
+
post_delete_btn(
|
|
835
|
+
`/models/delete/${encodeURIComponent(r.id)}`,
|
|
836
|
+
req
|
|
837
|
+
),
|
|
838
|
+
},
|
|
839
|
+
],
|
|
840
|
+
models
|
|
841
|
+
),
|
|
842
|
+
a(
|
|
843
|
+
{ href: `/models/new/${table.id}`, class: "btn btn-primary" },
|
|
844
|
+
i({ class: "fas fa-plus-square me-1" }),
|
|
845
|
+
req.__("Create model")
|
|
846
|
+
)
|
|
847
|
+
);
|
|
848
|
+
|
|
809
849
|
// Table Data card
|
|
810
850
|
const dataCard = div(
|
|
811
851
|
{ class: "d-flex text-center" },
|
|
@@ -965,6 +1005,15 @@ router.get(
|
|
|
965
1005
|
titleAjaxIndicator: true,
|
|
966
1006
|
contents: renderForm(tblForm, req.csrfToken()),
|
|
967
1007
|
},
|
|
1008
|
+
...(Model.has_templates
|
|
1009
|
+
? [
|
|
1010
|
+
{
|
|
1011
|
+
type: "card",
|
|
1012
|
+
title: req.__("Models"),
|
|
1013
|
+
contents: modelCard,
|
|
1014
|
+
},
|
|
1015
|
+
]
|
|
1016
|
+
: []),
|
|
968
1017
|
],
|
|
969
1018
|
});
|
|
970
1019
|
})
|
|
@@ -1357,11 +1406,19 @@ const constraintForm = (req, table_id, fields, type) => {
|
|
|
1357
1406
|
blurb: req.__(
|
|
1358
1407
|
"Tick the boxes for the fields that should be jointly unique"
|
|
1359
1408
|
),
|
|
1360
|
-
fields:
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1409
|
+
fields: [
|
|
1410
|
+
...fields.map((f) => ({
|
|
1411
|
+
name: f.name,
|
|
1412
|
+
label: f.label,
|
|
1413
|
+
type: "Bool",
|
|
1414
|
+
})),
|
|
1415
|
+
{
|
|
1416
|
+
name: "errormsg",
|
|
1417
|
+
label: "Error message",
|
|
1418
|
+
sublabel: "Shown the user if joint uniqueness is violated",
|
|
1419
|
+
type: "String",
|
|
1420
|
+
},
|
|
1421
|
+
],
|
|
1365
1422
|
});
|
|
1366
1423
|
case "Index":
|
|
1367
1424
|
return new Form({
|
|
@@ -1453,11 +1510,12 @@ router.post(
|
|
|
1453
1510
|
if (form.hasErrors) req.flash("error", req.__("An error occurred"));
|
|
1454
1511
|
else {
|
|
1455
1512
|
let configuration = {};
|
|
1456
|
-
if (type === "Unique")
|
|
1513
|
+
if (type === "Unique") {
|
|
1457
1514
|
configuration.fields = fields
|
|
1458
1515
|
.map((f) => f.name)
|
|
1459
1516
|
.filter((f) => form.values[f]);
|
|
1460
|
-
|
|
1517
|
+
configuration.errormsg = form.values.errormsg;
|
|
1518
|
+
} else configuration = form.values;
|
|
1461
1519
|
await TableConstraint.create({
|
|
1462
1520
|
table_id: table.id,
|
|
1463
1521
|
type,
|
|
@@ -1841,7 +1899,7 @@ const get_provider_workflow = (table, req) => {
|
|
|
1841
1899
|
|
|
1842
1900
|
return {
|
|
1843
1901
|
redirect: `/table/${table.id}`,
|
|
1844
|
-
flash: ["success", `Table ${
|
|
1902
|
+
flash: ["success", `Table ${table.name || ""} saved`],
|
|
1845
1903
|
};
|
|
1846
1904
|
};
|
|
1847
1905
|
return workflow;
|
package/routes/view.js
CHANGED
|
@@ -8,6 +8,7 @@ const Router = require("express-promise-router");
|
|
|
8
8
|
|
|
9
9
|
const View = require("@saltcorn/data/models/view");
|
|
10
10
|
const Table = require("@saltcorn/data/models/table");
|
|
11
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
11
12
|
|
|
12
13
|
const { text, style } = require("@saltcorn/markup/tags");
|
|
13
14
|
const {
|
|
@@ -51,7 +52,7 @@ router.get(
|
|
|
51
52
|
res.redirect("/");
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
|
-
const tic =
|
|
55
|
+
const tic = new Date();
|
|
55
56
|
|
|
56
57
|
view.rewrite_query_from_slug(query, req.params);
|
|
57
58
|
if (
|
|
@@ -85,11 +86,14 @@ router.get(
|
|
|
85
86
|
res.set("SaltcornModalSaveIndicator", `true`);
|
|
86
87
|
if (isModal && view.attributes?.popup_link_out)
|
|
87
88
|
res.set("SaltcornModalLinkOut", `true`);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
const tock = new Date();
|
|
90
|
+
const ms = tock.getTime() - tic.getTime();
|
|
91
|
+
Trigger.emitEvent("PageLoad", null, req.user, {
|
|
92
|
+
text: req.__("View '%s' was loaded", viewname),
|
|
93
|
+
type: "view",
|
|
94
|
+
name: viewname,
|
|
95
|
+
render_time: ms,
|
|
96
|
+
});
|
|
93
97
|
if (typeof contents === "object" && contents.goto)
|
|
94
98
|
res.redirect(contents.goto);
|
|
95
99
|
else
|
package/routes/viewedit.js
CHANGED
|
@@ -13,7 +13,12 @@ const { p, a, div, script, text, domReady, code, pre, tbody, tr, th, td } =
|
|
|
13
13
|
tags;
|
|
14
14
|
|
|
15
15
|
const { getState } = require("@saltcorn/data/db/state");
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
isAdmin,
|
|
18
|
+
error_catcher,
|
|
19
|
+
addOnDoneRedirect,
|
|
20
|
+
is_relative_url,
|
|
21
|
+
} = require("./utils.js");
|
|
17
22
|
const { setTableRefs, viewsList } = require("./common_lists");
|
|
18
23
|
const Form = require("@saltcorn/data/models/form");
|
|
19
24
|
const Field = require("@saltcorn/data/models/field");
|
|
@@ -675,7 +680,11 @@ router.post(
|
|
|
675
680
|
view.name
|
|
676
681
|
)
|
|
677
682
|
);
|
|
678
|
-
|
|
683
|
+
let redirectTarget =
|
|
684
|
+
req.query.on_done_redirect && is_relative_url(req.query.on_done_redirect)
|
|
685
|
+
? `/${req.query.on_done_redirect}`
|
|
686
|
+
: "/viewedit";
|
|
687
|
+
res.redirect(redirectTarget);
|
|
679
688
|
})
|
|
680
689
|
);
|
|
681
690
|
|
|
@@ -696,7 +705,11 @@ router.post(
|
|
|
696
705
|
"success",
|
|
697
706
|
req.__("View %s duplicated as %s", view.name, newview.name)
|
|
698
707
|
);
|
|
699
|
-
|
|
708
|
+
let redirectTarget =
|
|
709
|
+
req.query.on_done_redirect && is_relative_url(req.query.on_done_redirect)
|
|
710
|
+
? `/${req.query.on_done_redirect}`
|
|
711
|
+
: "/viewedit";
|
|
712
|
+
res.redirect(redirectTarget);
|
|
700
713
|
})
|
|
701
714
|
);
|
|
702
715
|
|
|
@@ -713,7 +726,11 @@ router.post(
|
|
|
713
726
|
const { id } = req.params;
|
|
714
727
|
await View.delete({ id });
|
|
715
728
|
req.flash("success", req.__("View deleted"));
|
|
716
|
-
|
|
729
|
+
let redirectTarget =
|
|
730
|
+
req.query.on_done_redirect && is_relative_url(req.query.on_done_redirect)
|
|
731
|
+
? `/${req.query.on_done_redirect}`
|
|
732
|
+
: "/viewedit";
|
|
733
|
+
res.redirect(redirectTarget);
|
|
717
734
|
})
|
|
718
735
|
);
|
|
719
736
|
|
|
@@ -805,7 +822,12 @@ router.post(
|
|
|
805
822
|
: req.__(`Minimum role updated`);
|
|
806
823
|
if (!req.xhr) {
|
|
807
824
|
req.flash("success", message);
|
|
808
|
-
|
|
825
|
+
let redirectTarget =
|
|
826
|
+
req.query.on_done_redirect &&
|
|
827
|
+
is_relative_url(req.query.on_done_redirect)
|
|
828
|
+
? `/${req.query.on_done_redirect}`
|
|
829
|
+
: "/viewedit";
|
|
830
|
+
res.redirect(redirectTarget);
|
|
809
831
|
} else res.json({ okay: true, responseText: message });
|
|
810
832
|
})
|
|
811
833
|
);
|
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
|
+
});
|