tempest-express-sdk 0.29.0 → 0.30.0
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/README.md +2 -2
- package/dist/{chunk-7T4MZJZT.js → chunk-DK46733U.js} +3 -3
- package/dist/{chunk-7T4MZJZT.js.map → chunk-DK46733U.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +477 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +268 -4
- package/dist/index.d.ts +268 -4
- package/dist/index.js +474 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8673,6 +8673,7 @@ var TaskManager = class {
|
|
|
8673
8673
|
broker;
|
|
8674
8674
|
queue;
|
|
8675
8675
|
handlers = /* @__PURE__ */ new Map();
|
|
8676
|
+
metadata = /* @__PURE__ */ new Map();
|
|
8676
8677
|
unsubscribe = null;
|
|
8677
8678
|
/**
|
|
8678
8679
|
* @param options - Broker and queue name.
|
|
@@ -8686,9 +8687,32 @@ var TaskManager = class {
|
|
|
8686
8687
|
*
|
|
8687
8688
|
* @param name - The task name.
|
|
8688
8689
|
* @param handler - The handler invoked with the task payload.
|
|
8690
|
+
* @param options - Description and declared schedule, surfaced by
|
|
8691
|
+
* {@link TaskManager.inventory} and by the admin panel's tasks page.
|
|
8689
8692
|
*/
|
|
8690
|
-
register(name, handler) {
|
|
8693
|
+
register(name, handler, options = {}) {
|
|
8691
8694
|
this.handlers.set(name, handler);
|
|
8695
|
+
this.metadata.set(name, options);
|
|
8696
|
+
}
|
|
8697
|
+
/**
|
|
8698
|
+
* Return what this process would run, ordered by name.
|
|
8699
|
+
*
|
|
8700
|
+
* This is the **declared** side of background work — the handlers this
|
|
8701
|
+
* process knows about — not queue state. A broker's pending depth is not
|
|
8702
|
+
* something the manager can see, and a screen that implied otherwise would
|
|
8703
|
+
* be worse than one that says nothing.
|
|
8704
|
+
*
|
|
8705
|
+
* @returns One entry per registered task.
|
|
8706
|
+
*/
|
|
8707
|
+
inventory() {
|
|
8708
|
+
return [...this.handlers.keys()].sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
8709
|
+
const meta = this.metadata.get(name) ?? {};
|
|
8710
|
+
return {
|
|
8711
|
+
name,
|
|
8712
|
+
description: meta.description ?? null,
|
|
8713
|
+
schedule: meta.schedule ?? null
|
|
8714
|
+
};
|
|
8715
|
+
});
|
|
8692
8716
|
}
|
|
8693
8717
|
/**
|
|
8694
8718
|
* Enqueue a task by name.
|
|
@@ -8722,6 +8746,180 @@ var TaskManager = class {
|
|
|
8722
8746
|
this.unsubscribe = null;
|
|
8723
8747
|
}
|
|
8724
8748
|
};
|
|
8749
|
+
var JobStatus = {
|
|
8750
|
+
/** Written, not started. */
|
|
8751
|
+
QUEUED: "queued",
|
|
8752
|
+
/** A worker picked it up. */
|
|
8753
|
+
RUNNING: "running",
|
|
8754
|
+
/** Finished cleanly. */
|
|
8755
|
+
SUCCEEDED: "succeeded",
|
|
8756
|
+
/** Finished with an error. */
|
|
8757
|
+
FAILED: "failed",
|
|
8758
|
+
/** An operator stopped it before it finished. */
|
|
8759
|
+
CANCELLED: "cancelled"
|
|
8760
|
+
};
|
|
8761
|
+
var TERMINAL = [
|
|
8762
|
+
JobStatus.SUCCEEDED,
|
|
8763
|
+
JobStatus.FAILED,
|
|
8764
|
+
JobStatus.CANCELLED
|
|
8765
|
+
];
|
|
8766
|
+
var BaseJobModel = class extends BaseModel {
|
|
8767
|
+
/** The task name this run belongs to. */
|
|
8768
|
+
name = tempestDbJs.column.varchar(128).notNull();
|
|
8769
|
+
/** Lifecycle state — a {@link JobStatus} value. */
|
|
8770
|
+
status = tempestDbJs.column.varchar(16).notNull().default(JobStatus.QUEUED);
|
|
8771
|
+
/** The payload the run was started with. */
|
|
8772
|
+
payload = tempestDbJs.column.json();
|
|
8773
|
+
/** Whatever the run produced, for an operator to read afterwards. */
|
|
8774
|
+
result = tempestDbJs.column.json();
|
|
8775
|
+
/** The failure message, when the run failed. */
|
|
8776
|
+
error = tempestDbJs.column.text();
|
|
8777
|
+
/** How many times the run has been attempted. */
|
|
8778
|
+
attempts = tempestDbJs.column.integer().notNull().default(0);
|
|
8779
|
+
/** When a worker picked it up. */
|
|
8780
|
+
startedAt = tempestDbJs.column.datetime();
|
|
8781
|
+
/** When it reached a terminal state. */
|
|
8782
|
+
finishedAt = tempestDbJs.column.datetime();
|
|
8783
|
+
};
|
|
8784
|
+
var JobStore = class {
|
|
8785
|
+
/**
|
|
8786
|
+
* @param model - The concrete {@link BaseJobModel} subclass.
|
|
8787
|
+
* @param session - The session rows are written on.
|
|
8788
|
+
*/
|
|
8789
|
+
constructor(model, session) {
|
|
8790
|
+
this.model = model;
|
|
8791
|
+
this.repository = new tempestDbJs.BaseRepository(model, session);
|
|
8792
|
+
}
|
|
8793
|
+
model;
|
|
8794
|
+
repository;
|
|
8795
|
+
/**
|
|
8796
|
+
* Record a job about to run.
|
|
8797
|
+
*
|
|
8798
|
+
* @param name - The task name.
|
|
8799
|
+
* @param payload - The payload the run was started with.
|
|
8800
|
+
* @returns The created row.
|
|
8801
|
+
*/
|
|
8802
|
+
async enqueue(name, payload = {}) {
|
|
8803
|
+
return await this.repository.create({
|
|
8804
|
+
name,
|
|
8805
|
+
status: JobStatus.QUEUED,
|
|
8806
|
+
payload,
|
|
8807
|
+
result: null,
|
|
8808
|
+
error: null,
|
|
8809
|
+
attempts: 0,
|
|
8810
|
+
startedAt: null,
|
|
8811
|
+
finishedAt: null
|
|
8812
|
+
});
|
|
8813
|
+
}
|
|
8814
|
+
/**
|
|
8815
|
+
* Mark a job as picked up, counting the attempt.
|
|
8816
|
+
*
|
|
8817
|
+
* @param id - The job id.
|
|
8818
|
+
* @param attempt - Which attempt this is. Default `1`.
|
|
8819
|
+
* @returns How many rows changed.
|
|
8820
|
+
*/
|
|
8821
|
+
async start(id, attempt = 1) {
|
|
8822
|
+
return await this.repository.update(
|
|
8823
|
+
{ id },
|
|
8824
|
+
{
|
|
8825
|
+
status: JobStatus.RUNNING,
|
|
8826
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
8827
|
+
attempts: attempt
|
|
8828
|
+
}
|
|
8829
|
+
);
|
|
8830
|
+
}
|
|
8831
|
+
/**
|
|
8832
|
+
* Mark a job as finished cleanly.
|
|
8833
|
+
*
|
|
8834
|
+
* @param id - The job id.
|
|
8835
|
+
* @param result - Whatever the run produced.
|
|
8836
|
+
* @returns How many rows changed.
|
|
8837
|
+
*/
|
|
8838
|
+
async succeed(id, result = {}) {
|
|
8839
|
+
return await this.repository.update(
|
|
8840
|
+
{ id },
|
|
8841
|
+
{
|
|
8842
|
+
status: JobStatus.SUCCEEDED,
|
|
8843
|
+
result,
|
|
8844
|
+
error: null,
|
|
8845
|
+
finishedAt: /* @__PURE__ */ new Date()
|
|
8846
|
+
}
|
|
8847
|
+
);
|
|
8848
|
+
}
|
|
8849
|
+
/**
|
|
8850
|
+
* Mark a job as failed.
|
|
8851
|
+
*
|
|
8852
|
+
* @param id - The job id.
|
|
8853
|
+
* @param error - The failure, as an `Error` or a message.
|
|
8854
|
+
* @returns How many rows changed.
|
|
8855
|
+
*/
|
|
8856
|
+
async fail(id, error) {
|
|
8857
|
+
return await this.repository.update(
|
|
8858
|
+
{ id },
|
|
8859
|
+
{
|
|
8860
|
+
status: JobStatus.FAILED,
|
|
8861
|
+
error: error instanceof Error ? error.message : String(error),
|
|
8862
|
+
finishedAt: /* @__PURE__ */ new Date()
|
|
8863
|
+
}
|
|
8864
|
+
);
|
|
8865
|
+
}
|
|
8866
|
+
/**
|
|
8867
|
+
* Ask a job to stop.
|
|
8868
|
+
*
|
|
8869
|
+
* A job already in a terminal state is left alone and reported as not
|
|
8870
|
+
* cancelled, so an operator clicking cancel on a run that just finished sees
|
|
8871
|
+
* the truth rather than a row rewritten under them.
|
|
8872
|
+
*
|
|
8873
|
+
* @param id - The job id.
|
|
8874
|
+
* @returns Whether the row moved to `cancelled`.
|
|
8875
|
+
*/
|
|
8876
|
+
async cancel(id) {
|
|
8877
|
+
const row = await this.repository.getByIdOrNull(id);
|
|
8878
|
+
if (row === null || TERMINAL.includes(String(row.status))) return false;
|
|
8879
|
+
const changed = await this.repository.update(
|
|
8880
|
+
{ id },
|
|
8881
|
+
{
|
|
8882
|
+
status: JobStatus.CANCELLED,
|
|
8883
|
+
finishedAt: /* @__PURE__ */ new Date()
|
|
8884
|
+
}
|
|
8885
|
+
);
|
|
8886
|
+
return changed > 0;
|
|
8887
|
+
}
|
|
8888
|
+
/**
|
|
8889
|
+
* Read one job row.
|
|
8890
|
+
*
|
|
8891
|
+
* @param id - The job id.
|
|
8892
|
+
* @returns The row, or `null`.
|
|
8893
|
+
*/
|
|
8894
|
+
async get(id) {
|
|
8895
|
+
return await this.repository.getByIdOrNull(id);
|
|
8896
|
+
}
|
|
8897
|
+
/**
|
|
8898
|
+
* Read a page of jobs, newest first.
|
|
8899
|
+
*
|
|
8900
|
+
* @param filter - Page, size and optional `name` / `status` filters.
|
|
8901
|
+
* @returns The page plus its metadata.
|
|
8902
|
+
*/
|
|
8903
|
+
async list(filter = {}) {
|
|
8904
|
+
const filters = {};
|
|
8905
|
+
if (filter.name !== void 0 && filter.name !== "") filters.name = filter.name;
|
|
8906
|
+
if (filter.status !== void 0) filters.status = filter.status;
|
|
8907
|
+
const result = await this.repository.paginate({
|
|
8908
|
+
page: filter.page ?? 1,
|
|
8909
|
+
pageSize: filter.pageSize ?? 25,
|
|
8910
|
+
orderBy: "createdAt",
|
|
8911
|
+
ascending: false,
|
|
8912
|
+
...Object.keys(filters).length === 0 ? {} : { filters }
|
|
8913
|
+
});
|
|
8914
|
+
return {
|
|
8915
|
+
items: result.items,
|
|
8916
|
+
total: result.total,
|
|
8917
|
+
page: result.page,
|
|
8918
|
+
pageSize: result.pageSize,
|
|
8919
|
+
pages: result.pages
|
|
8920
|
+
};
|
|
8921
|
+
}
|
|
8922
|
+
};
|
|
8725
8923
|
|
|
8726
8924
|
// src/flags/backends.ts
|
|
8727
8925
|
function coerceFlag(value) {
|
|
@@ -9794,8 +9992,8 @@ function humanizeField(name) {
|
|
|
9794
9992
|
function adminColumns(model) {
|
|
9795
9993
|
return tempestDbJs.columnsOf(model);
|
|
9796
9994
|
}
|
|
9797
|
-
function widgetForColumn(
|
|
9798
|
-
const { kind, meta } =
|
|
9995
|
+
function widgetForColumn(column7) {
|
|
9996
|
+
const { kind, meta } = column7.type;
|
|
9799
9997
|
const plain = (widget) => ({
|
|
9800
9998
|
widget,
|
|
9801
9999
|
step: null,
|
|
@@ -9839,11 +10037,11 @@ function widgetForColumn(column6) {
|
|
|
9839
10037
|
return plain("text");
|
|
9840
10038
|
}
|
|
9841
10039
|
}
|
|
9842
|
-
function isColumnOptional(
|
|
9843
|
-
return !
|
|
10040
|
+
function isColumnOptional(column7) {
|
|
10041
|
+
return !column7.flags.notNull || column7.flags.hasDefault || column7.flags.primaryKey;
|
|
9844
10042
|
}
|
|
9845
|
-
function filterForColumn(
|
|
9846
|
-
const { kind, meta } =
|
|
10043
|
+
function filterForColumn(column7) {
|
|
10044
|
+
const { kind, meta } = column7.type;
|
|
9847
10045
|
if (kind === "boolean") {
|
|
9848
10046
|
return {
|
|
9849
10047
|
kind: "select",
|
|
@@ -9867,11 +10065,11 @@ function filterForColumn(column6) {
|
|
|
9867
10065
|
}
|
|
9868
10066
|
return { kind: "text", options: [] };
|
|
9869
10067
|
}
|
|
9870
|
-
function foreignKeyTable(
|
|
9871
|
-
return
|
|
10068
|
+
function foreignKeyTable(column7) {
|
|
10069
|
+
return column7.reference?.table ?? null;
|
|
9872
10070
|
}
|
|
9873
|
-
function isSearchableColumn(
|
|
9874
|
-
const { kind } =
|
|
10071
|
+
function isSearchableColumn(column7) {
|
|
10072
|
+
const { kind } = column7.type;
|
|
9875
10073
|
return kind === "varchar" || kind === "text" || kind === "char";
|
|
9876
10074
|
}
|
|
9877
10075
|
|
|
@@ -10153,8 +10351,8 @@ function formatFieldValue(widget, value) {
|
|
|
10153
10351
|
}
|
|
10154
10352
|
return String(value);
|
|
10155
10353
|
}
|
|
10156
|
-
function literalDefault(
|
|
10157
|
-
const fallback =
|
|
10354
|
+
function literalDefault(column7) {
|
|
10355
|
+
const fallback = column7.defaultValue;
|
|
10158
10356
|
if (fallback === null || fallback.kind !== "literal") return void 0;
|
|
10159
10357
|
return fallback.value;
|
|
10160
10358
|
}
|
|
@@ -10167,19 +10365,19 @@ function buildFormFields(admin, options = {}) {
|
|
|
10167
10365
|
const autocompleteLabels = options.autocompleteLabels ?? {};
|
|
10168
10366
|
const uploads = new Set(admin.uploadFields);
|
|
10169
10367
|
return admin.editableFieldNames().flatMap((name) => {
|
|
10170
|
-
const
|
|
10171
|
-
if (
|
|
10368
|
+
const column7 = columns[name];
|
|
10369
|
+
if (column7 === void 0) return [];
|
|
10172
10370
|
const related = foreignKeys[name];
|
|
10173
10371
|
const autocompleteUrl = autocompleteUrls[name];
|
|
10174
|
-
const spec = uploads.has(name) ? { widget: "file", step: null, options: [] } : autocompleteUrl !== void 0 ? { widget: "autocomplete", step: null, options: [] } : related === void 0 ? widgetForColumn(
|
|
10175
|
-
const raw = name in values ? values[name] : literalDefault(
|
|
10372
|
+
const spec = uploads.has(name) ? { widget: "file", step: null, options: [] } : autocompleteUrl !== void 0 ? { widget: "autocomplete", step: null, options: [] } : related === void 0 ? widgetForColumn(column7) : { widget: "select", step: null, options: related };
|
|
10373
|
+
const raw = name in values ? values[name] : literalDefault(column7);
|
|
10176
10374
|
return [
|
|
10177
10375
|
{
|
|
10178
10376
|
name,
|
|
10179
10377
|
label: humanizeField(name),
|
|
10180
10378
|
widget: spec.widget,
|
|
10181
10379
|
value: spec.widget === "checkbox" ? "" : formatFieldValue(spec.widget, raw),
|
|
10182
|
-
required: !isColumnOptional(
|
|
10380
|
+
required: !isColumnOptional(column7),
|
|
10183
10381
|
checked: spec.widget === "checkbox" && toBoolean(raw),
|
|
10184
10382
|
step: spec.step,
|
|
10185
10383
|
options: spec.options,
|
|
@@ -10196,8 +10394,8 @@ function toBoolean(value) {
|
|
|
10196
10394
|
if (typeof value !== "string") return false;
|
|
10197
10395
|
return ["true", "on", "yes", "1"].includes(value.trim().toLowerCase());
|
|
10198
10396
|
}
|
|
10199
|
-
function coerceValue(
|
|
10200
|
-
const { kind, meta } =
|
|
10397
|
+
function coerceValue(column7, widget, raw) {
|
|
10398
|
+
const { kind, meta } = column7.type;
|
|
10201
10399
|
switch (widget) {
|
|
10202
10400
|
case "number": {
|
|
10203
10401
|
const parsed = Number(raw);
|
|
@@ -10241,10 +10439,10 @@ function parseFormBody(admin, body, options = {}) {
|
|
|
10241
10439
|
const only = options.only === void 0 ? null : new Set(options.only);
|
|
10242
10440
|
for (const name of admin.editableFieldNames()) {
|
|
10243
10441
|
if (only !== null && !only.has(name)) continue;
|
|
10244
|
-
const
|
|
10245
|
-
if (
|
|
10442
|
+
const column7 = columns[name];
|
|
10443
|
+
if (column7 === void 0) continue;
|
|
10246
10444
|
if (uploads.has(name)) continue;
|
|
10247
|
-
const { widget } = widgetForColumn(
|
|
10445
|
+
const { widget } = widgetForColumn(column7);
|
|
10248
10446
|
if (widget === "checkbox") {
|
|
10249
10447
|
data[name] = toBoolean(body[name]);
|
|
10250
10448
|
continue;
|
|
@@ -10252,16 +10450,16 @@ function parseFormBody(admin, body, options = {}) {
|
|
|
10252
10450
|
const submitted = body[name];
|
|
10253
10451
|
const raw = typeof submitted === "string" ? submitted.trim() : "";
|
|
10254
10452
|
if (raw === "") {
|
|
10255
|
-
if (!isColumnOptional(
|
|
10453
|
+
if (!isColumnOptional(column7)) {
|
|
10256
10454
|
errors[name] = "This field is required.";
|
|
10257
10455
|
continue;
|
|
10258
10456
|
}
|
|
10259
|
-
if (
|
|
10457
|
+
if (column7.flags.hasDefault && !(name in body)) continue;
|
|
10260
10458
|
data[name] = null;
|
|
10261
10459
|
continue;
|
|
10262
10460
|
}
|
|
10263
10461
|
try {
|
|
10264
|
-
data[name] = coerceValue(
|
|
10462
|
+
data[name] = coerceValue(column7, widget, raw);
|
|
10265
10463
|
} catch (error) {
|
|
10266
10464
|
errors[name] = error instanceof Error ? error.message : "Invalid value.";
|
|
10267
10465
|
}
|
|
@@ -10279,9 +10477,9 @@ function foreignKeyFields(admin) {
|
|
|
10279
10477
|
const columns = adminColumns(admin.model);
|
|
10280
10478
|
const out = {};
|
|
10281
10479
|
for (const name of admin.editableFieldNames()) {
|
|
10282
|
-
const
|
|
10283
|
-
if (
|
|
10284
|
-
const table = foreignKeyTable(
|
|
10480
|
+
const column7 = columns[name];
|
|
10481
|
+
if (column7 === void 0) continue;
|
|
10482
|
+
const table = foreignKeyTable(column7);
|
|
10285
10483
|
if (table !== null) out[name] = table;
|
|
10286
10484
|
}
|
|
10287
10485
|
return out;
|
|
@@ -10667,7 +10865,14 @@ var AdminSite = class {
|
|
|
10667
10865
|
};
|
|
10668
10866
|
|
|
10669
10867
|
// src/admin/styles.ts
|
|
10670
|
-
var
|
|
10868
|
+
var ADMIN_CSS_EXTRA = `
|
|
10869
|
+
.tempest-log-badge--succeeded { background: #dcfce7; color: #166534; }
|
|
10870
|
+
.tempest-log-badge--failed { background: #fee2e2; color: #991b1b; }
|
|
10871
|
+
.tempest-log-badge--running { background: #dbeafe; color: #1e40af; }
|
|
10872
|
+
.tempest-log-badge--queued { background: #f1f5f9; color: #475569; }
|
|
10873
|
+
.tempest-log-badge--cancelled { background: #fef3c7; color: #92400e; }
|
|
10874
|
+
`;
|
|
10875
|
+
var ADMIN_CSS_BASE = `:root {
|
|
10671
10876
|
--tempest-bg: #0f172a;
|
|
10672
10877
|
--tempest-bg-soft: #1e293b;
|
|
10673
10878
|
--tempest-bg-row: #f8fafc;
|
|
@@ -12226,6 +12431,7 @@ a.tempest-admin-list__new:hover {
|
|
|
12226
12431
|
}
|
|
12227
12432
|
}
|
|
12228
12433
|
`;
|
|
12434
|
+
var ADMIN_CSS = `${ADMIN_CSS_BASE}${ADMIN_CSS_EXTRA}`;
|
|
12229
12435
|
|
|
12230
12436
|
// src/admin/theme.ts
|
|
12231
12437
|
var FORBIDDEN_CHARS = ["<", ">", "{", "}", '"'];
|
|
@@ -12486,11 +12692,11 @@ function renderFilter(filter) {
|
|
|
12486
12692
|
function renderListPage(context, view) {
|
|
12487
12693
|
const bulk = view.bulkActions.length > 0 && context.session !== null;
|
|
12488
12694
|
const checkColumn = bulk ? 1 : 0;
|
|
12489
|
-
const headers = view.columns.map((
|
|
12490
|
-
const state = view.sort[
|
|
12491
|
-
if (state === void 0) return `<th>${escapeHtml(
|
|
12695
|
+
const headers = view.columns.map((column7) => {
|
|
12696
|
+
const state = view.sort[column7];
|
|
12697
|
+
if (state === void 0) return `<th>${escapeHtml(column7)}</th>`;
|
|
12492
12698
|
const arrow = state.active ? state.ascending ? "\u25B2" : "\u25BC" : "\u2195";
|
|
12493
|
-
return `<th><a class="tempest-sort${state.active ? " tempest-sort--active" : ""}" href="${escapeHtml(state.url)}"><span>${escapeHtml(
|
|
12699
|
+
return `<th><a class="tempest-sort${state.active ? " tempest-sort--active" : ""}" href="${escapeHtml(state.url)}"><span>${escapeHtml(column7)}</span><span class="tempest-sort__arrow" aria-hidden="true">${arrow}</span></a></th>`;
|
|
12494
12700
|
}).join("");
|
|
12495
12701
|
const rows = view.rows.length > 0 ? view.rows.map((row) => {
|
|
12496
12702
|
const check = bulk ? `<td class="tempest-admin-list__check"><input type="checkbox" name="ids" value="${escapeHtml(row.identity)}" data-row-check aria-label="Select row"></td>` : "";
|
|
@@ -12631,7 +12837,7 @@ function renderInline(inline, csrfToken, error) {
|
|
|
12631
12837
|
if (inline.rows.length === 0) {
|
|
12632
12838
|
return `<section class="tempest-admin-inline">${heading}<p><em>No related records.</em></p></section>`;
|
|
12633
12839
|
}
|
|
12634
|
-
const head2 = `<tr>${inline.columns.map((
|
|
12840
|
+
const head2 = `<tr>${inline.columns.map((column7) => `<th>${escapeHtml(column7)}</th>`).join("")}<th></th></tr>`;
|
|
12635
12841
|
const body = inline.rows.map(
|
|
12636
12842
|
(row) => `<tr>${row.cells.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}<td>${row.url === null ? "" : `<a href="${escapeHtml(row.url)}">View</a>`}</td></tr>`
|
|
12637
12843
|
).join("");
|
|
@@ -12646,7 +12852,7 @@ function renderInline(inline, csrfToken, error) {
|
|
|
12646
12852
|
${more}
|
|
12647
12853
|
</section>`;
|
|
12648
12854
|
}
|
|
12649
|
-
const head = `<tr>${inline.columns.map((
|
|
12855
|
+
const head = `<tr>${inline.columns.map((column7) => `<th>${escapeHtml(column7)}</th>`).join("")}${inline.canDelete ? "<th>Delete</th>" : ""}</tr>`;
|
|
12650
12856
|
const renderRow = (row, isNew) => `<tr${isNew ? ' class="tempest-admin-inline__new"' : ""}>${row.fields.map((field) => `<td>${renderInlineCell(field)}</td>`).join("")}${inline.canDelete ? `<td class="tempest-admin-inline__del">${isNew ? "" : `<input type="checkbox" name="row.${escapeHtml(row.key)}.__delete" value="true">`}</td>` : ""}</tr>`;
|
|
12651
12857
|
const rows = inline.rows.map((row) => renderRow(row, false)).join("");
|
|
12652
12858
|
const blank = inline.newRow === null ? "" : renderRow(inline.newRow, true);
|
|
@@ -12817,12 +13023,86 @@ function renderLogsPage(context, view) {
|
|
|
12817
13023
|
</section>`;
|
|
12818
13024
|
return renderLayout(context, `Logs \xB7 ${context.site.title}`, body);
|
|
12819
13025
|
}
|
|
13026
|
+
function renderTasksPage(context, view) {
|
|
13027
|
+
const declared = view.inventory === null ? "" : `<section class="tempest-admin-tasks__declared">
|
|
13028
|
+
<h2>Declared</h2>
|
|
13029
|
+
${view.inventory.length === 0 ? "<p><em>No tasks registered in this process.</em></p>" : `<div class="tempest-admin-table-wrap"><table class="tempest-admin-list__table">
|
|
13030
|
+
<thead><tr><th>Task</th><th>Schedule</th><th>Description</th></tr></thead>
|
|
13031
|
+
<tbody>${view.inventory.map(
|
|
13032
|
+
(entry) => `<tr><td>${escapeHtml(entry.name)}</td><td>${escapeHtml(entry.schedule)}</td><td>${escapeHtml(entry.description)}</td></tr>`
|
|
13033
|
+
).join("")}</tbody>
|
|
13034
|
+
</table></div>`}
|
|
13035
|
+
</section>`;
|
|
13036
|
+
const runs = view.runs === null ? "" : `<section class="tempest-admin-tasks__runs">
|
|
13037
|
+
<h2>Runs</h2>
|
|
13038
|
+
<div class="tempest-admin-list__toolbar">
|
|
13039
|
+
<form method="get" class="tempest-admin-list__filters">
|
|
13040
|
+
<label><span>Status</span><select name="status">${view.runs.statuses.map(
|
|
13041
|
+
(status) => `<option value="${escapeHtml(status.value)}"${status.selected ? " selected" : ""}>${escapeHtml(status.label)}</option>`
|
|
13042
|
+
).join("")}</select></label>
|
|
13043
|
+
<input type="search" name="task" value="${escapeHtml(view.runs.nameQuery)}" placeholder="Task name\u2026" aria-label="Task name">
|
|
13044
|
+
<button type="submit">Apply</button>
|
|
13045
|
+
</form>
|
|
13046
|
+
</div>
|
|
13047
|
+
<div class="tempest-admin-table-wrap"><table class="tempest-admin-list__table">
|
|
13048
|
+
<thead><tr><th>Task</th><th>Status</th><th>Started</th><th>Finished</th><th>Attempts</th><th></th></tr></thead>
|
|
13049
|
+
<tbody>${view.runs.rows.length === 0 ? '<tr><td colspan="6">No runs recorded yet.</td></tr>' : view.runs.rows.map(
|
|
13050
|
+
(row) => `<tr>
|
|
13051
|
+
<td>${escapeHtml(row.name)}</td>
|
|
13052
|
+
<td><span class="tempest-log-badge tempest-log-badge--${escapeHtml(row.status)}">${escapeHtml(row.status)}</span></td>
|
|
13053
|
+
<td>${escapeHtml(row.startedAt)}</td>
|
|
13054
|
+
<td>${escapeHtml(row.finishedAt)}</td>
|
|
13055
|
+
<td>${escapeHtml(row.attempts)}</td>
|
|
13056
|
+
<td><a href="${escapeHtml(row.url)}">View</a></td>
|
|
13057
|
+
</tr>`
|
|
13058
|
+
).join("")}</tbody>
|
|
13059
|
+
</table></div>
|
|
13060
|
+
${view.runs.pages > 1 ? `<nav class="tempest-admin-list__pagination" aria-label="Pagination">
|
|
13061
|
+
${view.runs.prevUrl !== null ? `<a href="${escapeHtml(view.runs.prevUrl)}">\u2190 Prev</a>` : ""}
|
|
13062
|
+
<span>Page ${escapeHtml(view.runs.page)} of ${escapeHtml(view.runs.pages)}</span>
|
|
13063
|
+
${view.runs.nextUrl !== null ? `<a href="${escapeHtml(view.runs.nextUrl)}">Next \u2192</a>` : ""}
|
|
13064
|
+
</nav>` : ""}
|
|
13065
|
+
</section>`;
|
|
13066
|
+
const body = `<section class="tempest-admin-tasks">
|
|
13067
|
+
<header class="tempest-admin-list__header">
|
|
13068
|
+
<h1>Tasks</h1>
|
|
13069
|
+
<p>What this process declares, and what its workers recorded.</p>
|
|
13070
|
+
</header>
|
|
13071
|
+
${declared}
|
|
13072
|
+
${runs}
|
|
13073
|
+
<p class="tempest-admin-form__hint">Live queue depth is not shown: a broker does not expose it, and a number that looked like one would be worse than none.</p>
|
|
13074
|
+
</section>`;
|
|
13075
|
+
return renderLayout(context, `Tasks \xB7 ${context.site.title}`, body);
|
|
13076
|
+
}
|
|
13077
|
+
function renderTaskDetailPage(context, view) {
|
|
13078
|
+
if (context.session === null) throw new Error("The task detail requires a session");
|
|
13079
|
+
const block = (label, content) => content === null ? "" : `<h2>${escapeHtml(label)}</h2><pre class="tempest-admin-detail__json">${escapeHtml(content)}</pre>`;
|
|
13080
|
+
const body = `<section class="tempest-admin-detail">
|
|
13081
|
+
<header class="tempest-admin-detail__header">
|
|
13082
|
+
<h1>${escapeHtml(view.name)} \xB7 <span class="tempest-log-badge tempest-log-badge--${escapeHtml(view.status)}">${escapeHtml(view.status)}</span></h1>
|
|
13083
|
+
<div class="tempest-admin-detail__actions">
|
|
13084
|
+
<a href="${escapeHtml(view.backUrl)}">\u2190 Back to tasks</a>
|
|
13085
|
+
${view.cancelUrl !== null ? `<form method="post" action="${escapeHtml(view.cancelUrl)}" class="tempest-admin-detail__delete" onsubmit="return confirm('Ask this run to stop?');">
|
|
13086
|
+
<input type="hidden" name="csrf_token" value="${escapeHtml(context.session.csrfToken)}">
|
|
13087
|
+
<button type="submit" class="tempest-admin-btn--danger">Cancel</button>
|
|
13088
|
+
</form>` : ""}
|
|
13089
|
+
</div>
|
|
13090
|
+
</header>
|
|
13091
|
+
<dl class="tempest-admin-detail__fields">${view.fields.map(
|
|
13092
|
+
(field) => `<dt>${escapeHtml(field.label)}</dt><dd>${field.value === "" ? "<em>\u2014</em>" : escapeHtml(field.value)}</dd>`
|
|
13093
|
+
).join("")}</dl>
|
|
13094
|
+
${view.error === null ? "" : `<p class="tempest-admin-form__error">${escapeHtml(view.error)}</p>`}
|
|
13095
|
+
${block("Payload", view.payload)}
|
|
13096
|
+
${block("Result", view.result)}
|
|
13097
|
+
</section>`;
|
|
13098
|
+
return renderLayout(context, `${view.name} \xB7 ${context.site.title}`, body);
|
|
13099
|
+
}
|
|
12820
13100
|
function renderSqlPage(context, view) {
|
|
12821
13101
|
if (context.session === null) throw new Error("The SQL console requires a session");
|
|
12822
13102
|
const result = view.rowCount === null ? "" : `<p class="tempest-admin-form__hint">${escapeHtml(view.rowCount)} row${view.rowCount === 1 ? "" : "s"}${view.durationMs === null ? "" : ` in ${escapeHtml(view.durationMs)} ms`}${view.truncated ? " (truncated by the row cap)" : ""}.</p>
|
|
12823
13103
|
<div class="tempest-admin-table-wrap">
|
|
12824
13104
|
<table class="tempest-admin-list__table">
|
|
12825
|
-
<thead><tr>${view.columns.map((
|
|
13105
|
+
<thead><tr>${view.columns.map((column7) => `<th>${escapeHtml(column7)}</th>`).join("")}</tr></thead>
|
|
12826
13106
|
<tbody>${view.rows.map(
|
|
12827
13107
|
(row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`
|
|
12828
13108
|
).join("")}</tbody>
|
|
@@ -12992,6 +13272,15 @@ function makeLogsRouter(options) {
|
|
|
12992
13272
|
return router;
|
|
12993
13273
|
}
|
|
12994
13274
|
var logger2 = new JSONLogger("tempest_express_sdk.admin.router");
|
|
13275
|
+
var JOB_STATUSES = [
|
|
13276
|
+
"queued",
|
|
13277
|
+
"running",
|
|
13278
|
+
"succeeded",
|
|
13279
|
+
"failed",
|
|
13280
|
+
"cancelled"
|
|
13281
|
+
];
|
|
13282
|
+
var TERMINAL_JOB_STATUSES = ["succeeded", "failed", "cancelled"];
|
|
13283
|
+
var TASK_PAGE_SIZE = 25;
|
|
12995
13284
|
var LOG_SOURCES = ["all", "debug", "info", "warning", "error", "500"];
|
|
12996
13285
|
var LOG_PAGE_SIZE = 50;
|
|
12997
13286
|
var LOG_EXPORT_MAX = 500;
|
|
@@ -13033,6 +13322,9 @@ function makeAdminRouter(site, options) {
|
|
|
13033
13322
|
if (options.sqlConsole !== void 0) {
|
|
13034
13323
|
systemNav.push({ label: "SQL console", url: `${prefix}/sql` });
|
|
13035
13324
|
}
|
|
13325
|
+
if (options.tasks !== void 0) {
|
|
13326
|
+
systemNav.push({ label: "Tasks", url: `${prefix}/tasks` });
|
|
13327
|
+
}
|
|
13036
13328
|
const maxUploadBytes = options.maxUploadBytes ?? 10 * 1024 * 1024;
|
|
13037
13329
|
const sessions = new AdminSessionStore({
|
|
13038
13330
|
secret: options.secretKey,
|
|
@@ -13437,7 +13729,7 @@ function makeAdminRouter(site, options) {
|
|
|
13437
13729
|
error: null,
|
|
13438
13730
|
columns,
|
|
13439
13731
|
rows: window.map(
|
|
13440
|
-
(row) => columns.map((
|
|
13732
|
+
(row) => columns.map((column7) => formatCellValue(row[column7]))
|
|
13441
13733
|
),
|
|
13442
13734
|
rowCount: rows.length,
|
|
13443
13735
|
truncated: rows.length > window.length,
|
|
@@ -13446,6 +13738,115 @@ function makeAdminRouter(site, options) {
|
|
|
13446
13738
|
})
|
|
13447
13739
|
);
|
|
13448
13740
|
}
|
|
13741
|
+
if (options.tasks !== void 0) {
|
|
13742
|
+
const tasks = options.tasks;
|
|
13743
|
+
router.get(
|
|
13744
|
+
`${prefix}/tasks`,
|
|
13745
|
+
guarded(async (req, res) => {
|
|
13746
|
+
const state = await authenticate(req, res);
|
|
13747
|
+
if (state === null) return;
|
|
13748
|
+
const inventory = tasks.manager === void 0 ? null : tasks.manager.inventory().map((entry) => ({
|
|
13749
|
+
name: entry.name,
|
|
13750
|
+
description: entry.description ?? "",
|
|
13751
|
+
schedule: entry.schedule ?? "\u2014"
|
|
13752
|
+
}));
|
|
13753
|
+
let runs = null;
|
|
13754
|
+
if (tasks.jobs !== void 0) {
|
|
13755
|
+
const store = tasks.jobs(state.dbSession);
|
|
13756
|
+
const page2 = Math.max(1, Number.parseInt(queryString(req.query.page), 10) || 1);
|
|
13757
|
+
const status = queryString(req.query.status);
|
|
13758
|
+
const name = queryString(req.query.task);
|
|
13759
|
+
const result = await store.list({
|
|
13760
|
+
page: page2,
|
|
13761
|
+
pageSize: TASK_PAGE_SIZE,
|
|
13762
|
+
...name === "" ? {} : { name },
|
|
13763
|
+
...JOB_STATUSES.includes(status) ? { status } : {}
|
|
13764
|
+
});
|
|
13765
|
+
const pageUrl = (target) => `?${buildQuery({ status, task: name, page: target })}`;
|
|
13766
|
+
runs = {
|
|
13767
|
+
rows: result.items.map((row) => ({
|
|
13768
|
+
id: String(row.id),
|
|
13769
|
+
name: String(row.name ?? ""),
|
|
13770
|
+
status: String(row.status ?? ""),
|
|
13771
|
+
startedAt: formatCellValue(row.startedAt),
|
|
13772
|
+
finishedAt: formatCellValue(row.finishedAt),
|
|
13773
|
+
attempts: formatCellValue(row.attempts),
|
|
13774
|
+
url: `${prefix}/tasks/${String(row.id)}`
|
|
13775
|
+
})),
|
|
13776
|
+
total: result.total,
|
|
13777
|
+
page: result.page,
|
|
13778
|
+
pages: result.pages,
|
|
13779
|
+
prevUrl: result.page > 1 ? pageUrl(result.page - 1) : null,
|
|
13780
|
+
nextUrl: result.page < result.pages ? pageUrl(result.page + 1) : null,
|
|
13781
|
+
statuses: ["", ...JOB_STATUSES].map((value) => ({
|
|
13782
|
+
value,
|
|
13783
|
+
label: value === "" ? "\u2014 any \u2014" : humanizeField(value),
|
|
13784
|
+
selected: value === status
|
|
13785
|
+
})),
|
|
13786
|
+
nameQuery: name
|
|
13787
|
+
};
|
|
13788
|
+
}
|
|
13789
|
+
html(
|
|
13790
|
+
res,
|
|
13791
|
+
renderTasksPage(context(req, state.session, state.visible), {
|
|
13792
|
+
inventory,
|
|
13793
|
+
runs
|
|
13794
|
+
})
|
|
13795
|
+
);
|
|
13796
|
+
})
|
|
13797
|
+
);
|
|
13798
|
+
if (tasks.jobs !== void 0) {
|
|
13799
|
+
const jobs = tasks.jobs;
|
|
13800
|
+
router.get(
|
|
13801
|
+
`${prefix}/tasks/:id`,
|
|
13802
|
+
guarded(async (req, res) => {
|
|
13803
|
+
const state = await authenticate(req, res);
|
|
13804
|
+
if (state === null) return;
|
|
13805
|
+
const row = await jobs(state.dbSession).get(String(req.params.id));
|
|
13806
|
+
if (row === null) {
|
|
13807
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
13808
|
+
return;
|
|
13809
|
+
}
|
|
13810
|
+
const status = String(row.status ?? "");
|
|
13811
|
+
html(
|
|
13812
|
+
res,
|
|
13813
|
+
renderTaskDetailPage(context(req, state.session, state.visible), {
|
|
13814
|
+
id: String(row.id),
|
|
13815
|
+
name: String(row.name ?? ""),
|
|
13816
|
+
status,
|
|
13817
|
+
fields: [
|
|
13818
|
+
{ label: "Created At", value: formatCellValue(row.createdAt) },
|
|
13819
|
+
{ label: "Started At", value: formatCellValue(row.startedAt) },
|
|
13820
|
+
{ label: "Finished At", value: formatCellValue(row.finishedAt) },
|
|
13821
|
+
{ label: "Attempts", value: formatCellValue(row.attempts) }
|
|
13822
|
+
],
|
|
13823
|
+
payload: jsonBlock(row.payload),
|
|
13824
|
+
result: jsonBlock(row.result),
|
|
13825
|
+
error: typeof row.error === "string" && row.error !== "" ? row.error : null,
|
|
13826
|
+
backUrl: `${prefix}/tasks`,
|
|
13827
|
+
cancelUrl: TERMINAL_JOB_STATUSES.includes(status) ? null : `${prefix}/tasks/${String(row.id)}/cancel`
|
|
13828
|
+
})
|
|
13829
|
+
);
|
|
13830
|
+
})
|
|
13831
|
+
);
|
|
13832
|
+
router.post(
|
|
13833
|
+
`${prefix}/tasks/:id/cancel`,
|
|
13834
|
+
guarded(async (req, res) => {
|
|
13835
|
+
const state = await authenticate(req, res);
|
|
13836
|
+
if (state === null) return;
|
|
13837
|
+
if (!checkCsrf(req, res, state)) return;
|
|
13838
|
+
const id = String(req.params.id);
|
|
13839
|
+
const cancelled = await jobs(state.dbSession).cancel(id);
|
|
13840
|
+
res.redirect(
|
|
13841
|
+
`${prefix}/tasks/${id}?${buildQuery({
|
|
13842
|
+
flash: cancelled ? "The run was asked to stop." : "That run had already finished.",
|
|
13843
|
+
level: cancelled ? "success" : "warning"
|
|
13844
|
+
})}`
|
|
13845
|
+
);
|
|
13846
|
+
})
|
|
13847
|
+
);
|
|
13848
|
+
}
|
|
13849
|
+
}
|
|
13449
13850
|
router.get(
|
|
13450
13851
|
`${prefix}/m/:slug`,
|
|
13451
13852
|
guarded(async (req, res) => {
|
|
@@ -13756,8 +14157,8 @@ function makeAdminRouter(site, options) {
|
|
|
13756
14157
|
res.status(404).json({ options: [] });
|
|
13757
14158
|
return;
|
|
13758
14159
|
}
|
|
13759
|
-
const
|
|
13760
|
-
const table =
|
|
14160
|
+
const column7 = adminColumns(admin.model)[field];
|
|
14161
|
+
const table = column7 === void 0 ? null : foreignKeyTable(column7);
|
|
13761
14162
|
const referenced = table === null ? null : site.get(table);
|
|
13762
14163
|
if (referenced === null) {
|
|
13763
14164
|
res.json({ options: [] });
|
|
@@ -14066,7 +14467,7 @@ function makeAdminRouter(site, options) {
|
|
|
14066
14467
|
formAction: "",
|
|
14067
14468
|
rows: visible.map((child) => ({
|
|
14068
14469
|
key: String(child[childAdmin?.identityField ?? "id"]),
|
|
14069
|
-
cells: columns.map((
|
|
14470
|
+
cells: columns.map((column7) => formatCellValue(child[column7])),
|
|
14070
14471
|
fields: [],
|
|
14071
14472
|
url: childAdmin === null ? null : `${prefix}/m/${inline.slug}/${String(child[childAdmin.identityField])}`
|
|
14072
14473
|
})),
|
|
@@ -14198,8 +14599,8 @@ function makeAdminRouter(site, options) {
|
|
|
14198
14599
|
for (const field of admin.uploadFields) {
|
|
14199
14600
|
const file = files.find((candidate) => candidate.field === field);
|
|
14200
14601
|
if (file === void 0) {
|
|
14201
|
-
const
|
|
14202
|
-
if (creating &&
|
|
14602
|
+
const column7 = columns[field];
|
|
14603
|
+
if (creating && column7 !== void 0 && !isColumnOptional(column7)) {
|
|
14203
14604
|
errors[field] = "This field is required.";
|
|
14204
14605
|
}
|
|
14205
14606
|
continue;
|
|
@@ -14231,14 +14632,14 @@ function makeAdminRouter(site, options) {
|
|
|
14231
14632
|
});
|
|
14232
14633
|
const displayed = admin.listDisplayNames();
|
|
14233
14634
|
const sort = {};
|
|
14234
|
-
for (const
|
|
14235
|
-
if (!(
|
|
14236
|
-
const active = (query.sortColumn ?? admin.orderKey) ===
|
|
14635
|
+
for (const column7 of displayed) {
|
|
14636
|
+
if (!(column7 in columns)) continue;
|
|
14637
|
+
const active = (query.sortColumn ?? admin.orderKey) === column7;
|
|
14237
14638
|
const nextAscending = active ? !query.ascending : true;
|
|
14238
|
-
sort[
|
|
14639
|
+
sort[column7] = {
|
|
14239
14640
|
url: `?${buildQuery({
|
|
14240
14641
|
...query.baseQuery,
|
|
14241
|
-
sort:
|
|
14642
|
+
sort: column7,
|
|
14242
14643
|
dir: nextAscending ? "asc" : "desc"
|
|
14243
14644
|
})}`,
|
|
14244
14645
|
active,
|
|
@@ -14259,7 +14660,7 @@ function makeAdminRouter(site, options) {
|
|
|
14259
14660
|
const identity = String(row[admin.identityField]);
|
|
14260
14661
|
return {
|
|
14261
14662
|
identity,
|
|
14262
|
-
cells: displayed.map((
|
|
14663
|
+
cells: displayed.map((column7) => formatCellValue(row[column7])),
|
|
14263
14664
|
url: `${prefix}/m/${admin.slug()}/${identity}`
|
|
14264
14665
|
};
|
|
14265
14666
|
}),
|
|
@@ -14305,9 +14706,9 @@ function makeAdminRouter(site, options) {
|
|
|
14305
14706
|
}
|
|
14306
14707
|
const filterViews = [];
|
|
14307
14708
|
for (const field of admin.listFilter) {
|
|
14308
|
-
const
|
|
14309
|
-
if (
|
|
14310
|
-
const spec = filterForColumn(
|
|
14709
|
+
const column7 = columns[field];
|
|
14710
|
+
if (column7 === void 0) continue;
|
|
14711
|
+
const spec = filterForColumn(column7);
|
|
14311
14712
|
if (spec.kind === "daterange") {
|
|
14312
14713
|
const from = queryString(req.query[`filter_${field}_from`]);
|
|
14313
14714
|
const to = queryString(req.query[`filter_${field}_to`]);
|
|
@@ -14326,12 +14727,12 @@ function makeAdminRouter(site, options) {
|
|
|
14326
14727
|
});
|
|
14327
14728
|
continue;
|
|
14328
14729
|
}
|
|
14329
|
-
const related = await relatedOptions(
|
|
14730
|
+
const related = await relatedOptions(column7, dbSession);
|
|
14330
14731
|
const options2 = related ?? spec.options;
|
|
14331
14732
|
const value = queryString(req.query[`filter_${field}`]);
|
|
14332
14733
|
if (value !== "") {
|
|
14333
14734
|
conditions.push({
|
|
14334
|
-
[field]:
|
|
14735
|
+
[field]: column7.type.kind === "boolean" ? value === "true" : value
|
|
14335
14736
|
});
|
|
14336
14737
|
}
|
|
14337
14738
|
filterViews.push({
|
|
@@ -14349,8 +14750,8 @@ function makeAdminRouter(site, options) {
|
|
|
14349
14750
|
});
|
|
14350
14751
|
}
|
|
14351
14752
|
const searchable = admin.searchFields.filter((field) => {
|
|
14352
|
-
const
|
|
14353
|
-
return
|
|
14753
|
+
const column7 = columns[field];
|
|
14754
|
+
return column7 !== void 0 && isSearchableColumn(column7);
|
|
14354
14755
|
});
|
|
14355
14756
|
if (search !== "" && searchable.length > 0) {
|
|
14356
14757
|
conditions.push(
|
|
@@ -14383,8 +14784,8 @@ function makeAdminRouter(site, options) {
|
|
|
14383
14784
|
lens: lens?.slug ?? ""
|
|
14384
14785
|
};
|
|
14385
14786
|
}
|
|
14386
|
-
async function relatedOptions(
|
|
14387
|
-
const table = foreignKeyTable(
|
|
14787
|
+
async function relatedOptions(column7, dbSession) {
|
|
14788
|
+
const table = foreignKeyTable(column7);
|
|
14388
14789
|
if (table === null) return null;
|
|
14389
14790
|
const referenced = site.get(table);
|
|
14390
14791
|
if (referenced === null) return null;
|
|
@@ -14399,9 +14800,9 @@ function makeAdminRouter(site, options) {
|
|
|
14399
14800
|
const options2 = {};
|
|
14400
14801
|
for (const field of Object.keys(foreignKeyFields(admin))) {
|
|
14401
14802
|
if (admin.autocompleteFields.includes(field)) continue;
|
|
14402
|
-
const
|
|
14403
|
-
if (
|
|
14404
|
-
const related = await relatedOptions(
|
|
14803
|
+
const column7 = columns[field];
|
|
14804
|
+
if (column7 === void 0) continue;
|
|
14805
|
+
const related = await relatedOptions(column7, dbSession);
|
|
14405
14806
|
if (related !== null) options2[field] = related;
|
|
14406
14807
|
}
|
|
14407
14808
|
return options2;
|
|
@@ -14420,8 +14821,8 @@ function makeAdminRouter(site, options) {
|
|
|
14420
14821
|
for (const field of admin.autocompleteFields) {
|
|
14421
14822
|
const value = row[field];
|
|
14422
14823
|
if (value === null || value === void 0 || value === "") continue;
|
|
14423
|
-
const
|
|
14424
|
-
const table =
|
|
14824
|
+
const column7 = columns[field];
|
|
14825
|
+
const table = column7 === void 0 ? null : foreignKeyTable(column7);
|
|
14425
14826
|
const referenced = table === null ? null : site.get(table);
|
|
14426
14827
|
if (referenced === null) continue;
|
|
14427
14828
|
const related = await referenced.repository(dbSession).first({ [referenced.identityField]: value });
|
|
@@ -14460,6 +14861,11 @@ function inlineFieldNames(childAdmin, inline) {
|
|
|
14460
14861
|
function inlineFields(childAdmin, names, key, values, errors) {
|
|
14461
14862
|
return buildFormFields(childAdmin, { values, errors }).filter((field) => names.includes(field.name)).map((field) => ({ ...field, name: `row.${key}.${field.name}` }));
|
|
14462
14863
|
}
|
|
14864
|
+
function jsonBlock(value) {
|
|
14865
|
+
if (value === null || value === void 0) return null;
|
|
14866
|
+
if (typeof value === "object" && Object.keys(value).length === 0) return null;
|
|
14867
|
+
return JSON.stringify(value, null, 2);
|
|
14868
|
+
}
|
|
14463
14869
|
async function audit(hook, entry) {
|
|
14464
14870
|
if (hook === void 0) return;
|
|
14465
14871
|
try {
|
|
@@ -14623,7 +15029,7 @@ function csvField(value) {
|
|
|
14623
15029
|
function toCsv(columns, rows) {
|
|
14624
15030
|
const lines = [columns.map(csvField).join(",")];
|
|
14625
15031
|
for (const row of rows) {
|
|
14626
|
-
lines.push(columns.map((
|
|
15032
|
+
lines.push(columns.map((column7) => csvField(exportValue(row[column7]))).join(","));
|
|
14627
15033
|
}
|
|
14628
15034
|
return `${lines.join("\r\n")}\r
|
|
14629
15035
|
`;
|
|
@@ -14631,7 +15037,7 @@ function toCsv(columns, rows) {
|
|
|
14631
15037
|
function toJson(columns, rows) {
|
|
14632
15038
|
return JSON.stringify(
|
|
14633
15039
|
rows.map(
|
|
14634
|
-
(row) => Object.fromEntries(columns.map((
|
|
15040
|
+
(row) => Object.fromEntries(columns.map((column7) => [column7, exportValue(row[column7])]))
|
|
14635
15041
|
),
|
|
14636
15042
|
null,
|
|
14637
15043
|
2
|
|
@@ -16588,7 +16994,7 @@ async function withTestDatabase(models, fn) {
|
|
|
16588
16994
|
}
|
|
16589
16995
|
|
|
16590
16996
|
// src/version.ts
|
|
16591
|
-
var VERSION = "0.
|
|
16997
|
+
var VERSION = "0.30.0";
|
|
16592
16998
|
|
|
16593
16999
|
Object.defineProperty(exports, "OpenAPIRegistry", {
|
|
16594
17000
|
enumerable: true,
|
|
@@ -16754,6 +17160,7 @@ exports.AttemptThrottle = AttemptThrottle;
|
|
|
16754
17160
|
exports.AuditAction = AuditAction;
|
|
16755
17161
|
exports.BaseAuditLogModel = BaseAuditLogModel;
|
|
16756
17162
|
exports.BaseController = BaseController;
|
|
17163
|
+
exports.BaseJobModel = BaseJobModel;
|
|
16757
17164
|
exports.BaseModel = BaseModel;
|
|
16758
17165
|
exports.BaseOAuthClient = BaseOAuthClient;
|
|
16759
17166
|
exports.BaseOutboxModel = BaseOutboxModel;
|
|
@@ -16789,6 +17196,8 @@ exports.IDEMPOTENCY_HEADER = IDEMPOTENCY_HEADER;
|
|
|
16789
17196
|
exports.InvalidTokenException = InvalidTokenException;
|
|
16790
17197
|
exports.JSONLogger = JSONLogger;
|
|
16791
17198
|
exports.JWTUtils = JWTUtils;
|
|
17199
|
+
exports.JobStatus = JobStatus;
|
|
17200
|
+
exports.JobStore = JobStore;
|
|
16792
17201
|
exports.LEVEL_LOG_FILES = LEVEL_LOG_FILES;
|
|
16793
17202
|
exports.LocalUploadStorage = LocalUploadStorage;
|
|
16794
17203
|
exports.MemoryBroker = MemoryBroker;
|
|
@@ -17017,6 +17426,8 @@ exports.renderLogsPage = renderLogsPage;
|
|
|
17017
17426
|
exports.renderMfaPage = renderMfaPage;
|
|
17018
17427
|
exports.renderPasswordResetFormPage = renderPasswordResetFormPage;
|
|
17019
17428
|
exports.renderSqlPage = renderSqlPage;
|
|
17429
|
+
exports.renderTaskDetailPage = renderTaskDetailPage;
|
|
17430
|
+
exports.renderTasksPage = renderTasksPage;
|
|
17020
17431
|
exports.requestIdMiddleware = requestIdMiddleware;
|
|
17021
17432
|
exports.requestTracingMiddleware = requestTracingMiddleware;
|
|
17022
17433
|
exports.requireRoles = requireRoles;
|