@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 +2 -4
- package/auth/roleadmin.js +17 -4
- package/locales/en.json +3 -1
- package/package.json +8 -8
- package/public/saltcorn-common.js +23 -6
- package/routes/admin.js +29 -5
- package/routes/tables.js +16 -4
- package/serve.js +1 -0
- package/tests/clientjs.test.js +71 -0
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
|
|
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
|
-
...
|
|
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
|
-
|
|
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.
|
|
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.
|
|
10
|
-
"@saltcorn/builder": "0.8.
|
|
11
|
-
"@saltcorn/data": "0.8.
|
|
12
|
-
"@saltcorn/admin-models": "0.8.
|
|
13
|
-
"@saltcorn/filemanager": "0.8.
|
|
14
|
-
"@saltcorn/markup": "0.8.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.
|
|
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
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
|
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",
|
|
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",
|
|
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
|
|
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
|
-
...
|
|
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
package/tests/clientjs.test.js
CHANGED
|
@@ -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
|
+
});
|