@saltcorn/server 0.7.3-beta.3 → 0.7.3

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/routes/files.js CHANGED
@@ -184,11 +184,11 @@ router.get(
184
184
  * @function
185
185
  */
186
186
  router.get(
187
- "/resize/:id/:width_str",
187
+ "/resize/:id/:width_str/:height_str?",
188
188
  error_catcher(async (req, res) => {
189
189
  const role = req.user && req.user.id ? req.user.role_id : 10;
190
190
  const user_id = req.user && req.user.id;
191
- const { id, width_str } = req.params;
191
+ const { id, width_str, height_str } = req.params;
192
192
  let file;
193
193
  if (typeof strictParseInt(id) !== "undefined")
194
194
  file = await File.findOne({ id });
@@ -208,15 +208,17 @@ router.get(
208
208
  if (file.s3_store) s3storage.serveObject(file, res, false);
209
209
  else {
210
210
  const width = strictParseInt(width_str);
211
+ const height = height_str ? strictParseInt(height_str) : null;
211
212
  if (!width) {
212
213
  res.sendFile(file.location);
213
214
  return;
214
215
  }
215
- const fnm = `${file.location}_w${width}`;
216
+ const fnm = `${file.location}_w${width}${height ? `_h${height}` : ""}`;
216
217
  if (!fs.existsSync(fnm)) {
217
218
  await resizer({
218
219
  fromFileName: file.location,
219
220
  width,
221
+ height,
220
222
  toFileName: fnm,
221
223
  });
222
224
  }
@@ -376,9 +378,6 @@ const storage_form = async (req) => {
376
378
  ],
377
379
  action: "/files/storage",
378
380
  });
379
- form.submitButtonClass = "btn-outline-primary";
380
- form.submitLabel = req.__("Save");
381
- form.onChange = "remove_outline(this)";
382
381
  return form;
383
382
  };
384
383
 
@@ -429,8 +428,11 @@ router.post(
429
428
  });
430
429
  } else {
431
430
  await save_config_from_form(form);
432
- req.flash("success", req.__("Storage settings updated"));
433
- res.redirect("/files/storage");
431
+
432
+ if (!req.xhr) {
433
+ req.flash("success", req.__("Storage settings updated"));
434
+ res.redirect("/files/storage");
435
+ } else res.json({ success: "ok" });
434
436
  }
435
437
  })
436
438
  );
@@ -49,7 +49,7 @@ const tableTable = (tables, req) =>
49
49
  */
