@saltcorn/server 0.7.4 → 0.8.0-beta.1

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.
Files changed (50) hide show
  1. package/app.js +18 -11
  2. package/auth/admin.js +370 -120
  3. package/auth/roleadmin.js +5 -23
  4. package/auth/routes.js +40 -15
  5. package/locales/de.json +1049 -273
  6. package/locales/en.json +58 -3
  7. package/locales/es.json +134 -134
  8. package/locales/it.json +6 -1
  9. package/locales/ru.json +44 -7
  10. package/markup/admin.js +46 -42
  11. package/markup/forms.js +4 -3
  12. package/package.json +8 -7
  13. package/public/blockly.js +19 -31
  14. package/public/diagram_utils.js +530 -0
  15. package/public/gridedit.js +4 -1
  16. package/public/jquery-menu-editor.min.js +112 -112
  17. package/public/saltcorn-common.js +31 -8
  18. package/public/saltcorn.css +11 -0
  19. package/public/saltcorn.js +211 -70
  20. package/restart_watcher.js +1 -0
  21. package/routes/actions.js +6 -14
  22. package/routes/admin.js +229 -79
  23. package/routes/api.js +19 -2
  24. package/routes/common_lists.js +137 -134
  25. package/routes/delete.js +6 -5
  26. package/routes/diagram.js +43 -117
  27. package/routes/edit.js +5 -10
  28. package/routes/fields.js +63 -29
  29. package/routes/files.js +137 -101
  30. package/routes/homepage.js +2 -2
  31. package/routes/infoarch.js +2 -2
  32. package/routes/list.js +12 -13
  33. package/routes/page.js +16 -3
  34. package/routes/pageedit.js +13 -8
  35. package/routes/scapi.js +1 -1
  36. package/routes/search.js +1 -1
  37. package/routes/tables.js +9 -14
  38. package/routes/tag_entries.js +31 -10
  39. package/routes/tags.js +10 -10
  40. package/routes/tenant.js +114 -50
  41. package/routes/utils.js +12 -0
  42. package/routes/view.js +3 -4
  43. package/routes/viewedit.js +57 -55
  44. package/serve.js +5 -0
  45. package/tests/admin.test.js +6 -2
  46. package/tests/auth.test.js +20 -0
  47. package/tests/fields.test.js +1 -0
  48. package/tests/files.test.js +11 -20
  49. package/tests/tenant.test.js +12 -2
  50. package/tests/viewedit.test.js +15 -1
package/routes/fields.js CHANGED
@@ -25,7 +25,10 @@ const db = require("@saltcorn/data/db");
25
25
 
26
26
  const { isAdmin, error_catcher } = require("./utils.js");
27
27
  const expressionBlurb = require("../markup/expression_blurb");
28
- const { readState, add_free_variables_to_joinfields } = require("@saltcorn/data/plugin-helper");
28
+ const {
29
+ readState,
30
+ add_free_variables_to_joinfields,
31
+ } = require("@saltcorn/data/plugin-helper");
29
32
  const { wizardCardTitle } = require("../markup/forms.js");
30
33
  const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
31
34
  const { applyAsync } = require("@saltcorn/data/utils");
@@ -185,6 +188,7 @@ const fieldFlow = (req) =>
185
188
  attributes.summary_field = context.summary_field;
186
189
  attributes.include_fts = context.include_fts;
187
190
  attributes.on_delete_cascade = context.on_delete_cascade;
