@saltcorn/server 1.1.1-beta.1 → 1.1.1-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/routes/admin.js CHANGED
@@ -19,6 +19,7 @@ const Plugin = require("@saltcorn/data/models/plugin");
19
19
  const File = require("@saltcorn/data/models/file");
20
20
  const { spawn, exec } = require("child_process");
21
21
  const User = require("@saltcorn/data/models/user");
22
+ const Trigger = require("@saltcorn/data/models/trigger");
22
23
  const path = require("path");
23
24
  const { X509Certificate } = require("crypto");
24
25
  const { getAllTenants } = require("@saltcorn/admin-models/models/tenant");
@@ -114,6 +115,8 @@ const { get_help_markup } = require("../help/index.js");
114
115
  const Docker = require("dockerode");
115
116
  const npmFetch = require("npm-registry-fetch");
116
117
  const Tag = require("@saltcorn/data/models/tag");
118
+ const MarkdownIt = require("markdown-it"),
119
+ md = new MarkdownIt();
117
120
 
118
121
  const router = new Router();
119
122
  module.exports = router;
@@ -146,6 +149,24 @@ const app_files_table = (files, buildDirName, req) =>
146
149
  ],
147
150
  files
148
151
  );
152
+ const intermediate_build_result = (outDirName, buildDir, req) => {
153
+ return div(
154
+ h3("Intermediate build result"),
155
+ div(
156
+ button(
157
+ {
158
+ id: "finishMobileAppBtnId",
159
+ type: "button",
160
+ onClick: `finish_mobile_app(this, '${outDirName}', '${buildDir}');`,
161
+ class: "btn btn-warning",
162
+ },
163
+ i({ class: "fas fa-hammer pe-2" }),
164
+
165
+ req.__("Finish the build")
166
+ )
167
+ )
168
+ );
169
+ };
149
170
 
150
171
  admin_config_route({
151
172
  router,
@@ -268,6 +289,18 @@ router.get(
268
289
  })
269
290
  );
270
291
 