50
50
  const tableCard = (tables, req) => ({
51
51
  type: "card",
52
- class: "welcome-page-entity-list",
52
+ class: "welcome-page-entity-list mt-1",
53
53
  title: link("/table", req.__("Tables")),
54
54
  contents:
55
55
  (tables.length <= 1
@@ -102,7 +102,7 @@ const viewTable = (views, req) =>
102
102
  const viewCard = (views, req) => ({
103
103
  type: "card",
104
104
  title: link("/viewedit", req.__("Views")),
105
- class: "welcome-page-entity-list",
105
+ class: "welcome-page-entity-list mt-1",
106
106
  bodyClass: "py-0 pe-0",
107
107
  contents:
108
108
  (views.length <= 1
@@ -156,7 +156,7 @@ const pageTable = (pages, req) =>
156
156
  const pageCard = (pages, req) => ({
157
157
  type: "card",
158
158
  title: link("/pageedit", req.__("Pages")),
159
- class: "welcome-page-entity-list",
159
+ class: "welcome-page-entity-list mt-1",
160
160
  contents:
161
161
  (pages.length <= 1
162
162
  ? p(
@@ -369,9 +369,9 @@ const welcome_page = async (req) => {
369
369
  above: [
370
370
  {
371
371
  besides: [
372
- pageCard(pages, req),
373
- viewCard(views, req),
374
372
  tableCard(tables, req),
373
+ viewCard(views, req),
374
+ pageCard(pages, req),
375
375
  ],
376
376
  },
377
377
  {
@@ -380,7 +380,7 @@ const welcome_page = async (req) => {
380
380
  type: "card",
381
381
  //title: req.__("Install pack"),
382
382
  bodyClass: "py-0 pe-0",
383
- class: "welcome-page-entity-list",
383
+ class: "welcome-page-entity-list mt-2",
384
384
 
385
385
  tabContents:
386
386
  triggers.length > 0
@@ -399,7 +399,7 @@ const welcome_page = async (req) => {
399
399
  type: "card",
400
400
  //title: req.__("Learn"),
401
401
  bodyClass: "py-0 pe-0",
402
- class: "welcome-page-entity-list",
402
+ class: "welcome-page-entity-list mt-2",
403
403
  tabContents:
404
404
  users.length > 4
405
405
  ? {
@@ -48,8 +48,8 @@ router.get(
48
48
  const languageForm = (req) =>
49
49
  new Form({
50
50
  action: "/site-structure/localizer/save-lang",
51
- submitButtonClass: "btn-outline-primary",
52
- onChange: "remove_outline(this)",
51
+ onChange: "saveAndContinue(this)",
52
+ noSubmitButton: true,
53
53
  fields: [
54
54
  {
55
55
  name: "name",
@@ -270,7 +270,10 @@ router.post(
270
270
  ...cfgLangs,
271
271
  [lang.locale]: lang,
272
272
  });
273
- res.redirect(`/site-structure/localizer/edit/${lang.locale}`);
273
+
274
+ if (!req.xhr)
275
+ res.redirect(`/site-structure/localizer/edit/${lang.locale}`);
276
+ else res.json({ success: "ok" });
274
277
  }
275
278
  })
276
279
  );
@@ -326,6 +326,7 @@ router.get(
326
326
  {
327
327
  type: "card",
328
328
  title: req.__("Your pages"),
329
+ class: "mt-0",
329
330
  contents: getPageList(pages, roles, req),
330
331
  },
331
332
  {
package/routes/plugins.js CHANGED
@@ -506,6 +506,7 @@ const plugin_store_html = (items, req) => {
506
506
  },
507
507
  {
508
508
  type: "card",
509
+ class: "mt-0",
509
510
  contents: div(
510
511
  { class: "d-flex justify-content-between" },
511
512
  storeNavPills(req),
@@ -560,12 +561,30 @@ router.get(
560
561
  }
561
562
  const flow = module.configuration_workflow();
562
563
  flow.action = `/plugins/configure/${encodeURIComponent(plugin.name)}`;
564
+ flow.autoSave = true;
565
+ flow.saveURL = `/plugins/saveconfig/${encodeURIComponent(plugin.name)}`;
563
566
  const wfres = await flow.run(plugin.configuration || {});
567
+ if (module.layout) {
568
+ wfres.renderForm.additionalButtons = [
569
+ ...(wfres.renderForm.additionalButtons || []),
570
+ {
571
+ label: "Reload page to see changes",
572
+ id: "btnReloadNow",
573
+ class: "btn btn-outline-secondary",
574
+ onclick: "location.reload()",
575
+ },
576
+ ];
577
+ wfres.renderForm.onChange = `${
578
+ wfres.renderForm.onChange || ""
579
+ };$('#btnReloadNow').removeClass('btn-outline-secondary').addClass('btn-secondary')`;
580
+ }
564
581
 
565
- res.sendWrap(
566
- req.__(`Configure %s Plugin`, plugin.name),
567
- renderForm(wfres.renderForm, req.csrfToken())
568
- );
582
+ res.sendWrap(req.__(`Configure %s Plugin`, plugin.name), {
583
+ type: "card",
584
+ class: "mt-0",
585
+ title: req.__(`Configure %s Plugin`, plugin.name),
586
+ contents: renderForm(wfres.renderForm, req.csrfToken()),
587
+ });
569
588
  })
570
589
  );
571
590
 
@@ -587,13 +606,31 @@ router.post(
587
606
  }
588
607
  const flow = module.configuration_workflow();
589
608
  flow.action = `/plugins/configure/${encodeURIComponent(plugin.name)}`;
609
+ flow.autoSave = true;
610
+ flow.saveURL = `/plugins/saveconfig/${encodeURIComponent(plugin.name)}`;
590
611
  const wfres = await flow.run(req.body);
591
- if (wfres.renderForm)
592
- res.sendWrap(
593
- req.__(`Configure %s Plugin`, plugin.name),
594
- renderForm(wfres.renderForm, req.csrfToken())
595
- );
596
- else {
612
+ if (wfres.renderForm) {
613
+ if (module.layout) {
614
+ wfres.renderForm.additionalButtons = [
615
+ ...(wfres.renderForm.additionalButtons || []),
616
+ {
617
+ label: "Reload page to see changes",
618
+ id: "btnReloadNow",
619
+ class: "btn btn-outline-secondary",
620
+ onclick: "location.reload()",
621
+ },
622
+ ];
623
+ wfres.renderForm.onChange = `${
624
+ wfres.renderForm.onChange || ""
625
+ };$('#btnReloadNow').removeClass('btn-outline-secondary').addClass('btn-secondary')`;
626
+ }
627
+ res.sendWrap(req.__(`Configure %s Plugin`, plugin.name), {
628
+ type: "card",
629
+ class: "mt-0",
630
+ title: req.__(`Configure %s Plugin`, plugin.name),
631
+ contents: renderForm(wfres.renderForm, req.csrfToken()),
632
+ });
633
+ } else {
597
634
  plugin.configuration = wfres;
598
635
  await plugin.upsert();
599
636
  await load_plugins.loadPlugin(plugin);
@@ -605,12 +642,42 @@ router.post(
605
642
  refresh_plugin_cfg: plugin.name,
606
643
  tenant: db.getTenantSchema(),
607
644
  });
608
- await sleep(500); // Allow other workers to reload this plugin
645
+ if (module.layout) await sleep(500); // Allow other workers to reload this plugin
609
646
  res.redirect("/plugins");
610
647
  }
611
648
  })
612
649
  );
613
650
 
651
+ router.post(
652
+ "/saveconfig/:name",
653
+ isAdmin,
654
+ error_catcher(async (req, res) => {
655
+ const { name } = req.params;
656
+ const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
657
+ let module = getState().plugins[plugin.name];
658
+ if (!module) {
659
+ module = getState().plugins[getState().plugin_module_names[plugin.name]];
660
+ }
661
+ const flow = module.configuration_workflow();
662
+ const step = await flow.singleStepForm(req.body, req);
663
+ if (step?.renderForm) {
664
+ if (!step.renderForm.hasErrors) {
665
+ plugin.configuration = {
666
+ ...plugin.configuration,
667
+ ...step.renderForm.values,
668
+ };
669
+ await plugin.upsert();
670
+ await load_plugins.loadPlugin(plugin);
671
+ process.send &&
672
+ process.send({
673
+ refresh_plugin_cfg: plugin.name,
674
+ tenant: db.getTenantSchema(),
675
+ });
676
+ res.json({ success: "ok" });
677
+ }
678
+ }
679
+ })
680
+ );
614
681
  /**
615
682
  * @name get/new
616
683
  * @function
package/routes/search.js CHANGED
@@ -55,7 +55,8 @@ const searchConfigForm = (tables, views, req) => {
55
55
  );
56
56
  return new Form({
57
57
  action: "/search/config",
58
- submitLabel: req.__("Save"),
58
+ noSubmitButton: true,
59
+ onChange: `saveAndContinue(this)`,
59
60
  blurb:
60
61
  blurb1 +
61
62
  (tbls_noviews.length > 0
@@ -111,7 +112,8 @@ router.post(
111
112
 
112
113
  if (result.success) {
113
114
  await getState().setConfig("globalSearch", result.success);
114
- res.redirect("/search/config");
115
+ if (!req.xhr) res.redirect("/search/config");
116
+ else res.json({ success: "ok" });
115
117
  } else {
116
118
  send_infoarch_page({
117
119
  res,
package/routes/tables.js CHANGED
@@ -83,8 +83,8 @@ const tableForm = async (table, req) => {
83
83
  .map((f) => ({ value: f.id, label: f.name }));
84
84
  const form = new Form({
85
85
  action: "/table",
86
- submitButtonClass: "btn-outline-primary",
87
- onChange: "remove_outline(this)",
86
+ noSubmitButton: true,
87
+ onChange: "saveAndContinue(this)",
88
88
  fields: [
89
89
  ...(!table.external
90
90
  ? [
@@ -448,6 +448,7 @@ router.get(
448
448
  },
449
449
  {
450
450
  type: "card",
451
+ class: "mt-0",
451
452
  title: cardHeaderTabs([
452
453
  { label: req.__("Your tables"), href: "/table" },
453
454
  {
@@ -636,7 +637,9 @@ router.get(
636
637
  }
637
638
  var viewCard;
638
639
  if (fields.length > 0) {
639
- const views = await View.find({ table_id: table.id });
640
+ const views = await View.find(
641
+ table.external ? { exttable_name: table.name } : { table_id: table.id }
642
+ );
640
643
  var viewCardContents;
641
644
  if (views.length > 0) {
642
645
  viewCardContents = mkTable(
@@ -801,15 +804,12 @@ router.get(
801
804
  type: "breadcrumbs",
802
805
  crumbs: [
803
806
  { text: req.__("Tables"), href: "/table" },
804
- { text: table.name },
807
+ { text: span({ class: "fw-bold text-body" }, table.name) },
805
808
  ],
806
809
  },
807
- {
808
- type: "pageHeader",
809
- title: req.__(`%s table`, table.name),
810
- },
811
810
  {
812
811
  type: "card",
812
+ class: "mt-0",
813
813
  title: req.__("Fields"),
814
814
  contents: fieldCard,
815
815
  },
@@ -910,7 +910,8 @@ router.post(
910
910
  );
911
911
  else if (!hasError) req.flash("success", req.__("Table saved"));
912
912
 
913
- res.redirect(`/table/${id}`);
913
+ if (!req.xhr) res.redirect(`/table/${id}`);
914
+ else res.json({ success: "ok" });
914
915
  }
915
916
  })
916
917
  );
@@ -1076,6 +1077,7 @@ router.get(
1076
1077
  },
1077
1078
  {
1078
1079
  type: "card",
1080
+ class: "mt-0",
1079
1081
  title: cardHeaderTabs([
1080
1082
  { label: req.__("Your tables"), href: "/table", active: true },
1081
1083
  {
package/routes/tenant.js CHANGED
@@ -452,8 +452,10 @@ router.post(
452
452
  } else {
453
453
  await save_config_from_form(form);
454
454
 
455
- req.flash("success", req.__("Tenant settings updated"));
456
- res.redirect("/tenant/settings");
455
+ if (!req.xhr) {
456
+ req.flash("success", req.__("Tenant settings updated"));
457
+ res.redirect("/tenant/settings");
458
+ } else res.json({ success: "ok" });
457
459
  }
458
460
  })
459
461
  );
@@ -197,6 +197,7 @@ router.get(
197
197
  },
198
198
  {
199
199
  type: "card",
200
+ class: "mt-0",
200
201
  title: req.__("Your views"),
201
202
  contents: [
202
203
  viewMarkup,
@@ -378,7 +379,13 @@ router.get(
378
379
  },
379
380
  {
380
381
  type: "card",
381
- title: req.__(`Edit %s view`, viewname),
382
+ class: "mt-0",
383
+ title: req.__(
384
+ `%s view - %s on %s`,
385
+ viewname,
386
+ viewrow.viewtemplate,
387
+ viewrow.table_name
388
+ ),
382
389
  contents: renderForm(form, req.csrfToken()),
383
390
  },
384
391
  ],
@@ -415,6 +422,7 @@ router.get(
415
422
  },
416
423
  {
417
424
  type: "card",
425
+ class: "mt-0",
418
426
  title: req.__(`Create view`),
419
427
  contents: renderForm(form, req.csrfToken()),
420
428
  },
@@ -452,6 +460,7 @@ router.post(
452
460
  },
453
461
  {
454
462
  type: "card",
463
+ class: "mt-0",
455
464
  title: req.__(`Edit view`),
456
465
  contents: renderForm(form, req.csrfToken()),
457
466
  },
@@ -527,6 +536,7 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
527
536
  },
528
537
  {
529
538
  type: noCard ? "container" : "card",
539
+ class: !noCard && "mt-0",
530
540
  title: wfres.title,
531
541
  contents,
532
542
  },
@@ -574,7 +584,7 @@ router.get(
574
584
  isAdmin,
575
585
  error_catcher(async (req, res) => {
576
586
  const { name } = req.params;
577
-
587
+ const { step } = req.query;
578
588
  const view = await View.findOne({ name });
579
589
  if (!view) {
580
590
  req.flash("error", `View not found: ${text(name)}`);
@@ -591,6 +601,7 @@ router.get(
591
601
  table_id: view.table_id,
592
602
  exttable_name: view.exttable_name,
593
603
  viewname: name,
604
+ ...(step ? { stepName: step } : {}),
594
605
  },
595
606
  req
596
607
  );
@@ -10,6 +10,7 @@ const {
10
10
  toInclude,
11
11
  toNotInclude,
12
12
  } = require("../auth/testhelp");
13
+ const { getState } = require("@saltcorn/data/db/state");
13
14
 
14
15
  afterAll(db.close);
15
16
 
@@ -24,6 +25,8 @@ describe("tenant routes", () => {
24
25
  if (!db.isSQLite) {
25
26
  it("shows create form", async () => {
26
27
  db.enable_multi_tenant();
28
+ await getState().setConfig("role_to_create_tenant", "10");
29
+
27
30
  const app = await getApp({ disableCsrf: true });
28
31
  await request(app).get("/tenant/create").expect(toInclude("subdomain"));
29
32
  });
@@ -37,6 +40,8 @@ describe("tenant routes", () => {
37
40
  });
38
41
  it("creates tenant with capital letter", async () => {
39
42
  db.enable_multi_tenant();
43
+ await getState().setConfig("role_to_create_tenant", "10");
44
+
40
45
  const app = await getApp({ disableCsrf: true });
41
46
  await request(app)
42
47
  .post("/tenant/create")
@@ -46,6 +51,7 @@ describe("tenant routes", () => {
46
51
  });
47
52
  it("rejects existing tenant", async () => {
48
53
  db.enable_multi_tenant();
54
+ await getState().setConfig("role_to_create_tenant", "10");
49
55
  const app = await getApp({ disableCsrf: true });
50
56
  await request(app)
51
57
  .post("/tenant/create")