@saltcorn/server 0.7.4-beta.3 → 0.7.4

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/app.js CHANGED
@@ -85,6 +85,8 @@ const getApp = async (opts = {}) => {
85
85
  const development_mode = getState().getConfig("development_mode", false);
86
86
  // switch on sql logging - but it was initiated before???
87
87
  if (getState().getConfig("log_sql", false)) db.set_sql_logging();
88
+ // for multi-tenant with localhost, we need 1 instead of the default of 2
89
+ if (opts.subdomainOffset) app.set("subdomain offset", opts.subdomainOffset);
88
90
 
89
91
  // https://www.npmjs.com/package/helmet
90
92
  // helmet is secure app by adding HTTP headers
@@ -225,8 +227,9 @@ const getApp = async (opts = {}) => {
225
227
  })
226
228
  );
227
229
  passport.use(
228
- new JwtStrategy(jwtOpts, (jwt_payload, done) => {
229
- User.findOne({ email: jwt_payload.sub }).then((u) => {
230
+ new JwtStrategy(jwtOpts, async (jwt_payload, done) => {
231
+ const userCheck = async () => {
232
+ const u = await User.findOne({ email: jwt_payload.sub });
230
233
  if (
231
234
  u &&
232
235
  u.last_mobile_login &&
@@ -242,7 +245,16 @@ const getApp = async (opts = {}) => {
242
245
  } else {
243
246
  return done(null, { role_id: 10 });
244
247
  }
245
- });
248
+ };
249
+ if (
250
+ db.is_it_multi_tenant() &&
251
+ jwt_payload.tenant?.length > 0 &&
252
+ jwt_payload.tenant !== db.connectObj.default_schema
253
+ ) {
254
+ return await db.runWithTenant(jwt_payload.tenant, userCheck);
255
+ } else {
256
+ return await userCheck();
257
+ }
246
258
  })
247
259
  );
248
260
  passport.use(
@@ -259,6 +271,12 @@ const getApp = async (opts = {}) => {
259
271
  passport.deserializeUser(function (user, done) {
260
272
  done(null, user);
261
273
  });
274
+ app.use(function (req, res, next) {
275
+ if (req.headers["x-saltcorn-client"] === "mobile-app") {
276
+ req.smr = true; // saltcorn-mobile-request
277
+ }
278
+ return next();
279
+ });
262
280
  app.use(setTenant);
263
281
 
264
282
  // Change into s3storage compatible selector
@@ -267,12 +285,15 @@ const getApp = async (opts = {}) => {
267
285
  app.use(s3storage.middlewareTransform);
268
286
 
269
287
  app.use(wrapper(version_tag));
288
+
270
289
  const csurf = csrf();
271
290
  if (!opts.disableCsrf)
272
291
  app.use(function (req, res, next) {
273
292
  if (
274
- req.url.startsWith("/api/") ||
275
- req.url === "/auth/login-with/jwt" ||
293
+ (req.smr &&
294
+ (req.url.startsWith("/api/") ||
295
+ req.url === "/auth/login-with/jwt" ||
296
+ req.url === "/auth/signup")) ||
276
297
  jwt_extractor(req)
277
298
  )
278
299
  return disabledCsurf(req, res, next);
@@ -280,13 +301,6 @@ const getApp = async (opts = {}) => {
280
301
  });
281
302
  else app.use(disabledCsurf);
282
303
 
283
- app.use(function (req, res, next) {
284
- if (req.headers["x-saltcorn-client"] === "mobile-app") {
285
- req.smr = true; // saltcorn-mobile-request
286
- }
287
- return next();
288
- });
289
-
290
304
  mountRoutes(app);
291
305
  // set tenant homepage as / root
292
306
  app.get("/", error_catcher(homepage));
package/auth/routes.js CHANGED
@@ -199,33 +199,41 @@ const getAuthLinks = (current, noMethods) => {
199
199
  return links;
200
200
  };
201
201
 
202
- const loginWithJwt = async (email, password, res) => {
203
- const user = await User.findOne({ email });
204
- if (user && user.checkPassword(password)) {
205
- const now = new Date();
206
- const jwt_secret = db.connectObj.jwt_secret;
207
- const token = jwt.sign(
208
- {
209
- sub: email,
210
- user: {
211
- id: user.id,
212
- email: user.email,
213
- role_id: user.role_id,
214
- language: user.language ? user.language : "en",
215
- disabled: user.disabled,
202
+ const loginWithJwt = async (email, password, saltcornApp, res) => {
203
+ const loginFn = async () => {
204
+ const user = await User.findOne({ email });
205
+ if (user && user.checkPassword(password)) {
206
+ const now = new Date();
207
+ const jwt_secret = db.connectObj.jwt_secret;
208
+ const token = jwt.sign(
209
+ {
210
+ sub: email,
211
+ user: {
212
+ id: user.id,
213
+ email: user.email,
214
+ role_id: user.role_id,
215
+ language: user.language ? user.language : "en",
216
+ disabled: user.disabled,
217
+ },
218
+ iss: "saltcorn@saltcorn",
219
+ aud: "saltcorn-mobile-app",
220
+ iat: now.valueOf(),
221
+ tenant: db.getTenantSchema(),
216
222
  },
217
- iss: "saltcorn@saltcorn",
218
- aud: "saltcorn-mobile-app",
219
- iat: now.valueOf(),
220
- },
221
- jwt_secret
222
- );
223
- if (!user.last_mobile_login) await user.updateLastMobileLogin(now);
224
- res.json(token);
223
+ jwt_secret
224
+ );
225
+ if (!user.last_mobile_login) await user.updateLastMobileLogin(now);
226
+ res.json(token);
227
+ } else {
228
+ res.json({
229
+ alerts: [{ type: "danger", msg: "Incorrect user or password" }],
230
+ });
231
+ }
232
+ };
233
+ if (saltcornApp && saltcornApp !== db.connectObj.default_schema) {
234
+ await db.runWithTenant(saltcornApp, loginFn);
225
235
  } else {
226
- res.json({
227
- alerts: [{ type: "danger", msg: "Incorrect user or password" }],
228
- });
236
+ await loginFn();
229
237
  }
230
238
  };
231
239
 
@@ -899,7 +907,13 @@ router.post(
899
907
  } else {
900
908
  const u = await User.create({ email, password });
901
909
  await send_verification_email(u, req);
902
- if (req.smr) await loginWithJwt(email, password, res);
910
+ if (req.smr)
911
+ await loginWithJwt(
912
+ email,
913
+ password,
914
+ req.headers["x-saltcorn-app"],
915
+ res
916
+ );
903
917
  else signup_login_with_user(u, req, res);
904
918
  }
905
919
  }
@@ -1008,7 +1022,7 @@ router.get(
1008
1022
  const { method } = req.params;
1009
1023
  if (method === "jwt") {
1010
1024
  const { email, password } = req.query;
1011
- await loginWithJwt(email, password, res);
1025
+ await loginWithJwt(email, password, req.headers["x-saltcorn-app"], res);
1012
1026
  } else {
1013
1027
  const auth = getState().auth_methods[method];
1014
1028
  if (auth) {
package/locales/en.json CHANGED
@@ -983,5 +983,14 @@
983
983
  "Restore/download automated backups »": "Restore/download automated backups »",
984
984
  "Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).": "Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).",
985
985
  "List/download snapshots &raquo;": "List/download snapshots &raquo;",
986
- "Discover tables that are already in the Database, but not known to Saltcorn": "Discover tables that are already in the Database, but not known to Saltcorn"
986
+ "Discover tables that are already in the Database, but not known to Saltcorn": "Discover tables that are already in the Database, but not known to Saltcorn",
987
+ "Split paste": "Split paste",
988
+ "Separate paste content into separate inputs": "Separate paste content into separate inputs",
989
+ "Add entries to tag": "Add entries to tag",
990
+ "Add pages": "Add pages",
991
+ "Add triggers": "Add triggers",
992
+ "Formula value": "Formula value",
993
+ "The build was successfully": "The build was successfully",
994
+ "Unable to build the app:": "Unable to build the app:",
995
+ "Add tag": "Add tag"
987
996
  }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.7.4-beta.3",
3
+ "version": "0.7.4",
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.4-beta.3",
10
- "@saltcorn/builder": "0.7.4-beta.3",
11
- "@saltcorn/data": "0.7.4-beta.3",
12
- "@saltcorn/admin-models": "0.7.4-beta.3",
13
- "@saltcorn/markup": "0.7.4-beta.3",
14
- "@saltcorn/sbadmin2": "0.7.4-beta.3",
9
+ "@saltcorn/base-plugin": "0.7.4",
10
+ "@saltcorn/builder": "0.7.4",
11
+ "@saltcorn/data": "0.7.4",
12
+ "@saltcorn/admin-models": "0.7.4",
13
+ "@saltcorn/markup": "0.7.4",
14
+ "@saltcorn/sbadmin2": "0.7.4",
15
15
  "@socket.io/cluster-adapter": "^0.1.0",
16
16
  "@socket.io/sticky": "^1.0.1",
17
17
  "aws-sdk": "^2.1037.0",
@@ -34,7 +34,12 @@ function add_repeater(nm) {
34
34
  });
35
35
  newe.appendTo($("div.repeats-" + nm));
36
36
  }
37
- // "e.closest('.form-namespace').find('.coltype').val()==='Field';"
37
+
38
+ const _apply_showif_plugins = []
39
+
40
+ const add_apply_showif_plugin = p => {
41
+ _apply_showif_plugins.push(p)
42
+ }
38
43
  function apply_showif() {
39
44
  $("[data-show-if]").each(function (ix, element) {
40
45
  var e = $(element);
@@ -110,26 +115,54 @@ function apply_showif() {
110
115
  e.change(function (ec) {
111
116
  e.attr("data-selected", ec.target.value);
112
117
  });
113
- $.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
114
- if (resp.success) {
115
- e.empty();
116
- if (!dynwhere.required) e.append($(`<option></option>`));
117
- resp.success.forEach((r) => {
118
- e.append(
119
- $(
120
- `<option ${`${current}` === `${r[dynwhere.refname]}` ? "selected" : ""
121
- } value="${r[dynwhere.refname]}">${dynwhere.label_formula
122
- ? new Function(
123
- `{${Object.keys(r).join(",")}}`,
124
- "return " + dynwhere.label_formula
125
- )(r)
126
- : r[dynwhere.summary_field]
127
- }</option>`
128
- )
129
- );
130
- });
118
+
119
+ const currentOptionsSet = e.prop('data-fetch-options-current-set')
120
+ if (currentOptionsSet === qs) return;
121
+
122
+ const activate = (success, qs) => {
123
+ e.empty();
124
+ e.prop('data-fetch-options-current-set', qs)
125
+ if (!dynwhere.required) e.append($(`<option></option>`));
126
+ let currentDataOption = undefined;
127
+ const dataOptions = []
128
+ success.forEach((r) => {
129
+ const label = dynwhere.label_formula
130
+ ? new Function(
131
+ `{${Object.keys(r).join(",")}}`,
132
+ "return " + dynwhere.label_formula
133
+ )(r)
134
+ : r[dynwhere.summary_field]
135
+ const value = r[dynwhere.refname]
136
+ const selected = `${current}` === `${r[dynwhere.refname]}`
137
+ dataOptions.push({ text: label, value });
138
+ if (selected) currentDataOption = value;
139
+ const html = `<option ${selected ? "selected" : ""
140
+ } value="${value}">${label}</option>`
141
+ e.append(
142
+ $(html)
143
+ );
144
+ });
145
+ element.dispatchEvent(new Event('RefreshSelectOptions'))
146
+ if (e.hasClass("selectized") && $().selectize) {
147
+ e.selectize()[0].selectize.clearOptions();
148
+ e.selectize()[0].selectize.addOption(dataOptions);
149
+ if (typeof currentDataOption !== "undefined")
150
+ e.selectize()[0].selectize.setValue(currentDataOption);
151
+
131
152
  }
132
- });
153
+ }
154
+
155
+ const cache = e.prop('data-fetch-options-cache') || {}
156
+ if (cache[qs]) {
157
+ activate(cache[qs], qs)
158
+ } else
159
+ $.ajax(`/api/${dynwhere.table}?${qs}`).then((resp) => {
160
+ if (resp.success) {
161
+ activate(resp.success, qs)
162
+ const cacheNow = e.prop('data-fetch-options-cache') || {}
163
+ e.prop('data-fetch-options-cache', { ...cacheNow, [qs]: resp.success })
164
+ }
165
+ });
133
166
  });
134
167
 
135
168
  $("[data-source-url]").each(function (ix, element) {
@@ -145,7 +178,17 @@ function apply_showif() {
145
178
  },
146
179
  });
147
180
  });
181
+ _apply_showif_plugins.forEach(p => p())
182
+ }
183
+
184
+ function splitTargetMatch(elemValue, target, keySpec) {
185
+ if (!elemValue) return false;
186
+ const [fld, keySpec1] = keySpec.split("|_")
187
+ const [sep, pos] = keySpec1.split("_")
188
+ const elemValueShort = elemValue.split(sep)[pos]
189
+ return elemValueShort === target;
148
190
  }
191
+
149
192
  function get_form_record(e, select_labels) {
150
193
  const rec = {};
151
194
  e.closest("form")
@@ -576,6 +619,8 @@ const columnSummary = (col) => {
576
619
  return `Field ${col.field_name} ${col.fieldview || ""}`;
577
620
  case "Link":
578
621
  return `Link ${col.link_text}`;
622
+ case "FormulaValue":
623
+ return `Formula ${col.formula}`;
579
624
  case "JoinField":
580
625
  return `Join ${col.join_field}`;
581
626
  case "ViewLink":
@@ -583,7 +628,7 @@ const columnSummary = (col) => {
583
628
  case "Action":
584
629
  return `Action ${col.action_label || col.action_name}`;
585
630
  case "Aggregation":
586
- return `${col.stat} ${col.agg_field} ${col.agg_relation}`;
631
+ return `${col.stat} ${col.agg_field.split("@")[0]} ${col.agg_relation}`;
587
632
  default:
588
633
  return "Unknown";
589
634
  }
@@ -678,3 +723,23 @@ function cancel_form(form) {
678
723
  $(form).append(`<input type="hidden" name="_cancel" value="on">`);
679
724
  $(form).submit();
680
725
  }
726
+
727
+ function split_paste_handler(e) {
728
+ e.preventDefault();
729
+ let clipboardData = e.clipboardData || window.clipboardData || e.originalEvent.clipboardData;
730
+
731
+ const lines = clipboardData.getData('text').split(/\r\n/g)
732
+
733
+ const form = $(e.target).closest('form')
734
+
735
+ let matched = false;
736
+
737
+ form.find('input:not(:disabled):not([readonly]):not(:hidden)').each(function (ix, element) {
738
+ if (!matched && element === e.target) matched = true;
739
+ if (matched && lines.length > 0) {
740
+ $(element).val(lines.shift())
741
+ }
742
+ })
743
+
744
+
745
+ }
@@ -299,7 +299,9 @@ section.range-slider input[type="range"]::-moz-focus-outer {
299
299
  padding: 0.1rem 0.4rem !important;
300
300
  }
301
301
 
302
- table.table-inner-grid, table.table-inner-grid th, table.table-inner-grid td {
302
+ table.table-inner-grid,
303
+ table.table-inner-grid th,
304
+ table.table-inner-grid td {
303
305
  border: 1px solid black;
304
306
  border-collapse: collapse;
305
307
  }
@@ -307,18 +309,18 @@ table.table-inner-grid, table.table-inner-grid th, table.table-inner-grid td {
307
309
  /* https://codepen.io/pezmotion/pen/RQERdm */
308
310
 
309
311
  .editStarRating {
310
- direction: rtl;
311
- unicode-bidi: bidi-override;
312
- color: #ddd;
312
+ direction: rtl;
313
+ unicode-bidi: bidi-override;
314
+ color: #ddd;
313
315
  }
314
316
  .editStarRating input {
315
- display: none;
317
+ display: none;
316
318
  }
317
319
  .editStarRating label:hover,
318
320
  .editStarRating label:hover ~ label,
319
321
  .editStarRating input:checked + label,
320
322
  .editStarRating input:checked + label ~ label {
321
- color: #ffc107;
323
+ color: #ffc107;
322
324
  }
323
325
 
324
326
  .CodeMirror {
@@ -326,12 +328,12 @@ table.table-inner-grid, table.table-inner-grid th, table.table-inner-grid td {
326
328
  }
327
329
 
328
330
  /* copied from bootstrap and adjusted to show the arrow on the left */
329
- .card .card-header-left-collapse[data-bs-toggle=collapse] {
331
+ .card .card-header-left-collapse[data-bs-toggle="collapse"] {
330
332
  text-decoration: none;
331
333
  position: relative;
332
334
  padding: 0.75rem 3.25rem 0.75rem 1.25rem;
333
335
  }
334
- .card .card-header-left-collapse[data-bs-toggle=collapse]::before {
336
+ .card .card-header-left-collapse[data-bs-toggle="collapse"]::before {
335
337
  position: absolute;
336
338
  left: 0;
337
339
  top: 0;
@@ -341,9 +343,13 @@ table.table-inner-grid, table.table-inner-grid th, table.table-inner-grid td {
341
343
  font-family: "Font Awesome 5 Free";
342
344
  color: #d1d3e2;
343
345
  }
344
- .card .card-header-left-collapse[data-bs-toggle=collapse].collapsed {
346
+ .card .card-header-left-collapse[data-bs-toggle="collapse"].collapsed {
345
347
  border-radius: 0.35rem;
346
348
  }
347
- .card .card-header-left-collapse[data-bs-toggle=collapse].collapsed::before {
349
+ .card .card-header-left-collapse[data-bs-toggle="collapse"].collapsed::before {
348
350
  content: "\f105";
349
351
  }
352
+
353
+ .d-inline-maybe {
354
+ display: inline;
355
+ }
@@ -317,10 +317,11 @@ function ajaxSubmitForm(e) {
317
317
  data: new FormData(form[0]),
318
318
  processData: false,
319
319
  contentType: false,
320
- success: function () {
320
+ success: function (res) {
321
321
  var no_reload = $("#scmodal").hasClass("no-submit-reload");
322
322
  $("#scmodal").modal("hide");
323
323
  if (!no_reload) location.reload();
324
+ else common_done(res);
324
325
  },
325
326
  error: function (request) {
326
327
  var title = request.getResponseHeader("Page-Title");
@@ -444,12 +445,17 @@ async function fill_formula_btn_click(btn, k) {
444
445
  }
445
446
  }
446
447
  }
447
- const val = new Function(
448
- `{${Object.keys(rec).join(",")}}`,
449
- "return " + formula
450
- )(rec);
451
- $(btn).closest(".input-group").find("input").val(val);
452
- if (k) k();
448
+ try {
449
+ const val = new Function(
450
+ `{${Object.keys(rec).join(",")}}`,
451
+ "return " + formula
452
+ )(rec);
453
+ $(btn).closest(".input-group").find("input").val(val);
454
+ if (k) k();
455
+ } catch (e) {
456
+ notifyAlert({ type: "danger", text: `Error evaluating fill formula: ${e.message}` })
457
+ console.error(e)
458
+ }
453
459
  }
454
460
 
455
461
  /*
package/routes/actions.js CHANGED
@@ -5,7 +5,12 @@
5
5
  * @subcategory routes
6
6
  */
7
7
  const Router = require("express-promise-router");
8
- const { isAdmin, error_catcher, get_base_url } = require("./utils.js");
8
+ const {
9
+ isAdmin,
10
+ error_catcher,
11
+ get_base_url,
12
+ addOnDoneRedirect,
13
+ } = require("./utils.js");
9
14
  const { getState } = require("@saltcorn/data/db/state");
10
15
  const Trigger = require("@saltcorn/data/models/trigger");
11
16
  const { getTriggerList } = require("./common_lists");
@@ -149,6 +154,7 @@ const triggerForm = async (req, trigger) => {
149
154
  id = trigger.id;
150
155
  form_action = `/actions/edit/${id}`;
151
156
  } else form_action = "/actions/new";
157
+ form_action = addOnDoneRedirect(form_action, req);
152
158
  const hasChannel = Object.entries(getState().eventTypes)
153
159
  .filter(([k, v]) => v.hasChannel)
154
160
  .map(([k, v]) => k);
@@ -323,7 +329,7 @@ router.post(
323
329
  const tr = await Trigger.create(form.values);
324
330
  id = tr.id;
325
331
  }
326
- res.redirect(`/actions/configure/${id}`);
332
+ res.redirect(addOnDoneRedirect(`/actions/configure/${id}`, req));
327
333
  }
328
334
  })
329
335
  );
@@ -389,7 +395,7 @@ router.get(
389
395
  } else if (trigger.action === "blocks") {
390
396
  const locale = req.getLocale();
391
397
  const form = new Form({
392
- action: `/actions/configure/${id}`,
398
+ action: addOnDoneRedirect(`/actions/configure/${id}`, req),
393
399
  fields: action.configFields,
394
400
  noSubmitButton: true,
395
401
  id: "blocklyForm",
@@ -464,7 +470,7 @@ router.get(
464
470
  const cfgFields = await getActionConfigFields(action, table);
465
471
  // create form
466
472
  const form = new Form({
467
- action: `/actions/configure/${id}`,
473
+ action: addOnDoneRedirect(`/actions/configure/${id}`, req),
468
474
  fields: cfgFields,
469
475
  });
470
476
  // populate form values
@@ -522,7 +528,11 @@ router.post(
522
528
  } else {
523
529
  await Trigger.update(trigger.id, { configuration: form.values });
524
530
  req.flash("success", "Action configuration saved");
525
- res.redirect(`/actions/`);
531
+ res.redirect(
532
+ req.query.on_done_redirect
533
+ ? `/${req.query.on_done_redirect}`
534
+ : "/actions/"
535
+ );
526
536
  }
527
537
  })
528
538
  );
package/routes/admin.js CHANGED
@@ -476,10 +476,10 @@ router.get(
476
476
  li(
477
477
  a({ href: "/admin/clear-all" }, req.__("Clear this application")),
478
478
  " ",
479
- req.__("(tick all boxes)")
479
+ req.__("(tick all boxes)")
480
480
  ),
481
481
  li(
482
- req.__("When prompted to create the first user, click the link to restore a backup")
482
+ req.__("When prompted to create the first user, click the link to restore a backup")
483
483
  ),
484
484
  li(req.__("Select the downloaded backup file"))
485
485
  )
@@ -980,7 +980,7 @@ router.post(
980
980
  res.attachment(fileName);
981
981
  const file = fs.createReadStream(fileName);
982
982
  file.on("end", function () {
983
- fs.unlink(fileName, function () { });
983
+ fs.unlink(fileName, function () {});
984
984
  });
985
985
  file.pipe(res);
986
986
  })
@@ -1003,7 +1003,7 @@ router.post(
1003
1003
  );
1004
1004
  if (err) req.flash("error", err);
1005
1005
  else req.flash("success", req.__("Successfully restored backup"));
1006
- fs.unlink(newPath, function () { });
1006
+ fs.unlink(newPath, function () {});
1007
1007
  res.redirect(`/admin`);
1008
1008
  })
1009
1009
  );
@@ -1198,12 +1198,17 @@ router.get(
1198
1198
  "/configuration-check",
1199
1199
  isAdmin,
1200
1200
  error_catcher(async (req, res) => {
1201
- const { passes, errors, pass } = await runConfigurationCheck(req);
1201
+ const { passes, errors, pass, warnings } = await runConfigurationCheck(req);
1202
1202
  const mkError = (err) =>
1203
1203
  div(
1204
1204
  { class: "alert alert-danger", role: "alert" },
1205
1205
  pre({ class: "mb-0" }, code(err))
1206
1206
  );
1207
+ const mkWarning = (err) =>
1208
+ div(
1209
+ { class: "alert alert-warning", role: "alert" },
1210
+ pre({ class: "mb-0" }, code(err))
1211
+ );
1207
1212
  res.sendWrap(req.__(`Admin`), {
1208
1213
  above: [
1209
1214
  {
@@ -1227,7 +1232,8 @@ router.get(
1227
1232
  req.__("No errors detected during configuration check")
1228
1233
  )
1229
1234
  )
1230
- : errors.map(mkError)
1235
+ : errors.map(mkError),
1236
+ (warnings || []).map(mkWarning)
1231
1237
  ),
1232
1238
  },
1233
1239
  {
@@ -1269,11 +1275,10 @@ const buildDialogScript = () => {
1269
1275
 
1270
1276
  function handleMessages() {
1271
1277
  notifyAlert("This is still under development and might run longer.")
1272
- ${
1273
- getState().getConfig("apple_team_id") &&
1278
+ ${getState().getConfig("apple_team_id") &&
1274
1279
  getState().getConfig("apple_team_id") !== "null"
1275
- ? ""
1276
- : `
1280
+ ? ""
1281
+ : `
1277
1282
  if ($("#iOSCheckboxId")[0].checked) {
1278
1283
  notifyAlert(
1279
1284
  "No 'Apple Team ID' is configured, I will try to build a project for the iOS simulator."
@@ -1547,6 +1552,12 @@ router.post(
1547
1552
  }
1548
1553
  if (appFile) spawnParams.push("-a", appFile);
1549
1554
  if (serverURL) spawnParams.push("-s", serverURL);
1555
+ if (
1556
+ db.is_it_multi_tenant() &&
1557
+ db.getTenantSchema() !== db.connectObj.default_schema
1558
+ ) {
1559
+ spawnParams.push("--tenantAppName", db.getTenantSchema());
1560
+ }
1550
1561
  const child = spawn("saltcorn", spawnParams, {
1551
1562
  stdio: ["ignore", "pipe", "pipe"],
1552
1563
  cwd: ".",