@saltcorn/server 0.9.5-beta.8 → 0.9.5

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
@@ -16,7 +16,7 @@ const {
16
16
  const Table = require("@saltcorn/data/models/table");
17
17
  const Plugin = require("@saltcorn/data/models/plugin");
18
18
  const File = require("@saltcorn/data/models/file");
19
- const { spawn } = require("child_process");
19
+ const { spawn, exec } = require("child_process");
20
20
  const User = require("@saltcorn/data/models/user");
21
21
  const path = require("path");
22
22
  const { X509Certificate } = require("crypto");
@@ -284,9 +284,26 @@ router.get(
284
284
  backupForm.values.auto_backup_destination = getState().getConfig(
285
285
  "auto_backup_destination"
286
286
  );
287
+ backupForm.values.auto_backup_tenants = getState().getConfig(
288
+ "auto_backup_tenants"
289
+ );
287
290
  backupForm.values.auto_backup_directory = getState().getConfig(
288
291
  "auto_backup_directory"
289
292
  );
293
+ backupForm.values.auto_backup_retain_local_directory = getState().getConfig(
294
+ "auto_backup_retain_local_directory"
295
+ );
296
+ backupForm.values.auto_backup_username = getState().getConfig(
297
+ "auto_backup_username"
298
+ );
299
+ backupForm.values.auto_backup_server =
300
+ getState().getConfig("auto_backup_server");
301
+ backupForm.values.auto_backup_password = getState().getConfig(
302
+ "auto_backup_password"
303
+ );
304
+ backupForm.values.auto_backup_port =
305
+ getState().getConfig("auto_backup_port");
306
+
290
307
  backupForm.values.auto_backup_expire_days = getState().getConfig(
291
308
  "auto_backup_expire_days"
292
309
  );
@@ -691,8 +708,10 @@ const backupFilePrefixForm = (req) =>
691
708
  * @param {object} req
692
709
  * @returns {Form} form
693
710
  */
694
- const autoBackupForm = (req) =>
695
- new Form({
711
+ const autoBackupForm = (req) => {
712
+ const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
713
+
714
+ return new Form({
696
715
  action: "/admin/set-auto-backup",
697
716
  onChange: `saveAndContinue(this);$('#btnBackupNow').prop('disabled', $('#inputauto_backup_frequency').val()==='Never');`,
698
717
  noSubmitButton: true,
@@ -718,7 +737,47 @@ const autoBackupForm = (req) =>
718
737
  name: "auto_backup_destination",
719
738
  required: true,
720
739
  showIf: { auto_backup_frequency: ["Daily", "Weekly"] },
721
- attributes: { options: ["Saltcorn files", "Local directory"] },
740
+ attributes: {
741
+ auto_backup_frequency: ["Daily", "Weekly"],
742
+ options: ["Saltcorn files", "Local directory", "SFTP server"],
743
+ },
744
+ },
745
+ {
746
+ type: "String",
747
+ label: req.__("Server host"),
748
+ name: "auto_backup_server",
749
+ showIf: {
750
+ auto_backup_frequency: ["Daily", "Weekly"],
751
+ auto_backup_destination: "SFTP server",
752
+ },
753
+ },
754
+ {
755
+ type: "String",
756
+ label: req.__("Username"),
757
+ name: "auto_backup_username",
758
+ showIf: {
759
+ auto_backup_frequency: ["Daily", "Weekly"],
760
+ auto_backup_destination: "SFTP server",
761
+ },
762
+ },
763
+ {
764
+ type: "String",
765
+ label: req.__("Password"),
766
+ fieldview: "password",
767
+ name: "auto_backup_password",
768
+ showIf: {
769
+ auto_backup_frequency: ["Daily", "Weekly"],
770
+ auto_backup_destination: "SFTP server",
771
+ },
772
+ },
773
+ {
774
+ type: "Integer",
775
+ label: req.__("Port"),
776
+ name: "auto_backup_port",
777
+ showIf: {
778
+ auto_backup_frequency: ["Daily", "Weekly"],
779
+ auto_backup_destination: "SFTP server",
780
+ },
722
781
  },
723
782
  {
724
783
  type: "String",
@@ -730,6 +789,19 @@ const autoBackupForm = (req) =>
730
789
  //auto_backup_destination: "Local directory",
731
790
  },
732
791
  },
792
+ {
793
+ type: "String",
794
+ label: req.__("Retain local directory"),
795
+ name: "auto_backup_retain_local_directory",
796
+ sublabel: req.__(
797
+ "Retain a local backup copy in this directory (optional)"
798
+ ),
799
+ showIf: {
800
+ auto_backup_frequency: ["Daily", "Weekly"],
801
+ auto_backup_destination: "SFTP server",
802
+ //auto_backup_destination: "Local directory",
803
+ },
804
+ },
733
805
  {
734
806
  type: "Integer",
735
807
  label: req.__("Expiration in days"),
@@ -742,6 +814,19 @@ const autoBackupForm = (req) =>
742
814
  auto_backup_destination: "Local directory",
743
815
  },
744
816
  },
817
+ ...(isRoot
818
+ ? [
819
+ {
820
+ type: "Bool",
821
+ label: req.__("All tenants"),
822
+ sublabel: req.__("Also backup all tenants"),
823
+ name: "auto_backup_tenants",
824
+ showIf: {
825
+ auto_backup_frequency: ["Daily", "Weekly"],
826
+ },
827
+ },
828
+ ]
829
+ : []),
745
830
  {
746
831
  type: "Bool",
747
832
  label: req.__("Include Event Logs"),
@@ -753,6 +838,7 @@ const autoBackupForm = (req) =>
753
838
  },
754
839
  ],
755
840
  });
841
+ };
756
842
 
