@saltcorn/server 0.9.4-beta.19 → 0.9.4-beta.20

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/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.9.4-beta.19",
3
+ "version": "0.9.4-beta.20",
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
9
  "@aws-sdk/client-s3": "^3.451.0",
10
- "@saltcorn/base-plugin": "0.9.4-beta.19",
11
- "@saltcorn/builder": "0.9.4-beta.19",
12
- "@saltcorn/data": "0.9.4-beta.19",
13
- "@saltcorn/admin-models": "0.9.4-beta.19",
14
- "@saltcorn/filemanager": "0.9.4-beta.19",
15
- "@saltcorn/markup": "0.9.4-beta.19",
16
- "@saltcorn/sbadmin2": "0.9.4-beta.19",
10
+ "@saltcorn/base-plugin": "0.9.4-beta.20",
11
+ "@saltcorn/builder": "0.9.4-beta.20",
12
+ "@saltcorn/data": "0.9.4-beta.20",
13
+ "@saltcorn/admin-models": "0.9.4-beta.20",
14
+ "@saltcorn/filemanager": "0.9.4-beta.20",
15
+ "@saltcorn/markup": "0.9.4-beta.20",
16
+ "@saltcorn/sbadmin2": "0.9.4-beta.20",
17
17
  "@socket.io/cluster-adapter": "^0.2.1",
18
18
  "@socket.io/sticky": "^1.0.1",
19
19
  "adm-zip": "0.5.10",
