@saltcorn/server 1.1.1-beta.4 → 1.1.1-beta.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## 1.1.1 - In beta
4
4
 
5
+ * You can now permit to non-admin (role ID > 1) users to edit or inspect tables, or
6
+ edit views, pages or triggers. In the permissions tab of the Users and security
7
+ settings, minimum roles can be set for these capabilities. The appropriate
8
+ menu items will be added to any users with the roles that match these permissions.
9
+
5
10
  * Stored calculated fields that contain joinfields in the expression are now automatically
6
11
  updated when the values they reference are changed, i.e. changes occur in the tables they
7
12
  reference. This is limited to single (expression contains x.y) and double joinfields
@@ -34,6 +39,7 @@
34
39
 
35
40
  ### Fixes
36
41
 
42
+ * fix workflows on SQLite
37
43
  * fix query string build on check_state_field (#2948). Author: St0rml
38
44
 
39
45
  ### Translations
package/auth/admin.js CHANGED
@@ -414,6 +414,14 @@ const permissions_settings_form = async (req) =>
414
414
  "min_role_upload",
415
415
  "min_role_apikeygen",
416
416
  "min_role_search",
417
+ {
418
+ section_header: req.__("Development permissions"),
419
+ },
420
+ "min_role_inspect_tables",
421
+ "min_role_edit_tables",
422
+ "min_role_edit_views",
423
+ "min_role_edit_pages",
424
+ "min_role_edit_triggers",
417
425
  //hidden "exttables_min_role_read",
418
426
  ],
419
427
  action: "/useradmin/permissions",
package/locales/en.json CHANGED
@@ -1528,5 +1528,20 @@
1528
1528
  "Finished": "Finished",
1529
1529
  "Error": "Error",
1530
1530
  "Waiting": "Waiting",
1531
- "Running": "Running"
1531
+ "Running": "Running",
1532
+ "Minimum role to access search page": "Minimum role to access search page",
1533
+ "Edit tables": "Edit tables",
1534
+ "Minimum role to edit tables": "Minimum role to edit tables",
1535
+ "Edit views": "Edit views",
1536
+ "Minimum role to edit views": "Minimum role to edit views",
1537
+ "Edit pages": "Edit pages",
1538
+ "Minimum role to edit pages": "Minimum role to edit pages",
1539
+ "Edit triggers": "Edit triggers",
1540
+ "Minimum role to edit triggers": "Minimum role to edit triggers",
1541
+ "Development permissions": "Development permissions",
1542
+ "Inspect tables": "Inspect tables",
1543
+ "Minimum role to inspect (see, without editing) tables": "Minimum role to inspect (see, without editing) tables",
1544
+ "Home pages": "Home pages",
1545
+ "The home page is the page that is served when the user visits the home location (/). This can be set for each user role.": "The home page is the page that is served when the user visits the home location (/). This can be set for each user role.",
1546
+ "Trigger %s deleted": "Trigger %s deleted"
1532
1547
  }