757
843
  /**
758
844
  * Snapshot Form
@@ -1545,17 +1631,6 @@ const buildDialogScript = (cordovaBuilderAvailable) => {
1545
1631
 
1546
1632
  function handleMessages() {
1547
1633
  notifyAlert("Building the app, please wait.", true)
1548
- ${
1549
- getState().getConfig("apple_team_id") &&
1550
- getState().getConfig("apple_team_id") !== "null"
1551
- ? ""
1552
- : `
1553
- if ($("#iOSCheckboxId")[0].checked) {
1554
- notifyAlert(
1555
- "No 'Apple Team ID' is configured, I will try to build a project for the iOS simulator."
1556
- );
1557
- }`
1558
- }
1559
1634
  }
1560
1635
  </script>`;
1561
1636
  };
@@ -1570,6 +1645,33 @@ const imageAvailable = async () => {
1570
1645
  }
1571
1646
  };
1572
1647
 
1648
+ const checkXcodebuild = () => {
1649
+ return new Promise((resolve) => {
1650
+ exec("xcodebuild -version", (error, stdout, stderr) => {
1651
+ if (error) {
1652
+ resolve({ installed: false });
1653
+ } else {
1654
+ const tokens = stdout.split(" ");
1655
+ resolve({
1656
+ installed: true,
1657
+ version: tokens.length > 1 ? tokens[1] : undefined,
1658
+ });
1659
+ }
1660
+ });
1661
+ });
1662
+ };
1663
+
1664
+ const versionMarker = (version) => {
1665
+ const tokens = version.split(".");
1666
+ const majVers = parseInt(tokens[0]);
1667
+ return i({
1668
+ id: "versionMarkerId",
1669
+ class: `fas ${
1670
+ majVers >= 11 ? "fa-check text-success" : "fa-times text-danger"
1671
+ }`,
1672
+ });
1673
+ };
1674
+
1573
1675
  /**
1574
1676
  * Build mobile app
1575
1677
  */