292
+ router.get(
293
+ "/whatsnew",
294
+ isAdmin,
295
+ error_catcher(async (req, res) => {
296
+ const fp = path.join(__dirname, "..", "CHANGELOG.md");
297
+ const fileBuf = await fs.promises.readFile(fp);
298
+ const mdContents = fileBuf.toString().replace("# Notable changes\n","");
299
+ const markup = md.render(mdContents);
300
+ res.sendWrap(`What's new in Saltcorn`, { above: [markup] });
301
+ })
302
+ );
303
+
271
304
  /**
272
305
  * @name get/backup
273
306
  * @function
@@ -1118,7 +1151,7 @@ router.get(
1118
1151
  table(
1119
1152
  tbody(
1120
1153
  tr(
1121
- th(req.__("Saltcorn version")),
1154
+ th({ valign: "top" }, req.__("Saltcorn version")),
1122
1155
  td(
1123
1156
  packagejson.version,
1124
1157
  isRoot && can_update
@@ -1158,7 +1191,15 @@ router.get(
1158
1191
  ` onError: (res) => { selectVersionError(res, '${rndid}') } });`,
1159
1192
  },
1160
1193
  req.__("Choose version")
1161
- )
1194
+ ),
1195
+ "<br>",
1196
+ a(
1197
+ {
1198
+ onclick: "ajax_modal('/admin/whatsnew')",
1199
+ href: `javascript:void(0)`,
1200
+ },
1201
+ "What's new?"
1202
+ )
1162
1203
  )
1163
1204
  ),
1164
1205
  git_commit &&
@@ -1993,9 +2034,6 @@ const buildDialogScript = (capacitorBuilderAvailable, isSbadmin2) =>
1993
2034
  $("#entryPointTypeID").attr("value", type);
1994
2035
  }
1995
2036
 
1996
- function handleMessages() {
1997
- notifyAlert("Building the app, please wait.", true)
1998
- }
1999
2037
  const versionPattern = /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/;
2000
2038
  ${domReady(`
2001
2039
  const versionInput = document.getElementById('appVersionInputId');
@@ -3077,6 +3115,48 @@ router.get(
3077
3115
  )
3078
3116
  )
3079
3117
  )
3118
+ // Share Extension provisioning profile
3119
+ // disabled for now
3120
+ // div(
3121
+ // { class: "row pb-3" },
3122
+ // div(
3123
+ // { class: "col-sm-8" },
3124
+ // label(
3125
+ // {
3126
+ // for: "shareProvisioningProfileInputId",
3127
+ // class: "form-label fw-bold",
3128
+ // },
3129
+ // req.__("Share Extension Provisioning Profile"),
3130
+ // a(
3131
+ // {
3132
+ // href: "javascript:ajax_modal('/admin/help/Provisioning Profile?')",
3133
+ // },
3134
+ // i({ class: "fas fa-question-circle ps-1" })
3135
+ // )
3136
+ // ),
3137
+ // select(
3138
+ // {
3139
+ // class: "form-select",
3140
+ // name: "shareProvisioningProfile",
3141
+ // id: "shareProvisioningProfileInputId",
3142
+ // },
3143
+ // [
3144
+ // option({ value: "" }, ""),
3145
+ // ...provisioningFiles.map((file) =>
3146
+ // option(
3147
+ // {
3148
+ // value: file.location,
3149
+ // selected:
3150
+ // builderSettings.shareProvisioningProfile ===
3151
+ // file.location,
3152
+ // },
3153
+ // file.filename
3154
+ // )
3155
+ // ),
3156
+ // ].join("")
3157
+ // )
3158
+ // )
3159
+ // )
3080
3160
  )
3081
3161
  )
3082
3162
  ),
@@ -3100,15 +3180,13 @@ router.get(
3100
3180
  })
3101
3181
  );
3102
3182
 
3103
- const checkFiles = async (outDir, fileNames) => {
3183
+ const checkFiles = async (outDirName, fileNames) => {
3104
3184
  const rootFolder = await File.rootFolder();
3105
- const mobile_app_dir = path.join(rootFolder.location, "mobile_app", outDir);
3185
+ const outDir = path.join(rootFolder.location, "mobile_app", outDirName);
3106
3186
  const unsafeFiles = await Promise.all(
3107
3187
  fs
3108
- .readdirSync(mobile_app_dir)
3109
- .map(
3110
- async (outFile) => await File.from_file_on_disk(outFile, mobile_app_dir)
3111
- )
3188
+ .readdirSync(outDir)
3189
+ .map(async (outFile) => await File.from_file_on_disk(outFile, outDir))
3112
3190
  );
3113
3191
  const entries = unsafeFiles
3114
3192
  .filter(
@@ -3127,9 +3205,18 @@ router.get(
3127
3205
  "/build-mobile-app/finished",
3128
3206
  isAdmin,
3129
3207
  error_catcher(async (req, res) => {
3130
- const { build_dir } = req.query;
3208
+ const { out_dir_name, mode } = req.query;
3209
+ const stepDesc =
3210
+ mode === "prepare"
3211
+ ? "_prepare_step"
3212
+ : mode === "finish"
3213
+ ? "_finish_step"
3214
+ : "";
3131
3215
  res.json({
3132
- finished: await checkFiles(build_dir, ["logs.txt", "error_logs.txt"]),
3216
+ finished: await checkFiles(out_dir_name, [
3217
+ `logs${stepDesc}.txt`,
3218
+ `error_logs${stepDesc}.txt`,
3219
+ ]),
3133
3220
  });
3134
3221
  })
3135
3222
  );
@@ -3164,8 +3251,8 @@ router.get(
3164
3251
  "/build-mobile-app/result",
3165
3252
  isAdmin,
3166
3253
  error_catcher(async (req, res) => {
3167
- const { build_dir_name } = req.query;
3168
- if (!validateBuildDirName(build_dir_name)) {
3254
+ const { out_dir_name, build_dir, mode } = req.query;
3255
+ if (!validateBuildDirName(out_dir_name)) {
3169
3256
  return res.sendWrap(req.__(`Admin`), {
3170
3257
  above: [
3171
3258
  {
@@ -3177,11 +3264,7 @@ router.get(
3177
3264
  });
3178
3265
  }
3179
3266
  const rootFolder = await File.rootFolder();
3180
- const buildDir = path.join(
3181
- rootFolder.location,
3182
- "mobile_app",
3183
- build_dir_name
3184
- );
3267
+ const buildDir = path.join(rootFolder.location, "mobile_app", out_dir_name);
3185
3268
  if (!validateBuildDir(buildDir, rootFolder.location)) {
3186
3269
  return res.sendWrap(req.__(`Admin`), {
3187
3270
  above: [
@@ -3199,7 +3282,15 @@ router.get(
3199
3282
  .readdirSync(buildDir)
3200
3283
  .map(async (outFile) => await File.from_file_on_disk(outFile, buildDir))
3201
3284
  );
3202
- const resultMsg = files.find((file) => file.filename === "logs.txt")
3285
+ const stepDesc =
3286
+ mode === "prepare"
3287
+ ? "_prepare_step"
3288
+ : mode === "finish"
3289
+ ? "_finish_step"
3290
+ : "";
3291
+ const resultMsg = files.find(
3292
+ (file) => file.filename === `logs${stepDesc}.txt`
3293
+ )
3203
3294
  ? req.__("The build was successfully")
3204
3295
  : req.__("Unable to build the app");
3205
3296
  res.sendWrap(req.__(`Admin`), {
@@ -3209,11 +3300,98 @@ router.get(
3209
3300
  title: req.__("Build Result"),
3210
3301
  contents: div(resultMsg),
3211
3302
  },
3212
- files.length > 0 ? app_files_table(files, build_dir_name, req) : "",
3303
+ files.length > 0 ? app_files_table(files, out_dir_name, req) : "",
3304
+ mode === "prepare"
3305
+ ? intermediate_build_result(out_dir_name, build_dir, req)
3306
+ : "",
3213
3307
  ],
3214
3308
  });
3215
3309
  })
3216
3310
  );
3311
+
3312
+ router.post(
3313
+ "/build-mobile-app/finish",
3314
+ isAdmin,
3315
+ error_catcher(async (req, res) => {
3316
+ const { out_dir_name, build_dir } = req.body;
3317
+ const content = await fs.promises.readFile(
3318
+ path.join(build_dir, "spawnParams.json")
3319
+ );
3320
+ const spawnParams = JSON.parse(content);
3321
+ const rootFolder = await File.rootFolder();
3322
+ const outDirFullPath = path.join(
3323
+ rootFolder.location,
3324
+ "mobile_app",
3325
+ out_dir_name
3326
+ );
3327
+ res.json({
3328
+ success: true,
3329
+ });
3330
+ const child = spawn(
3331
+ getSafeSaltcornCmd(),
3332
+ [...spawnParams, "-m", "finish"],
3333
+ {
3334
+ stdio: ["ignore", "pipe", "pipe"],
3335
+ cwd: ".",
3336
+ }
3337
+ );
3338
+ const childOutputs = [];
3339
+ child.stdout.on("data", (data) => {
3340
+ const outMsg = data.toString();
3341
+ getState().log(5, outMsg);
3342
+ if (data) childOutputs.push(outMsg);
3343
+ });
3344
+ child.stderr.on("data", (data) => {
3345
+ const errMsg = data ? data.toString() : req.__("An error occurred");
3346
+ getState().log(5, errMsg);
3347
+ childOutputs.push(errMsg);
3348
+ });
3349
+ child.on("exit", async (exitCode, signal) => {
3350
+ const logFile =
3351
+ exitCode === 0 ? "logs_finish_step.txt" : "error_logs_finish_step.txt";
3352
+ try {
3353
+ const exitMsg = childOutputs.join("\n");
3354
+ await fs.promises.writeFile(
3355
+ path.join(outDirFullPath, logFile),
3356
+ exitMsg
3357
+ );
3358
+ await File.set_xattr_of_existing_file(
3359
+ logFile,
3360
+ outDirFullPath,
3361
+ req.user
3362
+ );
3363
+ } catch (error) {
3364
+ console.log(`unable to write '${logFile}' to '${outDirFullPath}'`);
3365
+ console.log(error);
3366
+ }
3367
+ });
3368
+ child.on("error", (msg) => {
3369
+ const message = msg.message ? msg.message : msg.code;
3370
+ const stack = msg.stack ? msg.stack : "";
3371
+ const logFile = "error_logs.txt";
3372
+ const errMsg = [message, stack].join("\n");
3373
+ getState().log(5, msg);
3374
+ fs.writeFile(
3375
+ path.join(outDirFullPath, "error_logs.txt"),
3376
+ errMsg,
3377
+ async (error) => {
3378
+ if (error) {
3379
+ console.log(`unable to write logFile to '${outDirFullPath}'`);
3380
+ console.log(error);
3381
+ } else {
3382
+ // no transaction, '/build-mobile-app/finished' filters for valid attributes
3383
+ await File.set_xattr_of_existing_file(
3384
+ logFile,
3385
+ outDirFullPath,
3386
+ req.user
3387
+ );
3388
+ }
3389
+ }
3390
+ );
3391
+ });
3392
+ })
3393
+ );
3394
+
3217
3395
  /**
3218
3396
  * Do Build Mobile App
3219
3397
  */
