@saltcorn/server 0.7.3-beta.2 → 0.7.3-beta.3

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/markup/admin.js CHANGED
@@ -271,7 +271,7 @@ const send_events_page = (args) => {
271
271
  * @returns {void}
272
272
  */
273
273
  const send_admin_page = (args) => {
274
- //const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
274
+ const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
275
275
  return send_settings_page({
276
276
  main_section: "About application",
277
277
  main_section_href: "/admin",
@@ -280,6 +280,9 @@ const send_admin_page = (args) => {
280
280
  { text: "Backup", href: "/admin/backup" },
281
281
  { text: "Email", href: "/admin/email" },
282
282
  { text: "System", href: "/admin/system" },
283
+ ...(isRoot
284
+ ? [{ text: "Mobile app", href: "/admin/build-mobile-app" }]
285
+ : []),
283
286
  ],
284
287
  ...args,
285
288
  });
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.7.3-beta.2",
3
+ "version": "0.7.3-beta.3",
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.7.3-beta.2",
10
- "@saltcorn/builder": "0.7.3-beta.2",
11
- "@saltcorn/data": "0.7.3-beta.2",
12
- "@saltcorn/admin-models": "0.7.3-beta.2",
13
- "@saltcorn/markup": "0.7.3-beta.2",
14
- "@saltcorn/sbadmin2": "0.7.3-beta.2",
9
+ "@saltcorn/base-plugin": "0.7.3-beta.3",
10
+ "@saltcorn/builder": "0.7.3-beta.3",
11
+ "@saltcorn/data": "0.7.3-beta.3",
12
+ "@saltcorn/admin-models": "0.7.3-beta.3",
13
+ "@saltcorn/markup": "0.7.3-beta.3",
14
+ "@saltcorn/sbadmin2": "0.7.3-beta.3",
15
15
  "@socket.io/cluster-adapter": "^0.1.0",
16
16
  "@socket.io/sticky": "^1.0.1",
17
17
  "aws-sdk": "^2.1037.0",
@@ -48,7 +48,7 @@
48
48
  "pg": "^8.2.1",
49
49
  "pluralize": "^8.0.0",
50
50
  "qrcode": "1.5.0",
51
- "resize-with-sharp-or-jimp": "0.1.3",
51
+ "resize-with-sharp-or-jimp": "0.1.4",
52
52
  "socket.io": "4.2.0",
53
53
  "thirty-two": "1.0.2",
54
54
  "tmp-promise": "^3.0.2",
@@ -583,8 +583,52 @@ function unique_field_from_rows(
583
583
  }
584
584
  }
585
585
 
586
+ function room_older(viewname, room_id, btn) {
587
+ view_post(
588
+ viewname,
589
+ "fetch_older_msg",
590
+ { room_id, lt_msg_id: +$(btn).attr("data-lt-msg-id") },
591
+ (res) => {
592
+ if (res.prepend) $(`.msglist-${room_id}`).prepend(res.prepend);
593
+ if (res.new_fetch_older_lt)
594
+ $(btn).attr("data-lt-msg-id", res.new_fetch_older_lt);
595
+ if (res.remove_fetch_older) $(btn).remove();
596
+ }
597
+ );
598
+ }
599
+
600
+ function init_room(viewname, room_id) {
601
+ const socket = parent?.config?.server_path
602
+ ? io(parent.config.server_path, {
603
+ query: `jwt=${localStorage.getItem("auth_jwt")}`,
604
+ transports: ["websocket"],
605
+ })
606
+ : io({ transports: ["websocket"] });
607
+
608
+ socket.emit("join_room", [viewname, room_id]);
609
+ socket.on("message", (msg) => {
610
+ if (msg.not_for_user_id) {
611
+ const my_user_id = $(`.msglist-${room_id}`).attr("data-user-id");
612
+ if (+my_user_id === +msg.not_for_user_id) return;
613
+ }
614
+ if (msg.append) $(`.msglist-${room_id}`).append(msg.append);
615
+ if (msg.pls_ack_msg_id)
616
+ view_post(viewname, "ack_read", { room_id, id: msg.pls_ack_msg_id });
617
+ });
618
+
619
+ $(`form.room-${room_id}`).submit((e) => {
620
+ e.preventDefault();
621
+ var form_data = $(`form.room-${room_id}`).serialize();
622
+ view_post(viewname, "submit_msg_ajax", form_data, (vpres) => {
623
+ if (vpres.append) $(`.msglist-${room_id}`).append(vpres.append);
624
+ $(`form.room-${room_id}`).trigger("reset");
625
+ });
626
+ });
627
+ }
628
+
586
629
  function cancel_form(form) {
587
630
  if (!form) return;
588
631
  $(form).append(`<input type="hidden" name="_cancel" value="on">`);
589
632
  $(form).submit();
590
633
  }
634
+
@@ -298,3 +298,8 @@ section.range-slider input[type="range"]::-moz-focus-outer {
298
298
  .btn-xs {
299
299
  padding: 0.1rem 0.4rem !important;
300
300
  }
301
+
302
+ table.table-inner-grid, table.table-inner-grid th, table.table-inner-grid td {
303
+ border: 1px solid black;
304
+ border-collapse: collapse;
305
+ }
@@ -366,42 +366,6 @@ function test_formula(tablename, stored) {
366
366
  });
367
367
  }