@@ -1583,6 +1685,8 @@ router.get(
1583
1685
  const images = (await File.find({ mime_super: "image" })).filter((image) =>
1584
1686
  image.filename?.endsWith(".png")
1585
1687
  );
1688
+ const keystoreFiles = await File.find({ folder: "keystore_files" });
1689
+ const provisioningFiles = await File.find({ folder: "provisioning_files" });
1586
1690
  const withSyncInfo = await Table.find({ has_sync_info: true });
1587
1691
  const plugins = (await Plugin.find()).filter(
1588
1692
  (plugin) => ["base", "sbadmin2"].indexOf(plugin.name) < 0
@@ -1590,6 +1694,9 @@ router.get(
1590
1694
  const builderSettings =
1591
1695
  getState().getConfig("mobile_builder_settings") || {};
1592
1696
  const dockerAvailable = await imageAvailable();
1697
+ const xcodeCheckRes = await checkXcodebuild();
1698
+ const xcodebuildAvailable = xcodeCheckRes.installed;
1699
+ const xcodebuildVersion = xcodeCheckRes.version;
1593
1700
  send_admin_page({
1594
1701
  res,
1595
1702
  req,
@@ -1858,6 +1965,28 @@ router.get(
1858
1965
  })
1859
1966
  )
1860
1967
  ),
1968
+ // app id
1969
+ div(
1970
+ { class: "row pb-2" },
1971
+ div(
1972
+ { class: "col-sm-8" },
1973
+ label(
1974
+ {
1975
+ for: "appIdInputId",
1976
+ class: "form-label fw-bold",
1977
+ },
1978
+ req.__("App ID")
1979
+ ),
1980
+ input({
1981
+ type: "text",
1982
+ class: "form-control",
1983
+ name: "appId",
1984
+ id: "appIdInputId",
1985
+ placeholder: "com.saltcorn.app",
1986
+ value: builderSettings.appId || "",
1987
+ })
1988
+ )
1989
+ ),
1861
1990
  // app version
1862
1991
  div(
1863
1992
  { class: "row pb-2" },
@@ -2150,19 +2279,20 @@ router.get(
2150
2279
  class: "form-control form-select",
2151
2280
  multiple: true,
2152
2281
  },
2153
- plugins.map((plugin) =>
2154
- option({
2155
- id: `${plugin.name}_excluded_opt`,
2156
- value: plugin.name,
2157
- label: plugin.name,
2158
- hidden:
2282
+ plugins
2283
+ .filter(
2284
+ (plugin) =>
2159
2285
  builderSettings.excludedPlugins?.indexOf(
2160
2286
  plugin.name
2161
2287
  ) >= 0
2162
- ? false
2163
- : true,
2164
- })
2165
- )
2288
+ )
2289
+ .map((plugin) =>
2290
+ option({
2291
+ id: `${plugin.name}_excluded_opt`,
2292
+ value: plugin.name,
2293
+ label: plugin.name,
2294
+ })
2295
+ )
2166
2296
  )
2167
2297
  ),
2168
2298
  div(
@@ -2200,71 +2330,350 @@ router.get(
2200
2330
  class: "form-control form-select",
2201
2331
  multiple: true,
2202
2332
  },
2203
- plugins.map((plugin) =>
2204
- option({
2205
- id: `${plugin.name}_included_opt`,
2206
- value: plugin.name,
2207
- label: plugin.name,
2208
- hidden:
2209
- builderSettings.excludedPlugins?.indexOf(
2333
+ plugins
2334
+ .filter(
2335
+ (plugin) =>
2336
+ !builderSettings.excludedPlugins ||
2337
+ builderSettings.excludedPlugins.indexOf(
2210
2338
  plugin.name
2211
- ) >= 0
2212
- ? true
2213
- : false,
2214
- })
2215
- )
2339
+ ) < 0
2340
+ )
2341
+ .map((plugin) =>
2342
+ option({
2343
+ id: `${plugin.name}_included_opt`,
2344
+ value: plugin.name,
2345
+ label: plugin.name,
2346
+ })
2347
+ )
2216
2348
  )
2217
2349
  )
2218
2350
  )
2219
2351
  )
2220
2352
  ),
