@saltcorn/server 0.8.7-beta.6 → 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 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/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.8.7-beta.6",
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-beta.6",
10
- "@saltcorn/builder": "0.8.7-beta.6",
11
- "@saltcorn/data": "0.8.7-beta.6",
12
- "@saltcorn/admin-models": "0.8.7-beta.6",
13
- "@saltcorn/filemanager": "0.8.7-beta.6",
14
- "@saltcorn/markup": "0.8.7-beta.6",
15
- "@saltcorn/sbadmin2": "0.8.7-beta.6",
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 DOMURL = self.URL || self.webkitURL || self;
67
- const blob = new Blob([new XMLSerializer().serializeToString(svg)], {
68
- type: "image/svg+xml;charset=utf-8",
69
- });
70
- const url = DOMURL.createObjectURL(blob);
71
- window.open(url);
72
- DOMURL.revokeObjectURL(url);
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/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(-100)",
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(-100)",
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({ startOnLoad: false });
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
- { class: "mermaid", style: "height: calc(100vh - 250px);" },
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
  ],
@@ -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
+ });