191
+ attributes.on_delete = context.on_delete;
188
192
  const {
189
193
  table_id,
190
194
  name,
@@ -350,12 +354,13 @@ const fieldFlow = (req) =>
350
354
  // todo sublabel
351
355
  input_type: "custom_html",
352
356
  attributes: {
353
- html: `<button type="button" id="test_formula_btn" onclick="test_formula('${table.name
354
- }', ${JSON.stringify(
355
- context.stored
356
- )})" class="btn btn-outline-secondary">${req.__(
357
- "Test"
358
- )}</button>
357
+ html: `<button type="button" id="test_formula_btn" onclick="test_formula('${
358
+ table.name
359
+ }', ${JSON.stringify(
360
+ context.stored
361
+ )})" class="btn btn-outline-secondary">${req.__(
362
+ "Test"
363
+ )}</button>
359
364
  <div id="test_formula_output"></div>`,
360
365
  },
361
366
  }),
@@ -400,13 +405,32 @@ const fieldFlow = (req) =>
400
405
  type: "Bool",
401
406
  showIf: { summary_field: textfields },
402
407
  }),
403
- new Field({
408
+ /*new Field({
404
409
  name: "on_delete_cascade",
405
410
  label: req.__("On delete cascade"),
406
411
  type: "Bool",
407
412
  sublabel: req.__(
408
413
  "If the parent row is deleted, automatically delete the child rows."
409
414
  ),
415
+ }),*/
416
+ new Field({
417
+ name: "on_delete",
418
+ label: req.__("On delete"),
419
+ input_type: "select",
420
+ options: ["Fail", "Cascade", "Set null"],
421
+ required: true,
422
+ attributes: {
423
+ explainers: {
424
+ Fail: "Prevent any deletion of parent rows",
425
+ Cascade:
426
+ "If the parent row is deleted, automatically delete the child rows.",
427
+ "Set null":
428
+ "If the parent row is deleted, set key fields on child rows to null",
429
+ },
430
+ },
431
+ sublabel: req.__(
432
+ "If the parent row is deleted, do this to the child rows."
433
+ ),
410
434
  }),
411
435
  ],
412
436
  });
@@ -622,12 +646,18 @@ router.post(
622
646
  const { formula, tablename, stored } = req.body;
623
647
  const table = await Table.findOne({ name: tablename });
624
648
  const fields = await table.getFields();
625
- const freeVars = freeVariables(formula)
626
- const joinFields = {}
627
- if (stored)
628
- add_free_variables_to_joinfields(freeVars, joinFields, fields)
629
- const rows = await table.getJoinedRows({ joinFields, orderBy: "RANDOM()", limit: 1 });
630
- if (rows.length < 1) return "No rows in table";
649
+ const freeVars = freeVariables(formula);
650
+ const joinFields = {};
651
+ if (stored) add_free_variables_to_joinfields(freeVars, joinFields, fields);
652
+ const rows = await table.getJoinedRows({
653
+ joinFields,
654
+ orderBy: "RANDOM()",
655
+ limit: 1,
656
+ });
657
+ if (rows.length < 1) {
658
+ res.send("No rows in table");
659
+ return;
660
+ }
631
661
  let result;
632
662
  try {
633
663
  if (stored) {
@@ -638,7 +668,8 @@ router.post(
638
668
  result = f(rows[0]);
639
669
  }
640
670
  res.send(
641
- `Result of running on row with id=${rows[0].id
671
+ `Result of running on row with id=${
672
+ rows[0].id
642
673
  } is: <pre>${JSON.stringify(result)}</pre>`
643
674
  );