2353
+ // build type
2221
2354
  div(
2222
- { class: "row pb-3 pt-3" },
2355
+ { class: "row pb-3 pt-2" },
2223
2356
  div(
2357
+ { class: "col-sm-8" },
2224
2358
  label(
2225
- { class: "form-label fw-bold" },
2226
- req.__("Cordova builder") +
2227
- a(
2359
+ {
2360
+ for: "splashPageInputId",
2361
+ class: "form-label fw-bold",
2362
+ },
2363
+ req.__("Build type")
2364
+ ),
2365
+
2366
+ div(
2367
+ { class: "form-check" },
2368
+ input({
2369
+ type: "radio",
2370
+ id: "debugBuildTypeId",
2371
+ class: "form-check-input me-2",
2372
+ name: "buildType",
2373
+ value: "debug",
2374
+ checked: builderSettings.buildType === "debug",
2375
+ }),
2376
+ label(
2377
+ {
2378
+ for: "debugBuildTypeId",
2379
+ class: "form-label",
2380
+ },
2381
+ req.__("debug")
2382
+ )
2383
+ ),
2384
+ div(
2385
+ { class: "form-check" },
2386
+ input({
2387
+ type: "radio",
2388
+ id: "releaseBuildTypeId",
2389
+ class: "form-check-input me-2",
2390
+ name: "buildType",
2391
+ value: "release",
2392
+ checked:
2393
+ builderSettings.buildType === "release" ||
2394
+ !builderSettings.buildType,
2395
+ }),
2396
+ label(
2397
+ {
2398
+ for: "releaseBuildTypeId",
2399
+ class: "form-label",
2400
+ },
2401
+ req.__("release")
2402
+ )
2403
+ )
2404
+ )
2405
+ ),
2406
+ div(
2407
+ { class: "mt-3 mb-3" },
2408
+ p({ class: "h3 ps-3" }, "Android configuration"),
2409
+ div(
2410
+ { class: "form-group border border-2 p-3 rounded" },
2411
+
2412
+ div(
2413
+ { class: "row pb-3 pt-2" },
2414
+ div(
2415
+ label(
2416
+ { class: "form-label fw-bold" },
2417
+ req.__("Cordova builder") +
2418
+ a(
2419
+ {
2420
+ href: "javascript:ajax_modal('/admin/help/Cordova Builder?')",
2421
+ },
2422
+ i({ class: "fas fa-question-circle ps-1" })
2423
+ )
2424
+ )
2425
+ ),
2426
+ div(
2427
+ { class: "col-sm-4" },
2428
+ div(
2429
+ {
2430
+ id: "dockerBuilderStatusId",
2431
+ class: "",
2432
+ },
2433
+ dockerAvailable
2434
+ ? span(
2435
+ req.__("installed"),
2436
+ i({ class: "ps-2 fas fa-check text-success" })
2437
+ )
2438
+ : span(
2439
+ req.__("not available"),
2440
+ i({ class: "ps-2 fas fa-times text-danger" })
2441
+ )
2442
+ )
2443
+ ),
2444
+ div(
2445
+ { class: "col-sm-4" },
2446
+ button(
2228
2447
  {
2229
- href: "javascript:ajax_modal('/admin/help/Cordova Builder?')",
2448
+ id: "pullCordovaBtnId",
2449
+ type: "button",
2450
+ onClick: `pull_cordova_builder(this);`,
2451
+ class: "btn btn-warning",
2230
2452
  },
2231
- i({ class: "fas fa-question-circle ps-1" })
2453
+ req.__("pull")
2454
+ ),
2455
+ span(
2456
+ {
2457
+ role: "button",
2458
+ onClick: "check_cordova_builder()",
2459
+ },
2460
+ span({ class: "ps-3" }, req.__("refresh")),
2461
+ i({ class: "ps-2 fas fa-undo" })
2232
2462
  )
2463
+ )
2464
+ ),
2465
+ // keystore file
2466
+ div(
2467
+ { class: "row pb-3" },
2468
+ div(
2469
+ { class: "col-sm-8" },
2470
+ label(
2471
+ {
2472
+ for: "keystoreInputId",
2473
+ class: "form-label fw-bold",
2474
+ },
2475
+ req.__("Keystore File"),
2476
+ a(
2477
+ {
2478
+ href: "javascript:ajax_modal('/admin/help/Android App Signing?')",
2479
+ },
2480
+ i({ class: "fas fa-question-circle ps-1" })
2481
+ )
2482
+ ),
2483
+ select(
2484
+ {
2485
+ class: "form-select",
2486
+ name: "keystoreFile",
2487
+ id: "keystoreInputId",
2488
+ },
2489
+ [
2490
+ option({ value: "" }, ""),
2491
+ ...keystoreFiles.map((file) =>
2492
+ option(
2493
+ {
2494
+ value: file.location,
2495
+ selected:
2496
+ builderSettings.keystoreFile ===
2497
+ file.location,
2498
+ },
2499
+ file.filename
2500
+ )
2501
+ ),
2502
+ ].join("")
2503
+ )
2504
+ )
2505
+ ),
2506
+ // keystore alias
2507
+ div(
2508
+ { class: "row pb-2" },
2509
+ div(
2510
+ { class: "col-sm-8" },
2511
+ label(
2512
+ {
2513
+ for: "keystoreAliasInputId",
2514
+ class: "form-label fw-bold",
2515
+ },
2516
+ req.__("Keystore Alias")
2517
+ ),
2518
+ input({
2519
+ type: "text",
2520
+ class: "form-control",
2521
+ name: "keystoreAlias",
2522
+ id: "keystoreAliasInputId",
2523
+ value: builderSettings.keystoreAlias || "",
2524
+ placeholder: "",
2525
+ })
2526
+ )
2527
+ ),
2528
+ // keystore password
2529
+ div(
2530
+ { class: "row pb-2" },
2531
+ div(
2532
+ { class: "col-sm-8" },
2533
+ label(
2534
+ {
2535
+ for: "keystorePasswordInputId",
2536
+ class: "form-label fw-bold",
2537
+ },
2538
+ req.__("Keystore Password")
2539
+ ),
2540
+ input({
2541
+ type: "password",
2542
+ class: "form-control",
2543
+ name: "keystorePassword",
2544
+ id: "keystorePasswordInputId",
2545
+ value: "",
2546
+ placeholder: "",
2547
+ })
2548
+ )
2233
2549
  )
2234
- ),
2550
+ )
2551
+ ),
2552
+ div(
2553
+ { class: "mt-3" },
2554
+ p({ class: "h3 ps-3 mt-3" }, "iOS Configuration"),
2235
2555
  div(
2236
- { class: "col-sm-4" },
2556
+ { class: "form-group border border-2 p-3 rounded" },
2237
2557
  div(
2238
- {
2239
- id: "dockerBuilderStatusId",
2240
- class: "",
2241
- },
2242
- dockerAvailable
2243
- ? span(
2244
- req.__("installed"),
2245
- i({ class: "ps-2 fas fa-check text-success" })
2558
+ { class: "mb-3" },
2559
+ div(
2560
+ { class: "row pb-3 pt-2" },
2561
+ div(
2562
+ label(
2563
+ { class: "form-label fw-bold" },
2564
+ req.__("xcodebuild") +
2565
+ a(
2566
+ {
2567
+ href: "javascript:ajax_modal('/admin/help/xcodebuild?')",
2568
+ },
2569
+ i({ class: "fas fa-question-circle ps-1" })
2570
+ )
2246
2571
  )
2247
- : span(
2248
- req.__("not available"),
2249
- i({ class: "ps-2 fas fa-times text-danger" })
2572
+ ),
2573
+ div(
2574
+ { class: "col-sm-4" },
2575
+ div(
2576
+ {
2577
+ id: "xcodebuildStatusId",
2578
+ class: "",
2579
+ },
2580
+ xcodebuildAvailable
2581
+ ? span(
2582
+ req.__("installed"),
2583
+ i({
2584
+ class: "ps-2 fas fa-check text-success",
2585
+ })
2586
+ )
2587
+ : span(
2588
+ req.__("not available"),
2589
+ i({
2590
+ class: "ps-2 fas fa-times text-danger",
2591
+ })
2592
+ )
2250
2593
  )
2251
- )
2252
- ),
2253
- div(
2254
- { class: "col-sm-4" },
2255
- button(
2256
- {
2257
- id: "pullCordovaBtnId",
2258
- type: "button",
2259
- onClick: `pull_cordova_builder(this);`,
2260
- class: "btn btn-warning",
2261
- },
2262
- req.__("pull")
2594
+ ),
2595
+ div(
2596
+ { class: "col-sm-4" },
2597
+ // not sure if we should provide this
2598
+ // button(
2599
+ // {
2600
+ // id: "installXCodeBtnId",
2601
+ // type: "button",
2602
+ // onClick: `install_xcode(this);`,
2603
+ // class: "btn btn-warning",
2604
+ // },
2605
+ // req.__("install")
2606
+ // ),
2607
+ span(
2608
+ {
2609
+ role: "button",
2610
+ onClick: "check_xcodebuild()",
2611
+ },
2612
+ span({ class: "ps-3" }, req.__("refresh")),
2613
+ i({ class: "ps-2 fas fa-undo" })
2614
+ )
2615
+ )
2616
+ ),
2617
+ div(
2618
+ {
2619
+ class: `row mb-3 pb-3 ${
2620
+ xcodebuildAvailable ? "" : "d-none"
2621
+ }`,
2622
+ id: "xcodebuildVersionBoxId",
2623
+ },
2624
+ div(
2625
+ { class: "col-sm-4" },
2626
+ span(
2627
+ req.__("Version") +
2628
+ span(
2629
+ { id: "xcodebuildVersionId", class: "pe-2" },
2630
+ `: ${xcodebuildVersion || "unknown"}`
2631
+ ),
2632
+ versionMarker(xcodebuildVersion || "0")
2633
+ )
2634
+ )
2635
+ )
2263
2636
  ),
2264
- span(
2265
- { role: "button", onClick: "check_cordova_builder()" },
2266
- span({ class: "ps-3" }, req.__("refresh")),
2267
- i({ class: "ps-2 fas fa-undo" })
2637
+ // provisioning profile file
2638
+ div(
2639
+ { class: "row pb-3" },
2640
+ div(
2641
+ { class: "col-sm-8" },
2642
+ label(
2643
+ {
2644
+ for: "provisioningProfileInputId",
2645
+ class: "form-label fw-bold",
2646
+ },
2647
+ req.__("Provisioning Profile"),
2648
+ a(
2649
+ {
2650
+ href: "javascript:ajax_modal('/admin/help/Provisioning Profile?')",
2651
+ },
2652
+ i({ class: "fas fa-question-circle ps-1" })
2653
+ )
2654
+ ),
2655
+ select(
2656
+ {
2657
+ class: "form-select",
2658
+ name: "provisioningProfile",
2659
+ id: "provisioningProfileInputId",
2660
+ },
2661
+ [
2662
+ option({ value: "" }, ""),
2663
+ ...provisioningFiles.map((file) =>
2664
+ option(
2665
+ {
2666
+ value: file.location,
2667
+ selected:
2668
+ builderSettings.provisioningProfile ===
2669
+ file.location,
2670
+ },
2671
+ file.filename
2672
+ )
2673
+ ),
2674
+ ].join("")
2675
+ )
2676
+ )
2268
2677
  )
2269
2678
  )
2270
2679
  )
@@ -2369,6 +2778,7 @@ router.post(
2369
2778
  iOSPlatform,
2370
2779
  useDocker,
2371
2780
  appName,
2781
+ appId,
2372
2782
  appVersion,
2373
2783
  appIcon,
2374
2784
  serverURL,
@@ -2377,6 +2787,11 @@ router.post(
2377
2787
  allowOfflineMode,
2378
2788
  synchedTables,
2379
2789
  includedPlugins,
2790
+ provisioningProfile,
2791
+ buildType,
2792
+ keystoreFile,
2793
+ keystoreAlias,
2794
+ keystorePassword,
2380
2795
  } = req.body;
2381
2796
  if (!includedPlugins) includedPlugins = [];
2382
2797
  if (!synchedTables) synchedTables = [];
@@ -2398,6 +2813,20 @@ router.post(
2398
2813
  error: req.__("Please enter a valid server URL."),
2399
2814
  });
2400
2815
  }
2816
+ if (iOSPlatform && !provisioningProfile) {
2817
+ return res.json({
2818
+ error: req.__(
2819
+ "Please provide a Provisioning Profile for the iOS build."
2820
+ ),
2821
+ });
2822
+ }
2823
+ if (keystoreFile && (!keystoreAlias || !keystorePassword)) {
2824
+ return res.json({
2825
+ error: req.__(
2826
+ "Please provide the keystore alias and password for the android build."
2827
+ ),
2828
+ });
2829
+ }
2401
2830
  const outDirName = `build_${new Date().valueOf()}`;
2402
2831
  const rootFolder = await File.rootFolder();
2403
2832
  const buildDir = path.join(rootFolder.location, "mobile_app", outDirName);
@@ -2418,13 +2847,15 @@ router.post(
2418
2847
  if (useDocker) spawnParams.push("-d");
2419
2848
  if (androidPlatform) spawnParams.push("-p", "android");
2420
2849
  if (iOSPlatform) {
2421
- spawnParams.push("-p", "ios");
2422
- const teamId = getState().getConfig("apple_team_id");
2423
- if (!teamId || teamId === "null") {
2424
- spawnParams.push("--buildForEmulator");
2425
- }
2850
+ spawnParams.push(
2851
+ "-p",
2852
+ "ios",
2853
+ "--provisioningProfile",
2854
+ provisioningProfile
2855
+ );
2426
2856
  }
2427
2857
  if (appName) spawnParams.push("--appName", appName);
2858
+ if (appId) spawnParams.push("--appId", appId);
2428
2859
  if (appVersion) spawnParams.push("--appVersion", appVersion);
2429
2860
  if (appIcon) spawnParams.push("--appIcon", appIcon);
2430
2861
  if (serverURL) spawnParams.push("-s", serverURL);
@@ -2451,6 +2882,13 @@ router.post(
2451
2882
  includedPlugins.indexOf(plugin.name) < 0
2452
2883
  )
2453
2884
  .map((plugin) => plugin.name);
2885
+
2886
+ if (buildType) spawnParams.push("--buildType", buildType);
2887
+ if (keystoreFile) spawnParams.push("--androidKeystore", keystoreFile);
2888
+ if (keystoreAlias)
2889
+ spawnParams.push("--androidKeyStoreAlias", keystoreAlias);
2890
+ if (keystorePassword)
2891
+ spawnParams.push("--androidKeystorePassword", keystorePassword);
2454
2892
  await getState().setConfig("mobile_builder_settings", {
2455
2893
  entryPoint,
2456
2894
  entryPointType,
@@ -2458,6 +2896,7 @@ router.post(
2458
2896
  iOSPlatform,
2459
2897
  useDocker,
2460
2898
  appName,
2899
+ appId,
2461
2900
  appVersion,
2462
2901
  appIcon,
2463
2902
  serverURL,
@@ -2467,6 +2906,10 @@ router.post(
2467
2906
  synchedTables: synchedTables,
2468
2907
  includedPlugins: includedPlugins,
2469
2908
  excludedPlugins,
2909
+ provisioningProfile,
2910
+ keystoreFile,
2911
+ keystoreAlias,
2912
+ buildType,
2470
2913
  });
2471
2914
  // end http call, return the out directory name
2472
2915
  // the gui polls for results
@@ -2563,6 +3006,14 @@ router.get(
2563
3006
  })
2564
3007
  );
2565
3008
 
3009
+ router.get(
3010
+ "/mobile-app/check-xcodebuild",
3011
+ isAdmin,
3012
+ error_catcher(async (req, res) => {
3013
+ res.json(await checkXcodebuild());
3014
+ })
3015
+ );
3016
+
2566
3017
  /**
2567
3018
  * Do Clear All
2568
3019
  * @function
@@ -2765,6 +3216,7 @@ admin_config_route({
2765
3216
  "development_mode",
2766
3217
  "log_sql",
2767
3218
  "log_client_errors",
3219
+ "log_ip_address",
2768
3220
  "log_level",
2769
3221
  ...(isRoot || tenants_set_npm_modules ? ["npm_available_js_code"] : []),
2770
3222
  ],
@@ -2772,6 +3224,7 @@ admin_config_route({
2772
3224
  });
2773
3225
  },
2774
3226
  response(form, req, res) {
3227
+ const code_pages = getState().getConfig("function_code_pages", {});
2775
3228
  send_admin_page({
2776
3229
  res,
2777
3230
  req,
@@ -2798,11 +3251,138 @@ admin_config_route({
2798
3251
  ),
2799
3252
  ],
2800
3253
  },
3254
+ {
3255
+ type: "card",
3256
+ title: req.__("Constants and function code"),
3257
+ contents: [
3258
+ div(
3259
+ Object.keys(code_pages)
3260
+ .map((k) =>
3261
+ a(
3262
+ {
3263
+ href: `/admin/edit-codepage/${encodeURIComponent(k)}`,
3264
+ class: "",
3265
+ },
3266
+ k
3267
+ )
3268
+ )
3269
+ .join(" | "),
3270
+ button(
3271
+ {
3272
+ class: "btn btn-secondary btn-sm d-block mt-2",
3273
+ onclick: `location.href='/admin/edit-codepage/'+prompt('Name of the new page')`,
3274
+ },
3275
+ i({ class: "fas fa-plus me-1" }),
3276
+ "Add page"
3277
+ )
3278
+ ),
3279
+ ],
3280
+ },
2801
3281
  ],
2802
3282
  },
2803
3283
  });
2804
3284
  },
2805
3285
  });
3286
+
3287
+ router.get(
3288
+ "/edit-codepage/:name",
3289
+ isAdmin,
3290
+ error_catcher(async (req, res) => {
3291
+ const { name } = req.params;
3292
+ const code_pages = getState().getConfig("function_code_pages", {});
3293
+ const existing = code_pages[name] || "";
3294
+ const form = new Form({
3295
+ action: `/admin/edit-codepage/${encodeURIComponent(name)}`,
3296
+ onChange: "saveAndContinue(this)",
3297
+ values: { code: existing },
3298
+ noSubmitButton: true,
3299
+ labelCols: 0,
3300
+ additionalButtons: [
3301
+ {
3302
+ label: req.__("Delete code page"),
3303
+ class: "btn btn-outline-danger btn-sm",
3304
+ onclick: `if(confirm('Are you sure you would like to delete this code page?'))ajax_post('/admin/delete-codepage/${encodeURIComponent(
3305
+ name
3306
+ )}')`,
3307
+ },
3308
+ ],
3309
+ fields: [
3310
+ {
3311
+ name: "code",
3312
+ form_name: "code",
3313
+ label: "Code",
3314
+ sublabel:
3315
+ "Only functions declared as <code>function name(...) {...}</code> or <code>async function name(...) {...}</code> will be available in formulae and code actions. Declare a constant <code>k</code> as <code>globalThis.k = ...</code> In scope: " +
3316
+ a(
3317
+ {
3318
+ href: "https://saltcorn.github.io/saltcorn/classes/_saltcorn_data.models.Table-1.html",
3319
+ target: "_blank",
3320
+ },
3321
+ "Table"
3322
+ ),
3323
+ input_type: "code",
3324
+ attributes: { mode: "text/javascript" },
3325
+ class: "validate-statements",
3326
+ validator(s) {
3327
+ try {
3328
+ let AsyncFunction = Object.getPrototypeOf(
3329
+ async function () {}
3330
+ ).constructor;
3331
+ AsyncFunction(s);
3332
+ return true;
3333
+ } catch (e) {
3334
+ return e.message;
3335
+ }
3336
+ },
3337
+ },
3338
+ ],
3339
+ });
3340
+
3341
+ send_admin_page({
3342
+ res,
3343
+ req,
3344
+ active_sub: "Development",
3345
+ sub2_page: req.__(`%s code page`, name),
3346
+ contents: {
3347
+ type: "card",
3348
+ title: req.__(`%s code page`, name),
3349
+ contents: [renderForm(form, req.csrfToken())],
3350
+ },
3351
+ });
3352
+ })
3353
+ );
3354
+
3355
+ router.post(
3356
+ "/edit-codepage/:name",
3357
+ isAdmin,
3358
+ error_catcher(async (req, res) => {
3359
+ const { name } = req.params;
3360
+ const code_pages = getState().getConfigCopy("function_code_pages", {});
3361
+
3362
+ const code = req.body.code;
3363
+ await getState().setConfig("function_code_pages", {
3364
+ ...code_pages,
3365
+ [name]: code,
3366
+ });
3367
+ await getState().refresh_codepages();
3368
+
3369
+ res.json({ success: true });
3370
+ })
3371
+ );
3372
+ router.post(
3373
+ "/delete-codepage/:name",
3374
+ isAdmin,
3375
+ error_catcher(async (req, res) => {
3376
+ const { name } = req.params;
3377
+ const code_pages = getState().getConfigCopy("function_code_pages", {});
3378
+ delete code_pages[name];
3379
+ await getState().setConfig("function_code_pages", code_pages);
3380
+ await getState().refresh_codepages();
3381
+
3382
+ res.json({ goto: `/admin/dev` });
3383
+ })
3384
+ );
3385
+
2806
3386
  /**
2807
3387
  * Notifications
2808
3388
  */