@saltcorn/server 0.8.0-beta.1 → 0.8.0-beta.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/fields.js CHANGED
@@ -66,7 +66,7 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
66
66
  return new Form({
67
67
  action: "/field",
68
68
  validator: (vs) => {
69
- if (vs.calculated && vs.type == "File")
69
+ if (vs.calculated && vs.type === "File")
70
70
  return req.__("Calculated fields cannot have File type");
71
71
  if (vs.calculated && vs.type.startsWith("Key to"))
72
72
  return req.__("Calculated fields cannot have Key type");
@@ -182,8 +182,8 @@ const fieldFlow = (req) =>
182
182
  new Workflow({
183
183
  action: "/field",
184
184
  onDone: async (context) => {
185
- const thetype = getState().types[context.type];
186
- var attributes = context.attributes || {};
185
+ //const thetype = getState().types[context.type];
186
+ const attributes = context.attributes || {};
187
187
  attributes.default = context.default;
188
188
  attributes.summary_field = context.summary_field;
189
189
  attributes.include_fts = context.include_fts;
@@ -291,6 +291,8 @@ const fieldFlow = (req) =>
291
291
  form: async (context) => {
292
292
  if (context.type === "File") {
293
293
  const roles = await User.get_roles();
294
+ const default_file_accept_filter = await getState().getConfig("files_accept_filter_default");
295
+ //console.log("default_file_accept_filter",default_file_accept_filter);
294
296
  return new Form({
295
297
  fields: [
296
298
  {
@@ -310,6 +312,15 @@ const fieldFlow = (req) =>
310
312
  "Deleting a row will also delete the file referenced by this field"
311
313
  ),
312
314
  },
315
+ {
316
+ name: "files_accept_filter",
317
+ type: "String",
318
+ label: req.__("Files accept filter"),
319
+ sublabel: req.__(
320
+ "Specifies a filter for what file types the user can pick from the file input dialog box. Example is `.doc,audio/*,video/*,image/*`"
321
+ ),
322
+ default: default_file_accept_filter,
323
+ },
313
324
  ],
314
325
  });
315
326
  } else {
@@ -634,7 +645,7 @@ router.post(
634
645
  );
635
646
 
636
647
  /**
637
- * @name post/test-formula
648
+ * Test formula
638
649
  * @function
639
650
  * @memberof module:routes/fields~fieldsRouter
640
651
  * @function
package/routes/files.js CHANGED
@@ -13,24 +13,12 @@ const s3storage = require("../s3storage");
13
13
  const resizer = require("resize-with-sharp-or-jimp");
14
14
  const db = require("@saltcorn/data/db");
15
15
 
16
- const {
17
- mkTable,
18
- renderForm,
19
- link,
20
- //post_btn,
21
- post_delete_btn,
22
- } = require("@saltcorn/markup");
16
+ const { renderForm } = require("@saltcorn/markup");
23
17
  const { isAdmin, error_catcher, setTenant } = require("./utils.js");
24
- const { h1, div, text, button, i, a } = require("@saltcorn/markup/tags");
25
- // const { csrfField } = require("./utils");
18
+ const { h1, div, text } = require("@saltcorn/markup/tags");
26
19
  const { editRoleForm, fileUploadForm } = require("../markup/forms.js");
27
20
  const { strictParseInt } = require("@saltcorn/data/plugin-helper");
28
- const {
29
- send_files_page,
30
- config_fields_form,
31
- save_config_from_form,
32
- } = require("../markup/admin");
33
- // const fsp = require("fs").promises;
21
+ const { send_files_page, config_fields_form, save_config_from_form } = require("../markup/admin");
34
22
  const fs = require("fs");
35
23
  const path = require("path");
36
24
 
@@ -74,7 +62,6 @@ router.get(
74
62
  const safeDir = File.normalise(dir || "/")
75
63
  const rows = await File.find({ folder: dir }, { orderBy: "filename" });
76
64
  const roles = await User.get_roles();
77
- //console.log(rows);
78
65
  if (safeDir && safeDir !== "/" && safeDir !== ".") {
79
66
  let dirname = path.dirname(safeDir)
80
67
  if (dirname === ".") dirname = "/"
@@ -314,7 +301,7 @@ router.post(
314
301
  */
315
302
  router.post(
316
303
  "/upload",
317
- setTenant, // TODO why is this needed?????
304
+ setTenant,
318
305
  error_catcher(async (req, res) => {
319
306
  let { folder } = req.body
320
307
  let jsonResp = {};
@@ -395,12 +382,12 @@ router.post(
395
382
  );
396
383
 
397
384
  /**
398
- * Storage settings form definition
385
+ * S3 Storage settings form definition
399
386
  * @param {object} req request
400
387
  * @returns {Promise<Form>} form
401
388
  */
402
389
  const storage_form = async (req) => {
403
- const form = await config_fields_form({
390
+ return await config_fields_form({
404
391
  req,
405
392
  field_names: [
406
393
  "storage_s3_enabled",
@@ -414,11 +401,10 @@ const storage_form = async (req) => {
414
401
  ],
415
402
  action: "/files/storage",
416
403
  });
417
- return form;
418
404
  };
419
405
 
420
406
  /**
421
- * @name get/storage
407
+ * Show S3 Settings
422
408
  * @function
423
409
  * @memberof module:routes/admin~routes/adminRouter
424
410
  */
@@ -441,7 +427,7 @@ router.get(
441
427
  );
442
428
 
443
429
  /**
444
- * @name post/email
430
+ * Update S3 Settings
445
431
  * @function
446
432
  * @memberof module:routes/admin~routes/adminRouter
447
433
  */
@@ -472,3 +458,78 @@ router.post(
472
458
  }
473
459
  })
474
460
  );
461
+
462
+ /**
463
+ * Files settings form definition
464
+ * @param {object} req request
465
+ * @returns {Promise<Form>} form
466
+ */
467
+ const files_settings_form = async (req) => {
468
+ return await config_fields_form({
469
+ req,
470
+ field_names: [
471
+ "min_role_upload",
472
+ "file_accept_filter_default",
473
+ "file_upload_debug",
474
+ "file_upload_limit",
475
+ "file_upload_timeout",
476
+ ],
477
+ action: "/files/settings",
478
+ });
479
+ };
480
+
481
+ /**
482
+ * Show Files Settings
483
+ * @function
484
+ * @memberof module:routes/admin~routes/adminRouter
485
+ */
486
+ router.get(
487
+ "/settings",
488
+ isAdmin,
489
+ error_catcher(async (req, res) => {
490
+ const form = await files_settings_form(req);
491
+ send_files_page({
492
+ res,
493
+ req,
494
+ active_sub: "Settings",
495
+ contents: {
496
+ type: "card",
497
+ title: req.__("Files settings"),
498
+ contents: [renderForm(form, req.csrfToken())],
499
+ },
500
+ });
501
+ })
502
+ );
503
+
504
+ /**
505
+ * Update Files Settings
506
+ * @function
507
+ * @memberof module:routes/admin~routes/adminRouter
508
+ */
509
+ router.post(
510
+ "/settings",
511
+ isAdmin,
512
+ error_catcher(async (req, res) => {
513
+ const form = await files_settings_form(req);
514
+ form.validate(req.body);
515
+ if (form.hasErrors) {
516
+ send_admin_page({
517
+ res,
518
+ req,
519
+ active_sub: "Settings",
520
+ contents: {
521
+ type: "card",
522
+ title: req.__("Files settings"),
523
+ contents: [renderForm(form, req.csrfToken())],
524
+ },
525
+ });
526
+ } else {
527
+ await save_config_from_form(form);
528
+
529
+ if (!req.xhr) {
530
+ req.flash("success", req.__("Files settings updated"));
531
+ res.redirect("/files/settings");
532
+ } else res.json({ success: "ok" });
533
+ }
534
+ })
535
+ );
@@ -15,7 +15,7 @@ const Page = require("@saltcorn/data/models/page");
15
15
  const { link, mkTable } = require("@saltcorn/markup");
16
16
  const { div, a, p, i } = require("@saltcorn/markup/tags");
17
17
  const Table = require("@saltcorn/data/models/table");
18
- const { fetch_available_packs } = require("@saltcorn/admin-models/models/pack");
18
+ const { get_cached_packs } = require("@saltcorn/admin-models/models/pack");
19
19
  // const { restore_backup } = require("../markup/admin");
20
20
  const { get_latest_npm_version } = require("@saltcorn/data/models/config");
21
21
  const packagejson = require("../package.json");
@@ -54,9 +54,9 @@ const tableCard = (tables, req) => ({
54
54
  contents:
55
55
  (tables.length <= 1
56
56
  ? p(
57
- { class: "mt-2 pe-2" },
58
- i(req.__("Tables organise data by fields and rows."))
59
- )
57
+ { class: "mt-2 pe-2" },
58
+ i(req.__("Tables organise data by fields and rows."))
59
+ )
60
60
  : "") + tableTable(tables, req),
61
61
  bodyClass: "py-0 pe-0",
62
62
  footer: div(
@@ -107,13 +107,13 @@ const viewCard = (views, req) => ({
107
107
  contents:
108
108
  (views.length <= 1
109
109
  ? p(
110
- { class: "mt-2 pe-2" },
111
- i(
112
- req.__(
113
- "Views display data from tables. A view is a view pattern applied to a table, with configuration."
110
+ { class: "mt-2 pe-2" },
111
+ i(
112
+ req.__(
113
+ "Views display data from tables. A view is a view pattern applied to a table, with configuration."
114
+ )
114
115
  )
115
116
  )
116
- )
117
117
  : "") +
118
118
  (views.length > 0 ? viewTable(views, req) : p(req.__("No views"))),
119
119
 
@@ -160,13 +160,13 @@ const pageCard = (pages, req) => ({
160
160
  contents:
161
161
  (pages.length <= 1
162
162
  ? p(
163
- { class: "mt-2 pe-2" },
164
- i(
165
- req.__(
166
- "Pages are the web pages of your application built with a drag-and-drop builder. They have static content, and by embedding views, dynamic content."
163
+ { class: "mt-2 pe-2" },
164
+ i(
165
+ req.__(
166
+ "Pages are the web pages of your application built with a drag-and-drop builder. They have static content, and by embedding views, dynamic content."
167
+ )
167
168
  )
168
169
  )
169
- )
170
170
  : "") +
171
171
  (pages.length > 0
172
172
  ? pageTable(pages, req)
@@ -191,16 +191,19 @@ const filesTab = async (req) => {
191
191
  files.length === 0
192
192
  ? p(req.__("No files"))
193
193
  : mkTable(
194
- [
195
- {
196
- label: req.__("Filename"),
197
- key: (r) => r.isDirectory ? r.filename : link(`/files/serve/${r.path_to_serve}`, r.filename),
198
- },
199
- { label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
200
- { label: req.__("Media type"), key: (r) => r.mimetype },
201
- ],
202
- files
203
- ),
194
+ [
195
+ {
196
+ label: req.__("Filename"),
197
+ key: (r) =>
198
+ r.isDirectory
199
+ ? r.filename
200
+ : link(`/files/serve/${r.path_to_serve}`, r.filename),
201
+ },
202
+ { label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
203
+ { label: req.__("Media type"), key: (r) => r.mimetype },
204
+ ],
205
+ files
206
+ ),
204
207
  fileUploadForm(req)
205
208
  );
206
209
  };
@@ -244,30 +247,30 @@ const actionsTab = async (req, triggers) => {
244
247
  return div(
245
248
  { class: "pb-3" },
246
249
  triggers.length <= 1 &&
247
- p(
248
- { class: "mt-2 pe-2" },
249
- i(req.__("Triggers run actions in response to events."))
250
- ),
250
+ p(
251
+ { class: "mt-2 pe-2" },
252
+ i(req.__("Triggers run actions in response to events."))
253
+ ),
251
254
  triggers.length === 0
252
255
  ? p(req.__("No triggers"))
253
256
  : mkTable(
254
- [
255
- { label: req.__("Name"), key: "name" },
256
- { label: req.__("Action"), key: "action" },
257
- {
258
- label: req.__("Table or Channel"),
259
- key: (r) => r.table_name || r.channel,
260
- },
261
- {
262
- label: req.__("When"),
263
- key: (a) =>
264
- a.when_trigger === "API call"
265
- ? `API: ${base_url}api/action/${a.name}`
266
- : a.when_trigger,
267
- },
268
- ],
269
- triggers
270
- ),
257
+ [
258
+ { label: req.__("Name"), key: "name" },
259
+ { label: req.__("Action"), key: "action" },
260
+ {
261
+ label: req.__("Table or Channel"),
262
+ key: (r) => r.table_name || r.channel,
263
+ },
264
+ {
265
+ label: req.__("When"),
266
+ key: (a) =>
267
+ a.when_trigger === "API call"
268
+ ? `API: ${base_url}api/action/${a.name}`
269
+ : a.when_trigger,
270
+ },
271
+ ],
272
+ triggers
273
+ ),
271
274
  a(
272
275
  { href: "/actions/new", class: "btn btn-secondary my-3" },
273
276
  req.__("Add trigger")
@@ -350,7 +353,7 @@ const helpCard = (req) =>
350
353
  * @returns {Promise<object>}
351
354
  */
352
355
  const welcome_page = async (req) => {
353
- const packs_available = await fetch_available_packs();
356
+ const packs_available = await get_cached_packs();
354
357
  const packlist = [
355
358
  ...(packs_available || []).slice(0, 5),
356
359
  { name: req.__("More..."), description: "" },
@@ -385,15 +388,15 @@ const welcome_page = async (req) => {
385
388
  tabContents:
386
389
  triggers.length > 0
387
390
  ? {
388
- Triggers: await actionsTab(req, triggers),
389
- Files: await filesTab(req),
390
- Packs: packTab(req, packlist),
391
- }
391
+ Triggers: await actionsTab(req, triggers),
392
+ Files: await filesTab(req),
393
+ Packs: packTab(req, packlist),
394
+ }
392
395
  : {
393
- Packs: packTab(req, packlist),
394
- Triggers: await actionsTab(req, triggers),
395
- Files: await filesTab(req),
396
- },
396
+ Packs: packTab(req, packlist),
397
+ Triggers: await actionsTab(req, triggers),
398
+ Files: await filesTab(req),
399
+ },
397
400
  },
398
401
  {
399
402
  type: "card",
@@ -403,13 +406,13 @@ const welcome_page = async (req) => {
403
406
  tabContents:
404
407
  users.length > 4
405
408
  ? {
406
- Users: await usersTab(req, users, roleMap),
407
- Help: helpCard(req),
408
- }
409
+ Users: await usersTab(req, users, roleMap),
410
+ Help: helpCard(req),
411
+ }
409
412
  : {
410
- Help: helpCard(req),
411
- Users: await usersTab(req, users, roleMap),
412
- },
413
+ Help: helpCard(req),
414
+ Users: await usersTab(req, users, roleMap),
415
+ },
413
416
  },
414
417
  ],
415
418
  },
@@ -429,9 +432,12 @@ const no_views_logged_in = async (req, res) => {
429
432
  res.sendWrap(req.__("Hello"), req.__("Welcome to Saltcorn!"));
430
433
  else {
431
434
  const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
432
- const latest = isRoot && (await get_latest_npm_version("@saltcorn/cli"));
435
+ const latest =
436
+ isRoot && (await get_latest_npm_version("@saltcorn/cli", 500));
433
437
  const can_update =
434
- packagejson.version !== latest && !process.env.SALTCORN_DISABLE_UPGRADE;
438
+ packagejson.version !== latest &&
439
+ latest &&
440
+ !process.env.SALTCORN_DISABLE_UPGRADE;
435
441
  if (latest && can_update && isRoot)
436
442
  req.flash(
437
443
  "warning",
@@ -440,8 +446,8 @@ const no_views_logged_in = async (req, res) => {
440
446
  packagejson.version,
441
447
  latest
442
448
  ) +
443
- " " +
444
- a({ href: "/admin/system" }, req.__("Upgrade here"))
449
+ " " +
450
+ a({ href: "/admin/system" }, req.__("Upgrade here"))
445
451
  );
446
452
 
447
453
  res.sendWrap(req.__("Hello"), await welcome_page(req));
@@ -45,11 +45,11 @@ router.get(
45
45
  * @param {object} req
46
46
  * @returns {Form}
47
47
  */
48
- const languageForm = (req) =>
48
+ const languageForm = (req, hasSaveButton) =>
49
49
  new Form({
50
50
  action: "/site-structure/localizer/save-lang",
51
- onChange: "saveAndContinue(this)",
52
- noSubmitButton: true,
51
+ onChange: hasSaveButton ? undefined : "saveAndContinue(this)",
52
+ noSubmitButton: !hasSaveButton,
53
53
  fields: [
54
54
  {
55
55
  name: "name",
@@ -60,15 +60,18 @@ const languageForm = (req) =>
60
60
  {
61
61
  name: "locale",
62
62
  label: req.__("Locale"),
63
- sublabel: req.__("Locale identifier short code, e.g. en, zh, fr, ar etc. "),
63
+ sublabel: req.__(
64
+ "Locale identifier short code, e.g. en, zh, fr, ar etc. "
65
+ ),
64
66
  type: "String",
65
67
  required: true,
66
68
  },
67
69
  {
68
70
  name: "is_default",
69
71
  label: req.__("Default language"),
70
- sublabel:
71
- req.__("Is this the default language in which the application is built?"),
72
+ sublabel: req.__(
73
+ "Is this the default language in which the application is built?"
74
+ ),
72
75
  type: "Bool",
73
76
  },
74
77
  ],
@@ -150,7 +153,7 @@ router.get(
150
153
  sub2_page: "New",
151
154
  contents: {
152
155
  type: "card",
153
- contents: [renderForm(languageForm(req), req.csrfToken())],
156
+ contents: [renderForm(languageForm(req, true), req.csrfToken())],
154
157
  },
155
158
  });
156
159
  })
package/routes/menu.js CHANGED
@@ -25,7 +25,6 @@ const Table = require("@saltcorn/data/models/table");
25
25
  const Trigger = require("@saltcorn/data/models/trigger");
26
26
  const { run_action_column } = require("@saltcorn/data/plugin-helper");
27
27
 
28
-
29
28
  /**
30
29
  * @type {object}
31
30
  * @const
@@ -107,7 +106,7 @@ const menuForm = async (req) => {
107
106
  "Dynamic",
108
107
  "Search",
109
108
  "Separator",
110
- "Action"
109
+ "Action",
111
110
  ],
112
111
  },
113
112
  {
@@ -117,7 +116,15 @@ const menuForm = async (req) => {
117
116
  input_type: "text",
118
117
  required: true,
119
118
  showIf: {
120
- type: ["View", "Page", "Link", "Header", "Dynamic", "Search", "Action"],
119
+ type: [
120
+ "View",
121
+ "Page",
122
+ "Link",
123
+ "Header",
124
+ "Dynamic",
125
+ "Search",
126
+ "Action",
127
+ ],
121
128
  },
122
129
  },
123
130
  {
@@ -244,7 +251,9 @@ const menuForm = async (req) => {
244
251
  class: "item-menu",
245
252
  type: "String",
246
253
  required: true,
247
- showIf: { type: ["View", "Page", "Link", "Header", "Dynamic", "Action"] },
254
+ showIf: {
255
+ type: ["View", "Page", "Link", "Header", "Dynamic", "Action"],
256
+ },
248
257
  attributes: {
249
258
  options: [
250
259
  { name: "", label: "Link" },
@@ -266,7 +275,9 @@ const menuForm = async (req) => {
266
275
  {
267
276
  name: "location",
268
277
  label: req.__("Location"),
269
- showIf: { type: ["View", "Page", "Link", "Header", "Dynamic", "Action"] },
278
+ showIf: {
279
+ type: ["View", "Page", "Link", "Header", "Dynamic", "Action"],
280
+ },
270
281
  sublabel: req.__("Not all themes support all locations"),
271
282
  class: "item-menu",
272
283
  type: "String",
@@ -440,15 +451,14 @@ router.post(
440
451
  const state = getState();
441
452
  const menu_items = state.getConfig("menu_items");
442
453
  let menu_item;
443
- const search = items =>
454
+ const search = (items) =>
444
455
  items
445
456
  .filter((item) => role <= +item.min_role)
446
- .forEach(item => {
457
+ .forEach((item) => {
447
458
  if (item.type === "Action" && item.action_name === name)
448
459
  menu_item = item;
449
- else if (item.subitems)
450
- search(item.subitems);
451
- })
460
+ else if (item.subitems) search(item.subitems);
461
+ });
452
462
  search(menu_items);
453
463
  if (menu_item)
454
464
  try {
@@ -456,12 +466,12 @@ router.post(
456
466
  col: menu_item,
457
467
  referrer: req.get("Referrer"),
458
468
  req,
469
+ res,
459
470
  });
460
471
  res.json({ success: "ok", ...(result || {}) });
461
472
  } catch (e) {
462
473
  res.status(400).json({ error: e.message || e });
463
474
  }
464
-
465
475
  else res.status(404).json({ error: "Action not found" });
466
476
  })
467
- );
477
+ );
package/routes/page.js CHANGED
@@ -8,7 +8,11 @@ const Router = require("express-promise-router");
8
8
 
9
9
  const Page = require("@saltcorn/data/models/page");
10
10
  const { getState } = require("@saltcorn/data/db/state");
11
- const { error_catcher, scan_for_page_title, isAdmin } = require("../routes/utils.js");
11
+ const {
12
+ error_catcher,
13
+ scan_for_page_title,
14
+ isAdmin,
15
+ } = require("../routes/utils.js");
12
16
  const { add_edit_bar } = require("../markup/admin.js");
13
17
  const { traverseSync } = require("@saltcorn/data/models/layout");
14
18
  const { run_action_column } = require("@saltcorn/data/plugin-helper");
@@ -105,6 +109,7 @@ router.post(
105
109
  col,
106
110
  referrer: req.get("Referrer"),
107
111
  req,
112
+ res,
108
113
  });
109
114
  res.json({ success: "ok", ...(result || {}) });
110
115
  } catch (e) {