644
675
  } catch (e) {
@@ -668,10 +699,9 @@ router.post(
668
699
  const fields = await table.getFields();
669
700
  let row = { ...req.body };
670
701
  if (!row || Object.keys(row).length === 0) {
671
- const { id } = req.query
672
- if (id) row = await table.getRow({ id })
673
- } else
674
- readState(row, fields);
702
+ const { id } = req.query;
703
+ if (id) row = await table.getRow({ id });
704
+ } else readState(row, fields);
675
705
 
676
706
  if (fieldName.includes(".")) {
677
707
  //join field
@@ -691,12 +721,18 @@ router.post(
691
721
  const refRow = await reftable.getRow(q);
692
722
  let fv;
693
723
  if (targetField.type === "Key") {
694
- fv = getState().keyFieldviews[fieldview]
724
+ fv = getState().keyFieldviews[fieldview];
695
725
  if (!fv) {
696
- const reftable2 = Table.findOne({ name: targetField.reftable_name })
697
- const refRow2 = await reftable2.getRow({ [reftable2.pk_name]: refRow[kpath[1]] })
726
+ const reftable2 = Table.findOne({
727
+ name: targetField.reftable_name,
728
+ });
729
+ const refRow2 = await reftable2.getRow({
730
+ [reftable2.pk_name]: refRow[kpath[1]],
731
+ });
698
732
  if (refRow2) {
699
- res.send(text(`${refRow2[targetField.attributes.summary_field]}`));
733
+ res.send(
734
+ text(`${refRow2[targetField.attributes.summary_field]}`)
735
+ );
700
736
  } else {
701
737
  res.send("");
702
738
  }
@@ -708,7 +744,6 @@ router.post(
708
744
  fv =
709
745
  targetField.type.fieldviews.show ||
710
746
  targetField.type.fieldviews.as_text;
711
-
712
747
  }
713
748
 
714
749
  const configuration = req.query;
@@ -753,7 +788,7 @@ router.post(
753
788
  let result;
754
789
  try {
755
790
  if (!field.calculated) {
756
- result = row[field.name]
791
+ result = row[field.name];
757
792
  } else if (field.stored) {
758
793
  const f = get_async_expression_function(formula, fields);
759
794
  result = await f(row);
@@ -762,8 +797,7 @@ router.post(
762
797
  result = f(row);
763
798
  }
764
799
  const fv = field.type.fieldviews[fieldview];
765
- if (!fv)
766
- res.send(text(result));
800
+ if (!fv) res.send(text(result));
767
801
  else res.send(fv.run(result));
768
802
  } catch (e) {
769
803
  return res.status(400).send(`Error: ${e.message}`);
@@ -816,8 +850,8 @@ router.post(
816
850
  field.type === "Key"
817
851
  ? getState().keyFieldviews
818
852
  : field.type === "File"
819
- ? getState().fileviews
820
- : field.type.fieldviews;
853
+ ? getState().fileviews
854
+ : field.type.fieldviews;
821
855
  if (!field.type || !fieldviews) {
822
856
  res.send("");
823
857
  return;
package/routes/files.js CHANGED
@@ -11,6 +11,7 @@ const User = require("@saltcorn/data/models/user");
11
11
  const { getState } = require("@saltcorn/data/db/state");
12
12
  const s3storage = require("../s3storage");
13
13
  const resizer = require("resize-with-sharp-or-jimp");
14
+ const db = require("@saltcorn/data/db");
14
15
 
15
16
  const {
16
17
  mkTable,
@@ -20,7 +21,7 @@ const {
20
21
  post_delete_btn,
21
22
  } = require("@saltcorn/markup");
22
23
  const { isAdmin, error_catcher, setTenant } = require("./utils.js");
23
- const { h1, div, text } = require("@saltcorn/markup/tags");
24
+ const { h1, div, text, button, i, a } = require("@saltcorn/markup/tags");
24
25
  // const { csrfField } = require("./utils");
25
26
  const { editRoleForm, fileUploadForm } = require("../markup/forms.js");
26
27
  const { strictParseInt } = require("@saltcorn/data/plugin-helper");
@@ -31,6 +32,7 @@ const {
31
32
  } = require("../markup/admin");
32
33
  // const fsp = require("fs").promises;
33
34
  const fs = require("fs");
35
+ const path = require("path");
34
36
 
35
37
  /**
36
38
  * @type {object}
@@ -51,7 +53,7 @@ module.exports = router;
51
53
  */
52
54
  const editFileRoleForm = (file, roles, req) =>
53
55
  editRoleForm({
54
- url: `/files/setrole/${file.id}`,
56
+ url: `/files/setrole/${file.path_to_serve}`,
55
57
  current_role: file.min_role_read,
56
58
  roles,
57
59
  req,
@@ -68,49 +70,51 @@ router.get(
68
70
  isAdmin,
69
71
  error_catcher(async (req, res) => {
70
72
  // todo limit select from file by 10 or 20
71
- const rows = await File.find({}, { orderBy: "filename" });
73
+ const { dir } = req.query
74
+ const safeDir = File.normalise(dir || "/")
75
+ const rows = await File.find({ folder: dir }, { orderBy: "filename" });
72
76
  const roles = await User.get_roles();
77
+ //console.log(rows);
78
+ if (safeDir && safeDir !== "/" && safeDir !== ".") {
79
+ let dirname = path.dirname(safeDir)
80
+ if (dirname === ".") dirname = "/"
81
+ rows.unshift(new File({
82
+ filename: "..",
83
+ location: dirname,
84
+ isDirectory: true,
85
+ mime_super: "",
86
+ mime_sub: "",
87
+ }))
88
+ }
89
+ if (req.xhr) {
90
+ for (const file of rows) {
91
+ file.location = file.path_to_serve
92
+ }
93
+ const directories = await File.allDirectories()
94
+ for (const file of directories) {
95
+ file.location = file.path_to_serve
96
+ }
97
+ res.json({ files: rows, roles, directories })
98
+ return
99
+ }
73
100
  send_files_page({
74
101
  res,
75
102
  req,
103
+ headers: [
104
+ {
105
+ script: `/static_assets/${db.connectObj.version_tag}/bundle.js`,
106
+ defer: true
107
+ },
108
+ {
109
+ css: `/static_assets/${db.connectObj.version_tag}/bundle.css`,
110
+ },
111
+ ],
76
112
  active_sub: "Files",
77
113
  contents: {
78
114
  type: "card",
79
115
  contents: [
80
- mkTable(
81
- [
82
- {
83
- label: req.__("Filename"),
84
- key: (r) =>
85
- div(
86
- { "data-inline-edit-dest-url": `/files/setname/${r.id}` },
87
- r.filename
88
- ),
89
- },
90
- { label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
91
- { label: req.__("Media type"), key: (r) => r.mimetype },
92
- {
93
- label: req.__("Role to access"),
94
- key: (r) => editFileRoleForm(r, roles, req),
95
- },
96
- {
97
- label: req.__("Link"),
98
- key: (r) => link(`/files/serve/${r.id}`, req.__("Link")),
99
- },
100
- {
101
- label: req.__("Download"),
102
- key: (r) => link(`/files/download/${r.id}`, req.__("Download")),
103
- },
104
- {
105
- label: req.__("Delete"),
106
- key: (r) =>
107
- post_delete_btn(`/files/delete/${r.id}`, req, r.filename),
108
- },
109
- ],
110
- rows,
111
- { hover: true }
112
- ),
113
- fileUploadForm(req),
116
+ div({ id: "saltcorn-file-manager" }),
117
+ fileUploadForm(req, safeDir),
114
118
  ],
115
119
  },
116
120
  });
@@ -124,19 +128,21 @@ router.get(
124
128
  * @function
125
129
  */
126
130
  router.get(
127
- "/download/:id",
131
+ "/download/*",
128
132
  error_catcher(async (req, res) => {
129
133
  const role = req.user && req.user.id ? req.user.role_id : 10;
130
134
  const user_id = req.user && req.user.id;
131
- const { id } = req.params;
132
- const file = await File.findOne({ id });
133
- if (role <= file.min_role_read || (user_id && user_id === file.user_id)) {
135
+ const serve_path = req.params[0];
136
+ const file = await File.findOne(serve_path);
137
+
138
+ if (file && (role <= file.min_role_read || (user_id && user_id === file.user_id))) {
134
139
  res.type(file.mimetype);
135
140
  if (file.s3_store) s3storage.serveObject(file, res, true);
136
141
  else res.download(file.location, file.filename);
137
142
  } else {
138
- req.flash("warning", req.__("Not authorized"));
139
- res.redirect("/");
143
+ res
144
+ .status(404)
145
+ .sendWrap(req.__("Not found"), h1(req.__("File not found")));
140
146
  }
141
147
  })
142
148
  );
@@ -148,31 +154,25 @@ router.get(
148
154
  * @function
149
155
  */
150
156
  router.get(
151
- "/serve/:id",
157
+ "/serve/*",
152
158
  error_catcher(async (req, res) => {
153
159
  const role = req.user && req.user.id ? req.user.role_id : 10;
154
160
  const user_id = req.user && req.user.id;
155
- const { id } = req.params;
156
- let file;
157
- if (typeof strictParseInt(id) !== "undefined")
158
- file = await File.findOne({ id });
159
- else file = await File.findOne({ filename: id });
161
+ const serve_path = req.params[0];
162
+ //let file;
163
+ //if (typeof strictParseInt(id) !== "undefined")
164
+ const file = await File.findOne(serve_path);
160
165
 
161
- if (!file) {
162
- res
163
- .status(404)
164
- .sendWrap(req.__("Not found"), h1(req.__("File not found")));
165
- return;
166
- }
167
- if (role <= file.min_role_read || (user_id && user_id === file.user_id)) {
166
+ if (file && (role <= file.min_role_read || (user_id && user_id === file.user_id))) {
168
167
  res.type(file.mimetype);
169
168
  const cacheability = file.min_role_read === 10 ? "public" : "private";
170
169
  res.set("Cache-Control", `${cacheability}, max-age=86400`);
171
170
  if (file.s3_store) s3storage.serveObject(file, res, false);
172
171
  else res.sendFile(file.location);
173
172
  } else {
174
- req.flash("warning", req.__("Not authorized"));
175
- res.redirect("/");
173
+ res
174
+ .status(404)
175
+ .sendWrap(req.__("Not found"), h1(req.__("File not found")));
176
176
  }
177
177
  })
178
178
  );
@@ -184,23 +184,17 @@ router.get(
184
184
  * @function
185
185
  */
186
186
  router.get(
187
- "/resize/:id/:width_str/:height_str?",
187
+ "/resize/: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, height_str } = req.params;
192
- let file;
193
- if (typeof strictParseInt(id) !== "undefined")
194
- file = await File.findOne({ id });
195
- else file = await File.findOne({ filename: id });
191
+ const { width_str, height_str } = req.params;
192
+ const serve_path = req.params[0];
196
193
 
197
- if (!file) {
198
- res
199
- .status(404)
200
- .sendWrap(req.__("Not found"), h1(req.__("File not found")));
201
- return;
202
- }
203
- if (role <= file.min_role_read || (user_id && user_id === file.user_id)) {
194
+
195
+ const file = await File.findOne(serve_path);
196
+
197
+ if (file && (role <= file.min_role_read || (user_id && user_id === file.user_id))) {
204
198
  res.type(file.mimetype);
205
199
  const cacheability = file.min_role_read === 10 ? "public" : "private";
206
200
  res.set("Cache-Control", `${cacheability}, max-age=86400`);
@@ -208,12 +202,14 @@ router.get(
208
202
  if (file.s3_store) s3storage.serveObject(file, res, false);
209
203
  else {
210
204
  const width = strictParseInt(width_str);
211
- const height = height_str ? strictParseInt(height_str) : null;
205
+ const height = height_str && height_str !== "0"
206
+ ? strictParseInt(height_str) : null;
212
207
  if (!width) {
213
208
  res.sendFile(file.location);
214
209
  return;
215
210
  }
216
- const fnm = `${file.location}_w${width}${height ? `_h${height}` : ""}`;
211
+ const basenm = path.join(path.dirname(file.location), '_resized_' + path.basename(file.location))
212
+ const fnm = `${basenm}_w${width}${height ? `_h${height}` : ""}`;
217
213
  if (!fs.existsSync(fnm)) {
218
214
  await resizer({
219
215
  fromFileName: file.location,
@@ -225,8 +221,9 @@ router.get(
225
221
  res.sendFile(fnm);
226
222
  }
227
223
  } else {
228
- req.flash("warning", req.__("Not authorized"));
229
- res.redirect("/");
224
+ res
225
+ .status(404)
226
+ .sendWrap(req.__("Not found"), h1(req.__("File not found")));
230
227
  }
231
228
  })
232
229
  );
@@ -238,23 +235,42 @@ router.get(
238
235
  * @function
239
236
  */
240
237
  router.post(
241
- "/setrole/:id",
238
+ "/setrole/*",
242
239
  isAdmin,
243
240
  error_catcher(async (req, res) => {
244
- const { id } = req.params;
241
+ const serve_path = req.params[0];
242
+ const file = await File.findOne(serve_path);
245
243
  const role = req.body.role;
246
- await File.update(+id, { min_role_read: role });
247
- const file = await File.findOne({ id });
248
244
  const roles = await User.get_roles();
249
245
  const roleRow = roles.find((r) => r.id === +role);
250
- if (roleRow && file)
251
- req.flash(
252
- "success",
253
- req.__(`Minimum role for %s updated to %s`, file.filename, roleRow.role)
254
- );
255
- else req.flash("success", req.__(`Minimum role updated`));
256
246
 
257
- res.redirect("/files");
247
+ if (roleRow && file) {
248
+ await file.set_role(role);
249
+
250
+ }
251
+
252
+
253
+ res.redirect(file ? `/files?dir=${encodeURIComponent(file.current_folder)}` : "/files");
254
+ })
255
+ );
256
+
257
+
258
+ router.post(
259
+ "/move/*",
260
+ isAdmin,
261
+ error_catcher(async (req, res) => {
262
+ const serve_path = req.params[0];
263
+ const file = await File.findOne(serve_path);
264
+ const new_path = req.body.new_path;
265
+
266
+ if (file) {
267
+ await file.move_to_dir(new_path);
268
+ }
269
+ if (req.xhr) {
270
+ res.json({ success: "ok" })
271
+ return
272
+ }
273
+ res.redirect(file ? `/files?dir=${encodeURIComponent(file.current_folder)}` : "/files");
258
274
  })
259
275
  );
260
276
 
@@ -265,14 +281,28 @@ router.post(
265
281
  * @function
266
282
  */
267
283
  router.post(
268
- "/setname/:id",
284
+ "/setname/*",
269
285
  isAdmin,
270
286
  error_catcher(async (req, res) => {
271
- const { id } = req.params;
287
+ const serve_path = req.params[0];
272
288
  const filename = req.body.value;
273
- await File.update(+id, { filename });
274
289
 
275
- res.redirect("/files");
290
+ const file = await File.findOne(serve_path);
291
+ await file.rename(filename);
292
+
293
+ res.redirect(`/files?dir=${encodeURIComponent(file.current_folder)}`);
294
+
295
+ })
296
+ );
297
+
298
+ router.post(
299
+ "/new-folder",
300
+ isAdmin,
301
+ error_catcher(async (req, res) => {
302
+ const { name, folder } = req.body
303
+ await File.new_folder(name, folder);
304
+
305
+ res.json({ success: "ok" });
276
306
  })
277
307
  );
278
308
 
@@ -286,9 +316,11 @@ router.post(
286
316
  "/upload",
287
317
  setTenant, // TODO why is this needed?????
288
318
  error_catcher(async (req, res) => {
319
+ let { folder } = req.body
289
320
  let jsonResp = {};
290
321
  const min_role_upload = getState().getConfig("min_role_upload", 1);
291
322
  const role = req.user && req.user.id ? req.user.role_id : 10;
323
+ let file_for_redirect
292
324
  if (role > +min_role_upload) {
293
325
  if (!req.xhr) req.flash("warning", req.__("Not authorized"));
294
326
  else jsonResp = { error: "Not authorized" };
@@ -300,9 +332,11 @@ router.post(
300
332
  const f = await File.from_req_files(
301
333
  req.files.file,
302
334
  req.user.id,
303
- +min_role_read
335
+ +min_role_read,
336
+ folder ? File.normalise(folder) : undefined
304
337
  );
305
338
  const many = Array.isArray(f);
339
+ file_for_redirect = many ? f[0] : f
306
340
  if (!req.xhr)
307
341
  req.flash(
308
342
  "success",
@@ -317,14 +351,18 @@ router.post(
317
351
  jsonResp = {
318
352
  success: {
319
353
  filename: many ? f.map((fl) => fl.filename) : f.filename,
320
- id: many ? f.map((fl) => fl.id) : f.id,
354
+ location: many ? f.map((fl) => fl.path_to_serve) : f.path_to_serve,
321
355
  url: many
322
- ? f.map((fl) => `/files/serve/${fl.id}`)
323
- : `/files/serve/${f.id}`,
356
+ ? f.map((fl) => `/files/serve/${fl.path_to_serve}`)
357
+ : `/files/serve/${f.path_to_serve}`,
324
358
  },
325
359
  };
326
360
  }
327
- if (!req.xhr) res.redirect("/files");
361
+ if (!req.xhr)
362
+ res.redirect(!file_for_redirect
363
+ ? '/files'
364
+ : `/files?dir=${encodeURIComponent(file_for_redirect.current_folder)}`);
365
+
328
366
  else res.json(jsonResp);
329
367
  })
330
368
  );
@@ -336,11 +374,11 @@ router.post(
336
374
  * @function
337
375
  */
338
376
  router.post(
339
- "/delete/:id",
377
+ "/delete/*",
340
378
  isAdmin,
341
379
  error_catcher(async (req, res) => {
342
- const { id } = req.params;
343
- const f = await File.findOne({ id });
380
+ const serve_path = req.params[0];
381
+ const f = await File.findOne(serve_path);
344
382
  if (!f) {
345
383
  req.flash("error", "File not found");
346
384
  res.redirect("/files");
@@ -351,10 +389,8 @@ router.post(
351
389
  );
352
390
  if (result && result.error) {
353
391
  req.flash("error", result.error);
354
- } else {
355
- req.flash("success", req.__(`File %s deleted`, text(f.filename)));
356
392
  }
357
- res.redirect(`/files`);
393
+ res.redirect(`/files?dir=${encodeURIComponent(f.current_folder)}`);
358
394
  })
359
395
  );
360
396
 
@@ -194,7 +194,7 @@ const filesTab = async (req) => {
194
194
  [
195
195
  {
196
196
  label: req.__("Filename"),
197
- key: (r) => link(`/files/serve/${r.id}`, r.filename),
197
+ key: (r) => r.isDirectory ? r.filename : link(`/files/serve/${r.path_to_serve}`, r.filename),
198
198
  },
199
199
  { label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
200
200
  { label: req.__("Media type"), key: (r) => r.mimetype },
@@ -352,7 +352,7 @@ const helpCard = (req) =>
352
352
  const welcome_page = async (req) => {
353
353
  const packs_available = await fetch_available_packs();
354
354
  const packlist = [
355
- ...packs_available.slice(0, 5),
355
+ ...(packs_available || []).slice(0, 5),
356
356
  { name: req.__("More..."), description: "" },
357
357
  ];
358
358
  const tables = await Table.find({}, { cached: true });
@@ -60,7 +60,7 @@ const languageForm = (req) =>
60
60
  {
61
61
  name: "locale",
62
62
  label: req.__("Locale"),
63
- sublabel: "Locale identifier short code, e.g. en, zh, fr, ar etc. ",
63
+ sublabel: req.__("Locale identifier short code, e.g. en, zh, fr, ar etc. "),
64
64
  type: "String",
65
65
  required: true,
66
66
  },
@@ -68,7 +68,7 @@ const languageForm = (req) =>
68
68
  name: "is_default",
69
69
  label: req.__("Default language"),
70
70
  sublabel:
71
- "Is this the default language in which the application is built?",
71
+ req.__("Is this the default language in which the application is built?"),
72
72
  type: "Bool",
73
73
  },
74
74
  ],