@saltcorn/server 1.1.1-beta.0 → 1.1.1-beta.2
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/help/Event types.tmd +2 -0
- package/locales/en.json +7 -1
- package/locales/pl.json +16 -1
- package/package.json +9 -9
- package/public/saltcorn.js +87 -17
- package/routes/actions.js +128 -183
- package/routes/admin.js +257 -46
- package/routes/fields.js +9 -0
- package/routes/menu.js +1 -0
- package/routes/notifications.js +20 -12
- package/routes/page.js +2 -2
- package/routes/pageedit.js +34 -1
- package/routes/utils.js +5 -0
- package/routes/view.js +14 -12
- package/routes/viewedit.js +26 -0
- package/tests/clientjs.test.js +19 -0
- package/tests/view.test.js +37 -0
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");
|
|
@@ -146,6 +147,24 @@ const app_files_table = (files, buildDirName, req) =>
|
|
|
146
147
|
],
|
|
147
148
|
files
|
|
148
149
|
);
|
|
150
|
+
const intermediate_build_result = (outDirName, buildDir, req) => {
|
|
151
|
+
return div(
|
|
152
|
+
h3("Intermediate build result"),
|
|
153
|
+
div(
|
|
154
|
+
button(
|
|
155
|
+
{
|
|
156
|
+
id: "finishMobileAppBtnId",
|
|
157
|
+
type: "button",
|
|
158
|
+
onClick: `finish_mobile_app(this, '${outDirName}', '${buildDir}');`,
|
|
159
|
+
class: "btn btn-warning",
|
|
160
|
+
},
|
|
161
|
+
i({ class: "fas fa-hammer pe-2" }),
|
|
162
|
+
|
|
163
|
+
req.__("Finish the build")
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
};
|
|
149
168
|
|
|
150
169
|
admin_config_route({
|
|
151
170
|
router,
|
|
@@ -1993,9 +2012,6 @@ const buildDialogScript = (capacitorBuilderAvailable, isSbadmin2) =>
|
|
|
1993
2012
|
$("#entryPointTypeID").attr("value", type);
|
|
1994
2013
|
}
|
|
1995
2014
|
|
|
1996
|
-
function handleMessages() {
|
|
1997
|
-
notifyAlert("Building the app, please wait.", true)
|
|
1998
|
-
}
|
|
1999
2015
|
const versionPattern = /^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/;
|
|
2000
2016
|
${domReady(`
|
|
2001
2017
|
const versionInput = document.getElementById('appVersionInputId');
|
|
@@ -3077,6 +3093,48 @@ router.get(
|
|
|
3077
3093
|
)
|
|
3078
3094
|
)
|
|
3079
3095
|
)
|
|
3096
|
+
// Share Extension provisioning profile
|
|
3097
|
+
// disabled for now
|
|
3098
|
+
// div(
|
|
3099
|
+
// { class: "row pb-3" },
|
|
3100
|
+
// div(
|
|
3101
|
+
// { class: "col-sm-8" },
|
|
3102
|
+
// label(
|
|
3103
|
+
// {
|
|
3104
|
+
// for: "shareProvisioningProfileInputId",
|
|
3105
|
+
// class: "form-label fw-bold",
|
|
3106
|
+
// },
|
|
3107
|
+
// req.__("Share Extension Provisioning Profile"),
|
|
3108
|
+
// a(
|
|
3109
|
+
// {
|
|
3110
|
+
// href: "javascript:ajax_modal('/admin/help/Provisioning Profile?')",
|
|
3111
|
+
// },
|
|
3112
|
+
// i({ class: "fas fa-question-circle ps-1" })
|
|
3113
|
+
// )
|
|
3114
|
+
// ),
|
|
3115
|
+
// select(
|
|
3116
|
+
// {
|
|
3117
|
+
// class: "form-select",
|
|
3118
|
+
// name: "shareProvisioningProfile",
|
|
3119
|
+
// id: "shareProvisioningProfileInputId",
|
|
3120
|
+
// },
|
|
3121
|
+
// [
|
|
3122
|
+
// option({ value: "" }, ""),
|
|
3123
|
+
// ...provisioningFiles.map((file) =>
|
|
3124
|
+
// option(
|
|
3125
|
+
// {
|
|
3126
|
+
// value: file.location,
|
|
3127
|
+
// selected:
|
|
3128
|
+
// builderSettings.shareProvisioningProfile ===
|
|
3129
|
+
// file.location,
|
|
3130
|
+
// },
|
|
3131
|
+
// file.filename
|
|
3132
|
+
// )
|
|
3133
|
+
// ),
|
|
3134
|
+
// ].join("")
|
|
3135
|
+
// )
|
|
3136
|
+
// )
|
|
3137
|
+
// )
|
|
3080
3138
|
)
|
|
3081
3139
|
)
|
|
3082
3140
|
),
|
|
@@ -3100,15 +3158,13 @@ router.get(
|
|
|
3100
3158
|
})
|
|
3101
3159
|
);
|
|
3102
3160
|
|
|
3103
|
-
const checkFiles = async (
|
|
3161
|
+
const checkFiles = async (outDirName, fileNames) => {
|
|
3104
3162
|
const rootFolder = await File.rootFolder();
|
|
3105
|
-
const
|
|
3163
|
+
const outDir = path.join(rootFolder.location, "mobile_app", outDirName);
|
|
3106
3164
|
const unsafeFiles = await Promise.all(
|
|
3107
3165
|
fs
|
|
3108
|
-
.readdirSync(
|
|
3109
|
-
.map(
|
|
3110
|
-
async (outFile) => await File.from_file_on_disk(outFile, mobile_app_dir)
|
|
3111
|
-
)
|
|
3166
|
+
.readdirSync(outDir)
|
|
3167
|
+
.map(async (outFile) => await File.from_file_on_disk(outFile, outDir))
|
|
3112
3168
|
);
|
|
3113
3169
|
const entries = unsafeFiles
|
|
3114
3170
|
.filter(
|
|
@@ -3127,9 +3183,18 @@ router.get(
|
|
|
3127
3183
|
"/build-mobile-app/finished",
|
|
3128
3184
|
isAdmin,
|
|
3129
3185
|
error_catcher(async (req, res) => {
|
|
3130
|
-
const {
|
|
3186
|
+
const { out_dir_name, mode } = req.query;
|
|
3187
|
+
const stepDesc =
|
|
3188
|
+
mode === "prepare"
|
|
3189
|
+
? "_prepare_step"
|
|
3190
|
+
: mode === "finish"
|
|
3191
|
+
? "_finish_step"
|
|
3192
|
+
: "";
|
|
3131
3193
|
res.json({
|
|
3132
|
-
finished: await checkFiles(
|
|
3194
|
+
finished: await checkFiles(out_dir_name, [
|
|
3195
|
+
`logs${stepDesc}.txt`,
|
|
3196
|
+
`error_logs${stepDesc}.txt`,
|
|
3197
|
+
]),
|
|
3133
3198
|
});
|
|
3134
3199
|
})
|
|
3135
3200
|
);
|
|
@@ -3164,8 +3229,8 @@ router.get(
|
|
|
3164
3229
|
"/build-mobile-app/result",
|
|
3165
3230
|
isAdmin,
|
|
3166
3231
|
error_catcher(async (req, res) => {
|
|
3167
|
-
const {
|
|
3168
|
-
if (!validateBuildDirName(
|
|
3232
|
+
const { out_dir_name, build_dir, mode } = req.query;
|
|
3233
|
+
if (!validateBuildDirName(out_dir_name)) {
|
|
3169
3234
|
return res.sendWrap(req.__(`Admin`), {
|
|
3170
3235
|
above: [
|
|
3171
3236
|
{
|
|
@@ -3177,11 +3242,7 @@ router.get(
|
|
|
3177
3242
|
});
|
|
3178
3243
|
}
|
|
3179
3244
|
const rootFolder = await File.rootFolder();
|
|
3180
|
-
const buildDir = path.join(
|
|
3181
|
-
rootFolder.location,
|
|
3182
|
-
"mobile_app",
|
|
3183
|
-
build_dir_name
|
|
3184
|
-
);
|
|
3245
|
+
const buildDir = path.join(rootFolder.location, "mobile_app", out_dir_name);
|
|
3185
3246
|
if (!validateBuildDir(buildDir, rootFolder.location)) {
|
|
3186
3247
|
return res.sendWrap(req.__(`Admin`), {
|
|
3187
3248
|
above: [
|
|
@@ -3199,7 +3260,15 @@ router.get(
|
|
|
3199
3260
|
.readdirSync(buildDir)
|
|
3200
3261
|
.map(async (outFile) => await File.from_file_on_disk(outFile, buildDir))
|
|
3201
3262
|
);
|
|
3202
|
-
const
|
|
3263
|
+
const stepDesc =
|
|
3264
|
+
mode === "prepare"
|
|
3265
|
+
? "_prepare_step"
|
|
3266
|
+
: mode === "finish"
|
|
3267
|
+
? "_finish_step"
|
|
3268
|
+
: "";
|
|
3269
|
+
const resultMsg = files.find(
|
|
3270
|
+
(file) => file.filename === `logs${stepDesc}.txt`
|
|
3271
|
+
)
|
|
3203
3272
|
? req.__("The build was successfully")
|
|
3204
3273
|
: req.__("Unable to build the app");
|
|
3205
3274
|
res.sendWrap(req.__(`Admin`), {
|
|
@@ -3209,11 +3278,98 @@ router.get(
|
|
|
3209
3278
|
title: req.__("Build Result"),
|
|
3210
3279
|
contents: div(resultMsg),
|
|
3211
3280
|
},
|
|
3212
|
-
files.length > 0 ? app_files_table(files,
|
|
3281
|
+
files.length > 0 ? app_files_table(files, out_dir_name, req) : "",
|
|
3282
|
+
mode === "prepare"
|
|
3283
|
+
? intermediate_build_result(out_dir_name, build_dir, req)
|
|
3284
|
+
: "",
|
|
3213
3285
|
],
|
|
3214
3286
|
});
|
|
3215
3287
|
})
|
|
3216
3288
|
);
|
|
3289
|
+
|
|
3290
|
+
router.post(
|
|
3291
|
+
"/build-mobile-app/finish",
|
|
3292
|
+
isAdmin,
|
|
3293
|
+
error_catcher(async (req, res) => {
|
|
3294
|
+
const { out_dir_name, build_dir } = req.body;
|
|
3295
|
+
const content = await fs.promises.readFile(
|
|
3296
|
+
path.join(build_dir, "spawnParams.json")
|
|
3297
|
+
);
|
|
3298
|
+
const spawnParams = JSON.parse(content);
|
|
3299
|
+
const rootFolder = await File.rootFolder();
|
|
3300
|
+
const outDirFullPath = path.join(
|
|
3301
|
+
rootFolder.location,
|
|
3302
|
+
"mobile_app",
|
|
3303
|
+
out_dir_name
|
|
3304
|
+
);
|
|
3305
|
+
res.json({
|
|
3306
|
+
success: true,
|
|
3307
|
+
});
|
|
3308
|
+
const child = spawn(
|
|
3309
|
+
getSafeSaltcornCmd(),
|
|
3310
|
+
[...spawnParams, "-m", "finish"],
|
|
3311
|
+
{
|
|
3312
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3313
|
+
cwd: ".",
|
|
3314
|
+
}
|
|
3315
|
+
);
|
|
3316
|
+
const childOutputs = [];
|
|
3317
|
+
child.stdout.on("data", (data) => {
|
|
3318
|
+
const outMsg = data.toString();
|
|
3319
|
+
getState().log(5, outMsg);
|
|
3320
|
+
if (data) childOutputs.push(outMsg);
|
|
3321
|
+
});
|
|
3322
|
+
child.stderr.on("data", (data) => {
|
|
3323
|
+
const errMsg = data ? data.toString() : req.__("An error occurred");
|
|
3324
|
+
getState().log(5, errMsg);
|
|
3325
|
+
childOutputs.push(errMsg);
|
|
3326
|
+
});
|
|
3327
|
+
child.on("exit", async (exitCode, signal) => {
|
|
3328
|
+
const logFile =
|
|
3329
|
+
exitCode === 0 ? "logs_finish_step.txt" : "error_logs_finish_step.txt";
|
|
3330
|
+
try {
|
|
3331
|
+
const exitMsg = childOutputs.join("\n");
|
|
3332
|
+
await fs.promises.writeFile(
|
|
3333
|
+
path.join(outDirFullPath, logFile),
|
|
3334
|
+
exitMsg
|
|
3335
|
+
);
|
|
3336
|
+
await File.set_xattr_of_existing_file(
|
|
3337
|
+
logFile,
|
|
3338
|
+
outDirFullPath,
|
|
3339
|
+
req.user
|
|
3340
|
+
);
|
|
3341
|
+
} catch (error) {
|
|
3342
|
+
console.log(`unable to write '${logFile}' to '${outDirFullPath}'`);
|
|
3343
|
+
console.log(error);
|
|
3344
|
+
}
|
|
3345
|
+
});
|
|
3346
|
+
child.on("error", (msg) => {
|
|
3347
|
+
const message = msg.message ? msg.message : msg.code;
|
|
3348
|
+
const stack = msg.stack ? msg.stack : "";
|
|
3349
|
+
const logFile = "error_logs.txt";
|
|
3350
|
+
const errMsg = [message, stack].join("\n");
|
|
3351
|
+
getState().log(5, msg);
|
|
3352
|
+
fs.writeFile(
|
|
3353
|
+
path.join(outDirFullPath, "error_logs.txt"),
|
|
3354
|
+
errMsg,
|
|
3355
|
+
async (error) => {
|
|
3356
|
+
if (error) {
|
|
3357
|
+
console.log(`unable to write logFile to '${outDirFullPath}'`);
|
|
3358
|
+
console.log(error);
|
|
3359
|
+
} else {
|
|
3360
|
+
// no transaction, '/build-mobile-app/finished' filters for valid attributes
|
|
3361
|
+
await File.set_xattr_of_existing_file(
|
|
3362
|
+
logFile,
|
|
3363
|
+
outDirFullPath,
|
|
3364
|
+
req.user
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
);
|
|
3369
|
+
});
|
|
3370
|
+
})
|
|
3371
|
+
);
|
|
3372
|
+
|
|
3217
3373
|
/**
|
|
3218
3374
|
* Do Build Mobile App
|
|
3219
3375
|
*/
|
|
@@ -3222,6 +3378,8 @@ router.post(
|
|
|
3222
3378
|
isAdmin,
|
|
3223
3379
|
error_catcher(async (req, res) => {
|
|
3224
3380
|
getState().log(2, `starting mobile build: ${JSON.stringify(req.body)}`);
|
|
3381
|
+
const msgs = [];
|
|
3382
|
+
let mode = "full";
|
|
3225
3383
|
let {
|
|
3226
3384
|
entryPoint,
|
|
3227
3385
|
entryPointType,
|
|
@@ -3239,11 +3397,27 @@ router.post(
|
|
|
3239
3397
|
synchedTables,
|
|
3240
3398
|
includedPlugins,
|
|
3241
3399
|
provisioningProfile,
|
|
3400
|
+
shareProvisioningProfile,
|
|
3242
3401
|
buildType,
|
|
3243
3402
|
keystoreFile,
|
|
3244
3403
|
keystoreAlias,
|
|
3245
3404
|
keystorePassword,
|
|
3246
3405
|
} = req.body;
|
|
3406
|
+
// const receiveShareTriggers = Trigger.find({
|
|
3407
|
+
// when_trigger: "ReceiveMobileShareData",
|
|
3408
|
+
// });
|
|
3409
|
+
// disabeling share to support for now
|
|
3410
|
+
let allowShareTo = false; // receiveShareTriggers.length > 0;
|
|
3411
|
+
if (allowShareTo && iOSPlatform && !shareProvisioningProfile) {
|
|
3412
|
+
allowShareTo = false;
|
|
3413
|
+
msgs.push({
|
|
3414
|
+
type: "warning",
|
|
3415
|
+
text: req.__(
|
|
3416
|
+
"A ReceiveMobileShareData trigger exists, but no Share Extension Provisioning Profile is provided. " +
|
|
3417
|
+
"Building without share to support."
|
|
3418
|
+
),
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3247
3421
|
if (!includedPlugins) includedPlugins = [];
|
|
3248
3422
|
if (!synchedTables) synchedTables = [];
|
|
3249
3423
|
if (!entryPoint) {
|
|
@@ -3279,14 +3453,26 @@ router.post(
|
|
|
3279
3453
|
),
|
|
3280
3454
|
});
|
|
3281
3455
|
}
|
|
3282
|
-
if (iOSPlatform
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3456
|
+
if (iOSPlatform) {
|
|
3457
|
+
if (!provisioningProfile)
|
|
3458
|
+
return res.json({
|
|
3459
|
+
error: req.__(
|
|
3460
|
+
"Please provide a Provisioning Profile for the iOS build."
|
|
3461
|
+
),
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3464
|
+
if (buildType === "debug" && keystoreFile) {
|
|
3465
|
+
msgs.push({
|
|
3466
|
+
type: "warning",
|
|
3467
|
+
text: req.__("Keystore file is not applied for debug builds."),
|
|
3287
3468
|
});
|
|
3288
3469
|
}
|
|
3289
|
-
|
|
3470
|
+
|
|
3471
|
+
if (
|
|
3472
|
+
buildType === "release" &&
|
|
3473
|
+
keystoreFile &&
|
|
3474
|
+
(!keystoreAlias || !keystorePassword)
|
|
3475
|
+
) {
|
|
3290
3476
|
return res.json({
|
|
3291
3477
|
error: req.__(
|
|
3292
3478
|
"Please provide the keystore alias and password for the android build."
|
|
@@ -3294,8 +3480,9 @@ router.post(
|
|
|
3294
3480
|
});
|
|
3295
3481
|
}
|
|
3296
3482
|
const outDirName = `build_${new Date().valueOf()}`;
|
|
3483
|
+
const buildDir = `${os.userInfo().homedir}/mobile_app_build`;
|
|
3297
3484
|
const rootFolder = await File.rootFolder();
|
|
3298
|
-
const
|
|
3485
|
+
const outDir = path.join(rootFolder.location, "mobile_app", outDirName);
|
|
3299
3486
|
await File.new_folder(outDirName, "/mobile_app");
|
|
3300
3487
|
const spawnParams = [
|
|
3301
3488
|
"build-app",
|
|
@@ -3304,9 +3491,9 @@ router.post(
|
|
|
3304
3491
|
"-t",
|
|
3305
3492
|
entryPointType === "pagegroup" ? "page" : entryPointType,
|
|
3306
3493
|
"-c",
|
|
3307
|
-
|
|
3494
|
+
outDir,
|
|
3308
3495
|
"-b",
|
|
3309
|
-
|
|
3496
|
+
buildDir,
|
|
3310
3497
|
"-u",
|
|
3311
3498
|
req.user.email, // ensured by isAdmin
|
|
3312
3499
|
];
|
|
@@ -3319,6 +3506,13 @@ router.post(
|
|
|
3319
3506
|
"--provisioningProfile",
|
|
3320
3507
|
provisioningProfile
|
|
3321
3508
|
);
|
|
3509
|
+
if (allowShareTo) {
|
|
3510
|
+
mode = "prepare";
|
|
3511
|
+
spawnParams.push(
|
|
3512
|
+
"--shareExtensionProvisioningProfile",
|
|
3513
|
+
shareProvisioningProfile
|
|
3514
|
+
);
|
|
3515
|
+
}
|
|
3322
3516
|
}
|
|
3323
3517
|
if (appName) spawnParams.push("--appName", appName);
|
|
3324
3518
|
if (appId) spawnParams.push("--appId", appId);
|
|
@@ -3327,6 +3521,7 @@ router.post(
|
|
|
3327
3521
|
if (serverURL) spawnParams.push("-s", serverURL);
|
|
3328
3522
|
if (splashPage) spawnParams.push("--splashPage", splashPage);
|
|
3329
3523
|
if (allowOfflineMode) spawnParams.push("--allowOfflineMode");
|
|
3524
|
+
if (allowShareTo) spawnParams.push("--allowShareTo");
|
|
3330
3525
|
if (autoPublicLogin) spawnParams.push("--autoPublicLogin");
|
|
3331
3526
|
if (synchedTables.length > 0)
|
|
3332
3527
|
spawnParams.push("--synchedTables", ...synchedTables.map((tbl) => tbl));
|
|
@@ -3348,10 +3543,15 @@ router.post(
|
|
|
3348
3543
|
spawnParams.push("--androidKeyStoreAlias", keystoreAlias);
|
|
3349
3544
|
if (keystorePassword)
|
|
3350
3545
|
spawnParams.push("--androidKeystorePassword", keystorePassword);
|
|
3351
|
-
// end http call, return the out directory name
|
|
3546
|
+
// end http call, return the out directory name, the build directory path and the mode
|
|
3352
3547
|
// the gui polls for results
|
|
3353
|
-
res.json({
|
|
3354
|
-
|
|
3548
|
+
res.json({
|
|
3549
|
+
out_dir_name: outDirName,
|
|
3550
|
+
build_dir: buildDir,
|
|
3551
|
+
mode: mode,
|
|
3552
|
+
msgs,
|
|
3553
|
+
});
|
|
3554
|
+
const child = spawn(getSafeSaltcornCmd(), [...spawnParams, "-m", mode], {
|
|
3355
3555
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3356
3556
|
cwd: ".",
|
|
3357
3557
|
});
|
|
@@ -3366,18 +3566,29 @@ router.post(
|
|
|
3366
3566
|
getState().log(5, errMsg);
|
|
3367
3567
|
childOutputs.push(errMsg);
|
|
3368
3568
|
});
|
|
3369
|
-
child.on("exit", (exitCode, signal) => {
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3569
|
+
child.on("exit", async (exitCode, signal) => {
|
|
3570
|
+
if (mode === "prepare" && exitCode === 0) {
|
|
3571
|
+
try {
|
|
3572
|
+
fs.promises.writeFile(
|
|
3573
|
+
path.join(buildDir, "spawnParams.json"),
|
|
3574
|
+
JSON.stringify(spawnParams)
|
|
3575
|
+
);
|
|
3576
|
+
} catch (error) {
|
|
3577
|
+
console.log(`unable to write spawnParams to '${buildDir}'`);
|
|
3375
3578
|
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
3579
|
}
|
|
3380
|
-
}
|
|
3580
|
+
}
|
|
3581
|
+
const stepDesc = mode === "prepare" ? "_prepare_step" : "";
|
|
3582
|
+
const logFile =
|
|
3583
|
+
exitCode === 0 ? `logs${stepDesc}.txt` : `error_logs${stepDesc}.txt`;
|
|
3584
|
+
try {
|
|
3585
|
+
const exitMsg = childOutputs.join("\n");
|
|
3586
|
+
await fs.promises.writeFile(path.join(outDir, logFile), exitMsg);
|
|
3587
|
+
await File.set_xattr_of_existing_file(logFile, outDir, req.user);
|
|
3588
|
+
} catch (error) {
|
|
3589
|
+
console.log(`unable to write '${logFile}' to '${outDir}'`);
|
|
3590
|
+
console.log(error);
|
|
3591
|
+
}
|
|
3381
3592
|
});
|
|
3382
3593
|
child.on("error", (msg) => {
|
|
3383
3594
|
const message = msg.message ? msg.message : msg.code;
|
|
@@ -3386,15 +3597,15 @@ router.post(
|
|
|
3386
3597
|
const errMsg = [message, stack].join("\n");
|
|
3387
3598
|
getState().log(5, msg);
|
|
3388
3599
|
fs.writeFile(
|
|
3389
|
-
path.join(
|
|
3600
|
+
path.join(outDir, "error_logs.txt"),
|
|
3390
3601
|
errMsg,
|
|
3391
3602
|
async (error) => {
|
|
3392
3603
|
if (error) {
|
|
3393
|
-
console.log(`unable to write logFile to '${
|
|
3604
|
+
console.log(`unable to write logFile to '${outDir}'`);
|
|
3394
3605
|
console.log(error);
|
|
3395
3606
|
} else {
|
|
3396
3607
|
// no transaction, '/build-mobile-app/finished' filters for valid attributes
|
|
3397
|
-
await File.set_xattr_of_existing_file(logFile,
|
|
3608
|
+
await File.set_xattr_of_existing_file(logFile, outDir, req.user);
|
|
3398
3609
|
}
|
|
3399
3610
|
}
|
|
3400
3611
|
);
|
|
@@ -3462,7 +3673,7 @@ router.post(
|
|
|
3462
3673
|
.filter(
|
|
3463
3674
|
(plugin) =>
|
|
3464
3675
|
["base", "sbadmin2"].indexOf(plugin.name) < 0 &&
|
|
3465
|
-
newCfg.includedPlugins.indexOf(plugin.name) < 0
|
|
3676
|
+
(newCfg.includedPlugins || []).indexOf(plugin.name) < 0
|
|
3466
3677
|
)
|
|
3467
3678
|
.map((plugin) => plugin.name);
|
|
3468
3679
|
newCfg.excludedPlugins = excludedPlugins;
|
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
package/routes/notifications.js
CHANGED
|
@@ -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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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`);
|
package/routes/pageedit.js
CHANGED
|
@@ -146,7 +146,9 @@ const pagePropertiesForm = async (req, isNew) => {
|
|
|
146
146
|
{
|
|
147
147
|
name: "request_fluid_layout",
|
|
148
148
|
label: req.__("Fluid layout"),
|
|
149
|
-
sublabel: req.__(
|
|
149
|
+
sublabel: req.__(
|
|
150
|
+
"Request fluid layout from theme for a wider display for this page"
|
|
151
|
+
),
|
|
150
152
|
type: "Bool",
|
|
151
153
|
},
|
|
152
154
|
],
|
|
@@ -503,12 +505,21 @@ router.post(
|
|
|
503
505
|
pageRow.layout = {};
|
|
504
506
|
}
|
|
505
507
|
await Page.update(+id, pageRow);
|
|
508
|
+
Trigger.emitEvent("AppChange", `Page ${dbPage.name}`, req.user, {
|
|
509
|
+
entity_type: "Page",
|
|
510
|
+
entity_name: dbPage.name,
|
|
511
|
+
});
|
|
506
512
|
if (req.xhr) res.json({ success: "ok" });
|
|
507
513
|
else res.redirect(`/pageedit/`);
|
|
508
514
|
} else {
|
|
509
515
|
if (!pageRow.layout) pageRow.layout = {};
|
|
510
516
|
if (!pageRow.fixed_states) pageRow.fixed_states = {};
|
|
511
517
|
await Page.create(pageRow);
|
|
518
|
+
Trigger.emitEvent("AppChange", `Page ${pageRow.name}`, req.user, {
|
|
519
|
+
entity_type: "Page",
|
|
520
|
+
entity_name: pageRow.name,
|
|
521
|
+
});
|
|
522
|
+
|
|
512
523
|
if (!html_file)
|
|
513
524
|
res.redirect(
|
|
514
525
|
addOnDoneRedirect(`/pageedit/edit/${pageRow.name}`, req)
|
|
@@ -679,6 +690,10 @@ router.post(
|
|
|
679
690
|
await Page.update(page.id, {
|
|
680
691
|
layout: decodeURIComponent(req.body.layout),
|
|
681
692
|
});
|
|
693
|
+
Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
|
|
694
|
+
entity_type: "Page",
|
|
695
|
+
entity_name: page.name,
|
|
696
|
+
});
|
|
682
697
|
req.flash("success", req.__(`Page %s saved`, pagename));
|
|
683
698
|
res.redirect(redirectTarget);
|
|
684
699
|
} else if (req.body.code) {
|
|
@@ -687,6 +702,10 @@ router.post(
|
|
|
687
702
|
const file = await File.findOne(page.html_file);
|
|
688
703
|
if (!file) throw new Error(req.__("File not found"));
|
|
689
704
|
await fsp.writeFile(file.location, req.body.code);
|
|
705
|
+
Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
|
|
706
|
+
entity_type: "Page",
|
|
707
|
+
entity_name: page.name,
|
|
708
|
+
});
|
|
690
709
|
if (!req.xhr) {
|
|
691
710
|
req.flash("success", req.__(`Page %s saved`, pagename));
|
|
692
711
|
res.redirect(redirectTarget);
|
|
@@ -723,6 +742,11 @@ router.post(
|
|
|
723
742
|
|
|
724
743
|
if (id && req.body.layout) {
|
|
725
744
|
await Page.update(+id, { layout: req.body.layout });
|
|
745
|
+
const page = await Page.findOne({ id });
|
|
746
|
+
Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
|
|
747
|
+
entity_type: "Page",
|
|
748
|
+
entity_name: page.name,
|
|
749
|
+
});
|
|
726
750
|
res.json({
|
|
727
751
|
success: "ok",
|
|
728
752
|
});
|
|
@@ -744,6 +768,10 @@ router.post(
|
|
|
744
768
|
error_catcher(async (req, res) => {
|
|
745
769
|
const { id } = req.params;
|
|
746
770
|
const page = await Page.findOne({ id });
|
|
771
|
+
Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
|
|
772
|
+
entity_type: "Page",
|
|
773
|
+
entity_name: page.name,
|
|
774
|
+
});
|
|
747
775
|
await page.delete();
|
|
748
776
|
req.flash("success", req.__(`Page deleted`));
|
|
749
777
|
res.redirect(`/pageedit`);
|
|
@@ -796,6 +824,7 @@ router.post(
|
|
|
796
824
|
min_role: page.min_role,
|
|
797
825
|
pagename: page.name,
|
|
798
826
|
});
|
|
827
|
+
Trigger.emitEvent("AppChange", `Menu`, req.user, {});
|
|
799
828
|
req.flash(
|
|
800
829
|
"success",
|
|
801
830
|
req.__(
|
|
@@ -820,6 +849,10 @@ router.post(
|
|
|
820
849
|
const { id } = req.params;
|
|
821
850
|
const page = await Page.findOne({ id });
|
|
822
851
|
const newpage = await page.clone();
|
|
852
|
+
Trigger.emitEvent("AppChange", `Page ${newpage.name}`, req.user, {
|
|
853
|
+
entity_type: "Page",
|
|
854
|
+
entity_name: newpage.name,
|
|
855
|
+
});
|
|
823
856
|
req.flash(
|
|
824
857
|
"success",
|
|
825
858
|
req.__("Page %s duplicated as %s", page.name, newpage.name)
|