@saltcorn/server 0.8.1-rc.3 → 0.8.2-beta.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/auth/admin.js CHANGED
@@ -754,9 +754,7 @@ router.get(
754
754
  for (const table of tables) {
755
755
  if (table.external) continue;
756
756
  const fields = await table.getFields();
757
- const userFields = fields
758
- .filter((f) => f.reftable_name === "users")
759
- .map((f) => ({ value: f.id, label: f.name }));
757
+ const ownership_opts = await table.ownership_options();
760
758
  const form = new Form({
761
759
  action: "/table",
762
760
  noSubmitButton: true,
@@ -771,7 +769,7 @@ router.get(
771
769
  input_type: "select",
772
770
  options: [
773
771
  { value: "", label: req.__("None") },
774
- ...userFields,
772
+ ...ownership_opts,
775
773
  { value: "_formula", label: req.__("Formula") },
776
774
  ],
777
775
  },
package/auth/roleadmin.js CHANGED
@@ -17,7 +17,7 @@ const {
17
17
  } = require("@saltcorn/markup");
18
18
  const { isAdmin, error_catcher, csrfField } = require("../routes/utils");
19
19
  const { getState } = require("@saltcorn/data/db/state");
20
- const { text, form, option, select } = require("@saltcorn/markup/tags");
20
+ const { text, form, option, select, a, i } = require("@saltcorn/markup/tags");
21
21
  const { send_users_page } = require("../markup/admin");
22
22
 
23
23
  /**
@@ -37,8 +37,19 @@ module.exports = router;
37
37
  * @param {object} req
38
38
  * @returns {Form}
39
39
  */
40
- const editRoleLayoutForm = (role, layouts, layout_by_role, req) =>
41
- form(
40
+ const editRoleLayoutForm = (role, layouts, layout_by_role, req) => {
41
+ //console.log(layouts);
42
+ let edit_link = "";
43
+ const current_layout = layout_by_role[role.id] || layouts[layouts.length - 1];
44
+ let plugin = getState().plugins[current_layout];
45
+
46
+ if (plugin?.configuration_workflow)
47
+ edit_link = a(
48
+ { href: `/plugins/configure/${encodeURIComponent(current_layout)}` },
49
+ i({ class: "fa fa-cog ms-2" })
50
+ );
51
+
52
+ return form(
42
53
  {
43
54
  action: `/roleadmin/setrolelayout/${role.id}`,
44
55
  method: "post",
@@ -57,8 +68,10 @@ const editRoleLayoutForm = (role, layouts, layout_by_role, req) =>
57
68
  text(layout)
58
69
  )
59
70
  )
60
- )
71
+ ),
72
+ edit_link
61
73
  );
74
+ };
62
75
 
63
76
  /**
64
77
  *
package/locales/en.json CHANGED
@@ -1080,5 +1080,7 @@
1080
1080
  "Save indicator": "Save indicator",
1081
1081
  "Public cache TTL (minutes)": "Public cache TTL (minutes)",
1082
1082
  "Cache-control max-age for public views and pages. 0 to disable": "Cache-control max-age for public views and pages. 0 to disable",
1083
- "Files accept filter": "Files accept filter"
1083
+ "Files accept filter": "Files accept filter",
1084
+ "User group": "User group",
1085
+ "Add relations to this table in dropdown options for ownership field": "Add relations to this table in dropdown options for ownership field"
1084
1086
  }
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.8.1-rc.3",
3
+ "version": "0.8.2-beta.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.8.1-rc.3",
10
- "@saltcorn/builder": "0.8.1-rc.3",
11
- "@saltcorn/data": "0.8.1-rc.3",
12
- "@saltcorn/admin-models": "0.8.1-rc.3",
13
- "@saltcorn/filemanager": "0.8.1-rc.3",
14
- "@saltcorn/markup": "0.8.1-rc.3",
15
- "@saltcorn/sbadmin2": "0.8.1-rc.3",
9
+ "@saltcorn/base-plugin": "0.8.2-beta.0",
10
+ "@saltcorn/builder": "0.8.2-beta.0",
11
+ "@saltcorn/data": "0.8.2-beta.0",
12
+ "@saltcorn/admin-models": "0.8.2-beta.0",
13
+ "@saltcorn/filemanager": "0.8.2-beta.0",
14
+ "@saltcorn/markup": "0.8.2-beta.0",
15
+ "@saltcorn/sbadmin2": "0.8.2-beta.0",
16
16
  "@socket.io/cluster-adapter": "^0.1.0",
17
17
  "@socket.io/sticky": "^1.0.1",
18
18
  "aws-sdk": "^2.1037.0",
@@ -766,17 +766,34 @@ function unique_field_from_rows(
766
766
  return i;
767
767
  }
768
768
  };
769
+ const char_to_i = (s) => {
770
+ switch (char_type) {
771
+ case "Lowercase Letters":
772
+ return s.charCodeAt(0) - "a".charCodeAt(0);
773
+ case "Uppercase Letters":
774
+ return s.charCodeAt(0) - "A".charCodeAt(0);
775
+ default:
776
+ return +s;
777
+ }
778
+ };
779
+ const value_wspace = `${value}${space ? " " : ""}`;
769
780
  const vals = rows
770
781
  .map((o) => o[field_name])
771
782
  .filter((s) => s.startsWith(value));
783
+
772
784
  if (vals.includes(value) || always_append) {
773
- for (let i = start || 0; i < vals.length + (start || 0) + 2; i++) {
774
- const newname = `${value}${space ? " " : ""}${gen_char(i)}`;
775
- if (!vals.includes(newname)) {
776
- $("#" + id).val(newname);
777
- return;
778
- }
785
+ let newname;
786
+ const stripped = vals
787
+ .filter((v) => v !== value)
788
+ .map((s) => s.replace(value_wspace, ""))
789
+ .sort();
790
+ if (stripped.length === 0) newname = `${value_wspace}${gen_char(start)}`;
791
+ else {
792
+ const last_i = char_to_i(stripped[stripped.length - 1]);
793
+
794
+ newname = `${value_wspace}${gen_char(last_i + 1)}`;
779
795
  }
796
+ $("#" + id).val(newname);
780
797
  }
781
798
  }
782
799
 
package/routes/admin.js CHANGED
@@ -1546,7 +1546,22 @@ router.get(
1546
1546
  const checkFiles = async (outDir, fileNames) => {
1547
1547
  const rootFolder = await File.rootFolder();
1548
1548
  const mobile_app_dir = path.join(rootFolder.location, "mobile_app", outDir);
1549
- const entries = fs.readdirSync(mobile_app_dir);
1549
+ const unsafeFiles = await Promise.all(
1550
+ fs
1551
+ .readdirSync(mobile_app_dir)
1552
+ .map(
1553
+ async (outFile) => await File.from_file_on_disk(outFile, mobile_app_dir)
1554
+ )
1555
+ );
1556
+ const entries = unsafeFiles
1557
+ .filter(
1558
+ (file) =>
1559
+ file.user_id &&
1560
+ !isNaN(file.user_id) &&
1561
+ file.min_role_read &&
1562
+ !isNaN(file.min_role_read)
1563
+ )
1564
+ .map((file) => file.filename);
1550
1565
  return fileNames.some((fileName) => entries.indexOf(fileName) >= 0);
1551
1566
  };
1552
1567
 
@@ -1639,6 +1654,8 @@ router.post(
1639
1654
  buildDir,
1640
1655
  "-b",
1641
1656
  `${os.userInfo().homedir}/mobile_app_build`,
1657
+ "-u",
1658
+ req.user.email, // ensured by isAdmin
1642
1659
  ];
1643
1660
  if (useDocker) spawnParams.push("-d");
1644
1661
  if (androidPlatform) spawnParams.push("-p", "android");
@@ -1673,29 +1690,36 @@ router.post(
1673
1690
  // console.log(data.toString());
1674
1691
  childOutputs.push(data ? data.toString() : req.__("An error occurred"));
1675
1692
  });
1676
- child.on("exit", async function (exitCode, signal) {
1693
+ child.on("exit", (exitCode, signal) => {
1677
1694
  const logFile = exitCode === 0 ? "logs.txt" : "error_logs.txt";
1678
1695
  fs.writeFile(
1679
1696
  path.join(buildDir, logFile),
1680
1697
  childOutputs.join("\n"),
1681
- (error) => {
1698
+ async (error) => {
1682
1699
  if (error) {
1683
1700
  console.log(`unable to write '${logFile}' to '${buildDir}'`);
1684
1701
  console.log(error);
1702
+ } else {
1703
+ // no transaction, '/build-mobile-app/finished' filters for valid attributes
1704
+ await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
1685
1705
  }
1686
1706
  }
1687
1707
  );
1688
1708
  });
1689
- child.on("error", function (msg) {
1709
+ child.on("error", (msg) => {
1690
1710
  const message = msg.message ? msg.message : msg.code;
1691
1711
  const stack = msg.stack ? msg.stack : "";
1712
+ const logFile = "error_logs.txt";
1692
1713
  fs.writeFile(
1693
1714
  path.join(buildDir, "error_logs.txt"),
1694
1715
  [message, stack].join("\n"),
1695
- (error) => {
1716
+ async (error) => {
1696
1717
  if (error) {
1697
1718
  console.log(`unable to write logFile to '${buildDir}'`);
1698
1719
  console.log(error);
1720
+ } else {
1721
+ // no transaction, '/build-mobile-app/finished' filters for valid attributes
1722
+ await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
1699
1723
  }
1700
1724
  }
1701
1725
  );
package/routes/tables.js CHANGED
@@ -73,9 +73,7 @@ const tableForm = async (table, req) => {
73
73
  value: r.id,
74
74
  label: r.role,
75
75
  }));
76
- const userFields = fields
77
- .filter((f) => f.reftable_name === "users")
78
- .map((f) => ({ value: f.id, label: f.name }));
76
+ const ownership_opts = await table.ownership_options();
79
77
  const form = new Form({
80
78
  action: "/table",
81
79
  noSubmitButton: true,
@@ -92,7 +90,7 @@ const tableForm = async (table, req) => {
92
90
  input_type: "select",
93
91
  options: [
94
92
  { value: "", label: req.__("None") },
95
- ...userFields,
93
+ ...ownership_opts,
96
94
  { value: "_formula", label: req.__("Formula") },
97
95
  ],
98
96
  },
@@ -109,6 +107,14 @@ const tableForm = async (table, req) => {
109
107
  .join(", "),
110
108
  showIf: { ownership_field_id: "_formula" },
111
109
  },
110
+ {
111
+ label: req.__("User group"),
112
+ sublabel: req.__(
113
+ "Add relations to this table in dropdown options for ownership field"
114
+ ),
115
+ name: "is_user_group",
116
+ type: "Bool",
117
+ },
112
118
  ]
113
119
  : []),
114
120
  // description of table
@@ -898,6 +904,12 @@ router.post(
898
904
  notify = req.__(`Invalid ownership formula: %s`, fmlValidRes);
899
905
  hasError = true;
900
906
  }
907
+ } else if (
908
+ typeof rest.ownership_field_id === "string" &&
909
+ rest.ownership_field_id.startsWith("Fml:")
910
+ ) {
911
+ rest.ownership_formula = rest.ownership_field_id.replace("Fml:", "");
912
+ rest.ownership_field_id = null;
901
913
  } else rest.ownership_formula = null;
902
914
  await table.update(rest);
903
915
 
package/serve.js CHANGED
@@ -291,6 +291,7 @@ module.exports =
291
291
  auto_backup_now,
292
292
  take_snapshot,
293
293
  });
294
+ require("./systemd")({ port });
294
295
  }
295
296
  Trigger.emitEvent("Startup");
296
297
  } else {
@@ -64,3 +64,74 @@ test("updateQueryStringParameter hash", () => {
64
64
  "/foo?name=Bar#Baz"
65
65
  );
66
66
  });
67
+ test("unique_field_from_rows test", () => {
68
+ $("body").append(`<input id="mkuniq" value="bar"></div>`);
69
+ unique_field_from_rows(
70
+ [{ foo: "bar" }, { foo: "bar0" }],
71
+ "mkuniq",
72
+ "foo",
73
+ false,
74
+ 0,
75
+ false,
76
+ "Digits",
77
+ "bar"
78
+ );
79
+ expect($("#mkuniq").val()).toBe("bar1");
80
+
81
+ $("body").append(`<input id="mkuniq" value="bar"></div>`);
82
+ unique_field_from_rows(
83
+ [{ foo: "bar" }],
84
+ "mkuniq",
85
+ "foo",
86
+ false,
87
+ 9,
88
+ false,
89
+ "Digits",
90
+ "bar"
91
+ );
92
+ expect($("#mkuniq").val()).toBe("bar9");
93
+
94
+ $("body").append(`<input id="mkuniq" value="bar"></div>`);
95
+ unique_field_from_rows(
96
+ [{ foo: "bar0" }],
97
+ "mkuniq",
98
+ "foo",
99
+ false,
100
+ 9,
101
+ false,
102
+ "Digits",
103
+ "bar"
104
+ );
105
+ expect($("#mkuniq").val()).toBe("bar9");
106
+
107
+ $("#mkuniq").val("bar");
108
+ unique_field_from_rows([], "mkuniq", "foo", false, 0, false, "Digits", "bar");
109
+ expect($("#mkuniq").val()).toBe("bar");
110
+
111
+ $("body").append(`<input id="mkuniq" value="bar"></div>`);
112
+ unique_field_from_rows(
113
+ [{ foo: "bar" }, { foo: "bar A" }],
114
+ "mkuniq",
115
+ "foo",
116
+ true,
117
+ 0,
118
+ false,
119
+ "Uppercase Letters",
120
+ "bar"
121
+ );
122
+ expect($("#mkuniq").val()).toBe("bar B");
123
+
124
+ //skips blanks
125
+ $("body").append(`<input id="mkuniq" value="bar"></div>`);
126
+ unique_field_from_rows(
127
+ [{ foo: "bar" }, { foo: "bar0" }, { foo: "bar1" }, { foo: "bar3" }],
128
+ "mkuniq",
129
+ "foo",
130
+ false,
131
+ 0,
132
+ false,
133
+ "Digits",
134
+ "bar"
135
+ );
136
+ expect($("#mkuniq").val()).toBe("bar4");
137
+ });