@saltcorn/server 0.9.6-beta.1 → 0.9.6-beta.10
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 +54 -53
- package/load_plugins.js +5 -1
- package/locales/en.json +6 -1
- package/locales/it.json +3 -2
- package/markup/admin.js +1 -0
- package/markup/forms.js +5 -1
- package/package.json +9 -9
- package/public/saltcorn-common.js +80 -18
- package/public/saltcorn.css +8 -0
- package/public/saltcorn.js +8 -2
- package/routes/actions.js +6 -0
- package/routes/fields.js +62 -41
- package/routes/index.js +2 -0
- package/routes/pageedit.js +18 -13
- package/routes/plugins.js +6 -1
- package/routes/registry.js +289 -0
- package/routes/tables.js +4 -0
- package/routes/utils.js +5 -2
- package/routes/view.js +1 -1
- package/routes/viewedit.js +11 -7
- package/tests/page.test.js +2 -2
- package/tests/plugins.test.js +2 -0
package/auth/admin.js
CHANGED
|
@@ -99,6 +99,10 @@ const getUserFields = async (req) => {
|
|
|
99
99
|
input_type: "email",
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
+
if (f.name === "role_id") {
|
|
103
|
+
f.fieldview = "role_select";
|
|
104
|
+
await f.fill_fkey_options();
|
|
105
|
+
}
|
|
102
106
|
}
|
|
103
107
|
return userFields;
|
|
104
108
|
};
|
|
@@ -110,65 +114,59 @@ const getUserFields = async (req) => {
|
|
|
110
114
|
* @param {User} user
|
|
111
115
|
* @returns {Promise<Form>}
|
|
112
116
|
*/
|
|
113
|
-
const userForm =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
117
|
+
const userForm = async (req, user) => {
|
|
118
|
+
const roleField = new Field({
|
|
119
|
+
label: req.__("Role"),
|
|
120
|
+
name: "role_id",
|
|
121
|
+
type: "Key",
|
|
122
|
+
reftable_name: "roles",
|
|
123
|
+
});
|
|
124
|
+
const roles = (await User.get_roles()).filter((r) => r.role !== "public");
|
|
125
|
+
roleField.options = roles.map((r) => ({ label: r.role, value: r.id }));
|
|
126
|
+
const can_reset = getState().getConfig("smtp_host", "") !== "";
|
|
127
|
+
const userFields = await getUserFields(req);
|
|
128
|
+
const form = new Form({
|
|
129
|
+
fields: userFields,
|
|
130
|
+
action: "/useradmin/save",
|
|
131
|
+
submitLabel: user ? req.__("Save") : req.__("Create"),
|
|
132
|
+
});
|
|
133
|
+
if (!user) {
|
|
134
|
+
form.fields.push(
|
|
135
|
+
new Field({
|
|
136
|
+
label: req.__("Set random password"),
|
|
137
|
+
name: "rnd_password",
|
|
138
|
+
type: "Bool",
|
|
139
|
+
default: true,
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
form.fields.push(
|
|
143
|
+
new Field({
|
|
144
|
+
label: req.__("Password"),
|
|
145
|
+
name: "password",
|
|
146
|
+
input_type: "password",
|
|
147
|
+
showIf: { rnd_password: false },
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
can_reset &&
|
|
135
151
|
form.fields.push(
|
|
136
152
|
new Field({
|
|
137
|
-
label: req.__("
|
|
138
|
-
name: "
|
|
153
|
+
label: req.__("Send password reset email"),
|
|
154
|
+
name: "send_pwreset_email",
|
|
139
155
|
type: "Bool",
|
|
140
156
|
default: true,
|
|
157
|
+
showIf: { rnd_password: true },
|
|
141
158
|
})
|
|
142
159
|
);
|
|
143
|
-
form.fields.push(
|
|
144
|
-
new Field({
|
|
145
|
-
label: req.__("Password"),
|
|
146
|
-
name: "password",
|
|
147
|
-
input_type: "password",
|
|
148
|
-
showIf: { rnd_password: false },
|
|
149
|
-
})
|
|
150
|
-
);
|
|
151
|
-
can_reset &&
|
|
152
|
-
form.fields.push(
|
|
153
|
-
new Field({
|
|
154
|
-
label: req.__("Send password reset email"),
|
|
155
|
-
name: "send_pwreset_email",
|
|
156
|
-
type: "Bool",
|
|
157
|
-
default: true,
|
|
158
|
-
showIf: { rnd_password: true },
|
|
159
|
-
})
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
if (user) {
|
|
163
|
-
form.hidden("id");
|
|
164
|
-
form.values = user;
|
|
165
|
-
delete form.values.password;
|
|
166
|
-
} else {
|
|
167
|
-
form.values.role_id = roles[roles.length - 1].id;
|
|
168
|
-
}
|
|
169
|
-
return form;
|
|
170
160
|
}
|
|
171
|
-
)
|
|
161
|
+
if (user) {
|
|
162
|
+
form.hidden("id");
|
|
163
|
+
form.values = user;
|
|
164
|
+
delete form.values.password;
|
|
165
|
+
} else {
|
|
166
|
+
form.values.role_id = roles[roles.length - 1].id;
|
|
167
|
+
}
|
|
168
|
+
return form;
|
|
169
|
+
};
|
|
172
170
|
|
|
173
171
|
/**
|
|
174
172
|
* Dropdown for User Info in left menu
|
|
@@ -531,7 +529,10 @@ router.post(
|
|
|
531
529
|
if (restart_required)
|
|
532
530
|
res.json({
|
|
533
531
|
success: "ok",
|
|
534
|
-
notify:
|
|
532
|
+
notify:
|
|
533
|
+
req.__("Restart required for changes to take effect.") +
|
|
534
|
+
" " +
|
|
535
|
+
a({ href: "/admin/system" }, req.__("Restart here")),
|
|
535
536
|
});
|
|
536
537
|
else res.json({ success: "ok" });
|
|
537
538
|
}
|
package/load_plugins.js
CHANGED
|
@@ -141,7 +141,11 @@ const loadAndSaveNewPlugin = async (
|
|
|
141
141
|
const existing = await Plugin.findOne({ location: loc });
|
|
142
142
|
if (!existing && loc !== plugin.location) {
|
|
143
143
|
await loadAndSaveNewPlugin(
|
|
144
|
-
new Plugin({
|
|
144
|
+
new Plugin({
|
|
145
|
+
name: loc.replace("@saltcorn/", ""),
|
|
146
|
+
location: loc,
|
|
147
|
+
source: "npm",
|
|
148
|
+
}),
|
|
145
149
|
force,
|
|
146
150
|
noSignalOrDB
|
|
147
151
|
);
|
package/locales/en.json
CHANGED
|
@@ -1425,5 +1425,10 @@
|
|
|
1425
1425
|
"Keystore Alias": "Keystore Alias",
|
|
1426
1426
|
"Keystore Password": "Keystore Password",
|
|
1427
1427
|
"xcodebuild": "xcodebuild",
|
|
1428
|
-
"Provisioning Profile": "Provisioning Profile"
|
|
1428
|
+
"Provisioning Profile": "Provisioning Profile",
|
|
1429
|
+
"Registry editor": "Registry editor",
|
|
1430
|
+
"A short name that will be in the page URL": "A short name that will be in the page URL",
|
|
1431
|
+
"A longer description that is not visible but appears in the page header and is indexed by search engines": "A longer description that is not visible but appears in the page header and is indexed by search engines",
|
|
1432
|
+
"User role required to access page": "User role required to access page",
|
|
1433
|
+
"Example: <code>`/view/TheOtherView?id=${id}`</code>": "Example: <code>`/view/TheOtherView?id=${id}`</code>"
|
|
1429
1434
|
}
|
package/locales/it.json
CHANGED
|
@@ -518,5 +518,6 @@
|
|
|
518
518
|
"Save before going back": "Save before going back",
|
|
519
519
|
"Reload after going back": "Reload after going back",
|
|
520
520
|
"Steps to go back": "Steps to go back",
|
|
521
|
-
"%s configuration": "%s configuration"
|
|
522
|
-
|
|
521
|
+
"%s configuration": "%s configuration",
|
|
522
|
+
"The current theme has no user specific settings": "The current theme has no user specific settings"
|
|
523
|
+
}
|
package/markup/admin.js
CHANGED
|
@@ -241,6 +241,7 @@ const send_infoarch_page = (args) => {
|
|
|
241
241
|
{ text: "Pagegroups", href: "/page_group/settings" },
|
|
242
242
|
{ text: "Tags", href: "/tag" },
|
|
243
243
|
{ text: "Diagram", href: "/diagram" },
|
|
244
|
+
{ text: "Registry editor", href: "/registry-editor" },
|
|
244
245
|
],
|
|
245
246
|
...args,
|
|
246
247
|
});
|
package/markup/forms.js
CHANGED
|
@@ -28,10 +28,14 @@ const editRoleForm = ({ url, current_role, roles, req }) =>
|
|
|
28
28
|
{
|
|
29
29
|
action: url,
|
|
30
30
|
method: "post",
|
|
31
|
+
onchange: "saveAndContinue(this)",
|
|
31
32
|
},
|
|
32
33
|
csrfField(req),
|
|
33
34
|
select(
|
|
34
|
-
{
|
|
35
|
+
{
|
|
36
|
+
name: "role",
|
|
37
|
+
class: "w-unset form-select form-select-sm",
|
|
38
|
+
},
|
|
35
39
|
roles.map((role) =>
|
|
36
40
|
option(
|
|
37
41
|
{
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.9.6-beta.
|
|
3
|
+
"version": "0.9.6-beta.10",
|
|
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": "0.9.6-beta.
|
|
11
|
-
"@saltcorn/builder": "0.9.6-beta.
|
|
12
|
-
"@saltcorn/data": "0.9.6-beta.
|
|
13
|
-
"@saltcorn/admin-models": "0.9.6-beta.
|
|
14
|
-
"@saltcorn/filemanager": "0.9.6-beta.
|
|
15
|
-
"@saltcorn/markup": "0.9.6-beta.
|
|
16
|
-
"@saltcorn/plugins-loader": "0.9.6-beta.
|
|
17
|
-
"@saltcorn/sbadmin2": "0.9.6-beta.
|
|
10
|
+
"@saltcorn/base-plugin": "0.9.6-beta.10",
|
|
11
|
+
"@saltcorn/builder": "0.9.6-beta.10",
|
|
12
|
+
"@saltcorn/data": "0.9.6-beta.10",
|
|
13
|
+
"@saltcorn/admin-models": "0.9.6-beta.10",
|
|
14
|
+
"@saltcorn/filemanager": "0.9.6-beta.10",
|
|
15
|
+
"@saltcorn/markup": "0.9.6-beta.10",
|
|
16
|
+
"@saltcorn/plugins-loader": "0.9.6-beta.10",
|
|
17
|
+
"@saltcorn/sbadmin2": "0.9.6-beta.10",
|
|
18
18
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
19
19
|
"@socket.io/sticky": "^1.0.1",
|
|
20
20
|
"adm-zip": "0.5.10",
|
|
@@ -179,14 +179,19 @@ function apply_showif() {
|
|
|
179
179
|
is_or ? "&_or_field=" + k : ""
|
|
180
180
|
}`;
|
|
181
181
|
};
|
|
182
|
-
const qss = Object.entries(dynwhere.whereParsed).map(kvToQs);
|
|
182
|
+
const qss = Object.entries(dynwhere.whereParsed).map((kv) => kvToQs(kv));
|
|
183
|
+
if (dynwhere.existingValue) {
|
|
184
|
+
qss.push(`id=${dynwhere.existingValue}`);
|
|
185
|
+
qss.push(`_or_field=id`);
|
|
186
|
+
}
|
|
183
187
|
if (dynwhere.dereference) {
|
|
184
188
|
if (Array.isArray(dynwhere.dereference))
|
|
185
189
|
qss.push(...dynwhere.dereference.map((d) => `dereference=${d}`));
|
|
186
190
|
else qss.push(`dereference=${dynwhere.dereference}`);
|
|
187
191
|
}
|
|
188
192
|
const qs = qss.join("&");
|
|
189
|
-
|
|
193
|
+
let current = e.attr("data-selected");
|
|
194
|
+
if (current === "null") current = null;
|
|
190
195
|
e.change(function (ec) {
|
|
191
196
|
e.attr("data-selected", ec.target.value);
|
|
192
197
|
});
|
|
@@ -195,12 +200,14 @@ function apply_showif() {
|
|
|
195
200
|
if (currentOptionsSet === qs) return;
|
|
196
201
|
|
|
197
202
|
const activate = (success, qs) => {
|
|
203
|
+
//re-fetch current, because it may have changed
|
|
204
|
+
let current = e.attr("data-selected");
|
|
205
|
+
if (current === "null") current = null;
|
|
198
206
|
if (e.prop("data-fetch-options-current-set") === qs) return;
|
|
199
207
|
e.empty();
|
|
200
208
|
e.prop("data-fetch-options-current-set", qs);
|
|
201
209
|
const toAppend = [];
|
|
202
|
-
|
|
203
|
-
toAppend.push({ label: dynwhere.neutral_label || "", value: "" });
|
|
210
|
+
|
|
204
211
|
let currentDataOption = undefined;
|
|
205
212
|
const dataOptions = [];
|
|
206
213
|
//console.log(success);
|
|
@@ -231,13 +238,24 @@ function apply_showif() {
|
|
|
231
238
|
? 1
|
|
232
239
|
: -1
|
|
233
240
|
);
|
|
241
|
+
if (!dynwhere.required)
|
|
242
|
+
toAppend.unshift({ label: dynwhere.neutral_label || "", value: "" });
|
|
243
|
+
if (dynwhere.required && dynwhere.placeholder)
|
|
244
|
+
toAppend.unshift({
|
|
245
|
+
disabled: true,
|
|
246
|
+
label: dynwhere.placeholder,
|
|
247
|
+
value: "",
|
|
248
|
+
selected: !current,
|
|
249
|
+
});
|
|
234
250
|
e.html(
|
|
235
251
|
toAppend
|
|
236
252
|
.map(
|
|
237
|
-
({ label, value, selected }) =>
|
|
253
|
+
({ label, value, selected, disabled }) =>
|
|
238
254
|
`<option${selected ? ` selected` : ""}${
|
|
239
|
-
|
|
240
|
-
}
|
|
255
|
+
disabled ? ` disabled` : ""
|
|
256
|
+
}${typeof value !== "undefined" ? ` value="${value}"` : ""}>${
|
|
257
|
+
label || ""
|
|
258
|
+
}</option>`
|
|
241
259
|
)
|
|
242
260
|
.join("")
|
|
243
261
|
);
|
|
@@ -428,7 +446,7 @@ function get_form_record(e_in, select_labels) {
|
|
|
428
446
|
|
|
429
447
|
const e = e_in.viewname
|
|
430
448
|
? $(`form[data-viewname="${e_in.viewname}"]`)
|
|
431
|
-
: e_in.closest(".form-namespace");
|
|
449
|
+
: $(e_in).closest(".form-namespace");
|
|
432
450
|
|
|
433
451
|
const form = $(e).closest("form");
|
|
434
452
|
|
|
@@ -462,6 +480,38 @@ function get_form_record(e_in, select_labels) {
|
|
|
462
480
|
rec[name] = f(rec[name], $this);
|
|
463
481
|
}
|
|
464
482
|
});
|
|
483
|
+
|
|
484
|
+
const joinFieldsStr =
|
|
485
|
+
typeof e_in !== "string" && $(e_in).attr("data-show-if-joinfields");
|
|
486
|
+
if (joinFieldsStr) {
|
|
487
|
+
const joinFields = JSON.parse(decodeURIComponent(joinFieldsStr));
|
|
488
|
+
|
|
489
|
+
const joinVals = $(e_in).prop("data-join-values");
|
|
490
|
+
const kvals = $(e_in).prop("data-join-key-values") || {};
|
|
491
|
+
let differentKeys = false;
|
|
492
|
+
for (const { ref } of joinFields) {
|
|
493
|
+
if (rec[ref] != kvals[ref]) differentKeys = true;
|
|
494
|
+
}
|
|
495
|
+
if (!joinVals || differentKeys) {
|
|
496
|
+
$(e_in).prop("data-join-values", {});
|
|
497
|
+
const keyVals = {};
|
|
498
|
+
for (const { ref, target, refTable } of joinFields) {
|
|
499
|
+
keyVals[ref] = rec[ref];
|
|
500
|
+
$.ajax(`/api/${refTable}?id=${rec[ref]}`, {
|
|
501
|
+
success: (val) => {
|
|
502
|
+
const jvs = $(e_in).prop("data-join-values") || {};
|
|
503
|
+
|
|
504
|
+
jvs[ref] = val.success[0];
|
|
505
|
+
$(e_in).prop("data-join-values", jvs);
|
|
506
|
+
apply_showif();
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
$(e_in).prop("data-join-key-values", keyVals);
|
|
511
|
+
} else if (joinFieldsStr) {
|
|
512
|
+
Object.assign(rec, joinVals);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
465
515
|
return rec;
|
|
466
516
|
}
|
|
467
517
|
function showIfFormulaInputs(e, fml) {
|
|
@@ -1219,7 +1269,13 @@ function restore_old_button(btnId) {
|
|
|
1219
1269
|
btn.removeData("old-text");
|
|
1220
1270
|
}
|
|
1221
1271
|
|
|
1222
|
-
async function common_done(res,
|
|
1272
|
+
async function common_done(res, viewnameOrElem, isWeb = true) {
|
|
1273
|
+
const viewname =
|
|
1274
|
+
typeof viewnameOrElem === "string"
|
|
1275
|
+
? viewnameOrElem
|
|
1276
|
+
: $(viewnameOrElem)
|
|
1277
|
+
.closest("[data-sc-embed-viewname]")
|
|
1278
|
+
.attr("data-sc-embed-viewname");
|
|
1223
1279
|
if (window._sc_loglevel > 4)
|
|
1224
1280
|
console.log("ajax result directives", viewname, res);
|
|
1225
1281
|
const handle = async (element, fn) => {
|
|
@@ -1231,15 +1287,15 @@ async function common_done(res, viewname, isWeb = true) {
|
|
|
1231
1287
|
if (res.row && res.field_names) {
|
|
1232
1288
|
const f = new Function(`viewname, row, {${res.field_names}}`, s);
|
|
1233
1289
|
const evalres = await f(viewname, res.row, res.row);
|
|
1234
|
-
if (evalres) await common_done(evalres,
|
|
1290
|
+
if (evalres) await common_done(evalres, viewnameOrElem, isWeb);
|
|
1235
1291
|
} else if (res.row) {
|
|
1236
1292
|
const f = new Function(`viewname, row`, s);
|
|
1237
1293
|
const evalres = await f(viewname, res.row);
|
|
1238
|
-
if (evalres) await common_done(evalres,
|
|
1294
|
+
if (evalres) await common_done(evalres, viewnameOrElem, isWeb);
|
|
1239
1295
|
} else {
|
|
1240
1296
|
const f = new Function(`viewname`, s);
|
|
1241
1297
|
const evalres = await f(viewname);
|
|
1242
|
-
if (evalres) await common_done(evalres,
|
|
1298
|
+
if (evalres) await common_done(evalres, viewnameOrElem, isWeb);
|
|
1243
1299
|
}
|
|
1244
1300
|
};
|
|
1245
1301
|
if (res.notify) await handle(res.notify, notifyAlert);
|
|
@@ -1252,9 +1308,10 @@ async function common_done(res, viewname, isWeb = true) {
|
|
|
1252
1308
|
notifyAlert({ type: "success", text: text })
|
|
1253
1309
|
);
|
|
1254
1310
|
if (res.set_fields && (viewname || res.set_fields._viewname)) {
|
|
1255
|
-
const form =
|
|
1256
|
-
|
|
1257
|
-
|
|
1311
|
+
const form =
|
|
1312
|
+
typeof viewnameOrElem === "string" || res.set_fields._viewname
|
|
1313
|
+
? $(`form[data-viewname="${res.set_fields._viewname || viewname}"]`)
|
|
1314
|
+
: $(viewnameOrElem).closest("form[data-viewname]");
|
|
1258
1315
|
if (form.length === 0 && set_state_fields) {
|
|
1259
1316
|
// assume this is a filter
|
|
1260
1317
|
set_state_fields(
|
|
@@ -1279,9 +1336,14 @@ async function common_done(res, viewname, isWeb = true) {
|
|
|
1279
1336
|
if (input.attr("type") === "checkbox")
|
|
1280
1337
|
input.prop("checked", res.set_fields[k]);
|
|
1281
1338
|
else input.val(res.set_fields[k]);
|
|
1339
|
+
if (input.attr("data-selected")) {
|
|
1340
|
+
input.attr("data-selected", res.set_fields[k]);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1282
1343
|
input.trigger("set_form_field");
|
|
1283
1344
|
});
|
|
1284
1345
|
}
|
|
1346
|
+
form.trigger("change");
|
|
1285
1347
|
}
|
|
1286
1348
|
|
|
1287
1349
|
if (res.download) {
|
|
@@ -1440,10 +1502,10 @@ const columnSummary = (col) => {
|
|
|
1440
1502
|
};
|
|
1441
1503
|
|
|
1442
1504
|
function submitWithEmptyAction(form) {
|
|
1443
|
-
var formAction = form.action;
|
|
1444
|
-
form.action
|
|
1505
|
+
var formAction = form.getAttribute("action");
|
|
1506
|
+
form.setAttribute("action", "javascript:void(0)");
|
|
1445
1507
|
form.submit();
|
|
1446
|
-
form.action
|
|
1508
|
+
form.setAttribute("action", formAction);
|
|
1447
1509
|
}
|
|
1448
1510
|
|
|
1449
1511
|
function unique_field_from_rows(
|
package/public/saltcorn.css
CHANGED
package/public/saltcorn.js
CHANGED
|
@@ -236,7 +236,13 @@ function reset_spinners() {
|
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
function view_post(
|
|
239
|
+
function view_post(viewnameOrElem, route, data, onDone, sendState) {
|
|
240
|
+
const viewname =
|
|
241
|
+
typeof viewnameOrElem === "string"
|
|
242
|
+
? viewnameOrElem
|
|
243
|
+
: $(viewnameOrElem)
|
|
244
|
+
.closest("[data-sc-embed-viewname]")
|
|
245
|
+
.attr("data-sc-embed-viewname");
|
|
240
246
|
const query = sendState
|
|
241
247
|
? `?${new URL(get_current_state_url()).searchParams.toString()}`
|
|
242
248
|
: "";
|
|
@@ -254,7 +260,7 @@ function view_post(viewname, route, data, onDone, sendState) {
|
|
|
254
260
|
})
|
|
255
261
|
.done(function (res) {
|
|
256
262
|
if (onDone) onDone(res);
|
|
257
|
-
ajax_done(res,
|
|
263
|
+
ajax_done(res, viewnameOrElem);
|
|
258
264
|
reset_spinners();
|
|
259
265
|
})
|
|
260
266
|
.fail(function (res) {
|
package/routes/actions.js
CHANGED
|
@@ -375,6 +375,7 @@ router.get(
|
|
|
375
375
|
|
|
376
376
|
const form = await triggerForm(req, trigger);
|
|
377
377
|
form.values = trigger;
|
|
378
|
+
form.onChange = `saveAndContinue(this)`;
|
|
378
379
|
send_events_page({
|
|
379
380
|
res,
|
|
380
381
|
req,
|
|
@@ -383,6 +384,7 @@ router.get(
|
|
|
383
384
|
contents: {
|
|
384
385
|
type: "card",
|
|
385
386
|
title: req.__("Edit trigger %s", id),
|
|
387
|
+
titleAjaxIndicator: true,
|
|
386
388
|
contents: renderForm(form, req.csrfToken()),
|
|
387
389
|
},
|
|
388
390
|
});
|
|
@@ -464,6 +466,10 @@ router.post(
|
|
|
464
466
|
...form.values.configuration,
|
|
465
467
|
};
|
|
466
468
|
await Trigger.update(trigger.id, form.values); //{configuration: form.values});
|
|
469
|
+
if (req.xhr) {
|
|
470
|
+
res.json({ success: "ok" });
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
467
473
|
req.flash("success", req.__("Action information saved"));
|
|
468
474
|
res.redirect(`/actions/`);
|
|
469
475
|
}
|
package/routes/fields.js
CHANGED
|
@@ -255,6 +255,7 @@ const fieldFlow = (req) =>
|
|
|
255
255
|
expression = "__aggregation";
|
|
256
256
|
attributes.agg_relation = context.agg_relation;
|
|
257
257
|
attributes.agg_field = context.agg_field;
|
|
258
|
+
attributes.agg_order_by = context.agg_order_by;
|
|
258
259
|
attributes.aggwhere = context.aggwhere;
|
|
259
260
|
attributes.aggregate = context.aggregate;
|
|
260
261
|
const [table, ref] = context.agg_relation.split(".");
|
|
@@ -435,46 +436,64 @@ const fieldFlow = (req) =>
|
|
|
435
436
|
|
|
436
437
|
const { child_field_list, child_relations } =
|
|
437
438
|
await table.get_child_relations(true);
|
|
438
|
-
const agg_field_opts =
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
439
|
+
const agg_field_opts = [];
|
|
440
|
+
const agg_order_opts = [];
|
|
441
|
+
child_relations.forEach(({ table, key_field, through }) => {
|
|
442
|
+
const aggKey =
|
|
443
|
+
(through ? `${through.name}->` : "") +
|
|
444
|
+
`${table.name}.${key_field.name}`;
|
|
445
|
+
aggStatOptions[aggKey] = [
|
|
446
|
+
"Count",
|
|
447
|
+
"CountUnique",
|
|
448
|
+
"Avg",
|
|
449
|
+
"Sum",
|
|
450
|
+
"Max",
|
|
451
|
+
"Min",
|
|
452
|
+
"Array_Agg",
|
|
453
|
+
];
|
|
454
|
+
table.fields.forEach((f) => {
|
|
455
|
+
if (f.type && f.type.name === "Date") {
|
|
456
|
+
aggStatOptions[aggKey].push(`Latest ${f.name}`);
|
|
457
|
+
aggStatOptions[aggKey].push(`Earliest ${f.name}`);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
agg_field_opts.push({
|
|
461
|
+
name: `agg_field`,
|
|
462
|
+
label: req.__("On Field"),
|
|
463
|
+
type: "String",
|
|
464
|
+
required: true,
|
|
465
|
+
attributes: {
|
|
466
|
+
options: table.fields
|
|
467
|
+
.filter((f) => !f.calculated || f.stored)
|
|
468
|
+
.map((f) => ({
|
|
469
|
+
label: f.name,
|
|
470
|
+
name: `${f.name}@${f.type_name}`,
|
|
471
|
+
})),
|
|
472
|
+
},
|
|
473
|
+
showIf: {
|
|
474
|
+
agg_relation: aggKey,
|
|
475
|
+
expression_type: "Aggregation",
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
agg_order_opts.push({
|
|
479
|
+
name: `agg_order_by`,
|
|
480
|
+
label: req.__("Order by"),
|
|
481
|
+
type: "String",
|
|
482
|
+
attributes: {
|
|
483
|
+
options: table.fields
|
|
484
|
+
.filter((f) => !f.calculated || f.stored)
|
|
485
|
+
.map((f) => ({
|
|
486
|
+
label: f.name,
|
|
487
|
+
name: f.name,
|
|
488
|
+
})),
|
|
489
|
+
},
|
|
490
|
+
showIf: {
|
|
491
|
+
agg_relation: aggKey,
|
|
492
|
+
expression_type: "Aggregation",
|
|
493
|
+
aggregate: "Array_Agg",
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
});
|
|
478
497
|
return new Form({
|
|
479
498
|
fields: [
|
|
480
499
|
{
|
|
@@ -520,6 +539,7 @@ const fieldFlow = (req) =>
|
|
|
520
539
|
required: false,
|
|
521
540
|
showIf: { expression_type: "Aggregation" },
|
|
522
541
|
},
|
|
542
|
+
...agg_order_opts,
|
|
523
543
|
{
|
|
524
544
|
name: "model",
|
|
525
545
|
label: req.__("Model"),
|
|
@@ -1156,7 +1176,8 @@ router.post(
|
|
|
1156
1176
|
if (!fv) res.send(text(result));
|
|
1157
1177
|
else res.send(fv.run(result, req, { row, ...configuration }));
|
|
1158
1178
|
} catch (e) {
|
|
1159
|
-
|
|
1179
|
+
console.error("show-calculated error", e);
|
|
1180
|
+
return res.status(200).send(``);
|
|
1160
1181
|
}
|
|
1161
1182
|
})
|
|
1162
1183
|
);
|
package/routes/index.js
CHANGED
|
@@ -36,6 +36,7 @@ const roleadmin = require("../auth/roleadmin");
|
|
|
36
36
|
const tags = require("./tags");
|
|
37
37
|
const tagentries = require("./tag_entries");
|
|
38
38
|
const diagram = require("./diagram");
|
|
39
|
+
const registry = require("./registry");
|
|
39
40
|
const sync = require("./sync");
|
|
40
41
|
|
|
41
42
|
module.exports =
|
|
@@ -78,5 +79,6 @@ module.exports =
|
|
|
78
79
|
app.use("/tag", tags);
|
|
79
80
|
app.use("/tag-entries", tagentries);
|
|
80
81
|
app.use("/diagram", diagram);
|
|
82
|
+
app.use("/registry-editor", registry);
|
|
81
83
|
app.use("/sync", sync);
|
|
82
84
|
};
|
package/routes/pageedit.js
CHANGED
|
@@ -94,7 +94,7 @@ const pagePropertiesForm = async (req, isNew) => {
|
|
|
94
94
|
if (groups.includes(s) && isNew)
|
|
95
95
|
return req.__("A page group with this name already exists");
|
|
96
96
|
},
|
|
97
|
-
sublabel: req.__("A short name that will be in
|
|
97
|
+
sublabel: req.__("A short name that will be in the page URL"),
|
|
98
98
|
type: "String",
|
|
99
99
|
attributes: { autofocus: true },
|
|
100
100
|
}),
|
|
@@ -107,13 +107,15 @@ const pagePropertiesForm = async (req, isNew) => {
|
|
|
107
107
|
new Field({
|
|
108
108
|
label: req.__("Description"),
|
|
109
109
|
name: "description",
|
|
110
|
-
sublabel: req.__(
|
|
110
|
+
sublabel: req.__(
|
|
111
|
+
"A longer description that is not visible but appears in the page header and is indexed by search engines"
|
|
112
|
+
),
|
|
111
113
|
input_type: "text",
|
|
112
114
|
}),
|
|
113
115
|
{
|
|
114
116
|
name: "min_role",
|
|
115
117
|
label: req.__("Minimum role"),
|
|
116
|
-
sublabel: req.__("
|
|
118
|
+
sublabel: req.__("User role required to access page"),
|
|
117
119
|
input_type: "select",
|
|
118
120
|
options: roles.map((r) => ({ value: r.id, label: r.role })),
|
|
119
121
|
},
|
|
@@ -336,6 +338,15 @@ router.get(
|
|
|
336
338
|
)
|
|
337
339
|
),
|
|
338
340
|
},
|
|
341
|
+
{
|
|
342
|
+
type: "card",
|
|
343
|
+
title: req.__("Root pages"),
|
|
344
|
+
titleAjaxIndicator: true,
|
|
345
|
+
contents: renderForm(
|
|
346
|
+
getRootPageForm(pages, pageGroups, roles, req),
|
|
347
|
+
req.csrfToken()
|
|
348
|
+
),
|
|
349
|
+
},
|
|
339
350
|
{
|
|
340
351
|
type: "card",
|
|
341
352
|
title: req.__("Your page groups"),
|
|
@@ -357,15 +368,6 @@ router.get(
|
|
|
357
368
|
)
|
|
358
369
|
),
|
|
359
370
|
},
|
|
360
|
-
{
|
|
361
|
-
type: "card",
|
|
362
|
-
title: req.__("Root pages"),
|
|
363
|
-
titleAjaxIndicator: true,
|
|
364
|
-
contents: renderForm(
|
|
365
|
-
getRootPageForm(pages, pageGroups, roles, req),
|
|
366
|
-
req.csrfToken()
|
|
367
|
-
),
|
|
368
|
-
},
|
|
369
371
|
],
|
|
370
372
|
});
|
|
371
373
|
})
|
|
@@ -392,6 +394,7 @@ const wrap = (contents, noCard, req, page) => ({
|
|
|
392
394
|
{
|
|
393
395
|
type: noCard ? "container" : "card",
|
|
394
396
|
title: page ? page.name : req.__("New"),
|
|
397
|
+
titleAjaxIndicator: true,
|
|
395
398
|
contents,
|
|
396
399
|
},
|
|
397
400
|
],
|
|
@@ -418,6 +421,7 @@ router.get(
|
|
|
418
421
|
form.hidden("id");
|
|
419
422
|
form.values = page;
|
|
420
423
|
form.values.no_menu = page.attributes?.no_menu;
|
|
424
|
+
form.onChange = `saveAndContinue(this)`;
|
|
421
425
|
res.sendWrap(
|
|
422
426
|
req.__(`Page attributes`),
|
|
423
427
|
wrap(renderForm(form, req.csrfToken()), false, req, page)
|
|
@@ -477,7 +481,8 @@ router.post(
|
|
|
477
481
|
pageRow.layout = {};
|
|
478
482
|
}
|
|
479
483
|
await Page.update(+id, pageRow);
|
|
480
|
-
res.
|
|
484
|
+
if (req.xhr) res.json({ success: "ok" });
|
|
485
|
+
else res.redirect(`/pageedit/`);
|
|
481
486
|
} else {
|
|
482
487
|
if (!pageRow.layout) pageRow.layout = {};
|
|
483
488
|
if (!pageRow.fixed_states) pageRow.fixed_states = {};
|
package/routes/plugins.js
CHANGED
|
@@ -1315,7 +1315,12 @@ router.get(
|
|
|
1315
1315
|
await upgrade_all_tenants_plugins((p, f) =>
|
|
1316
1316
|
load_plugins.loadPlugin(p, f)
|
|
1317
1317
|
);
|
|
1318
|
-
req.flash(
|
|
1318
|
+
req.flash(
|
|
1319
|
+
"success",
|
|
1320
|
+
req.__(`Modules up-to-date. Please restart server`) +
|
|
1321
|
+
". " +
|
|
1322
|
+
a({ href: "/admin/system" }, req.__("Restart here"))
|
|
1323
|
+
);
|
|
1319
1324
|
} else {
|
|
1320
1325
|
const installed_plugins = await Plugin.find({});
|
|
1321
1326
|
for (const plugin of installed_plugins) {
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const Router = require("express-promise-router");
|
|
2
|
+
|
|
3
|
+
const db = require("@saltcorn/data/db");
|
|
4
|
+
const { mkTable, link, post_btn, renderForm } = require("@saltcorn/markup");
|
|
5
|
+
const {
|
|
6
|
+
script,
|
|
7
|
+
domReady,
|
|
8
|
+
a,
|
|
9
|
+
div,
|
|
10
|
+
i,
|
|
11
|
+
text,
|
|
12
|
+
button,
|
|
13
|
+
input,
|
|
14
|
+
label,
|
|
15
|
+
form,
|
|
16
|
+
ul,
|
|
17
|
+
li,
|
|
18
|
+
details,
|
|
19
|
+
summary,
|
|
20
|
+
} = require("@saltcorn/markup/tags");
|
|
21
|
+
const Table = require("@saltcorn/data/models/table");
|
|
22
|
+
const { isAdmin, error_catcher } = require("./utils");
|
|
23
|
+
const { send_infoarch_page } = require("../markup/admin.js");
|
|
24
|
+
const View = require("@saltcorn/data/models/view");
|
|
25
|
+
const Page = require("@saltcorn/data/models/page");
|
|
26
|
+
const Form = require("@saltcorn/data/models/form");
|
|
27
|
+
const {
|
|
28
|
+
table_pack,
|
|
29
|
+
view_pack,
|
|
30
|
+
plugin_pack,
|
|
31
|
+
page_pack,
|
|
32
|
+
page_group_pack,
|
|
33
|
+
role_pack,
|
|
34
|
+
library_pack,
|
|
35
|
+
trigger_pack,
|
|
36
|
+
tag_pack,
|
|
37
|
+
model_pack,
|
|
38
|
+
model_instance_pack,
|
|
39
|
+
event_log_pack,
|
|
40
|
+
install_pack,
|
|
41
|
+
} = require("@saltcorn/admin-models/models/pack");
|
|
42
|
+
const Trigger = require("@saltcorn/data/models/trigger");
|
|
43
|
+
/**
|
|
44
|
+
* @type {object}
|
|
45
|
+
* @const
|
|
46
|
+
* @namespace listRouter
|
|
47
|
+
* @category server
|
|
48
|
+
* @subcategory routes
|
|
49
|
+
*/
|
|
50
|
+
const router = new Router();
|
|
51
|
+
|
|
52
|
+
// export our router to be mounted by the parent application
|
|
53
|
+
module.exports = router;
|
|
54
|
+
|
|
55
|
+
async function asyncFilter(arr, cb) {
|
|
56
|
+
const filtered = [];
|
|
57
|
+
|
|
58
|
+
for (const element of arr) {
|
|
59
|
+
const needAdd = await cb(element);
|
|
60
|
+
|
|
61
|
+
if (needAdd) {
|
|
62
|
+
filtered.push(element);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return filtered;
|
|
67
|
+
}
|
|
68
|
+
router.get(
|
|
69
|
+
"/",
|
|
70
|
+
isAdmin,
|
|
71
|
+
error_catcher(async (req, res) => {
|
|
72
|
+
const { etype, ename, q } = req.query;
|
|
73
|
+
const qlink = q ? `&q=${encodeURIComponent(q)}` : "";
|
|
74
|
+
let edContents = "Choose an entity to edit";
|
|
75
|
+
const all_tables = await Table.find({}, { orderBy: "name", nocase: true });
|
|
76
|
+
const all_views = await View.find({}, { orderBy: "name", nocase: true });
|
|
77
|
+
const all_pages = await Page.find({}, { orderBy: "name", nocase: true });
|
|
78
|
+
const all_triggers = await Trigger.find(
|
|
79
|
+
{},
|
|
80
|
+
{ orderBy: "name", nocase: true }
|
|
81
|
+
);
|
|
82
|
+
let tables, views, pages, triggers;
|
|
83
|
+
if (q) {
|
|
84
|
+
const qlower = q.toLowerCase();
|
|
85
|
+
const includesQ = (s) => s.toLowerCase().includes(qlower);
|
|
86
|
+
|
|
87
|
+
tables = await asyncFilter(all_tables, async (t) => {
|
|
88
|
+
const pack = await table_pack(t);
|
|
89
|
+
return includesQ(JSON.stringify(pack));
|
|
90
|
+
});
|
|
91
|
+
views = await asyncFilter(all_views, async (t) => {
|
|
92
|
+
const pack = await view_pack(t);
|
|
93
|
+
return includesQ(JSON.stringify(pack));
|
|
94
|
+
});
|
|
95
|
+
pages = await asyncFilter(all_pages, async (t) => {
|
|
96
|
+
const pack = await page_pack(t);
|
|
97
|
+
return includesQ(JSON.stringify(pack));
|
|
98
|
+
});
|
|
99
|
+
triggers = await asyncFilter(all_triggers, async (t) => {
|
|
100
|
+
const pack = await trigger_pack(t);
|
|
101
|
+
return includesQ(JSON.stringify(pack));
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
tables = all_tables;
|
|
105
|
+
views = all_views;
|
|
106
|
+
pages = all_pages;
|
|
107
|
+
triggers = all_triggers;
|
|
108
|
+
}
|
|
109
|
+
const li_link = (etype1, ename1) =>
|
|
110
|
+
li(
|
|
111
|
+
a(
|
|
112
|
+
{
|
|
113
|
+
href: `/registry-editor?etype=${etype1}&ename=${encodeURIComponent(
|
|
114
|
+
ename1
|
|
115
|
+
)}${qlink}`,
|
|
116
|
+
class: etype1 === etype && ename1 === ename ? "fw-bold" : undefined,
|
|
117
|
+
},
|
|
118
|
+
ename1
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
const mkForm = (jsonVal) =>
|
|
122
|
+
new Form({
|
|
123
|
+
labelCols: 0,
|
|
124
|
+
action: `/registry-editor?etype=${etype}&ename=${encodeURIComponent(
|
|
125
|
+
ename
|
|
126
|
+
)}${qlink}`,
|
|
127
|
+
|
|
128
|
+
values: { regval: JSON.stringify(jsonVal, null, 2) },
|
|
129
|
+
fields: [
|
|
130
|
+
{
|
|
131
|
+
name: "regval",
|
|
132
|
+
label: "",
|
|
133
|
+
input_type: "code",
|
|
134
|
+
attributes: { mode: "application/json" },
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
switch (etype) {
|
|
139
|
+
case "table":
|
|
140
|
+
const tpack = await table_pack(
|
|
141
|
+
all_tables.find((t) => t.name === ename)
|
|
142
|
+
);
|
|
143
|
+
edContents = renderForm(mkForm(tpack), req.csrfToken());
|
|
144
|
+
break;
|
|
145
|
+
case "view":
|
|
146
|
+
const vpack = await view_pack(all_views.find((v) => v.name === ename));
|
|
147
|
+
edContents = renderForm(mkForm(vpack), req.csrfToken());
|
|
148
|
+
break;
|
|
149
|
+
case "page":
|
|
150
|
+
const ppack = await page_pack(all_pages.find((v) => v.name === ename));
|
|
151
|
+
edContents = renderForm(mkForm(ppack), req.csrfToken());
|
|
152
|
+
break;
|
|
153
|
+
case "trigger":
|
|
154
|
+
const trpack = await trigger_pack(
|
|
155
|
+
all_triggers.find((t) => t.name === ename)
|
|
156
|
+
);
|
|
157
|
+
edContents = renderForm(mkForm(trpack), req.csrfToken());
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
send_infoarch_page({
|
|
161
|
+
res,
|
|
162
|
+
req,
|
|
163
|
+
active_sub: "Registry editor",
|
|
164
|
+
contents: {
|
|
165
|
+
widths: [3, 9],
|
|
166
|
+
besides: [
|
|
167
|
+
{
|
|
168
|
+
type: "card",
|
|
169
|
+
bodyClass: "p-1",
|
|
170
|
+
title: "Entities",
|
|
171
|
+
contents: div(
|
|
172
|
+
form(
|
|
173
|
+
{ method: "GET", action: `/registry-editor` },
|
|
174
|
+
div(
|
|
175
|
+
{ class: "input-group search-bar mb-2" },
|
|
176
|
+
etype &&
|
|
177
|
+
input({ type: "hidden", name: "etype", value: etype }),
|
|
178
|
+
ename &&
|
|
179
|
+
input({ type: "hidden", name: "ename", value: ename }),
|
|
180
|
+
input({
|
|
181
|
+
type: "search",
|
|
182
|
+
class: "form-control search-bar ps-2 hasbl",
|
|
183
|
+
placeholder: "Search",
|
|
184
|
+
name: "q",
|
|
185
|
+
value: q,
|
|
186
|
+
"aria-label": "Search",
|
|
187
|
+
"aria-describedby": "button-search-submit",
|
|
188
|
+
}),
|
|
189
|
+
button(
|
|
190
|
+
{
|
|
191
|
+
class: "btn btn-outline-secondary search-bar",
|
|
192
|
+
type: "submit",
|
|
193
|
+
},
|
|
194
|
+
i({ class: "fas fa-search" })
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
),
|
|
198
|
+
// following https://iamkate.com/code/tree-views/
|
|
199
|
+
ul(
|
|
200
|
+
{ class: "katetree ps-2" },
|
|
201
|
+
li(
|
|
202
|
+
details(
|
|
203
|
+
{ open: q || etype === "table" },
|
|
204
|
+
summary("Tables"),
|
|
205
|
+
ul(
|
|
206
|
+
{ class: "ps-3" },
|
|
207
|
+
tables.map((t) => li_link("table", t.name))
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
),
|
|
211
|
+
li(
|
|
212
|
+
details(
|
|
213
|
+
{ open: q || etype === "view" },
|
|
214
|
+
summary("Views"),
|
|
215
|
+
ul(
|
|
216
|
+
{ class: "ps-3" },
|
|
217
|
+
views.map((v) => li_link("view", v.name))
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
),
|
|
221
|
+
li(
|
|
222
|
+
details(
|
|
223
|
+
{ open: q || etype === "page" }, //
|
|
224
|
+
summary("Pages"),
|
|
225
|
+
ul(
|
|
226
|
+
{ class: "ps-3" },
|
|
227
|
+
pages.map((p) => li_link("page", p.name))
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
),
|
|
231
|
+
li(
|
|
232
|
+
details(
|
|
233
|
+
{ open: q || etype === "trigger" }, //
|
|
234
|
+
summary("Triggers"),
|
|
235
|
+
ul(
|
|
236
|
+
{ class: "ps-3" },
|
|
237
|
+
triggers.map((t) => li_link("trigger", t.name))
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
type: "card",
|
|
246
|
+
title:
|
|
247
|
+
ename && etype
|
|
248
|
+
? `Registry editor: ${ename} ${etype}`
|
|
249
|
+
: "Registry editor",
|
|
250
|
+
contents: edContents,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
router.post(
|
|
259
|
+
"/",
|
|
260
|
+
isAdmin,
|
|
261
|
+
error_catcher(async (req, res) => {
|
|
262
|
+
const { etype, ename, q } = req.query;
|
|
263
|
+
const qlink = q ? `&q=${encodeURIComponent(q)}` : "";
|
|
264
|
+
|
|
265
|
+
const entVal = JSON.parse(req.body.regval);
|
|
266
|
+
let pack = { plugins: [], tables: [], views: [], pages: [], triggers: [] };
|
|
267
|
+
|
|
268
|
+
switch (etype) {
|
|
269
|
+
case "table":
|
|
270
|
+
pack.tables = [entVal];
|
|
271
|
+
break;
|
|
272
|
+
case "view":
|
|
273
|
+
pack.views = [entVal];
|
|
274
|
+
break;
|
|
275
|
+
case "page":
|
|
276
|
+
pack.pages = [entVal];
|
|
277
|
+
break;
|
|
278
|
+
case "trigger":
|
|
279
|
+
pack.triggers = [entVal];
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
await install_pack(pack);
|
|
283
|
+
res.redirect(
|
|
284
|
+
`/registry-editor?etype=${etype}&ename=${encodeURIComponent(
|
|
285
|
+
ename
|
|
286
|
+
)}${qlink}`
|
|
287
|
+
);
|
|
288
|
+
})
|
|
289
|
+
);
|
package/routes/tables.js
CHANGED
package/routes/utils.js
CHANGED
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
} = require("@saltcorn/data/db/state");
|
|
15
15
|
const { get_base_url } = require("@saltcorn/data/models/config");
|
|
16
16
|
const { hash } = require("@saltcorn/data/utils");
|
|
17
|
-
const { input, script, domReady } = require("@saltcorn/markup/tags");
|
|
17
|
+
const { input, script, domReady, a } = require("@saltcorn/markup/tags");
|
|
18
18
|
const session = require("express-session");
|
|
19
19
|
const cookieSession = require("cookie-session");
|
|
20
20
|
const is = require("contractis/is");
|
|
@@ -398,7 +398,10 @@ const admin_config_route = ({
|
|
|
398
398
|
if (restart_required)
|
|
399
399
|
res.json({
|
|
400
400
|
success: "ok",
|
|
401
|
-
notify:
|
|
401
|
+
notify:
|
|
402
|
+
req.__("Restart required for changes to take effect.") +
|
|
403
|
+
" " +
|
|
404
|
+
a({ href: "/admin/system" }, req.__("Restart here")),
|
|
402
405
|
});
|
|
403
406
|
else res.json({ success: "ok" });
|
|
404
407
|
}
|
package/routes/view.js
CHANGED
package/routes/viewedit.js
CHANGED
|
@@ -371,6 +371,7 @@ router.get(
|
|
|
371
371
|
const form = await viewForm(req, tableOptions, roles, pages, viewrow);
|
|
372
372
|
const inbound_connected = await viewrow.inbound_connected_objects();
|
|
373
373
|
form.hidden("id");
|
|
374
|
+
form.onChange = `saveAndContinue(this)`;
|
|
374
375
|
res.sendWrap(req.__(`Edit view`), {
|
|
375
376
|
above: [
|
|
376
377
|
{
|
|
@@ -383,6 +384,7 @@ router.get(
|
|
|
383
384
|
{
|
|
384
385
|
type: "card",
|
|
385
386
|
class: "mt-0",
|
|
387
|
+
titleAjaxIndicator: true,
|
|
386
388
|
title: req.__(
|
|
387
389
|
`%s view - %s on %s`,
|
|
388
390
|
viewname,
|
|
@@ -541,12 +543,14 @@ router.post(
|
|
|
541
543
|
//console.log(v);
|
|
542
544
|
await View.create(v);
|
|
543
545
|
}
|
|
544
|
-
res.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
546
|
+
if (req.xhr) res.json({ success: "ok" });
|
|
547
|
+
else
|
|
548
|
+
res.redirect(
|
|
549
|
+
addOnDoneRedirect(
|
|
550
|
+
`/viewedit/config/${encodeURIComponent(v.name)}`,
|
|
551
|
+
req
|
|
552
|
+
)
|
|
553
|
+
);
|
|
550
554
|
}
|
|
551
555
|
} else {
|
|
552
556
|
sendForm(form);
|
|
@@ -902,7 +906,7 @@ router.post(
|
|
|
902
906
|
? `/${req.query.on_done_redirect}`
|
|
903
907
|
: "/viewedit";
|
|
904
908
|
res.redirect(redirectTarget);
|
|
905
|
-
} else res.json({
|
|
909
|
+
} else res.json({ success: "ok" });
|
|
906
910
|
})
|
|
907
911
|
);
|
|
908
912
|
|
package/tests/page.test.js
CHANGED
|
@@ -76,7 +76,7 @@ describe("page create", () => {
|
|
|
76
76
|
await request(app)
|
|
77
77
|
.get("/pageedit/new")
|
|
78
78
|
.set("Cookie", loginCookie)
|
|
79
|
-
.expect(toInclude("A short name that will be in
|
|
79
|
+
.expect(toInclude("A short name that will be in the page URL"));
|
|
80
80
|
});
|
|
81
81
|
it("shows new with html file selector", async () => {
|
|
82
82
|
const app = await getApp({ disableCsrf: true });
|
|
@@ -237,7 +237,7 @@ describe("pageedit", () => {
|
|
|
237
237
|
await request(app)
|
|
238
238
|
.get("/pageedit/edit-properties/a_page")
|
|
239
239
|
.set("Cookie", loginCookie)
|
|
240
|
-
.expect(toInclude("A short name that will be in
|
|
240
|
+
.expect(toInclude("A short name that will be in the page URL"));
|
|
241
241
|
|
|
242
242
|
//TODO full context
|
|
243
243
|
const ctx = encodeURIComponent(JSON.stringify({}));
|
package/tests/plugins.test.js
CHANGED
|
@@ -137,8 +137,10 @@ describe("Plugin dependency resolution and upgrade", () => {
|
|
|
137
137
|
.expect(toRedirect("/plugins"));
|
|
138
138
|
const quill = await Plugin.findOne({ name: "quill-editor" });
|
|
139
139
|
expect(quill.location).toBe("@saltcorn/quill-editor");
|
|
140
|
+
expect(quill.name).toBe("quill-editor");
|
|
140
141
|
const html = await Plugin.findOne({ location: "@saltcorn/html" });
|
|
141
142
|
expect(html.location).toBe("@saltcorn/html");
|
|
143
|
+
expect(html.name).toBe("html");
|
|
142
144
|
const html_type = getState().types.HTML;
|
|
143
145
|
expect(!!html_type.fieldviews.Quill).toBe(true);
|
|
144
146
|
});
|