368
368
 
369
- function init_room(viewname, room_id) {
370
- const socket = io({ transports: ["websocket"] });
371
- socket.emit("join_room", [viewname, room_id]);
372
- socket.on("message", (msg) => {
373
- if (msg.not_for_user_id) {
374
- const my_user_id = $(`.msglist-${room_id}`).attr("data-user-id");
375
- if (+my_user_id === +msg.not_for_user_id) return;
376
- }
377
- if (msg.append) $(`.msglist-${room_id}`).append(msg.append);
378
- if (msg.pls_ack_msg_id)
379
- view_post(viewname, "ack_read", { room_id, id: msg.pls_ack_msg_id });
380
- });
381
-
382
- $(`form.room-${room_id}`).submit((e) => {
383
- e.preventDefault();
384
- var form_data = $(`form.room-${room_id}`).serialize();
385
- view_post(viewname, "submit_msg_ajax", form_data, (vpres) => {
386
- if (vpres.append) $(`.msglist-${room_id}`).append(vpres.append);
387
- $(`form.room-${room_id}`).trigger("reset");
388
- });
389
- });
390
- }
391
- function room_older(viewname, room_id, btn) {
392
- view_post(
393
- viewname,
394
- "fetch_older_msg",
395
- { room_id, lt_msg_id: +$(btn).attr("data-lt-msg-id") },
396
- (res) => {
397
- if (res.prepend) $(`.msglist-${room_id}`).prepend(res.prepend);
398
- if (res.new_fetch_older_lt)
399
- $(btn).attr("data-lt-msg-id", res.new_fetch_older_lt);
400
- if (res.remove_fetch_older) $(btn).remove();
401
- }
402
- );
403
- }
404
-
405
369
  async function fill_formula_btn_click(btn, k) {
406
370
  const formula = decodeURIComponent($(btn).attr("data-formula"));
407
371
  const free_vars = JSON.parse(
package/routes/admin.js CHANGED
@@ -18,7 +18,7 @@ const { spawn } = require("child_process");
18
18
  const User = require("@saltcorn/data/models/user");
19
19
  const path = require("path");
20
20
  const { getAllTenants } = require("@saltcorn/admin-models/models/tenant");
21
- const { post_btn, renderForm } = require("@saltcorn/markup");
21
+ const { post_btn, renderForm, mkTable, link } = require("@saltcorn/markup");
22
22
  const {
23
23
  div,
24
24
  a,
@@ -35,6 +35,14 @@ const {
35
35
  code,
36
36
  h5,
37
37
  pre,
38
+ button,
39
+ form,
40
+ label,
41
+ input,
42
+ select,
43
+ option,
44
+ fieldset,
45
+ legend,
38
46
  } = require("@saltcorn/markup/tags");
39
47
  const db = require("@saltcorn/data/db");
40
48
  const {
@@ -136,6 +144,23 @@ const email_form = async (req) => {
136
144
  return form;
137
145
  };
138
146
 
147
+ const app_files_table = (file, req) =>
148
+ mkTable(
149
+ [
150
+ {
151
+ label: req.__("Filename"),
152
+ key: (r) => div(r.filename),
153
+ },
154
+ { label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
155
+ { label: req.__("Media type"), key: (r) => r.mimetype },
156
+ {
157
+ label: req.__("Download"),
158
+ key: (r) => link(`/files/download/${r.id}`, req.__("Download")),
159
+ },
160
+ ],
161
+ [file]
162
+ );
163
+
139
164
  /**
140
165
  * Router get /
141
166
  * @name get
@@ -503,6 +528,7 @@ router.get(
503
528
  " ",
504
529
  req.__("Configuration check")
505
530
  ),
531
+
506
532
  hr(),
507
533
 
508
534
  a(
@@ -935,6 +961,255 @@ router.get(
935
961
  })
936
962
  );
937
963
 
964
+ router.get(
965
+ "/build-mobile-app",
966
+ isAdmin,
967
+ error_catcher(async (req, res) => {
968
+ const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
969
+ const views = await View.find();
970
+ const execBuildMsg =
971
+ "This is still under development and might run longer.";
972
+
973
+ send_admin_page({
974
+ res,
975
+ req,
976
+ active_sub: "Mobile app",
977
+ contents: {
978
+ above: [
979
+ {
980
+ type: "card",
981
+ title: req.__("Build mobile app"),
982
+ contents: form(
983
+ {
984
+ action: "/admin/build-mobile-app",
985
+ method: "post",
986
+ },
987
+
988
+ fieldset(
989
+ input({
990
+ type: "hidden",
991
+ name: "_csrf",
992
+ value: req.csrfToken(),
993
+ }),
994
+ div(
995
+ { class: "container ps-2" },
996
+ div(
997
+ { class: "row pb-2" },
998
+ div({ class: "col-sm-4 fw-bold" }, "Entry view"),
999
+ div({ class: "col-sm-4 fw-bold" }, "Platform"),
1000
+ div(
1001
+ {
1002
+ class: "col-sm-1 fw-bold d-flex justify-content-center",
1003
+ },
1004
+ "docker"
1005
+ )
1006
+ ),
1007
+ div(
1008
+ { class: "row" },
1009
+ div(
1010
+ { class: "col-sm-4" },
1011
+ select(
1012
+ {
1013
+ class: "form-control",
1014
+ name: "entryView",
1015
+ id: "entryViewInput",
1016
+ },
1017
+ views
1018
+ .map((view) =>
1019
+ option({ value: view.name }, view.name)
1020
+ )
1021
+ .join(",")
1022
+ )
1023
+ ),
1024
+ div(
1025
+ { class: "col-sm-4" },
1026
+
1027
+ div(
1028
+ { class: "container ps-0" },
1029
+ div(
1030
+ { class: "row" },
1031
+ div({ class: "col-sm-8" }, "android"),
1032
+ div(
1033
+ { class: "col-sm" },
1034
+ input({
1035
+ type: "checkbox",
1036
+ class: "form-check-input",
1037
+ name: "androidPlatform",
1038
+ id: "androidCheckboxId",
1039
+ })
1040
+ )
1041
+ ),
1042
+ div(
1043
+ { class: "row" },
1044
+ div({ class: "col-sm-8" }, "iOS"),
1045
+ div(
1046
+ { class: "col-sm" },
1047
+ input({
1048
+ type: "checkbox",
1049
+ class: "form-check-input",
1050
+ name: "iOSPlatform",
1051
+ id: "iOSCheckboxId",
1052
+ })
1053
+ )
1054
+ )
1055
+ )
1056
+ ),
1057
+ div(
1058
+ { class: "col-sm-1 d-flex justify-content-center" },
1059
+ input({
1060
+ type: "checkbox",
1061
+ class: "form-check-input",
1062
+ name: "useDocker",
1063
+ id: "dockerCheckboxId",
1064
+ })
1065
+ )
1066
+ ),
1067
+ div(
1068
+ { class: "row pb-2" },
1069
+ div(
1070
+ { class: "col-sm-8" },
1071
+ label(
1072
+ {
1073
+ for: "appNameInputId",
1074
+ class: "form-label fw-bold",
1075
+ },
1076
+ "App file"
1077
+ ),
1078
+ input({
1079
+ type: "text",
1080
+ class: "form-control",
1081
+ name: "appFile",
1082
+ id: "appFileInputId",
1083
+ placeholder: "app-debug",
1084
+ })
1085
+ )
1086
+ ),
1087
+ div(
1088
+ { class: "row pb-3" },
1089
+ div(
1090
+ { class: "col-sm-8" },
1091
+ label(
1092
+ {
1093
+ for: "serverURLInputId",
1094
+ class: "form-label fw-bold",
1095
+ },
1096
+ "Server URL"
1097
+ ),
1098
+ input({
1099
+ type: "text",
1100
+ class: "form-control",
1101
+ name: "serverURL",
1102
+ id: "serverURLInputId",
1103
+ placeholder: "http://10.0.2.2:3000",
1104
+ })
1105
+ )
1106
+ )
1107
+ ),
1108
+ button(
1109
+ {
1110
+ type: "submit",
1111
+ onClick: `notifyAlert('${execBuildMsg}'); press_store_button(this);`,
1112
+ class: "btn btn-warning",
1113
+ },
1114
+ i({ class: "fas fa-hammer pe-2" }),
1115
+
1116
+ "Build mobile app"
1117
+ )
1118
+ )
1119
+ ),
1120
+ },
1121
+ ],
1122
+ },
1123
+ });
1124
+ })
1125
+ );
1126
+
1127
+ router.post(
1128
+ "/build-mobile-app",
1129
+ isAdmin,
1130
+ error_catcher(async (req, res) => {
1131
+ let {
1132
+ entryView,
1133
+ androidPlatform,
1134
+ iOSPlatform,
1135
+ useDocker,
1136
+ appFile,
1137
+ serverURL,
1138
+ } = req.body;
1139
+ if (!androidPlatform && !iOSPlatform) {
1140
+ req.flash(
1141
+ "error",
1142
+ req.__("Please select at least one platform (android or iOS).")
1143
+ );
1144
+ return res.redirect("/admin/system");
1145
+ }
1146
+ if (!androidPlatform && useDocker) {
1147
+ req.flash("error", req.__("Only the android build supports docker."));
1148
+ return res.redirect("/admin/system");
1149
+ }
1150
+ if (appFile && !appFile.endsWith(".apk")) appFile = `${appFile}.apk`;
1151
+ const appOut = path.join(__dirname, "..", "mobile-app-out");
1152
+ const spawnParams = ["build-app", "-v", entryView, "-c", appOut];
1153
+ if (useDocker) spawnParams.push("-d");
1154
+ if (androidPlatform) spawnParams.push("-p", "android");
1155
+ if (iOSPlatform) spawnParams.push("-p", "ios");
1156
+ if (appFile) spawnParams.push("-a", appFile);
1157
+ if (serverURL) spawnParams.push("-s", serverURL);
1158
+ const child = spawn("saltcorn", spawnParams, {
1159
+ stdio: ["ignore", "pipe", process.stderr],
1160
+ cwd: ".",
1161
+ });
1162
+ const childOutputs = [];
1163
+ child.stdout.on("data", (data) => {
1164
+ // console.log(data.toString());
1165
+ childOutputs.push(data.toString());
1166
+ });
1167
+ child.on("exit", async function (code, signal) {
1168
+ if (code === 0) {
1169
+ const file = await File.from_existing_file(
1170
+ appOut,
1171
+ appFile ? appFile : "app-debug.apk",
1172
+ req.user.id
1173
+ );
1174
+ res.sendWrap(req.__(`Admin`), {
1175
+ above: [
1176
+ {
1177
+ type: "card",
1178
+ title: req.__("Build Result"),
1179
+ contents: div("The build was successfully"),
1180
+ },
1181
+ app_files_table(file, req),
1182
+ ],
1183
+ });
1184
+ } else
1185
+ res.sendWrap(req.__(`Admin`), {
1186
+ above: [
1187
+ {
1188
+ type: "card",
1189
+ title: req.__("Build Result"),
1190
+ contents: div("Unable to build the app"),
1191
+ },
1192
+ childOutputs.join("<br/>"),
1193
+ ],
1194
+ });
1195
+ });
1196
+ child.on("error", function (msg) {
1197
+ const message = msg.message ? msg.message : msg.code;
1198
+ const stack = msg.stack ? msg.stack : "";
1199
+ res.sendWrap(req.__(`Admin`), {
1200
+ above: [
1201
+ {
1202
+ type: "card",
1203
+ title: req.__("Build Result"),
1204
+ contents: div("Unable to build the app"),
1205
+ },
1206
+ `${message} <br/> ${stack}`,
1207
+ ],
1208
+ });
1209
+ });
1210
+ })
1211
+ );
1212
+
938
1213
  /**
939
1214
  * @name post/clear-all
940
1215
  * @function
package/routes/menu.js CHANGED
@@ -84,7 +84,15 @@ const menuForm = async (req) => {
84
84
  input_type: "select",
85
85
  class: "menutype item-menu",
86
86
  required: true,
87
- options: ["View", "Page", "Link", "Header", "Dynamic", "Search"],
87
+ options: [
88
+ "View",
89
+ "Page",
90
+ "Link",
91
+ "Header",
92
+ "Dynamic",
93
+ "Search",
94
+ "Separator",
95
+ ],
88
96
  },
89
97
  {
90
98
  name: "text",
@@ -92,6 +100,9 @@ const menuForm = async (req) => {
92
100
  class: "item-menu",
93
101
  input_type: "text",
94
102
  required: true,
103
+ showIf: {
104
+ type: ["View", "Page", "Link", "Header", "Dynamic", "Search"],
105
+ },
95
106
  },
96
107
  {
97
108
  name: "icon_btn",
@@ -271,6 +282,7 @@ const menuEditorScript = (menu_items) => `
271
282
  {
272
283
  listOptions: sortableListOptions,
273
284
  iconPicker: iconPickerOptions,
285
+ getLabelText: (item) => item?.text || item?.type,
274
286
  labelEdit: 'Edit&nbsp;<i class="fas fa-edit clickable"></i>',
275
287
  maxLevel: 1 // (Optional) Default is -1 (no level limit)
276
288
  // Valid levels are from [0, 1, 2, 3,...N]
package/serve.js CHANGED
@@ -348,7 +348,7 @@ const setupSocket = (...servers) => {
348
348
  io.use(wrap(setTenant));
349
349
  io.use(wrap(getSessionStore()));
350
350
  io.use(wrap(passport.initialize()));
351
- io.use(wrap(passport.session()));
351
+ io.use(wrap(passport.authenticate(["jwt", "session"])));
352
352
  if (process.send && !cluster.isMaster) io.adapter(createAdapter());
353
353
  getState().setRoomEmitter((viewname, room_id, msg) => {
354
354
  io.to(`${viewname}_${room_id}`).emit("message", msg);