@@ -1189,6 +1189,13 @@ async function common_done(res, viewname, isWeb = true) {
1189
1189
  const input = form.find(
1190
1190
  `input[name=${k}], textarea[name=${k}], select[name=${k}]`
1191
1191
  );
1192
+ if (k === "id" && input.length === 0) {
1193
+ //TODO table.pk_name instead of id
1194
+ form.append(
1195
+ `<input type="hidden" name="id" value="${res.set_fields[k]}">`
1196
+ );
1197
+ return;
1198
+ }
1192
1199
  if (input.attr("type") === "checkbox")
1193
1200
  input.prop("checked", res.set_fields[k]);
1194
1201
  else input.val(res.set_fields[k]);
@@ -252,6 +252,21 @@ function ajax_done(res, viewname) {
252
252
  common_done(res, viewname);
253
253
  }
254
254
 
255
+ function spin_action_link(e) {
256
+ const $e = $(e);
257
+ const width = $e.width();
258
+ $e.attr("data-innerhtml-prespin", $e.html());
259
+ $e.html('<i class="fas fa-spinner fa-spin"></i>').width(width);
260
+ }
261
+
262
+ function reset_spinners() {
263
+ $("[data-innerhtml-prespin]").each(function () {
264
+ $e = $(this);
265
+ $e.html($e.attr("data-innerhtml-prespin"));
266
+ $e.removeAttr("data-innerhtml-prespin");
267
+ });
268
+ }
269
+
255
270
  function view_post(viewname, route, data, onDone, sendState) {
256
271
  const query = sendState
257
272
  ? `?${new URL(get_current_state_url()).searchParams.toString()}`
@@ -271,9 +286,11 @@ function view_post(viewname, route, data, onDone, sendState) {
271
286
  .done(function (res) {
272
287
  if (onDone) onDone(res);
273
288
  ajax_done(res, viewname);
289
+ reset_spinners();
274
290
  })
275
291
  .fail(function (res) {
276
292
  notifyAlert({ type: "danger", text: res.responseText });
293
+ reset_spinners();
277
294
  });
278
295
  }
279
296
  let logged_errors = [];
package/routes/fields.js CHANGED
@@ -1180,9 +1180,24 @@ router.post(
1180
1180
  type,
1181
1181
  join_field,
1182
1182
  join_fieldview,
1183
+ agg_outcome_type,
1184
+ agg_fieldview,
1185
+ agg_field,
1183
1186
  _columndef,
1184
1187
  } = req.body;
1185
1188
  const table = Table.findOne({ name: tableName });
1189
+ if (agg_outcome_type && agg_fieldview) {
1190
+ const type = getState().types[agg_outcome_type];
1191
+ const fv = type?.fieldviews?.[agg_fieldview];
1192
+ if (!fv?.configFields) {
1193
+ res.send(req.query?.accept == "json" ? "[]" : "");
1194
+ return;
1195
+ }
1196
+ const field = table.getField(agg_field);
1197
+ const cfgfields = await applyAsync(fv.configFields, field || { table });
1198
+ res.json(cfgfields);
1199
+ return;
1200
+ }
1186
1201
  if (typeof type !== "string") {
1187
1202
  try {
1188
1203
  type = JSON.parse(_columndef).type;
@@ -1193,19 +1208,19 @@ router.post(
1193
1208
  const fieldName = type == "Field" ? field_name : join_field;
1194
1209
  const fv_name = type == "Field" ? fieldview : join_fieldview;
1195
1210
  if (!fieldName) {
1196
- res.send("");
1211
+ res.send(req.query?.accept == "json" ? "[]" : "");
1197
1212
  return;
1198
1213
  }
1199
1214
 
1200
1215
  const field = table.getField(fieldName);
1201
1216
  if (!field) {
1202
- res.send("");
1217
+ res.send(req.query?.accept == "json" ? "[]" : "");
1203
1218
  return;
1204
1219
  }
1205
1220
  const fieldViewConfigForms = await calcfldViewConfig([field], false, 0);
1206
1221
  const formFields = fieldViewConfigForms[field.name][fv_name];
1207
1222
  if (!formFields) {
1208
- res.send("");
1223
+ res.send(req.query?.accept == "json" ? "[]" : "");
1209
1224
  return;
1210
1225
  }
1211
1226
  formFields.forEach((ff) => {
@@ -186,7 +186,7 @@ const pageBuilderData = async (req, context) => {
186
186
  for (const view of views) {
187
187
  fixed_state_fields[view.name] = [];
188
188
  const table = Table.findOne(view.table_id || view.exttable_name);
189
-
189
+ if (table) view.table_name = table.name;
190
190
  const fs = await view.get_state_fields();
191
191
  for (const frec of fs) {
192
192
  const f = new Field(frec);
@@ -219,9 +219,10 @@ const pageBuilderData = async (req, context) => {
219
219
  }
220
220
  }
221
221
  }
222
+
222
223
  //console.log(fixed_state_fields.ListTasks);
223
224
  return {
224
- views,
225
+ views: views.map((v) => v.select_option),
225
226
  images,
226
227
  pages,
227
228
  page_groups,
package/routes/tables.js CHANGED
@@ -832,6 +832,7 @@ router.get(
832
832
  }
833
833
  viewCard = {
834
834
  type: "card",
835
+ id: "table-views",
835
836
  title: req.__("Views of this table"),
836
837
  contents:
837
838
  viewCardContents +
@@ -1173,6 +1174,32 @@ router.post(
1173
1174
  res.redirect(`/table`);
1174
1175
  return;
1175
1176
  }
1177
+ const views = await View.find(
1178
+ t.id ? { table_id: t.id } : { exttable_name: t.name }
1179
+ );
1180
+ if (views.length) {
1181
+ req.flash(
1182
+ "error",
1183
+ `${text(t.name)} has views. Delete these first: <a href="/table/${
1184
+ t.name
1185
+ }#table-views">Views for ${text(t.name)}</a>`
1186
+ );
1187
+ res.redirect(`/table`);
1188
+ return;
1189
+ }
1190
+ if (t.id) {
1191
+ const triggers = await Trigger.find({ table_id: t.id });
1192
+ if (triggers.length) {
1193
+ req.flash(
1194
+ "error",
1195
+ `${text(
1196
+ t.name
1197
+ )} has triggers. Delete these first: <a href="/actions">Trigger list</a>`
1198
+ );
1199
+ res.redirect(`/table`);
1200
+ return;
1201
+ }
1202
+ }
1176
1203
  try {
1177
1204
  await t.delete();
1178
1205
  req.flash("success", req.__(`Table %s deleted`, t.name));
package/serve.js CHANGED
@@ -394,21 +394,29 @@ const setupSocket = (subdomainOffset, ...servers) => {
394
394
  io.attach(server);
395
395
  }
396
396
 
397
- //io.use(wrap(setTenant));
398
- io.use(wrap(getSessionStore()));
399
- io.use(wrap(passport.initialize()));
400
- io.use(wrap(passport.authenticate(["jwt", "session"])));
397
+ const passportInit = passport.initialize();
398
+ const sessionStore = getSessionStore();
399
+ const setupNamespace = (namespace) => {
400
+ //io.of(namespace).use(wrap(setTenant));
401
+ io.of(namespace).use(wrap(sessionStore));
402
+ io.of(namespace).use(wrap(passportInit));
403
+ io.of(namespace).use(wrap(passport.authenticate(["jwt", "session"])));
404
+ };
405
+ setupNamespace("/");
406
+ setupNamespace("/datastream");
401
407
  if (process.send && !cluster.isMaster) io.adapter(createAdapter());
402
408
  getState().setRoomEmitter((tenant, viewname, room_id, msg) => {
403
- io.to(`${tenant}_${viewname}_${room_id}`).emit("message", msg);
409
+ io.of("/").to(`${tenant}_${viewname}_${room_id}`).emit("message", msg);
404
410
  });
405
411
 
406
412
  getState().setLogEmitter((tenant, level, msg) => {
407
413
  const time = new Date().valueOf();
408
- io.to(`_logs_${tenant}_`).emit("log_msg", { text: msg, time, level });
414
+ io.of("/")
415
+ .to(`_logs_${tenant}_`)
416
+ .emit("log_msg", { text: msg, time, level });
409
417
  });
410
418
 
411
- io.on("connection", (socket) => {
419
+ io.of("/").on("connection", (socket) => {
412
420
  socket.on("join_room", ([viewname, room_id]) => {
413
421
  const ten = get_tenant_from_req(socket.request) || "public";
414
422
  const f = () => {
@@ -466,4 +474,116 @@ const setupSocket = (subdomainOffset, ...servers) => {
466
474
  else f();
467
475
  });
468
476
  });
477
+
478
+ io.of("/datastream").on("connection", (socket) => {
479
+ let dataStream = null;
480
+ let dataTarget = null;
481
+ socket.on(
482
+ "open_data_stream",
483
+ async ([viewName, id, fieldName, fieldView, targetOpts], callback) => {
484
+ const tenant =
485
+ get_tenant_from_req(socket.request, subdomainOffset) || "public";
486
+ const f = async () => {
487
+ try {
488
+ const user = socket.request.user;
489
+ const view = View.findOne({ name: viewName });
490
+ if (view.viewtemplateObj.authorizeDataStream) {
491
+ const authorized = await view.viewtemplateObj.authorizeDataStream(
492
+ view,
493
+ id,
494
+ fieldName,
495
+ user,
496
+ targetOpts
497
+ );
498
+ if (!authorized) throw new Error("Not authorized");
499
+ }
500
+ const { stream, target } = await view.openDataStream(
501
+ id,
502
+ fieldName,
503
+ fieldView,
504
+ user,
505
+ targetOpts
506
+ );
507
+ dataStream = stream;
508
+ dataTarget = target;
509
+ getState().log(
510
+ 5,
511
+ `opened data stram to: ${JSON.stringify(dataTarget)}`
512
+ );
513
+ callback({ status: "ok", target });
514
+ } catch (err) {
515
+ getState().log(
516
+ 1,
517
+ `Socket open_data_stream: ${err.message || "unknown error"}`
518
+ );
519
+ callback({ status: "error", msg: err.message || "unknown error" });
520
+ }
521
+ };
522
+ if (tenant && tenant !== "public") db.runWithTenant(tenant, f);
523
+ else f();
524
+ }
525
+ );
526
+ socket.on("write_to_stream", async (data, callback) => {
527
+ if (!dataStream) {
528
+ getState().log(1, "Socket write_to_stream: No stream available");
529
+ callback({ status: "error", msg: "No stream available" });
530
+ } else
531
+ dataStream.write(data, (err) => {
532
+ if (err) {
533
+ getState().log(1, "Socket write_to_stream: No stream available");
534
+ callback({ status: "error", msg: err.message || "unknown error" });
535
+ } else callback({ status: "ok" });
536
+ });
537
+ });
538
+
539
+ socket.on("close_data_stream", async (callback) => {
540
+ if (!dataStream) {
541
+ getState().log(1, "Socket close_data_stream: No stream available");
542
+ callback({ status: "error", msg: "No stream available" });
543
+ } else {
544
+ dataStream.close((err) => {
545
+ if (err) {
546
+ getState().log(
547
+ 1,
548
+ `Socket close_data_stream: ${err.message || "unknown error"}`
549
+ );
550
+ callback({ status: "error", msg: err.message || "unknown error" });
551
+ } else {
552
+ getState().log(
553
+ 5,
554
+ `closed data stram of: ${JSON.stringify(dataTarget)}`
555
+ );
556
+ callback({ status: "ok" });
557
+ dataStream = null;
558
+ }
559
+ });
560
+ }
561
+ });
562
+
563
+ socket.on("disconnect", async () => {
564
+ const tenant =
565
+ get_tenant_from_req(socket.request, subdomainOffset) || "public";
566
+ const f = async () => {
567
+ if (dataStream)
568
+ dataStream.close((err) => {
569
+ if (err) {
570
+ getState().log(
571
+ 1,
572
+ `Socket disconnect close_data_stream: ${
573
+ err.message || "unknown error"
574
+ }`
575
+ );
576
+ } else {
577
+ getState().log(
578
+ 5,
579
+ `closed data stram of: ${JSON.stringify(dataTarget)}`
580
+ );
581
+ dataStream = null;
582
+ }
583
+ });
584
+ };
585
+ if (tenant && tenant !== "public") db.runWithTenant(tenant, f);
586
+ else f();
587
+ });
588
+ });
469
589
  };
@@ -254,11 +254,7 @@ Gordon Kane, 218`;
254
254
  await request(app)
255
255
  .get("/table/")
256
256
  .set("Cookie", loginCookie)
257
- .expect(
258
- toInclude(
259
- "cannot drop table books because other objects depend on it"
260
- )
261
- );
257
+ .expect(toInclude("has views. Delete these first"));
262
258
  });
263
259
  });
264
260
  describe("deletion to table with row ownership", () => {