package/markup/admin.js CHANGED
@@ -302,14 +302,19 @@ const send_tags_page = (args) => {
302
302
  */
303
303
  const send_events_page = (args) => {
304
304
  const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
305
+ const isUserAdmin = args.req?.user.role_id === 1;
305
306
  return send_settings_page({
306
307
  main_section: "Events",
307
308
  main_section_href: "/events",
308
309
  sub_sections: [
309
310
  { text: "Triggers", href: "/actions" },
310
- { text: "Custom", href: "/eventlog/custom" },
311
- { text: "Settings", href: "/eventlog/settings" },
312
- { text: "Event log", href: "/eventlog" },
311
+ ...(isUserAdmin
312
+ ? [
313
+ { text: "Custom", href: "/eventlog/custom" },
314
+ { text: "Settings", href: "/eventlog/settings" },
315
+ { text: "Event log", href: "/eventlog" },
316
+ ]
317
+ : []),
313
318
  { text: "Workflow runs", href: "/actions/runs" },
314
319
  ...(isRoot ? [{ text: "Crash log", href: "/crashlog" }] : []),
315
320
  ],
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "1.1.1-beta.4",
3
+ "version": "1.1.1-beta.6",
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
9
  "@aws-sdk/client-s3": "^3.451.0",
10
- "@saltcorn/base-plugin": "1.1.1-beta.4",
11
- "@saltcorn/builder": "1.1.1-beta.4",
12
- "@saltcorn/data": "1.1.1-beta.4",
13
- "@saltcorn/admin-models": "1.1.1-beta.4",
14
- "@saltcorn/filemanager": "1.1.1-beta.4",
15
- "@saltcorn/markup": "1.1.1-beta.4",
16
- "@saltcorn/plugins-loader": "1.1.1-beta.4",
17
- "@saltcorn/sbadmin2": "1.1.1-beta.4",
10
+ "@saltcorn/base-plugin": "1.1.1-beta.6",
11
+ "@saltcorn/builder": "1.1.1-beta.6",
12
+ "@saltcorn/data": "1.1.1-beta.6",
13
+ "@saltcorn/admin-models": "1.1.1-beta.6",
14
+ "@saltcorn/filemanager": "1.1.1-beta.6",
15
+ "@saltcorn/markup": "1.1.1-beta.6",
16
+ "@saltcorn/plugins-loader": "1.1.1-beta.6",
17
+ "@saltcorn/sbadmin2": "1.1.1-beta.6",
18
18
  "@socket.io/cluster-adapter": "^0.2.1",
19
19
  "@socket.io/sticky": "^1.0.1",
20
20
  "adm-zip": "0.5.10",
@@ -139,6 +139,9 @@ function valid_js_var_name(s) {
139
139
  if (!s) return false;
140
140
  return !!s.match(/^[a-zA-Z_$][a-zA-Z_$0-9]*$/);
141
141
  }
142
+
143
+ const apply_showif_fetching_urls = new Set();
144
+
142
145
  function apply_showif() {
143
146
  const isNode = getIsNode();
144
147
  $("[data-show-if]").each(function (ix, element) {
@@ -358,6 +361,7 @@ function apply_showif() {
358
361
  ...cache,
359
362
  [qs]: "fetching",
360
363
  });
364
+ apply_showif_fetching_urls.add(`/api/${dynwhere.table}?${qs}`);
361
365
  $.ajax(`/api/${dynwhere.table}?${qs}`)
362
366
  .then((resp) => {
363
367
  if (resp.success) {
@@ -378,7 +382,10 @@ function apply_showif() {
378
382
  });
379
383
  }
380
384
  })
381
- .fail(checkNetworkError);
385
+ .fail(checkNetworkError)
386
+ .always(() => {
387
+ apply_showif_fetching_urls.delete(`/api/${dynwhere.table}?${qs}`);
388
+ });
382
389
  }
383
390
  });
384
391
  $("[data-filter-table]").each(function (ix, element) {
@@ -459,6 +459,18 @@ function saveAndContinueIfValid(e, k, event) {
459
459
  });
460
460
  }
461
461
 
462
+ function saveAndContinueDelayed(e, k, event, retries = 1) {
463
+ //wait for applyShowIf
464
+ setTimeout(() => {
465
+ if (apply_showif_fetching_urls.size > 0 && retries < 5) {
466
+ setTimeout(() => {
467
+ saveAndContinueDelayed(e, k, event, retries + 1);
468
+ }, 200);
469
+ } else saveAndContinue(e, k, event);
470
+ });
471
+ return false;
472
+ }
473
+
462
474
  function saveAndContinue(e, k, event) {
463
475
  if (
464
476
  event &&
package/routes/actions.js CHANGED
@@ -7,6 +7,7 @@
7
7
  const Router = require("express-promise-router");
8
8
  const {
9
9
  isAdmin,
10
+ isAdminOrHasConfigMinRole,
10
11
  error_catcher,
11
12
  addOnDoneRedirect,
12
13
  is_relative_url,
@@ -85,7 +86,7 @@ const {
85
86
  */
86
87
  router.get(
87
88
  "/",
88
- isAdmin,
89
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
89
90
  error_catcher(async (req, res) => {
90
91
  let triggers = await Trigger.findAllWithTableName();
91
92
  let filterOnTag;
@@ -347,7 +348,7 @@ const triggerForm = async (req, trigger) => {
347
348
  */
348
349
  router.get(
349
350
  "/new",
350
- isAdmin,
351
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
351
352
  error_catcher(async (req, res) => {
352
353
  const form = await triggerForm(req);
353
354
  if (req.query.table) {
@@ -388,7 +389,7 @@ router.get(
388
389
  */
389
390
  router.get(
390
391
  "/edit/:id",
391
- isAdmin,
392
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
392
393
  error_catcher(async (req, res) => {
393
394
  const { id } = req.params;
394
395
  const trigger = await Trigger.findOne({ id });
@@ -419,7 +420,7 @@ router.get(
419
420
  */
420
421
  router.post(
421
422
  "/new",
422
- isAdmin,
423
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
423
424
  error_catcher(async (req, res) => {
424
425
  const form = await triggerForm(req);
425
426
 
@@ -462,7 +463,7 @@ router.post(
462
463
  */
463
464
  router.post(
464
465
  "/edit/:id",
465
- isAdmin,
466
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
466
467
  error_catcher(async (req, res) => {
467
468
  const { id } = req.params;
468
469
  const trigger = await Trigger.findOne({ id });
@@ -928,7 +929,7 @@ const getMultiStepForm = async (req, id, table) => {
928
929
  */
929
930
  router.get(
930
931
  "/configure/:idorname",
931
- isAdmin,
932
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
932
933
  error_catcher(async (req, res) => {
933
934
  const { idorname } = req.params;
934
935
  let trigger;
@@ -1130,7 +1131,7 @@ router.get(
1130
1131
  */
1131
1132
  router.post(
1132
1133
  "/configure/:id",
1133
- isAdmin,
1134
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1134
1135
  error_catcher(async (req, res) => {
1135
1136
  const { id } = req.params;
1136
1137
  const trigger = await Trigger.findOne({ id });
@@ -1202,7 +1203,7 @@ router.post(
1202
1203
  */
1203
1204
  router.post(
1204
1205
  "/delete/:id",
1205
- isAdmin,
1206
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1206
1207
  error_catcher(async (req, res) => {
1207
1208
  const { id } = req.params;
1208
1209
  const trigger = await Trigger.findOne({ id });
@@ -1211,6 +1212,7 @@ router.post(
1211
1212
  entity_name: trigger.name,
1212
1213
  });
1213
1214
  await trigger.delete();
1215
+ req.flash("success", req.__(`Trigger %s deleted`, trigger.name));
1214
1216
  res.redirect(`/actions/`);
1215
1217
  })
1216
1218
  );
@@ -1222,7 +1224,7 @@ router.post(
1222
1224
  */
1223
1225
  router.get(
1224
1226
  "/testrun/:id",
1225
- isAdmin,
1227
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1226
1228
  error_catcher(async (req, res) => {
1227
1229
  const { id } = req.params;
1228
1230
  const trigger = await Trigger.findOne({ id });
@@ -1333,7 +1335,7 @@ router.get(
1333
1335
  */
1334
1336
  router.post(
1335
1337
  "/clone/:id",
1336
- isAdmin,
1338
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1337
1339
  error_catcher(async (req, res) => {
1338
1340
  const { id } = req.params;
1339
1341
  const trig = await Trigger.findOne({ id });
@@ -1358,7 +1360,7 @@ router.post(
1358
1360
  */
1359
1361
  router.get(
1360
1362
  "/stepedit/:trigger_id/:step_id?",
1361
- isAdmin,
1363
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1362
1364
  error_catcher(async (req, res) => {
1363
1365
  const { trigger_id, step_id } = req.params;
1364
1366
  const { initial_step, after_step, before_step } = req.query;
@@ -1400,7 +1402,7 @@ router.get(
1400
1402
 
1401
1403
  router.post(
1402
1404
  "/stepedit/:trigger_id",
1403
- isAdmin,
1405
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1404
1406
  error_catcher(async (req, res) => {
1405
1407
  const { trigger_id } = req.params;
1406
1408
  const trigger = await Trigger.findOne({ id: trigger_id });
@@ -1499,7 +1501,7 @@ router.post(
1499
1501
 
1500
1502
  router.post(
1501
1503
  "/gen-copilot/:trigger_id",
1502
- isAdmin,
1504
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1503
1505
  error_catcher(async (req, res) => {
1504
1506
  const { trigger_id } = req.params;
1505
1507
  const trigger = await Trigger.findOne({ id: trigger_id });
@@ -1525,18 +1527,18 @@ router.post(
1525
1527
 
1526
1528
  router.post(
1527
1529
  "/delete-step/:step_id",
1528
- isAdmin,
1530
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1529
1531
  error_catcher(async (req, res) => {
1530
1532
  const { step_id } = req.params;
1531
1533
  const step = await WorkflowStep.findOne({ id: step_id });
1532
- await step.delete();
1534
+ await step.delete(true);
1533
1535
  res.json({ goto: `/actions/configure/${step.trigger_id}` });
1534
1536
  })
1535
1537
  );
1536
1538
 
1537
1539
  router.get(
1538
1540
  "/runs",
1539
- isAdmin,
1541
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1540
1542
  error_catcher(async (req, res) => {
1541
1543
  const trNames = {};
1542
1544
  const { _page, trigger } = req.query;
@@ -1607,7 +1609,7 @@ router.get(
1607
1609
 
1608
1610
  router.get(
1609
1611
  "/run/:id",
1610
- isAdmin,
1612
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1611
1613
  error_catcher(async (req, res) => {
1612
1614
  const { id } = req.params;
1613
1615
 
@@ -1730,7 +1732,7 @@ router.get(
1730
1732
 
1731
1733
  router.post(
1732
1734
  "/delete-run/:id",
1733
- isAdmin,
1735
+ isAdminOrHasConfigMinRole("min_role_edit_triggers"),
1734
1736
  error_catcher(async (req, res) => {
1735
1737
  const { id } = req.params;
1736
1738
 
package/routes/admin.js CHANGED
@@ -13,6 +13,7 @@ const {
13
13
  admin_config_route,
14
14
  get_sys_info,
15
15
  tenant_letsencrypt_name,
16
+ isAdminOrHasConfigMinRole,
16
17
  } = require("./utils.js");
17
18
  const Table = require("@saltcorn/data/models/table");
18
19
  const Plugin = require("@saltcorn/data/models/plugin");
@@ -295,7 +296,7 @@ router.get(
295
296
  error_catcher(async (req, res) => {
296
297
  const fp = path.join(__dirname, "..", "CHANGELOG.md");
297
298
  const fileBuf = await fs.promises.readFile(fp);
298
- const mdContents = fileBuf.toString().replace("# Notable changes\n","");
299
+ const mdContents = fileBuf.toString().replace("# Notable changes\n", "");
299
300
  const markup = md.render(mdContents);
300
301
  res.sendWrap(`What's new in Saltcorn`, { above: [markup] });
301
302
  })
@@ -626,13 +627,36 @@ router.get(
626
627
  })
627
628
  );
628
629
 
630
+ const checkEditPermission = (type, user) => {
631
+ if (user.role_id === 1) return true;
632
+ switch (type) {
633
+ case "view":
634
+ return getState().getConfig("min_role_edit_views", 1) >= user.role_id;
635
+ case "page":
636
+ return getState().getConfig("min_role_edit_pages", 1) >= user.role_id;
637
+ case "trigger":
638
+ return getState().getConfig("min_role_edit_triggers", 1) >= user.role_id;
639
+ default:
640
+ return false;
641
+ }
642
+ };
643
+
629
644
  router.get(
630
645
  "/snapshot-restore/:type/:name",
631
- isAdmin,
646
+ isAdminOrHasConfigMinRole([
647
+ "min_role_edit_views",
648
+ "min_role_edit_pages",
649
+ "min_role_edit_triggers",
650
+ ]),
632
651
  error_catcher(async (req, res) => {
633
652
  const { type, name } = req.params;
634
653
  const snaps = await Snapshot.entity_history(type, name);
635
654
  const locale = getState().getConfig("default_locale", "en");
655
+ const auth = checkEditPermission(type, req.user);
656
+ if (!auth) {
657
+ res.send("Not authorized");
658
+ return;
659
+ }
636
660
  res.set("Page-Title", `Restore ${text(name)}`);
637
661
  res.send(
638
662
  mkTable(
@@ -663,17 +687,26 @@ router.get(
663
687
 
664
688
  router.post(
665
689
  "/snapshot-restore/:type/:name/:id",
666
- isAdmin,
690
+ isAdminOrHasConfigMinRole([
691
+ "min_role_edit_views",
692
+ "min_role_edit_pages",
693
+ "min_role_edit_triggers",
694
+ ]),
667
695
  error_catcher(async (req, res) => {
668
696
  const { type, name, id } = req.params;
669
- const snap = await Snapshot.findOne({ id });
670
- await snap.restore_entity(type, name);
671
- req.flash(
672
- "success",
673
- `${type} ${name} restored to snapshot saved ${moment(
674
- snap.created
675
- ).fromNow()}`
676
- );
697
+ const auth = checkEditPermission(type, req.user);
698
+ if (!auth) {
699
+ req.flash("error", "Not authorized");
700
+ } else {
701
+ const snap = await Snapshot.findOne({ id });
702
+ await snap.restore_entity(type, name);
703
+ req.flash(
704
+ "success",
705
+ `${type} ${name} restored to snapshot saved ${moment(
706
+ snap.created
707
+ ).fromNow()}`
708
+ );
709
+ }
677
710
  res.redirect(
678
711
  type === "trigger"
679
712
  ? `/actions`
@@ -2414,7 +2447,7 @@ router.get(
2414
2447
  class: "form-control",
2415
2448
  name: "appId",
2416
2449
  id: "appIdInputId",
2417
- placeholder: "com.saltcorn.app",
2450
+ placeholder: "com.saltcorn.mobile.app",
2418
2451
  value: builderSettings.appId || "",
2419
2452
  })
2420
2453
  )
@@ -3114,49 +3147,48 @@ router.get(
3114
3147
  ].join("")
3115
3148
  )
3116
3149
  )
3117
- )
3150
+ ),
3118
3151
  // Share Extension provisioning profile
3119
- // disabled for now
3120
- // div(
3121
- // { class: "row pb-3" },
3122
- // div(
3123
- // { class: "col-sm-8" },
3124
- // label(
3125
- // {
3126
- // for: "shareProvisioningProfileInputId",
3127
- // class: "form-label fw-bold",
3128
- // },
3129
- // req.__("Share Extension Provisioning Profile"),
3130
- // a(
3131
- // {
3132
- // href: "javascript:ajax_modal('/admin/help/Provisioning Profile?')",
3133
- // },
3134
- // i({ class: "fas fa-question-circle ps-1" })
3135
- // )
3136
- // ),
3137
- // select(
3138
- // {
3139
- // class: "form-select",
3140
- // name: "shareProvisioningProfile",
3141
- // id: "shareProvisioningProfileInputId",
3142
- // },
3143
- // [
3144
- // option({ value: "" }, ""),
3145
- // ...provisioningFiles.map((file) =>
3146
- // option(
3147
- // {
3148
- // value: file.location,
3149
- // selected:
3150
- // builderSettings.shareProvisioningProfile ===
3151
- // file.location,
3152
- // },
3153
- // file.filename
3154
- // )
3155
- // ),
3156
- // ].join("")
3157
- // )
3158
- // )
3159
- // )
3152
+ div(
3153
+ { class: "row pb-3" },
3154
+ div(
3155
+ { class: "col-sm-8" },
3156
+ label(
3157
+ {
3158
+ for: "shareProvisioningProfileInputId",
3159
+ class: "form-label fw-bold",
3160
+ },
3161
+ req.__("Share Extension Provisioning Profile"),
3162
+ a(
3163
+ {
3164
+ href: "javascript:ajax_modal('/admin/help/Provisioning Profile?')",
3165
+ },
3166
+ i({ class: "fas fa-question-circle ps-1" })
3167
+ )
3168
+ ),
3169
+ select(
3170
+ {
3171
+ class: "form-select",
3172
+ name: "shareProvisioningProfile",
3173
+ id: "shareProvisioningProfileInputId",
3174
+ },
3175
+ [
3176
+ option({ value: "" }, ""),
3177
+ ...provisioningFiles.map((file) =>
3178
+ option(
3179
+ {
3180
+ value: file.location,
3181
+ selected:
3182
+ builderSettings.shareProvisioningProfile ===
3183
+ file.location,
3184
+ },
3185
+ file.filename
3186
+ )
3187
+ ),
3188
+ ].join("")
3189
+ )
3190
+ )
3191
+ )
3160
3192
  )
3161
3193
  )
3162
3194
  ),
@@ -3425,11 +3457,10 @@ router.post(
3425
3457
  keystoreAlias,
3426
3458
  keystorePassword,
3427
3459
  } = req.body;
3428
- // const receiveShareTriggers = Trigger.find({
3429
- // when_trigger: "ReceiveMobileShareData",
3430
- // });
3431
- // disabeling share to support for now
3432
- let allowShareTo = false; // receiveShareTriggers.length > 0;
3460
+ const receiveShareTriggers = Trigger.find({
3461
+ when_trigger: "ReceiveMobileShareData",
3462
+ });
3463
+ let allowShareTo = receiveShareTriggers.length > 0;
3433
3464
  if (allowShareTo && iOSPlatform && !shareProvisioningProfile) {
3434
3465
  allowShareTo = false;
3435
3466
  msgs.push({
@@ -4191,10 +4222,6 @@ admin_config_route({
4191
4222
  { section_header: "Progressive Web Application" },
4192
4223
  "pwa_enabled",
4193
4224
  { name: "pwa_display", showIf: { pwa_enabled: true } },
4194
- {
4195
- name: "pwa_share_to_enabled",
4196
- showIf: { pwa_enabled: true },
4197
- },
4198
4225
  { name: "pwa_set_colors", showIf: { pwa_enabled: true } },
4199
4226
  {
4200
4227
  name: "pwa_theme_color",
@@ -12,6 +12,7 @@ const {
12
12
  badge,
13
13
  } = require("@saltcorn/markup");
14
14
  const { get_base_url } = require("./utils.js");
15
+ const { getState } = require("@saltcorn/data/db/state");
15
16
  const { h4, p, div, a, i, text, span, nbsp } = require("@saltcorn/markup/tags");
16
17
 
17
18
  /**
@@ -57,7 +58,9 @@ const tablesList = async (
57
58
  });
58
59
  const tagsById = {};
59
60
  tags.forEach((t) => (tagsById[t.id] = t));
60
-
61
+ const user_can_edit_tables =
62
+ req.user.role_id === 1 ||
63
+ getState().getConfig("min_role_edit_tables", 1) >= req.user.role_id;
61
64
  const tagBadges = (table) => {
62
65
  const myTags = tag_entries.filter((te) => te.table_id === table.id);
63
66
  return myTags
@@ -95,23 +98,27 @@ const tablesList = async (
95
98
  ? `${getRole(t.min_role_read)} (read only)`
96
99
  : `${getRole(t.min_role_read)}/${getRole(t.min_role_write)}`,
97
100
  },
98
- !tagId
99
- ? {
100
- label: req.__("Delete"),
101
- key: (r) =>
102
- r.name === "users" || r.external
103
- ? ""
104
- : post_delete_btn(`/table/delete/${r.id}`, req, r.name),
105
- }
106
- : {
107
- label: req.__("Remove From Tag"),
108
- key: (r) =>
109
- post_delete_btn(
110
- `/tag-entries/remove/tables/${r.id}/${tagId}`,
111
- req,
112
- `${r.name} from this tag`
113
- ),
114
- },
101
+ ...(user_can_edit_tables
102
+ ? [
103
+ !tagId
104
+ ? {
105
+ label: req.__("Delete"),
106
+ key: (r) =>
107
+ r.name === "users" || r.external
108
+ ? ""
109
+ : post_delete_btn(`/table/delete/${r.id}`, req, r.name),
110
+ }
111
+ : {
112
+ label: req.__("Remove From Tag"),
113
+ key: (r) =>
114
+ post_delete_btn(
115
+ `/tag-entries/remove/tables/${r.id}/${tagId}`,
116
+ req,
117
+ `${r.name} from this tag`
118
+ ),
119
+ },
120
+ ]
121
+ : []),
115
122
  ],
116
123
  tables,
117
124
  {
@@ -286,6 +293,10 @@ const viewsList = async (
286
293
  });
287
294
  const tagsById = {};
288
295
  tags.forEach((t) => (tagsById[t.id] = t));
296
+ const user_can_inspect_tables =
297
+ req.user.role_id === 1 ||
298
+ getState().getConfig("min_role_edit_tables", 1) >= req.user.role_id ||
299
+ getState().getConfig("min_role_inspect_tables", 1) >= req.user.role_id;
289
300
 
290
301
  const tagBadges = (view) => {
291
302
  const myTags = tag_entries.filter((te) => te.view_id === view.id);
@@ -339,7 +350,10 @@ const viewsList = async (
339
350
  : [
340
351
  {
341
352
  label: req.__("Table"),
342
- key: (r) => link(`/table/${r.table}`, r.table),
353
+ key: (r) =>
354
+ user_can_inspect_tables
355
+ ? link(`/table/${r.table}`, r.table)
356
+ : r.table,
343
357
  sortlink: !tagId
344
358
  ? `set_state_field('_sortby', 'table', this)`
345
359
  : undefined,
@@ -634,6 +648,10 @@ const getTriggerList = async (
634
648
  });
635
649
  const tagsById = {};
636
650
  tags.forEach((t) => (tagsById[t.id] = t));
651
+ const user_can_inspect_tables =
652
+ req.user.role_id === 1 ||
653
+ getState().getConfig("min_role_edit_tables", 1) >= req.user.role_id ||
654
+ getState().getConfig("min_role_inspect_tables", 1) >= req.user.role_id;
637
655
 
638
656
  const tagBadges = (trigger) => {
639
657
  const myTags = tag_entries.filter((te) => te.trigger_id === trigger.id);
@@ -682,7 +700,9 @@ const getTriggerList = async (
682
700
  label: req.__("Table or Channel"),
683
701
  key: (r) =>
684
702
  r.table_name
685
- ? a({ href: `/table/${r.table_name}` }, r.table_name)
703
+ ? user_can_inspect_tables
704
+ ? a({ href: `/table/${r.table_name}` }, r.table_name)
705
+ : r.table_name
686
706
  : r.channel,
687
707
  },
688
708
  !tagId