@@ -3222,6 +3400,8 @@ router.post(
3222
3400
  isAdmin,
3223
3401
  error_catcher(async (req, res) => {
3224
3402
  getState().log(2, `starting mobile build: ${JSON.stringify(req.body)}`);
3403
+ const msgs = [];
3404
+ let mode = "full";
3225
3405
  let {
3226
3406
  entryPoint,
3227
3407
  entryPointType,
@@ -3239,11 +3419,27 @@ router.post(
3239
3419
  synchedTables,
3240
3420
  includedPlugins,
3241
3421
  provisioningProfile,
3422
+ shareProvisioningProfile,
3242
3423
  buildType,
3243
3424
  keystoreFile,
3244
3425
  keystoreAlias,
3245
3426
  keystorePassword,
3246
3427
  } = req.body;
3428
+ // const receiveShareTriggers = Trigger.find({
3429
+ // when_trigger: "ReceiveMobileShareData",
3430
+ // });
3431
+ // disabeling share to support for now
3432
+ let allowShareTo = false; // receiveShareTriggers.length > 0;
3433
+ if (allowShareTo && iOSPlatform && !shareProvisioningProfile) {
3434
+ allowShareTo = false;
3435
+ msgs.push({
3436
+ type: "warning",
3437
+ text: req.__(
3438
+ "A ReceiveMobileShareData trigger exists, but no Share Extension Provisioning Profile is provided. " +
3439
+ "Building without share to support."
3440
+ ),
3441
+ });
3442
+ }
3247
3443
  if (!includedPlugins) includedPlugins = [];
3248
3444
  if (!synchedTables) synchedTables = [];
3249
3445
  if (!entryPoint) {
@@ -3279,14 +3475,26 @@ router.post(
3279
3475
  ),
3280
3476
  });
3281
3477
  }
3282
- if (iOSPlatform && !provisioningProfile) {
3283
- return res.json({
3284
- error: req.__(
3285
- "Please provide a Provisioning Profile for the iOS build."
3286
- ),
3478
+ if (iOSPlatform) {
3479
+ if (!provisioningProfile)
3480
+ return res.json({
3481
+ error: req.__(
3482
+ "Please provide a Provisioning Profile for the iOS build."
3483
+ ),
3484
+ });
3485
+ }
3486
+ if (buildType === "debug" && keystoreFile) {
3487
+ msgs.push({
3488
+ type: "warning",
3489
+ text: req.__("Keystore file is not applied for debug builds."),
3287
3490
  });
3288
3491
  }
3289
- if (keystoreFile && (!keystoreAlias || !keystorePassword)) {
3492
+
3493
+ if (
3494
+ buildType === "release" &&
3495
+ keystoreFile &&
3496
+ (!keystoreAlias || !keystorePassword)
3497
+ ) {
3290
3498
  return res.json({
3291
3499
  error: req.__(
3292
3500
  "Please provide the keystore alias and password for the android build."
@@ -3294,8 +3502,9 @@ router.post(
3294
3502
  });
3295
3503
  }
3296
3504
  const outDirName = `build_${new Date().valueOf()}`;
3505
+ const buildDir = `${os.userInfo().homedir}/mobile_app_build`;
3297
3506
  const rootFolder = await File.rootFolder();
3298
- const buildDir = path.join(rootFolder.location, "mobile_app", outDirName);
3507
+ const outDir = path.join(rootFolder.location, "mobile_app", outDirName);
3299
3508
  await File.new_folder(outDirName, "/mobile_app");
3300
3509
  const spawnParams = [
3301
3510
  "build-app",
@@ -3304,9 +3513,9 @@ router.post(
3304
3513
  "-t",
3305
3514
  entryPointType === "pagegroup" ? "page" : entryPointType,
3306
3515
  "-c",
3307
- buildDir,
3516
+ outDir,
3308
3517
  "-b",
3309
- `${os.userInfo().homedir}/mobile_app_build`,
3518
+ buildDir,
3310
3519
  "-u",
3311
3520
  req.user.email, // ensured by isAdmin
3312
3521
  ];
@@ -3319,6 +3528,13 @@ router.post(
3319
3528
  "--provisioningProfile",
3320
3529
  provisioningProfile
3321
3530
  );
3531
+ if (allowShareTo) {
3532
+ mode = "prepare";
3533
+ spawnParams.push(
3534
+ "--shareExtensionProvisioningProfile",
3535
+ shareProvisioningProfile
3536
+ );
3537
+ }
3322
3538
  }
3323
3539
  if (appName) spawnParams.push("--appName", appName);
3324
3540
  if (appId) spawnParams.push("--appId", appId);
@@ -3327,6 +3543,7 @@ router.post(
3327
3543
  if (serverURL) spawnParams.push("-s", serverURL);
3328
3544
  if (splashPage) spawnParams.push("--splashPage", splashPage);
3329
3545
  if (allowOfflineMode) spawnParams.push("--allowOfflineMode");
3546
+ if (allowShareTo) spawnParams.push("--allowShareTo");
3330
3547
  if (autoPublicLogin) spawnParams.push("--autoPublicLogin");
3331
3548
  if (synchedTables.length > 0)
3332
3549
  spawnParams.push("--synchedTables", ...synchedTables.map((tbl) => tbl));
@@ -3348,10 +3565,15 @@ router.post(
3348
3565
  spawnParams.push("--androidKeyStoreAlias", keystoreAlias);
3349
3566
  if (keystorePassword)
3350
3567
  spawnParams.push("--androidKeystorePassword", keystorePassword);
3351
- // end http call, return the out directory name
3568
+ // end http call, return the out directory name, the build directory path and the mode
3352
3569
  // the gui polls for results
3353
- res.json({ build_dir_name: outDirName });
3354
- const child = spawn(getSafeSaltcornCmd(), spawnParams, {
3570
+ res.json({
3571
+ out_dir_name: outDirName,
3572
+ build_dir: buildDir,
3573
+ mode: mode,
3574
+ msgs,
3575
+ });
3576
+ const child = spawn(getSafeSaltcornCmd(), [...spawnParams, "-m", mode], {
3355
3577
  stdio: ["ignore", "pipe", "pipe"],
3356
3578
  cwd: ".",
3357
3579
  });
@@ -3366,18 +3588,29 @@ router.post(
3366
3588
  getState().log(5, errMsg);
3367
3589
  childOutputs.push(errMsg);
3368
3590
  });
3369
- child.on("exit", (exitCode, signal) => {
3370
- const logFile = exitCode === 0 ? "logs.txt" : "error_logs.txt";
3371
- const exitMsg = childOutputs.join("\n");
3372
- fs.writeFile(path.join(buildDir, logFile), exitMsg, async (error) => {
3373
- if (error) {
3374
- console.log(`unable to write '${logFile}' to '${buildDir}'`);
3591
+ child.on("exit", async (exitCode, signal) => {
3592
+ if (mode === "prepare" && exitCode === 0) {
3593
+ try {
3594
+ fs.promises.writeFile(
3595
+ path.join(buildDir, "spawnParams.json"),
3596
+ JSON.stringify(spawnParams)
3597
+ );
3598
+ } catch (error) {
3599
+ console.log(`unable to write spawnParams to '${buildDir}'`);
3375
3600
  console.log(error);
3376
- } else {
3377
- // no transaction, '/build-mobile-app/finished' filters for valid attributes
3378
- await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
3379
3601
  }
3380
- });
3602
+ }
3603
+ const stepDesc = mode === "prepare" ? "_prepare_step" : "";
3604
+ const logFile =
3605
+ exitCode === 0 ? `logs${stepDesc}.txt` : `error_logs${stepDesc}.txt`;
3606
+ try {
3607
+ const exitMsg = childOutputs.join("\n");
3608
+ await fs.promises.writeFile(path.join(outDir, logFile), exitMsg);
3609
+ await File.set_xattr_of_existing_file(logFile, outDir, req.user);
3610
+ } catch (error) {
3611
+ console.log(`unable to write '${logFile}' to '${outDir}'`);
3612
+ console.log(error);
3613
+ }
3381
3614
  });
3382
3615
  child.on("error", (msg) => {
3383
3616
  const message = msg.message ? msg.message : msg.code;
@@ -3386,15 +3619,15 @@ router.post(
3386
3619
  const errMsg = [message, stack].join("\n");
3387
3620
  getState().log(5, msg);
3388
3621
  fs.writeFile(
3389
- path.join(buildDir, "error_logs.txt"),
3622
+ path.join(outDir, "error_logs.txt"),
3390
3623
  errMsg,
3391
3624
  async (error) => {
3392
3625
  if (error) {
3393
- console.log(`unable to write logFile to '${buildDir}'`);
3626
+ console.log(`unable to write logFile to '${outDir}'`);
3394
3627
  console.log(error);
3395
3628
  } else {
3396
3629
  // no transaction, '/build-mobile-app/finished' filters for valid attributes
3397
- await File.set_xattr_of_existing_file(logFile, buildDir, req.user);
3630
+ await File.set_xattr_of_existing_file(logFile, outDir, req.user);
3398
3631
  }
3399
3632
  }
3400
3633
  );
@@ -3462,7 +3695,7 @@ router.post(
3462
3695
  .filter(
3463
3696
  (plugin) =>
3464
3697
  ["base", "sbadmin2"].indexOf(plugin.name) < 0 &&
3465
- newCfg.includedPlugins.indexOf(plugin.name) < 0
3698
+ (newCfg.includedPlugins || []).indexOf(plugin.name) < 0
3466
3699
  )
3467
3700
  .map((plugin) => plugin.name);
3468
3701
  newCfg.excludedPlugins = excludedPlugins;
package/routes/api.js CHANGED
@@ -398,15 +398,26 @@ router.all(
398
398
  async function (err, user, info) {
399
399
  if (accessAllowed(req, user, trigger)) {
400
400
  try {
401
- const action = getState().actions[trigger.action];
401
+ let resp;
402
402
  const row = req.method === "GET" ? req.query : req.body;
403
- const resp = await action.run({
404
- configuration: trigger.configuration,
405
- body: req.body,
406
- row,
407
- req,
408
- user: user || req.user,
409
- });
403
+ if (trigger.action === "Workflow") {
404
+ resp = await trigger.runWithoutRow({
405
+ req,
406
+ interactive: true,
407
+ row,
408
+ user: user || req.user,
409
+ });
410
+ delete resp.__wf_run_id;
411
+ } else {
412
+ const action = getState().actions[trigger.action];
413
+ resp = await action.run({
414
+ configuration: trigger.configuration,
415
+ body: req.body,
416
+ row,
417
+ req,
418
+ user: user || req.user,
419
+ });
420
+ }
410
421
  if (
411
422
  (row._process_result || req.headers?.scprocessresults) &&
412
423
  resp?.goto
package/routes/fields.js CHANGED
@@ -12,6 +12,7 @@ const { getState } = require("@saltcorn/data/db/state");
12
12
  const { renderForm } = require("@saltcorn/markup");
13
13
  const Field = require("@saltcorn/data/models/field");
14
14
  const Table = require("@saltcorn/data/models/table");
15
+ const Trigger = require("@saltcorn/data/models/trigger");
15
16
  const Form = require("@saltcorn/data/models/form");
16
17
  const Workflow = require("@saltcorn/data/models/workflow");
17
18
  const User = require("@saltcorn/data/models/user");
@@ -306,6 +307,10 @@ const fieldFlow = (req) =>
306
307
  }
307
308
 
308
309
  await field.update(fldRow);
310
+ Trigger.emitEvent("AppChange", `Field ${fldRow.name}`, req.user, {
311
+ entity_type: "Field",
312
+ entity_name: fldRow.name || fldRow.label,
313
+ });
309
314
  } catch (e) {
310
315
  return {
311
316
  redirect: `/table/${context.table_id}`,
@@ -315,6 +320,10 @@ const fieldFlow = (req) =>
315
320
  } else {
316
321
  try {
317
322
  await Field.create(fldRow);
323
+ Trigger.emitEvent("AppChange", `Field ${fldRow.name}`, req.user, {
324
+ entity_type: "Field",
325
+ entity_name: fldRow.name || fldRow.label,
326
+ });
318
327
  } catch (e) {
319
328
  return {
320
329
  redirect: `/table/${context.table_id}`,
package/routes/menu.js CHANGED
@@ -571,6 +571,7 @@ router.post(
571
571
  const new_menu = req.body;
572
572
  const menu_items = jQMEtoMenu(new_menu);
573
573
  await save_menu_items(menu_items);
574
+ Trigger.emitEvent("AppChange", `Menu`, req.user, {});
574
575
 
575
576
  res.json({ success: true });
576
577
  })
@@ -199,23 +199,31 @@ router.post(
199
199
  error_catcher(async (req, res) => {
200
200
  const role = req.user?.role_id || 100;
201
201
  if (role === 100) {
202
- req.flash("error", req.__("You must be logged in to share"));
203
- res.redirect("/auth/login");
202
+ const msg = req.__("You must be logged in to share");
203
+ if (!req.smr) {
204
+ req.flash("error", msg);
205
+ res.redirect("/auth/login");
206
+ } else res.json({ error: msg });
204
207
  } else if (!getState().getConfig("pwa_share_to_enabled", false)) {
205
- req.flash("error", req.__("Sharing not enabled"));
206
- res.redirect("/");
208
+ const msg = req.__("Sharing not enabled");
209
+ if (!req.smr) {
210
+ req.flash("error", msg);
211
+ res.redirect("/");
212
+ } else res.json({ error: msg });
207
213
  } else {
208
214
  Trigger.emitEvent("ReceiveMobileShareData", null, req.user, {
209
215
  row: req.body,
210
216
  });
211
- req.flash(
212
- "success",
213
- req.__(
214
- "Shared: %s",
215
- req.body.title || req.body.text || req.body.url || ""
216
- )
217
- );
218
- res.status(303).redirect("/");
217
+ if (!req.smr) {
218
+ req.flash(
219
+ "success",
220
+ req.__(
221
+ "Shared: %s",
222
+ req.body.title || req.body.text || req.body.url || ""
223
+ )
224
+ );
225
+ res.status(303).redirect("/");
226
+ } else res.json({ success: "ok" });
219
227
  }
220
228
  })
221
229
  );
package/routes/page.js CHANGED
@@ -68,13 +68,13 @@ const runPage = async (page, req, res, tic) => {
68
68
  no_menu: page.attributes?.no_menu,
69
69
  requestFluidLayout: page.attributes?.request_fluid_layout,
70
70
  } || `${page.name} page`,
71
- add_edit_bar({
71
+ req.smr ? contents : add_edit_bar({
72
72
  role,
73
73
  title: page.name,
74
74
  what: req.__("Page"),
75
75
  url: `/pageedit/edit/${encodeURIComponent(page.name)}`,
76
76
  contents,
77
- })
77
+ }),
78
78
  );
79
79
  } else {
80
80
  getState().log(2, `Page ${page.name} not authorized`);