@saltcorn/server 0.9.0-beta.9 → 0.9.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/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.0-beta.9",
3
+ "version": "0.9.0",
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.9.0-beta.9",
10
- "@saltcorn/builder": "0.9.0-beta.9",
11
- "@saltcorn/data": "0.9.0-beta.9",
12
- "@saltcorn/admin-models": "0.9.0-beta.9",
13
- "@saltcorn/filemanager": "0.9.0-beta.9",
14
- "@saltcorn/markup": "0.9.0-beta.9",
15
- "@saltcorn/sbadmin2": "0.9.0-beta.9",
9
+ "@saltcorn/base-plugin": "0.9.0",
10
+ "@saltcorn/builder": "0.9.0",
11
+ "@saltcorn/data": "0.9.0",
12
+ "@saltcorn/admin-models": "0.9.0",
13
+ "@saltcorn/filemanager": "0.9.0",
14
+ "@saltcorn/markup": "0.9.0",
15
+ "@saltcorn/sbadmin2": "0.9.0",
16
16
  "@socket.io/cluster-adapter": "^0.2.1",
17
17
  "@socket.io/sticky": "^1.0.1",
18
18
  "adm-zip": "0.5.10",
@@ -128,10 +128,13 @@ function apply_showif() {
128
128
  const dynwhere = JSON.parse(
129
129
  decodeURIComponent(e.attr("data-fetch-options"))
130
130
  );
131
- //console.log("dynwhere", dynwhere);
132
- const qss = Object.entries(dynwhere.whereParsed).map(
133
- ([k, v]) => `${k}=${v[0] === "$" ? rec[v.substring(1)] : v}`
134
- );
131
+ if (window._sc_loglevel > 4) console.log("dynwhere", dynwhere);
132
+ const kvToQs = ([k, v]) => {
133
+ return k === "or" && Array.isArray(v)
134
+ ? v.map((v1) => Object.entries(v1).map(kvToQs).join("&")).join("&")
135
+ : `${k}=${v[0] === "$" ? rec[v.substring(1)] : v}`;
136
+ };
137
+ const qss = Object.entries(dynwhere.whereParsed).map(kvToQs);
135
138
  if (dynwhere.dereference) {
136
139
  if (Array.isArray(dynwhere.dereference))
137
140
  qss.push(...dynwhere.dereference.map((d) => `dereference=${d}`));
@@ -205,6 +208,9 @@ function apply_showif() {
205
208
  });
206
209
  $.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
207
210
  if (resp.success) {
211
+ if (window._sc_loglevel > 4)
212
+ console.log("dynwhere fetch", qs, resp.success);
213
+
208
214
  activate(resp.success, qs);
209
215
  const cacheNow = e.prop("data-fetch-options-cache") || {};
210
216
  e.prop("data-fetch-options-cache", {
package/routes/actions.js CHANGED
@@ -390,6 +390,16 @@ router.get(
390
390
  return;
391
391
  }
392
392
  const action = getState().actions[trigger.action];
393
+ // get table related to trigger
394
+ const table = trigger.table_id
395
+ ? Table.findOne({ id: trigger.table_id })
396
+ : null;
397
+
398
+ const subtitle = span(
399
+ { class: "ms-3" },
400
+ trigger.action,
401
+ table ? ` on ` + a({ href: `/table/${table.name}` }, table.name) : ""
402
+ );
393
403
  if (!action) {
394
404
  req.flash("warning", req.__("Action not found"));
395
405
  res.redirect(`/actions/`);
@@ -420,6 +430,7 @@ router.get(
420
430
  type: "card",
421
431
  titleAjaxIndicator: true,
422
432
  title: req.__("Configure trigger %s", trigger.name),
433
+ subtitle,
423
434
  contents: {
424
435
  widths: [8, 4],
425
436
  besides: [
@@ -465,10 +476,6 @@ router.get(
465
476
  req.flash("warning", req.__("Action not configurable"));
466
477
  res.redirect(`/actions/`);
467
478
  } else {
468
- // get table related to trigger
469
- const table = trigger.table_id
470
- ? Table.findOne({ id: trigger.table_id })
471
- : null;
472
479
  // get configuration fields
473
480
  const cfgFields = await getActionConfigFields(action, table);
474
481
  // create form
@@ -491,6 +498,7 @@ router.get(
491
498
  type: "card",
492
499
  titleAjaxIndicator: true,
493
500
  title: req.__("Configure trigger %s", trigger.name),
501
+ subtitle,
494
502
  contents: renderForm(form, req.csrfToken()),
495
503
  },
496
504
  });
@@ -607,8 +615,10 @@ router.get(
607
615
  table = Table.findOne({ id: trigger.table_id });
608
616
  row = await table.getRow({});
609
617
  }
618
+ let runres;
619
+
610
620
  try {
611
- await trigger.runWithoutRow({
621
+ runres = await trigger.runWithoutRow({
612
622
  console: fakeConsole,
613
623
  table,
614
624
  row,
@@ -627,7 +637,9 @@ router.get(
627
637
  req.__(
628
638
  "Action %s run successfully with no console output",
629
639
  trigger.action
630
- )
640
+ ) + runres
641
+ ? script(domReady(`common_done(${JSON.stringify(runres)})`))
642
+ : ""
631
643
  );
632
644
  res.redirect(`/actions/`);
633
645
  } else {
@@ -641,7 +653,9 @@ router.get(
641
653
  title: req.__("Test run output"),
642
654
  contents: div(
643
655
  div({ class: "testrunoutput" }, output),
644
-
656
+ runres
657
+ ? script(domReady(`common_done(${JSON.stringify(runres)})`))
658
+ : "",
645
659
  a(
646
660
  { href: `/actions`, class: "mt-4 btn btn-primary me-1" },
647
661
  "« " + req.__("back to actions")
package/routes/page.js CHANGED
@@ -61,6 +61,7 @@ router.get(
61
61
  title,
62
62
  description: db_page.description,
63
63
  bodyClass: "page_" + db.sqlsanitize(pagename),
64
+ no_menu: db_page.attributes?.no_menu,
64
65
  } || `${pagename} page`,
65
66
  add_edit_bar({
66
67
  role,
@@ -92,6 +92,12 @@ const pagePropertiesForm = async (req, isNew) => {
92
92
  input_type: "select",
93
93
  options: roles.map((r) => ({ value: r.id, label: r.role })),
94
94
  },
95
+ {
96
+ name: "no_menu",
97
+ label: req.__("No menu"),
98
+ sublabel: req.__("Omit the menu from this page"),
99
+ type: "Bool",
100
+ },
95
101
  ],
96
102
  });
97
103
  return form;
@@ -315,6 +321,7 @@ router.get(
315
321
  const form = await pagePropertiesForm(req);
316
322
  form.hidden("id");
317
323
  form.values = page;
324
+ form.values.no_menu = page.attributes?.no_menu;
318
325
  res.sendWrap(
319
326
  req.__(`Page attributes`),
320
327
  wrap(renderForm(form, req.csrfToken()), false, req, page)
@@ -360,9 +367,9 @@ router.post(
360
367
  wrap(renderForm(form, req.csrfToken()), false, req)
361
368
  );
362
369
  } else {
363
- const { id, columns, ...pageRow } = form.values;
370
+ const { id, columns, no_menu, ...pageRow } = form.values;
364
371
  pageRow.min_role = +pageRow.min_role;
365
-
372
+ pageRow.attributes = { no_menu };
366
373
  if (+id) {
367
374
  await Page.update(+id, pageRow);
368
375
  res.redirect(`/pageedit/`);
@@ -638,8 +638,8 @@ router.post(
638
638
  newcfg = {
639
639
  ...view.configuration,
640
640
  [step.contextField]: {
641
- ...view.configuration?.[step.contextField],
642
- ...context,
641
+ ...(view.configuration?.[step.contextField] || {}),
642
+ ...(context?.[step.contextField] || {}),
643
643
  },
644
644
  };
645
645
  else newcfg = { ...view.configuration, ...context };
@@ -396,7 +396,7 @@ describe("viewedit new Show", () => {
396
396
  it("delete new view", async () => {
397
397
  const loginCookie = await getAdminLoginCookie();
398
398
  const app = await getApp({ disableCsrf: true });
399
- const id = (await View.findOne({ name: "mybook" })).id;
399
+ const id = View.findOne({ name: "mybook" }).id;
400
400
 
401
401
  await request(app)
402
402
  .post("/viewedit/delete/" + id)
@@ -404,6 +404,249 @@ describe("viewedit new Show", () => {
404
404
  .expect(toRedirect("/viewedit"));
405
405
  });
406
406
  });
407
+
408
+ describe("viewedit new Edit", () => {
409
+ // create two edit views for 'books'
410
+ // the first has inputs for all books-fields
411
+ // => 'Fixed and blocked fields (step 2 / max 3)' won't show up
412
+ // the second has no input for 'pages'
413
+ // => 'Fixed and blocked fields (step 2 / max 3)' shows up
414
+ // and the configration gets a fixed property
415
+ const colsWithoutPages = [
416
+ {
417
+ type: "Field",
418
+ fieldview: "edit",
419
+ field_name: "author",
420
+ },
421
+ {
422
+ type: "Field",
423
+ fieldview: "select",
424
+ field_name: "publisher",
425
+ },
426
+ {
427
+ type: "Action",
428
+ rndid: "f61f38",
429
+ minRole: 100,
430
+ action_name: "Save",
431
+ action_style: "btn-primary",
432
+ },
433
+ ];
434
+ const colsWithPages = [
435
+ {
436
+ type: "Field",
437
+ fieldview: "edit",
438
+ field_name: "pages",
439
+ },
440
+ ...colsWithoutPages,
441
+ ];
442
+
443
+ const layoutWithoutPages = {
444
+ above: [
445
+ {
446
+ type: "field",
447
+ fieldview: "edit",
448
+ field_name: "author",
449
+ },
450
+
451
+ {
452
+ type: "field",
453
+ fieldview: "select",
454
+ field_name: "publisher",
455
+ },
456
+ {
457
+ type: "action",
458
+ rndid: "f61f38",
459
+ minRole: 100,
460
+ action_name: "Save",
461
+ action_style: "btn-primary",
462
+ },
463
+ ],
464
+ };
465
+ const layoutWithPages = {
466
+ above: [
467
+ {
468
+ type: "field",
469
+ fieldview: "edit",
470
+ field_name: "pages",
471
+ },
472
+ ...layoutWithoutPages.above,
473
+ ],
474
+ };
475
+
476
+ it("submits new view", async () => {
477
+ const loginCookie = await getAdminLoginCookie();
478
+ const app = await getApp({ disableCsrf: true });
479
+ // edit_mybook
480
+ await request(app)
481
+ .post("/viewedit/save")
482
+ .send("viewtemplate=Edit")
483
+ .send("table_name=books")
484
+ .send("name=edit_mybook")
485
+ .send("min_role=100")
486
+ .set("Cookie", loginCookie)
487
+ .expect(toRedirect("/viewedit/config/edit_mybook"));
488
+ // edit_mybook_without_pages
489
+ await request(app)
490
+ .post("/viewedit/save")
491
+ .send("viewtemplate=Edit")
492
+ .send("table_name=books")
493
+ .send("name=edit_mybook_without_pages")
494
+ .send("min_role=100")
495
+ .set("Cookie", loginCookie)
496
+ .expect(toRedirect("/viewedit/config/edit_mybook_without_pages"));
497
+ });
498
+
499
+ it("saves new view layout", async () => {
500
+ const loginCookie = await getAdminLoginCookie();
501
+ const table = Table.findOne({ name: "books" });
502
+ const app = await getApp({ disableCsrf: true });
503
+ // edit_mybook
504
+ await request(app)
505
+ .post("/viewedit/config/edit_mybook")
506
+ .send(
507
+ "contextEnc=" +
508
+ encodeURIComponent(
509
+ JSON.stringify({
510
+ table_id: table.id,
511
+ viewname: "edit_mybook",
512
+ })
513
+ )
514
+ )
515
+ .send("stepName=Layout")
516
+ .send("columns=" + encodeURIComponent(JSON.stringify(colsWithPages)))
517
+ .send("layout=" + encodeURIComponent(JSON.stringify(layoutWithPages)))
518
+ .set("Cookie", loginCookie)
519
+ .expect(toInclude("Edit options (step 3 / 3)"));
520
+ // edit_mybook_without_pages
521
+ await request(app)
522
+ .post("/viewedit/config/edit_mybook_without_pages")
523
+ .send(
524
+ "contextEnc=" +
525
+ encodeURIComponent(
526
+ JSON.stringify({
527
+ table_id: table.id,
528
+ viewname: "edit_mybook_without_pages",
529
+ })
530
+ )
531
+ )
532
+ .send("stepName=Layout")
533
+ .send("columns=" + encodeURIComponent(JSON.stringify(colsWithoutPages)))
534
+ .send("layout=" + encodeURIComponent(JSON.stringify(layoutWithoutPages)))
535
+ .set("Cookie", loginCookie)
536
+ .expect(toInclude("Fixed and blocked fields (step 2 / max 3)"));
537
+ });
538
+
539
+ it("saves new view fixed fields", async () => {
540
+ const loginCookie = await getAdminLoginCookie();
541
+ const table = Table.findOne({ name: "books" });
542
+ const app = await getApp({ disableCsrf: true });
543
+ // only edit_mybook_without_pages
544
+ await request(app)
545
+ .post("/viewedit/config/edit_mybook_without_pages")
546
+ .send(
547
+ "contextEnc=" +
548
+ encodeURIComponent(
549
+ JSON.stringify({
550
+ table_id: table.id,
551
+ viewname: "edit_mybook_without_pages",
552
+ layout: layoutWithoutPages,
553
+ columns: colsWithoutPages,
554
+ })
555
+ )
556
+ )
557
+ .send("stepName=Fixed+and+blocked+fields")
558
+ .send("pages=2")
559
+ .set("Cookie", loginCookie)
560
+ .expect(toInclude("Edit options (step 3 / 3)"));
561
+ });
562
+
563
+ it("saves view when done", async () => {
564
+ const loginCookie = await getAdminLoginCookie();
565
+ const table = Table.findOne({ name: "books" });
566
+ const app = await getApp({ disableCsrf: true });
567
+ // edit_mybook
568
+ await request(app)
569
+ .post("/viewedit/config/edit_mybook")
570
+ .send(
571
+ "contextEnc=" +
572
+ encodeURIComponent(
573
+ JSON.stringify({
574
+ table_id: table.id,
575
+ viewname: "edit_mybook",
576
+ layout: layoutWithPages,
577
+ columns: colsWithPages,
578
+ })
579
+ )
580
+ )
581
+ .send("stepName=Edit+options")
582
+ .send("destination_type=View")
583
+ .send("view_when_done=authorlist")
584
+ .send("auto_save=on")
585
+ .send("split_paste=on")
586
+ .set("Cookie", loginCookie)
587
+ .expect(toRedirect("/viewedit"));
588
+ const viewWithPages = View.findOne({ name: "edit_mybook" });
589
+ expect(viewWithPages.configuration.layout).toEqual(layoutWithPages);
590
+ expect(viewWithPages.configuration.columns).toEqual(colsWithPages);
591
+ // edit_mybook_without_pages
592
+ await request(app)
593
+ .post("/viewedit/config/edit_mybook_without_pages")
594
+ .send(
595
+ "contextEnc=" +
596
+ encodeURIComponent(
597
+ JSON.stringify({
598
+ table_id: table.id,
599
+ viewname: "edit_mybook_without_pages",
600
+ layout: layoutWithoutPages,
601
+ columns: colsWithoutPages,
602
+ fixed: {
603
+ pages: 22,
604
+ _block_pages: false,
605
+ },
606
+ })
607
+ )
608
+ )
609
+ .send("stepName=Edit+options")
610
+ .send("destination_type=View")
611
+ .send("view_when_done=authorlist")
612
+ .send("auto_save=on")
613
+ .send("split_paste=on")
614
+ .set("Cookie", loginCookie)
615
+ .expect(toRedirect("/viewedit"));
616
+
617
+ const viewWithoutPages = View.findOne({
618
+ name: "edit_mybook_without_pages",
619
+ });
620
+ expect(viewWithoutPages.configuration.fixed).toEqual({
621
+ pages: 22,
622
+ _block_pages: false,
623
+ });
624
+ expect(viewWithoutPages.configuration.layout).toEqual(layoutWithoutPages);
625
+ expect(viewWithoutPages.configuration.columns).toEqual(colsWithoutPages);
626
+ });
627
+
628
+ it("deletes new view", async () => {
629
+ const loginCookie = await getAdminLoginCookie();
630
+ const app = await getApp({ disableCsrf: true });
631
+ const idA = View.findOne({ name: "edit_mybook" }).id;
632
+ // edit_mybook
633
+ await request(app)
634
+ .post("/viewedit/delete/" + idA)
635
+ .set("Cookie", loginCookie)
636
+ .expect(toRedirect("/viewedit"));
637
+ // edit_mybook_without_pages
638
+ const idB = View.findOne({ name: "edit_mybook_without_pages" }).id;
639
+ await request(app)
640
+ .post("/viewedit/delete/" + idB)
641
+ .set("Cookie", loginCookie)
642
+ .expect(toRedirect("/viewedit"));
643
+ });
644
+ });
645
+
646
+ describe("viewedit new Edit with fixed fields", () => {
647
+ it("submit new view", async () => {});
648
+ });
649
+
407
650
  describe("Library", () => {
408
651
  it("should save new from builder", async () => {
409
652
  const loginCookie = await getAdminLoginCookie();
package/wrapper.js CHANGED
@@ -187,7 +187,9 @@ const get_headers = (req, version_tag, description, extras = []) => {
187
187
  : [];
188
188
  const stdHeaders = [
189
189
  {
190
- headerTag: `<script>var _sc_globalCsrf = "${req.csrfToken()}"; var _sc_version_tag = "${version_tag}";</script>`,
190
+ headerTag: `<script>var _sc_loglevel = ${
191
+ state.logLevel
192
+ }, _sc_globalCsrf = "${req.csrfToken()}", _sc_version_tag = "${version_tag}";</script>`,
191
193
  },
192
194
  { css: `/static_assets/${version_tag}/saltcorn.css` },
193
195
  { script: `/static_assets/${version_tag}/saltcorn-common.js` },
@@ -309,6 +311,7 @@ module.exports = (version_tag) =>
309
311
  const alerts = getFlashes(req);
310
312
  const state = getState();
311
313
  const layout = state.getLayout(req.user);
314
+ const no_menu = opts.no_menu;
312
315
 
313
316
  if (req.xhr) {
314
317
  const renderToHtml = layout.renderBody
@@ -335,8 +338,8 @@ module.exports = (version_tag) =>
335
338
  res.send(
336
339
  layout.wrap({
337
340
  title,
338
- brand: get_brand(state),
339
- menu: get_menu(req),
341
+ brand: no_menu ? undefined : get_brand(state),
342
+ menu: no_menu ? undefined : get_menu(req),
340
343
  currentUrl,
341
344
  originalUrl: req.originalUrl,
342
345