tempest-express-sdk 0.28.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-3NS5KVHT.js → chunk-DK46733U.js} +3 -3
- package/dist/{chunk-3NS5KVHT.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 +1033 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +621 -4
- package/dist/index.d.ts +621 -4
- package/dist/index.js +1019 -123
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z, looseBoolean, toDict, PasswordUtils } from './chunk-
|
|
2
|
-
export { PasswordUtils, VERSION, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, centsField, corsSettingsShape, databaseSettingsShape, looseBoolean as envBoolean, hexColorField, latitudeField, loadSettings, longitudeField, looseBoolean, nonEmptyStrField, nonNegativeFloatField, nonNegativeIntField, percentField, portField, positiveFloatField, positiveIntField, priceField, ratingField, ratioField, serverSettingsShape, slugField, toDict, z } from './chunk-
|
|
1
|
+
import { z, looseBoolean, toDict, PasswordUtils } from './chunk-DK46733U.js';
|
|
2
|
+
export { PasswordUtils, VERSION, baseAppSettingsSchema, baseAppSettingsShape, baseResponseSchema, centsField, corsSettingsShape, databaseSettingsShape, looseBoolean as envBoolean, hexColorField, latitudeField, loadSettings, longitudeField, looseBoolean, nonEmptyStrField, nonNegativeFloatField, nonNegativeIntField, percentField, portField, positiveFloatField, positiveIntField, priceField, ratingField, ratioField, serverSettingsShape, slugField, toDict, z } from './chunk-DK46733U.js';
|
|
3
3
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
4
4
|
import { Model, column, sql, BaseRepository, RecordNotFound, detectDialect, columnsOf, NodeSqliteDriver, AsyncEngine, or, and } from 'tempest-db-js';
|
|
5
5
|
export { AsyncEngine, AsyncResult, AsyncSession, BaseRepository, Column, DeleteBuilder, InsertBuilder, Model, NoResultError, NodeSqliteDriver, PostgresDialect, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncSession, UpdateBuilder, and, belongsTo, column, columnsOf, createEngine, createSyncEngine, del, detectDialect, getDialect, hasMany, insert, join, loadRelations, not, or, parseDatabaseUrl, select, sql, update } from 'tempest-db-js';
|
|
@@ -8546,6 +8546,7 @@ var TaskManager = class {
|
|
|
8546
8546
|
broker;
|
|
8547
8547
|
queue;
|
|
8548
8548
|
handlers = /* @__PURE__ */ new Map();
|
|
8549
|
+
metadata = /* @__PURE__ */ new Map();
|
|
8549
8550
|
unsubscribe = null;
|
|
8550
8551
|
/**
|
|
8551
8552
|
* @param options - Broker and queue name.
|
|
@@ -8559,9 +8560,32 @@ var TaskManager = class {
|
|
|
8559
8560
|
*
|
|
8560
8561
|
* @param name - The task name.
|
|
8561
8562
|
* @param handler - The handler invoked with the task payload.
|
|
8563
|
+
* @param options - Description and declared schedule, surfaced by
|
|
8564
|
+
* {@link TaskManager.inventory} and by the admin panel's tasks page.
|
|
8562
8565
|
*/
|
|
8563
|
-
register(name, handler) {
|
|
8566
|
+
register(name, handler, options = {}) {
|
|
8564
8567
|
this.handlers.set(name, handler);
|
|
8568
|
+
this.metadata.set(name, options);
|
|
8569
|
+
}
|
|
8570
|
+
/**
|
|
8571
|
+
* Return what this process would run, ordered by name.
|
|
8572
|
+
*
|
|
8573
|
+
* This is the **declared** side of background work — the handlers this
|
|
8574
|
+
* process knows about — not queue state. A broker's pending depth is not
|
|
8575
|
+
* something the manager can see, and a screen that implied otherwise would
|
|
8576
|
+
* be worse than one that says nothing.
|
|
8577
|
+
*
|
|
8578
|
+
* @returns One entry per registered task.
|
|
8579
|
+
*/
|
|
8580
|
+
inventory() {
|
|
8581
|
+
return [...this.handlers.keys()].sort((left, right) => left.localeCompare(right)).map((name) => {
|
|
8582
|
+
const meta = this.metadata.get(name) ?? {};
|
|
8583
|
+
return {
|
|
8584
|
+
name,
|
|
8585
|
+
description: meta.description ?? null,
|
|
8586
|
+
schedule: meta.schedule ?? null
|
|
8587
|
+
};
|
|
8588
|
+
});
|
|
8565
8589
|
}
|
|
8566
8590
|
/**
|
|
8567
8591
|
* Enqueue a task by name.
|
|
@@ -8595,6 +8619,180 @@ var TaskManager = class {
|
|
|
8595
8619
|
this.unsubscribe = null;
|
|
8596
8620
|
}
|
|
8597
8621
|
};
|
|
8622
|
+
var JobStatus = {
|
|
8623
|
+
/** Written, not started. */
|
|
8624
|
+
QUEUED: "queued",
|
|
8625
|
+
/** A worker picked it up. */
|
|
8626
|
+
RUNNING: "running",
|
|
8627
|
+
/** Finished cleanly. */
|
|
8628
|
+
SUCCEEDED: "succeeded",
|
|
8629
|
+
/** Finished with an error. */
|
|
8630
|
+
FAILED: "failed",
|
|
8631
|
+
/** An operator stopped it before it finished. */
|
|
8632
|
+
CANCELLED: "cancelled"
|
|
8633
|
+
};
|
|
8634
|
+
var TERMINAL = [
|
|
8635
|
+
JobStatus.SUCCEEDED,
|
|
8636
|
+
JobStatus.FAILED,
|
|
8637
|
+
JobStatus.CANCELLED
|
|
8638
|
+
];
|
|
8639
|
+
var BaseJobModel = class extends BaseModel {
|
|
8640
|
+
/** The task name this run belongs to. */
|
|
8641
|
+
name = column.varchar(128).notNull();
|
|
8642
|
+
/** Lifecycle state — a {@link JobStatus} value. */
|
|
8643
|
+
status = column.varchar(16).notNull().default(JobStatus.QUEUED);
|
|
8644
|
+
/** The payload the run was started with. */
|
|
8645
|
+
payload = column.json();
|
|
8646
|
+
/** Whatever the run produced, for an operator to read afterwards. */
|
|
8647
|
+
result = column.json();
|
|
8648
|
+
/** The failure message, when the run failed. */
|
|
8649
|
+
error = column.text();
|
|
8650
|
+
/** How many times the run has been attempted. */
|
|
8651
|
+
attempts = column.integer().notNull().default(0);
|
|
8652
|
+
/** When a worker picked it up. */
|
|
8653
|
+
startedAt = column.datetime();
|
|
8654
|
+
/** When it reached a terminal state. */
|
|
8655
|
+
finishedAt = column.datetime();
|
|
8656
|
+
};
|
|
8657
|
+
var JobStore = class {
|
|
8658
|
+
/**
|
|
8659
|
+
* @param model - The concrete {@link BaseJobModel} subclass.
|
|
8660
|
+
* @param session - The session rows are written on.
|
|
8661
|
+
*/
|
|
8662
|
+
constructor(model, session) {
|
|
8663
|
+
this.model = model;
|
|
8664
|
+
this.repository = new BaseRepository(model, session);
|
|
8665
|
+
}
|
|
8666
|
+
model;
|
|
8667
|
+
repository;
|
|
8668
|
+
/**
|
|
8669
|
+
* Record a job about to run.
|
|
8670
|
+
*
|
|
8671
|
+
* @param name - The task name.
|
|
8672
|
+
* @param payload - The payload the run was started with.
|
|
8673
|
+
* @returns The created row.
|
|
8674
|
+
*/
|
|
8675
|
+
async enqueue(name, payload = {}) {
|
|
8676
|
+
return await this.repository.create({
|
|
8677
|
+
name,
|
|
8678
|
+
status: JobStatus.QUEUED,
|
|
8679
|
+
payload,
|
|
8680
|
+
result: null,
|
|
8681
|
+
error: null,
|
|
8682
|
+
attempts: 0,
|
|
8683
|
+
startedAt: null,
|
|
8684
|
+
finishedAt: null
|
|
8685
|
+
});
|
|
8686
|
+
}
|
|
8687
|
+
/**
|
|
8688
|
+
* Mark a job as picked up, counting the attempt.
|
|
8689
|
+
*
|
|
8690
|
+
* @param id - The job id.
|
|
8691
|
+
* @param attempt - Which attempt this is. Default `1`.
|
|
8692
|
+
* @returns How many rows changed.
|
|
8693
|
+
*/
|
|
8694
|
+
async start(id, attempt = 1) {
|
|
8695
|
+
return await this.repository.update(
|
|
8696
|
+
{ id },
|
|
8697
|
+
{
|
|
8698
|
+
status: JobStatus.RUNNING,
|
|
8699
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
8700
|
+
attempts: attempt
|
|
8701
|
+
}
|
|
8702
|
+
);
|
|
8703
|
+
}
|
|
8704
|
+
/**
|
|
8705
|
+
* Mark a job as finished cleanly.
|
|
8706
|
+
*
|
|
8707
|
+
* @param id - The job id.
|
|
8708
|
+
* @param result - Whatever the run produced.
|
|
8709
|
+
* @returns How many rows changed.
|
|
8710
|
+
*/
|
|
8711
|
+
async succeed(id, result = {}) {
|
|
8712
|
+
return await this.repository.update(
|
|
8713
|
+
{ id },
|
|
8714
|
+
{
|
|
8715
|
+
status: JobStatus.SUCCEEDED,
|
|
8716
|
+
result,
|
|
8717
|
+
error: null,
|
|
8718
|
+
finishedAt: /* @__PURE__ */ new Date()
|
|
8719
|
+
}
|
|
8720
|
+
);
|
|
8721
|
+
}
|
|
8722
|
+
/**
|
|
8723
|
+
* Mark a job as failed.
|
|
8724
|
+
*
|
|
8725
|
+
* @param id - The job id.
|
|
8726
|
+
* @param error - The failure, as an `Error` or a message.
|
|
8727
|
+
* @returns How many rows changed.
|
|
8728
|
+
*/
|
|
8729
|
+
async fail(id, error) {
|
|
8730
|
+
return await this.repository.update(
|
|
8731
|
+
{ id },
|
|
8732
|
+
{
|
|
8733
|
+
status: JobStatus.FAILED,
|
|
8734
|
+
error: error instanceof Error ? error.message : String(error),
|
|
8735
|
+
finishedAt: /* @__PURE__ */ new Date()
|
|
8736
|
+
}
|
|
8737
|
+
);
|
|
8738
|
+
}
|
|
8739
|
+
/**
|
|
8740
|
+
* Ask a job to stop.
|
|
8741
|
+
*
|
|
8742
|
+
* A job already in a terminal state is left alone and reported as not
|
|
8743
|
+
* cancelled, so an operator clicking cancel on a run that just finished sees
|
|
8744
|
+
* the truth rather than a row rewritten under them.
|
|
8745
|
+
*
|
|
8746
|
+
* @param id - The job id.
|
|
8747
|
+
* @returns Whether the row moved to `cancelled`.
|
|
8748
|
+
*/
|
|
8749
|
+
async cancel(id) {
|
|
8750
|
+
const row = await this.repository.getByIdOrNull(id);
|
|
8751
|
+
if (row === null || TERMINAL.includes(String(row.status))) return false;
|
|
8752
|
+
const changed = await this.repository.update(
|
|
8753
|
+
{ id },
|
|
8754
|
+
{
|
|
8755
|
+
status: JobStatus.CANCELLED,
|
|
8756
|
+
finishedAt: /* @__PURE__ */ new Date()
|
|
8757
|
+
}
|
|
8758
|
+
);
|
|
8759
|
+
return changed > 0;
|
|
8760
|
+
}
|
|
8761
|
+
/**
|
|
8762
|
+
* Read one job row.
|
|
8763
|
+
*
|
|
8764
|
+
* @param id - The job id.
|
|
8765
|
+
* @returns The row, or `null`.
|
|
8766
|
+
*/
|
|
8767
|
+
async get(id) {
|
|
8768
|
+
return await this.repository.getByIdOrNull(id);
|
|
8769
|
+
}
|
|
8770
|
+
/**
|
|
8771
|
+
* Read a page of jobs, newest first.
|
|
8772
|
+
*
|
|
8773
|
+
* @param filter - Page, size and optional `name` / `status` filters.
|
|
8774
|
+
* @returns The page plus its metadata.
|
|
8775
|
+
*/
|
|
8776
|
+
async list(filter = {}) {
|
|
8777
|
+
const filters = {};
|
|
8778
|
+
if (filter.name !== void 0 && filter.name !== "") filters.name = filter.name;
|
|
8779
|
+
if (filter.status !== void 0) filters.status = filter.status;
|
|
8780
|
+
const result = await this.repository.paginate({
|
|
8781
|
+
page: filter.page ?? 1,
|
|
8782
|
+
pageSize: filter.pageSize ?? 25,
|
|
8783
|
+
orderBy: "createdAt",
|
|
8784
|
+
ascending: false,
|
|
8785
|
+
...Object.keys(filters).length === 0 ? {} : { filters }
|
|
8786
|
+
});
|
|
8787
|
+
return {
|
|
8788
|
+
items: result.items,
|
|
8789
|
+
total: result.total,
|
|
8790
|
+
page: result.page,
|
|
8791
|
+
pageSize: result.pageSize,
|
|
8792
|
+
pages: result.pages
|
|
8793
|
+
};
|
|
8794
|
+
}
|
|
8795
|
+
};
|
|
8598
8796
|
|
|
8599
8797
|
// src/flags/backends.ts
|
|
8600
8798
|
function coerceFlag(value) {
|
|
@@ -9334,6 +9532,199 @@ function partitionTotal(partition) {
|
|
|
9334
9532
|
return partition.segments.reduce((total, segment) => total + segment.value, 0);
|
|
9335
9533
|
}
|
|
9336
9534
|
|
|
9535
|
+
// src/admin/logs.ts
|
|
9536
|
+
var LIFTED = /* @__PURE__ */ new Set(["level", "logger", "message", "timestamp", "stack"]);
|
|
9537
|
+
function toLogEntry(raw) {
|
|
9538
|
+
const context = {};
|
|
9539
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
9540
|
+
if (LIFTED.has(key) || value === null || value === void 0) continue;
|
|
9541
|
+
context[key] = value;
|
|
9542
|
+
}
|
|
9543
|
+
return {
|
|
9544
|
+
level: typeof raw.level === "string" ? raw.level : "info",
|
|
9545
|
+
logger: typeof raw.logger === "string" ? raw.logger : "",
|
|
9546
|
+
message: typeof raw.message === "string" ? raw.message : "",
|
|
9547
|
+
timestamp: typeof raw.timestamp === "string" ? raw.timestamp : "",
|
|
9548
|
+
stack: typeof raw.stack === "string" ? raw.stack : null,
|
|
9549
|
+
context,
|
|
9550
|
+
raw
|
|
9551
|
+
};
|
|
9552
|
+
}
|
|
9553
|
+
function filterLogEntries(entries, term) {
|
|
9554
|
+
const needle = term.trim().toLowerCase();
|
|
9555
|
+
if (needle === "") return entries;
|
|
9556
|
+
return entries.filter(
|
|
9557
|
+
(entry) => `${entry.message} ${entry.logger} ${entry.stack ?? ""}`.toLowerCase().includes(needle)
|
|
9558
|
+
);
|
|
9559
|
+
}
|
|
9560
|
+
function renderLogEntriesMarkdown(entries, options) {
|
|
9561
|
+
const lines = [
|
|
9562
|
+
"# Application logs",
|
|
9563
|
+
"",
|
|
9564
|
+
`- **Source:** \`${options.source}\``,
|
|
9565
|
+
`- **Search:** ${options.query === "" ? "_none_" : `\`${options.query}\``}`,
|
|
9566
|
+
`- **Exported:** ${entries.length} of ${options.total} matching record(s)`,
|
|
9567
|
+
""
|
|
9568
|
+
];
|
|
9569
|
+
if (entries.length < options.total) {
|
|
9570
|
+
lines.push(
|
|
9571
|
+
`> Truncated: the export is capped, so ${options.total - entries.length} older matching record(s) are not included.`,
|
|
9572
|
+
""
|
|
9573
|
+
);
|
|
9574
|
+
}
|
|
9575
|
+
for (const entry of entries) {
|
|
9576
|
+
lines.push(
|
|
9577
|
+
`## ${entry.level.toUpperCase()} \u2014 ${entry.message || "(no message)"}`,
|
|
9578
|
+
"",
|
|
9579
|
+
`- **When:** ${entry.timestamp || "unknown"}`,
|
|
9580
|
+
`- **Logger:** ${entry.logger || "unknown"}`
|
|
9581
|
+
);
|
|
9582
|
+
for (const [key, value] of Object.entries(entry.context)) {
|
|
9583
|
+
lines.push(
|
|
9584
|
+
`- **${key}:** ${typeof value === "string" ? value : JSON.stringify(value)}`
|
|
9585
|
+
);
|
|
9586
|
+
}
|
|
9587
|
+
lines.push("");
|
|
9588
|
+
if (entry.stack !== null) {
|
|
9589
|
+
lines.push("```text", entry.stack, "```", "");
|
|
9590
|
+
}
|
|
9591
|
+
}
|
|
9592
|
+
return lines.join("\n");
|
|
9593
|
+
}
|
|
9594
|
+
function renderLogEntriesJson(entries) {
|
|
9595
|
+
return JSON.stringify(
|
|
9596
|
+
entries.map((entry) => entry.raw),
|
|
9597
|
+
null,
|
|
9598
|
+
2
|
|
9599
|
+
);
|
|
9600
|
+
}
|
|
9601
|
+
|
|
9602
|
+
// src/admin/sqlConsole.ts
|
|
9603
|
+
var SqlCapability = {
|
|
9604
|
+
/** `SELECT`, `WITH … SELECT`, `EXPLAIN`, `SHOW`. */
|
|
9605
|
+
READ: "read",
|
|
9606
|
+
/** Adds rows. */
|
|
9607
|
+
INSERT: "insert",
|
|
9608
|
+
/** Changes rows. */
|
|
9609
|
+
UPDATE: "update",
|
|
9610
|
+
/** Removes rows. */
|
|
9611
|
+
DELETE: "delete",
|
|
9612
|
+
/** `CREATE` / `ALTER` / `COMMENT`. */
|
|
9613
|
+
DDL: "ddl",
|
|
9614
|
+
/** `DROP` and `TRUNCATE`: irreversible structure loss. */
|
|
9615
|
+
DROP: "drop",
|
|
9616
|
+
/**
|
|
9617
|
+
* `GRANT` / `REVOKE` / `SET`, and anything the analyser cannot classify.
|
|
9618
|
+
* Unknown statements land here on purpose, so a construct nobody anticipated
|
|
9619
|
+
* needs the most privileged capability rather than the least.
|
|
9620
|
+
*/
|
|
9621
|
+
ADMIN: "admin"
|
|
9622
|
+
};
|
|
9623
|
+
var STATEMENT_CAPABILITIES = {
|
|
9624
|
+
select: SqlCapability.READ,
|
|
9625
|
+
with: SqlCapability.READ,
|
|
9626
|
+
explain: SqlCapability.READ,
|
|
9627
|
+
show: SqlCapability.READ,
|
|
9628
|
+
desc: SqlCapability.READ,
|
|
9629
|
+
describe: SqlCapability.READ,
|
|
9630
|
+
insert: SqlCapability.INSERT,
|
|
9631
|
+
replace: SqlCapability.INSERT,
|
|
9632
|
+
update: SqlCapability.UPDATE,
|
|
9633
|
+
delete: SqlCapability.DELETE,
|
|
9634
|
+
create: SqlCapability.DDL,
|
|
9635
|
+
alter: SqlCapability.DDL,
|
|
9636
|
+
comment: SqlCapability.DDL,
|
|
9637
|
+
rename: SqlCapability.DDL,
|
|
9638
|
+
drop: SqlCapability.DROP,
|
|
9639
|
+
truncate: SqlCapability.DROP
|
|
9640
|
+
};
|
|
9641
|
+
var PARSER_HINT = "The admin SQL console needs the optional peer `node-sql-parser`. Install it with: npm install node-sql-parser";
|
|
9642
|
+
async function loadSqlParser() {
|
|
9643
|
+
try {
|
|
9644
|
+
const module = await import('node-sql-parser');
|
|
9645
|
+
const Parser = module.Parser ?? module.default?.Parser;
|
|
9646
|
+
if (Parser === void 0) throw new Error(PARSER_HINT);
|
|
9647
|
+
return new Parser();
|
|
9648
|
+
} catch {
|
|
9649
|
+
throw new Error(PARSER_HINT);
|
|
9650
|
+
}
|
|
9651
|
+
}
|
|
9652
|
+
function analyzeSql(sql5, dialect, parser) {
|
|
9653
|
+
const capabilities = /* @__PURE__ */ new Set();
|
|
9654
|
+
const tables = /* @__PURE__ */ new Set();
|
|
9655
|
+
let statements = 0;
|
|
9656
|
+
let parsed = true;
|
|
9657
|
+
let unscopedWrite = false;
|
|
9658
|
+
try {
|
|
9659
|
+
const ast = parser.astify(sql5, { database: dialect });
|
|
9660
|
+
const list = Array.isArray(ast) ? ast : [ast];
|
|
9661
|
+
statements = list.length;
|
|
9662
|
+
for (const statement of list) {
|
|
9663
|
+
const type = String(statement.type ?? "").toLowerCase();
|
|
9664
|
+
capabilities.add(STATEMENT_CAPABILITIES[type] ?? SqlCapability.ADMIN);
|
|
9665
|
+
if ((type === "update" || type === "delete") && !statement.where) {
|
|
9666
|
+
unscopedWrite = true;
|
|
9667
|
+
}
|
|
9668
|
+
}
|
|
9669
|
+
} catch {
|
|
9670
|
+
parsed = false;
|
|
9671
|
+
statements = 1;
|
|
9672
|
+
capabilities.add(SqlCapability.ADMIN);
|
|
9673
|
+
}
|
|
9674
|
+
try {
|
|
9675
|
+
for (const entry of parser.tableList(sql5, { database: dialect })) {
|
|
9676
|
+
const name = entry.split("::").pop();
|
|
9677
|
+
if (name !== void 0 && name !== "null") tables.add(name.toLowerCase());
|
|
9678
|
+
}
|
|
9679
|
+
} catch {
|
|
9680
|
+
}
|
|
9681
|
+
return {
|
|
9682
|
+
statements,
|
|
9683
|
+
capabilities: [...capabilities],
|
|
9684
|
+
tables: [...tables],
|
|
9685
|
+
parsed,
|
|
9686
|
+
unscopedWrite
|
|
9687
|
+
};
|
|
9688
|
+
}
|
|
9689
|
+
function checkSqlPolicy(analysis, policy) {
|
|
9690
|
+
const granted = new Set(policy.capabilities ?? [SqlCapability.READ]);
|
|
9691
|
+
for (const capability of analysis.capabilities) {
|
|
9692
|
+
if (!granted.has(capability)) {
|
|
9693
|
+
return {
|
|
9694
|
+
allowed: false,
|
|
9695
|
+
reason: analysis.parsed ? `This console may not run ${capability} statements.` : "The statement could not be parsed, so it needs the admin capability."
|
|
9696
|
+
};
|
|
9697
|
+
}
|
|
9698
|
+
}
|
|
9699
|
+
if ((policy.requireWhereOnWrites ?? true) && analysis.unscopedWrite) {
|
|
9700
|
+
return {
|
|
9701
|
+
allowed: false,
|
|
9702
|
+
reason: "An UPDATE or DELETE without a WHERE clause is refused."
|
|
9703
|
+
};
|
|
9704
|
+
}
|
|
9705
|
+
const denied = new Set((policy.denyTables ?? []).map((name) => name.toLowerCase()));
|
|
9706
|
+
for (const table of analysis.tables) {
|
|
9707
|
+
if (denied.has(table)) {
|
|
9708
|
+
return { allowed: false, reason: `Table "${table}" is not available here.` };
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
if (policy.allowTables !== void 0) {
|
|
9712
|
+
const allowed = new Set(policy.allowTables.map((name) => name.toLowerCase()));
|
|
9713
|
+
if (analysis.tables.length === 0) {
|
|
9714
|
+
return {
|
|
9715
|
+
allowed: false,
|
|
9716
|
+
reason: "This console only runs statements naming an allowed table."
|
|
9717
|
+
};
|
|
9718
|
+
}
|
|
9719
|
+
for (const table of analysis.tables) {
|
|
9720
|
+
if (!allowed.has(table)) {
|
|
9721
|
+
return { allowed: false, reason: `Table "${table}" is not on the allow list.` };
|
|
9722
|
+
}
|
|
9723
|
+
}
|
|
9724
|
+
}
|
|
9725
|
+
return { allowed: true, reason: null };
|
|
9726
|
+
}
|
|
9727
|
+
|
|
9337
9728
|
// src/admin/inlines.ts
|
|
9338
9729
|
function adminInline(options) {
|
|
9339
9730
|
const slug = options.model.tablename;
|
|
@@ -9474,8 +9865,8 @@ function humanizeField(name) {
|
|
|
9474
9865
|
function adminColumns(model) {
|
|
9475
9866
|
return columnsOf(model);
|
|
9476
9867
|
}
|
|
9477
|
-
function widgetForColumn(
|
|
9478
|
-
const { kind, meta } =
|
|
9868
|
+
function widgetForColumn(column7) {
|
|
9869
|
+
const { kind, meta } = column7.type;
|
|
9479
9870
|
const plain = (widget) => ({
|
|
9480
9871
|
widget,
|
|
9481
9872
|
step: null,
|
|
@@ -9519,11 +9910,11 @@ function widgetForColumn(column6) {
|
|
|
9519
9910
|
return plain("text");
|
|
9520
9911
|
}
|
|
9521
9912
|
}
|
|
9522
|
-
function isColumnOptional(
|
|
9523
|
-
return !
|
|
9913
|
+
function isColumnOptional(column7) {
|
|
9914
|
+
return !column7.flags.notNull || column7.flags.hasDefault || column7.flags.primaryKey;
|
|
9524
9915
|
}
|
|
9525
|
-
function filterForColumn(
|
|
9526
|
-
const { kind, meta } =
|
|
9916
|
+
function filterForColumn(column7) {
|
|
9917
|
+
const { kind, meta } = column7.type;
|
|
9527
9918
|
if (kind === "boolean") {
|
|
9528
9919
|
return {
|
|
9529
9920
|
kind: "select",
|
|
@@ -9547,11 +9938,11 @@ function filterForColumn(column6) {
|
|
|
9547
9938
|
}
|
|
9548
9939
|
return { kind: "text", options: [] };
|
|
9549
9940
|
}
|
|
9550
|
-
function foreignKeyTable(
|
|
9551
|
-
return
|
|
9941
|
+
function foreignKeyTable(column7) {
|
|
9942
|
+
return column7.reference?.table ?? null;
|
|
9552
9943
|
}
|
|
9553
|
-
function isSearchableColumn(
|
|
9554
|
-
const { kind } =
|
|
9944
|
+
function isSearchableColumn(column7) {
|
|
9945
|
+
const { kind } = column7.type;
|
|
9555
9946
|
return kind === "varchar" || kind === "text" || kind === "char";
|
|
9556
9947
|
}
|
|
9557
9948
|
|
|
@@ -9833,8 +10224,8 @@ function formatFieldValue(widget, value) {
|
|
|
9833
10224
|
}
|
|
9834
10225
|
return String(value);
|
|
9835
10226
|
}
|
|
9836
|
-
function literalDefault(
|
|
9837
|
-
const fallback =
|
|
10227
|
+
function literalDefault(column7) {
|
|
10228
|
+
const fallback = column7.defaultValue;
|
|
9838
10229
|
if (fallback === null || fallback.kind !== "literal") return void 0;
|
|
9839
10230
|
return fallback.value;
|
|
9840
10231
|
}
|
|
@@ -9847,19 +10238,19 @@ function buildFormFields(admin, options = {}) {
|
|
|
9847
10238
|
const autocompleteLabels = options.autocompleteLabels ?? {};
|
|
9848
10239
|
const uploads = new Set(admin.uploadFields);
|
|
9849
10240
|
return admin.editableFieldNames().flatMap((name) => {
|
|
9850
|
-
const
|
|
9851
|
-
if (
|
|
10241
|
+
const column7 = columns[name];
|
|
10242
|
+
if (column7 === void 0) return [];
|
|
9852
10243
|
const related = foreignKeys[name];
|
|
9853
10244
|
const autocompleteUrl = autocompleteUrls[name];
|
|
9854
|
-
const spec = uploads.has(name) ? { widget: "file", step: null, options: [] } : autocompleteUrl !== void 0 ? { widget: "autocomplete", step: null, options: [] } : related === void 0 ? widgetForColumn(
|
|
9855
|
-
const raw = name in values ? values[name] : literalDefault(
|
|
10245
|
+
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 };
|
|
10246
|
+
const raw = name in values ? values[name] : literalDefault(column7);
|
|
9856
10247
|
return [
|
|
9857
10248
|
{
|
|
9858
10249
|
name,
|
|
9859
10250
|
label: humanizeField(name),
|
|
9860
10251
|
widget: spec.widget,
|
|
9861
10252
|
value: spec.widget === "checkbox" ? "" : formatFieldValue(spec.widget, raw),
|
|
9862
|
-
required: !isColumnOptional(
|
|
10253
|
+
required: !isColumnOptional(column7),
|
|
9863
10254
|
checked: spec.widget === "checkbox" && toBoolean(raw),
|
|
9864
10255
|
step: spec.step,
|
|
9865
10256
|
options: spec.options,
|
|
@@ -9876,8 +10267,8 @@ function toBoolean(value) {
|
|
|
9876
10267
|
if (typeof value !== "string") return false;
|
|
9877
10268
|
return ["true", "on", "yes", "1"].includes(value.trim().toLowerCase());
|
|
9878
10269
|
}
|
|
9879
|
-
function coerceValue(
|
|
9880
|
-
const { kind, meta } =
|
|
10270
|
+
function coerceValue(column7, widget, raw) {
|
|
10271
|
+
const { kind, meta } = column7.type;
|
|
9881
10272
|
switch (widget) {
|
|
9882
10273
|
case "number": {
|
|
9883
10274
|
const parsed = Number(raw);
|
|
@@ -9921,10 +10312,10 @@ function parseFormBody(admin, body, options = {}) {
|
|
|
9921
10312
|
const only = options.only === void 0 ? null : new Set(options.only);
|
|
9922
10313
|
for (const name of admin.editableFieldNames()) {
|
|
9923
10314
|
if (only !== null && !only.has(name)) continue;
|
|
9924
|
-
const
|
|
9925
|
-
if (
|
|
10315
|
+
const column7 = columns[name];
|
|
10316
|
+
if (column7 === void 0) continue;
|
|
9926
10317
|
if (uploads.has(name)) continue;
|
|
9927
|
-
const { widget } = widgetForColumn(
|
|
10318
|
+
const { widget } = widgetForColumn(column7);
|
|
9928
10319
|
if (widget === "checkbox") {
|
|
9929
10320
|
data[name] = toBoolean(body[name]);
|
|
9930
10321
|
continue;
|
|
@@ -9932,16 +10323,16 @@ function parseFormBody(admin, body, options = {}) {
|
|
|
9932
10323
|
const submitted = body[name];
|
|
9933
10324
|
const raw = typeof submitted === "string" ? submitted.trim() : "";
|
|
9934
10325
|
if (raw === "") {
|
|
9935
|
-
if (!isColumnOptional(
|
|
10326
|
+
if (!isColumnOptional(column7)) {
|
|
9936
10327
|
errors[name] = "This field is required.";
|
|
9937
10328
|
continue;
|
|
9938
10329
|
}
|
|
9939
|
-
if (
|
|
10330
|
+
if (column7.flags.hasDefault && !(name in body)) continue;
|
|
9940
10331
|
data[name] = null;
|
|
9941
10332
|
continue;
|
|
9942
10333
|
}
|
|
9943
10334
|
try {
|
|
9944
|
-
data[name] = coerceValue(
|
|
10335
|
+
data[name] = coerceValue(column7, widget, raw);
|
|
9945
10336
|
} catch (error) {
|
|
9946
10337
|
errors[name] = error instanceof Error ? error.message : "Invalid value.";
|
|
9947
10338
|
}
|
|
@@ -9959,9 +10350,9 @@ function foreignKeyFields(admin) {
|
|
|
9959
10350
|
const columns = adminColumns(admin.model);
|
|
9960
10351
|
const out = {};
|
|
9961
10352
|
for (const name of admin.editableFieldNames()) {
|
|
9962
|
-
const
|
|
9963
|
-
if (
|
|
9964
|
-
const table = foreignKeyTable(
|
|
10353
|
+
const column7 = columns[name];
|
|
10354
|
+
if (column7 === void 0) continue;
|
|
10355
|
+
const table = foreignKeyTable(column7);
|
|
9965
10356
|
if (table !== null) out[name] = table;
|
|
9966
10357
|
}
|
|
9967
10358
|
return out;
|
|
@@ -10347,7 +10738,14 @@ var AdminSite = class {
|
|
|
10347
10738
|
};
|
|
10348
10739
|
|
|
10349
10740
|
// src/admin/styles.ts
|
|
10350
|
-
var
|
|
10741
|
+
var ADMIN_CSS_EXTRA = `
|
|
10742
|
+
.tempest-log-badge--succeeded { background: #dcfce7; color: #166534; }
|
|
10743
|
+
.tempest-log-badge--failed { background: #fee2e2; color: #991b1b; }
|
|
10744
|
+
.tempest-log-badge--running { background: #dbeafe; color: #1e40af; }
|
|
10745
|
+
.tempest-log-badge--queued { background: #f1f5f9; color: #475569; }
|
|
10746
|
+
.tempest-log-badge--cancelled { background: #fef3c7; color: #92400e; }
|
|
10747
|
+
`;
|
|
10748
|
+
var ADMIN_CSS_BASE = `:root {
|
|
10351
10749
|
--tempest-bg: #0f172a;
|
|
10352
10750
|
--tempest-bg-soft: #1e293b;
|
|
10353
10751
|
--tempest-bg-row: #f8fafc;
|
|
@@ -11906,6 +12304,7 @@ a.tempest-admin-list__new:hover {
|
|
|
11906
12304
|
}
|
|
11907
12305
|
}
|
|
11908
12306
|
`;
|
|
12307
|
+
var ADMIN_CSS = `${ADMIN_CSS_BASE}${ADMIN_CSS_EXTRA}`;
|
|
11909
12308
|
|
|
11910
12309
|
// src/admin/theme.ts
|
|
11911
12310
|
var FORBIDDEN_CHARS = ["<", ">", "{", "}", '"'];
|
|
@@ -11985,7 +12384,7 @@ function escapeHtml(value) {
|
|
|
11985
12384
|
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
11986
12385
|
}
|
|
11987
12386
|
function renderLayout(context, title, body) {
|
|
11988
|
-
const { site, theme, prefix, session, currentPath, navModels, messages } = context;
|
|
12387
|
+
const { site, theme, prefix, session, currentPath, navModels, navSystem, messages } = context;
|
|
11989
12388
|
const authed = session !== null;
|
|
11990
12389
|
const indexUrl = `${prefix}/`;
|
|
11991
12390
|
const navLink = (url, label, active) => `<a href="${escapeHtml(url)}" class="tempest-admin-sidebar__link${active ? " tempest-admin-sidebar__link--active" : ""}">${escapeHtml(label)}</a>`;
|
|
@@ -11995,6 +12394,9 @@ function renderLayout(context, title, body) {
|
|
|
11995
12394
|
${navLink(indexUrl, "Dashboard", currentPath === indexUrl)}
|
|
11996
12395
|
${navModels.length > 0 ? `<span class="tempest-admin-sidebar__heading">Models</span>${navModels.map(
|
|
11997
12396
|
(entry) => navLink(entry.url, entry.label, currentPath.startsWith(entry.url))
|
|
12397
|
+
).join("")}` : ""}
|
|
12398
|
+
${navSystem.length > 0 ? `<span class="tempest-admin-sidebar__heading">System</span>${navSystem.map(
|
|
12399
|
+
(entry) => navLink(entry.url, entry.label, currentPath.startsWith(entry.url))
|
|
11998
12400
|
).join("")}` : ""}
|
|
11999
12401
|
</nav>
|
|
12000
12402
|
</aside>` : "";
|
|
@@ -12163,11 +12565,11 @@ function renderFilter(filter) {
|
|
|
12163
12565
|
function renderListPage(context, view) {
|
|
12164
12566
|
const bulk = view.bulkActions.length > 0 && context.session !== null;
|
|
12165
12567
|
const checkColumn = bulk ? 1 : 0;
|
|
12166
|
-
const headers = view.columns.map((
|
|
12167
|
-
const state = view.sort[
|
|
12168
|
-
if (state === void 0) return `<th>${escapeHtml(
|
|
12568
|
+
const headers = view.columns.map((column7) => {
|
|
12569
|
+
const state = view.sort[column7];
|
|
12570
|
+
if (state === void 0) return `<th>${escapeHtml(column7)}</th>`;
|
|
12169
12571
|
const arrow = state.active ? state.ascending ? "\u25B2" : "\u25BC" : "\u2195";
|
|
12170
|
-
return `<th><a class="tempest-sort${state.active ? " tempest-sort--active" : ""}" href="${escapeHtml(state.url)}"><span>${escapeHtml(
|
|
12572
|
+
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>`;
|
|
12171
12573
|
}).join("");
|
|
12172
12574
|
const rows = view.rows.length > 0 ? view.rows.map((row) => {
|
|
12173
12575
|
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>` : "";
|
|
@@ -12308,7 +12710,7 @@ function renderInline(inline, csrfToken, error) {
|
|
|
12308
12710
|
if (inline.rows.length === 0) {
|
|
12309
12711
|
return `<section class="tempest-admin-inline">${heading}<p><em>No related records.</em></p></section>`;
|
|
12310
12712
|
}
|
|
12311
|
-
const head2 = `<tr>${inline.columns.map((
|
|
12713
|
+
const head2 = `<tr>${inline.columns.map((column7) => `<th>${escapeHtml(column7)}</th>`).join("")}<th></th></tr>`;
|
|
12312
12714
|
const body = inline.rows.map(
|
|
12313
12715
|
(row) => `<tr>${row.cells.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}<td>${row.url === null ? "" : `<a href="${escapeHtml(row.url)}">View</a>`}</td></tr>`
|
|
12314
12716
|
).join("");
|
|
@@ -12323,7 +12725,7 @@ function renderInline(inline, csrfToken, error) {
|
|
|
12323
12725
|
${more}
|
|
12324
12726
|
</section>`;
|
|
12325
12727
|
}
|
|
12326
|
-
const head = `<tr>${inline.columns.map((
|
|
12728
|
+
const head = `<tr>${inline.columns.map((column7) => `<th>${escapeHtml(column7)}</th>`).join("")}${inline.canDelete ? "<th>Delete</th>" : ""}</tr>`;
|
|
12327
12729
|
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>`;
|
|
12328
12730
|
const rows = inline.rows.map((row) => renderRow(row, false)).join("");
|
|
12329
12731
|
const blank = inline.newRow === null ? "" : renderRow(inline.newRow, true);
|
|
@@ -12345,11 +12747,11 @@ function renderInline(inline, csrfToken, error) {
|
|
|
12345
12747
|
${more}
|
|
12346
12748
|
</section>`;
|
|
12347
12749
|
}
|
|
12348
|
-
function renderAuditPanel(
|
|
12349
|
-
const rows =
|
|
12750
|
+
function renderAuditPanel(audit2) {
|
|
12751
|
+
const rows = audit2.fields.map(
|
|
12350
12752
|
(field) => `<dt>${escapeHtml(field.label)}</dt><dd>${field.value === "" ? "<em>\u2014</em>" : escapeHtml(field.value)}</dd>`
|
|
12351
12753
|
).join("");
|
|
12352
|
-
const history =
|
|
12754
|
+
const history = audit2.history.length > 0 ? `<ol class="tempest-admin-history">${audit2.history.map((entry) => {
|
|
12353
12755
|
const changes = entry.changes.length > 0 ? `<table class="tempest-admin-history__changes"><thead><tr><th>Field</th><th>Before</th><th>After</th></tr></thead><tbody>${entry.changes.map(
|
|
12354
12756
|
(change) => `<tr><td>${escapeHtml(change.field)}</td><td>${escapeHtml(change.before)}</td><td>${escapeHtml(change.after)}</td></tr>`
|
|
12355
12757
|
).join("")}</tbody></table>` : "<p><em>No field changes recorded.</em></p>";
|
|
@@ -12447,6 +12849,160 @@ function renderFormPage(context, view) {
|
|
|
12447
12849
|
</section>`;
|
|
12448
12850
|
return renderLayout(context, `${heading} \xB7 ${context.site.title}`, body);
|
|
12449
12851
|
}
|
|
12852
|
+
function renderLogsPage(context, view) {
|
|
12853
|
+
const options = view.sources.map(
|
|
12854
|
+
(source) => `<option value="${escapeHtml(source.value)}"${source.selected ? " selected" : ""}>${escapeHtml(source.label)}</option>`
|
|
12855
|
+
).join("");
|
|
12856
|
+
const rows = view.rows.length > 0 ? view.rows.map((row) => {
|
|
12857
|
+
const context_ = row.context.map(
|
|
12858
|
+
(entry) => `<span class="tempest-admin-logs__meta"><b>${escapeHtml(entry.key)}</b>: ${escapeHtml(entry.value)}</span>`
|
|
12859
|
+
).join(" ");
|
|
12860
|
+
const message = row.stack === null ? `${escapeHtml(row.message)}${context_ === "" ? "" : `<br>${context_}`}` : `<details><summary>${escapeHtml(row.message)}</summary>${context_ === "" ? "" : `<p>${context_}</p>`}<pre>${escapeHtml(row.stack)}</pre></details>`;
|
|
12861
|
+
return `<tr>
|
|
12862
|
+
<td data-label="Level"><span class="tempest-log-badge tempest-log-badge--${escapeHtml(row.level)}">${escapeHtml(row.level)}</span></td>
|
|
12863
|
+
<td data-label="When">${escapeHtml(row.timestamp)}</td>
|
|
12864
|
+
<td data-label="Logger">${escapeHtml(row.logger)}</td>
|
|
12865
|
+
<td data-label="Message">${message}</td>
|
|
12866
|
+
</tr>`;
|
|
12867
|
+
}).join("") : `<tr><td colspan="4">No log records yet. Point <code>configureFileLogging</code> at the same directory to populate this page.</td></tr>`;
|
|
12868
|
+
const body = `<section class="tempest-admin-logs">
|
|
12869
|
+
<header class="tempest-admin-list__header">
|
|
12870
|
+
<h1>Logs</h1>
|
|
12871
|
+
<p>${escapeHtml(view.total)} record${view.total === 1 ? "" : "s"}.</p>
|
|
12872
|
+
</header>
|
|
12873
|
+
<div class="tempest-admin-list__toolbar">
|
|
12874
|
+
<form method="get" class="tempest-admin-list__filters">
|
|
12875
|
+
<label><span>Source</span><select name="source">${options}</select></label>
|
|
12876
|
+
<input type="search" name="q" value="${escapeHtml(view.query)}" placeholder="Search\u2026" aria-label="Search">
|
|
12877
|
+
<button type="submit">Apply</button>
|
|
12878
|
+
</form>
|
|
12879
|
+
<div class="tempest-admin-list__actions">
|
|
12880
|
+
<a href="${escapeHtml(view.exportMarkdownUrl)}">Export Markdown</a>
|
|
12881
|
+
<a href="${escapeHtml(view.exportJsonUrl)}">Export JSON</a>
|
|
12882
|
+
</div>
|
|
12883
|
+
</div>
|
|
12884
|
+
<div class="tempest-admin-table-wrap">
|
|
12885
|
+
<table class="tempest-admin-list__table">
|
|
12886
|
+
<thead><tr><th>Level</th><th>When</th><th>Logger</th><th>Message</th></tr></thead>
|
|
12887
|
+
<tbody>${rows}</tbody>
|
|
12888
|
+
</table>
|
|
12889
|
+
</div>
|
|
12890
|
+
${view.pages > 1 ? `<nav class="tempest-admin-list__pagination" aria-label="Pagination">
|
|
12891
|
+
${view.prevUrl !== null ? `<a href="${escapeHtml(view.prevUrl)}">\u2190 Prev</a>` : ""}
|
|
12892
|
+
<span>Page ${escapeHtml(view.page)} of ${escapeHtml(view.pages)}</span>
|
|
12893
|
+
${view.nextUrl !== null ? `<a href="${escapeHtml(view.nextUrl)}">Next \u2192</a>` : ""}
|
|
12894
|
+
</nav>` : ""}
|
|
12895
|
+
<p class="tempest-admin-form__hint">Exports carry at most ${escapeHtml(view.exportMax)} records, newest first, honouring the filters above.</p>
|
|
12896
|
+
</section>`;
|
|
12897
|
+
return renderLayout(context, `Logs \xB7 ${context.site.title}`, body);
|
|
12898
|
+
}
|
|
12899
|
+
function renderTasksPage(context, view) {
|
|
12900
|
+
const declared = view.inventory === null ? "" : `<section class="tempest-admin-tasks__declared">
|
|
12901
|
+
<h2>Declared</h2>
|
|
12902
|
+
${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">
|
|
12903
|
+
<thead><tr><th>Task</th><th>Schedule</th><th>Description</th></tr></thead>
|
|
12904
|
+
<tbody>${view.inventory.map(
|
|
12905
|
+
(entry) => `<tr><td>${escapeHtml(entry.name)}</td><td>${escapeHtml(entry.schedule)}</td><td>${escapeHtml(entry.description)}</td></tr>`
|
|
12906
|
+
).join("")}</tbody>
|
|
12907
|
+
</table></div>`}
|
|
12908
|
+
</section>`;
|
|
12909
|
+
const runs = view.runs === null ? "" : `<section class="tempest-admin-tasks__runs">
|
|
12910
|
+
<h2>Runs</h2>
|
|
12911
|
+
<div class="tempest-admin-list__toolbar">
|
|
12912
|
+
<form method="get" class="tempest-admin-list__filters">
|
|
12913
|
+
<label><span>Status</span><select name="status">${view.runs.statuses.map(
|
|
12914
|
+
(status) => `<option value="${escapeHtml(status.value)}"${status.selected ? " selected" : ""}>${escapeHtml(status.label)}</option>`
|
|
12915
|
+
).join("")}</select></label>
|
|
12916
|
+
<input type="search" name="task" value="${escapeHtml(view.runs.nameQuery)}" placeholder="Task name\u2026" aria-label="Task name">
|
|
12917
|
+
<button type="submit">Apply</button>
|
|
12918
|
+
</form>
|
|
12919
|
+
</div>
|
|
12920
|
+
<div class="tempest-admin-table-wrap"><table class="tempest-admin-list__table">
|
|
12921
|
+
<thead><tr><th>Task</th><th>Status</th><th>Started</th><th>Finished</th><th>Attempts</th><th></th></tr></thead>
|
|
12922
|
+
<tbody>${view.runs.rows.length === 0 ? '<tr><td colspan="6">No runs recorded yet.</td></tr>' : view.runs.rows.map(
|
|
12923
|
+
(row) => `<tr>
|
|
12924
|
+
<td>${escapeHtml(row.name)}</td>
|
|
12925
|
+
<td><span class="tempest-log-badge tempest-log-badge--${escapeHtml(row.status)}">${escapeHtml(row.status)}</span></td>
|
|
12926
|
+
<td>${escapeHtml(row.startedAt)}</td>
|
|
12927
|
+
<td>${escapeHtml(row.finishedAt)}</td>
|
|
12928
|
+
<td>${escapeHtml(row.attempts)}</td>
|
|
12929
|
+
<td><a href="${escapeHtml(row.url)}">View</a></td>
|
|
12930
|
+
</tr>`
|
|
12931
|
+
).join("")}</tbody>
|
|
12932
|
+
</table></div>
|
|
12933
|
+
${view.runs.pages > 1 ? `<nav class="tempest-admin-list__pagination" aria-label="Pagination">
|
|
12934
|
+
${view.runs.prevUrl !== null ? `<a href="${escapeHtml(view.runs.prevUrl)}">\u2190 Prev</a>` : ""}
|
|
12935
|
+
<span>Page ${escapeHtml(view.runs.page)} of ${escapeHtml(view.runs.pages)}</span>
|
|
12936
|
+
${view.runs.nextUrl !== null ? `<a href="${escapeHtml(view.runs.nextUrl)}">Next \u2192</a>` : ""}
|
|
12937
|
+
</nav>` : ""}
|
|
12938
|
+
</section>`;
|
|
12939
|
+
const body = `<section class="tempest-admin-tasks">
|
|
12940
|
+
<header class="tempest-admin-list__header">
|
|
12941
|
+
<h1>Tasks</h1>
|
|
12942
|
+
<p>What this process declares, and what its workers recorded.</p>
|
|
12943
|
+
</header>
|
|
12944
|
+
${declared}
|
|
12945
|
+
${runs}
|
|
12946
|
+
<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>
|
|
12947
|
+
</section>`;
|
|
12948
|
+
return renderLayout(context, `Tasks \xB7 ${context.site.title}`, body);
|
|
12949
|
+
}
|
|
12950
|
+
function renderTaskDetailPage(context, view) {
|
|
12951
|
+
if (context.session === null) throw new Error("The task detail requires a session");
|
|
12952
|
+
const block = (label, content) => content === null ? "" : `<h2>${escapeHtml(label)}</h2><pre class="tempest-admin-detail__json">${escapeHtml(content)}</pre>`;
|
|
12953
|
+
const body = `<section class="tempest-admin-detail">
|
|
12954
|
+
<header class="tempest-admin-detail__header">
|
|
12955
|
+
<h1>${escapeHtml(view.name)} \xB7 <span class="tempest-log-badge tempest-log-badge--${escapeHtml(view.status)}">${escapeHtml(view.status)}</span></h1>
|
|
12956
|
+
<div class="tempest-admin-detail__actions">
|
|
12957
|
+
<a href="${escapeHtml(view.backUrl)}">\u2190 Back to tasks</a>
|
|
12958
|
+
${view.cancelUrl !== null ? `<form method="post" action="${escapeHtml(view.cancelUrl)}" class="tempest-admin-detail__delete" onsubmit="return confirm('Ask this run to stop?');">
|
|
12959
|
+
<input type="hidden" name="csrf_token" value="${escapeHtml(context.session.csrfToken)}">
|
|
12960
|
+
<button type="submit" class="tempest-admin-btn--danger">Cancel</button>
|
|
12961
|
+
</form>` : ""}
|
|
12962
|
+
</div>
|
|
12963
|
+
</header>
|
|
12964
|
+
<dl class="tempest-admin-detail__fields">${view.fields.map(
|
|
12965
|
+
(field) => `<dt>${escapeHtml(field.label)}</dt><dd>${field.value === "" ? "<em>\u2014</em>" : escapeHtml(field.value)}</dd>`
|
|
12966
|
+
).join("")}</dl>
|
|
12967
|
+
${view.error === null ? "" : `<p class="tempest-admin-form__error">${escapeHtml(view.error)}</p>`}
|
|
12968
|
+
${block("Payload", view.payload)}
|
|
12969
|
+
${block("Result", view.result)}
|
|
12970
|
+
</section>`;
|
|
12971
|
+
return renderLayout(context, `${view.name} \xB7 ${context.site.title}`, body);
|
|
12972
|
+
}
|
|
12973
|
+
function renderSqlPage(context, view) {
|
|
12974
|
+
if (context.session === null) throw new Error("The SQL console requires a session");
|
|
12975
|
+
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>
|
|
12976
|
+
<div class="tempest-admin-table-wrap">
|
|
12977
|
+
<table class="tempest-admin-list__table">
|
|
12978
|
+
<thead><tr>${view.columns.map((column7) => `<th>${escapeHtml(column7)}</th>`).join("")}</tr></thead>
|
|
12979
|
+
<tbody>${view.rows.map(
|
|
12980
|
+
(row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`
|
|
12981
|
+
).join("")}</tbody>
|
|
12982
|
+
</table>
|
|
12983
|
+
</div>`;
|
|
12984
|
+
const body = `<section class="tempest-admin-sql">
|
|
12985
|
+
<header class="tempest-admin-list__header">
|
|
12986
|
+
<h1>SQL console</h1>
|
|
12987
|
+
<p>Capabilities: <code>${escapeHtml(view.capabilities.join(", "))}</code></p>
|
|
12988
|
+
</header>
|
|
12989
|
+
${view.error !== null ? `<p class="tempest-admin-form__error">${escapeHtml(view.error)}</p>` : ""}
|
|
12990
|
+
<form method="post" action="${escapeHtml(`${context.prefix}/sql`)}" class="tempest-admin-form__form">
|
|
12991
|
+
<input type="hidden" name="csrf_token" value="${escapeHtml(context.session.csrfToken)}">
|
|
12992
|
+
<div class="tempest-admin-form__field">
|
|
12993
|
+
<label>
|
|
12994
|
+
<span>Statement</span>
|
|
12995
|
+
<textarea name="sql" rows="6" spellcheck="false" required>${escapeHtml(view.sql)}</textarea>
|
|
12996
|
+
</label>
|
|
12997
|
+
</div>
|
|
12998
|
+
<div class="tempest-admin-form__actions">
|
|
12999
|
+
<button type="submit">Run</button>
|
|
13000
|
+
</div>
|
|
13001
|
+
</form>
|
|
13002
|
+
${result}
|
|
13003
|
+
</section>`;
|
|
13004
|
+
return renderLayout(context, `SQL console \xB7 ${context.site.title}`, body);
|
|
13005
|
+
}
|
|
12450
13006
|
function renderImportPage(context, view) {
|
|
12451
13007
|
if (context.session === null) throw new Error("The import page requires a session");
|
|
12452
13008
|
const summary = view.created === null ? "" : `<p class="tempest-admin-import__summary">Created ${escapeHtml(view.created)} record${view.created === 1 ? "" : "s"}.</p>`;
|
|
@@ -12532,7 +13088,75 @@ var AUTOCOMPLETE_SCRIPT = `<script>
|
|
|
12532
13088
|
});
|
|
12533
13089
|
})();
|
|
12534
13090
|
</script>`;
|
|
13091
|
+
function filesFor(dir, source) {
|
|
13092
|
+
if (source === "500") return [join(dir, HTTP_500_LOG_FILE)];
|
|
13093
|
+
if (source === "all") return Object.values(LEVEL_LOG_FILES).map((f) => join(dir, f));
|
|
13094
|
+
return [join(dir, LEVEL_LOG_FILES[source])];
|
|
13095
|
+
}
|
|
13096
|
+
async function readLogEntries(dir, source) {
|
|
13097
|
+
return await readEntries(filesFor(dir, source));
|
|
13098
|
+
}
|
|
13099
|
+
async function readEntries(files) {
|
|
13100
|
+
const entries = [];
|
|
13101
|
+
for (const file of files) {
|
|
13102
|
+
let content;
|
|
13103
|
+
try {
|
|
13104
|
+
content = await readFile(file, "utf8");
|
|
13105
|
+
} catch {
|
|
13106
|
+
continue;
|
|
13107
|
+
}
|
|
13108
|
+
for (const line of content.split("\n")) {
|
|
13109
|
+
if (!line.trim()) continue;
|
|
13110
|
+
try {
|
|
13111
|
+
entries.push(JSON.parse(line));
|
|
13112
|
+
} catch {
|
|
13113
|
+
}
|
|
13114
|
+
}
|
|
13115
|
+
}
|
|
13116
|
+
return entries;
|
|
13117
|
+
}
|
|
13118
|
+
function makeLogsRouter(options) {
|
|
13119
|
+
const router = Router();
|
|
13120
|
+
const path = options.path ?? "/logs";
|
|
13121
|
+
const guards = options.guards ?? [];
|
|
13122
|
+
const handler = (req, res, next) => {
|
|
13123
|
+
const source = req.query.source ?? "all";
|
|
13124
|
+
const validSources = ["all", "debug", "info", "warning", "error", "500"];
|
|
13125
|
+
if (!validSources.includes(source)) {
|
|
13126
|
+
res.status(422).json({ detail: "Invalid log source", code: "VALIDATION_ERROR", details: {} });
|
|
13127
|
+
return;
|
|
13128
|
+
}
|
|
13129
|
+
const page2 = Math.max(1, Number(req.query.page) || 1);
|
|
13130
|
+
const pageSize = Math.min(500, Math.max(1, Number(req.query.pageSize) || 50));
|
|
13131
|
+
readEntries(filesFor(options.dir, source)).then((entries) => {
|
|
13132
|
+
entries.reverse();
|
|
13133
|
+
const total = entries.length;
|
|
13134
|
+
const start = (page2 - 1) * pageSize;
|
|
13135
|
+
res.json({
|
|
13136
|
+
items: entries.slice(start, start + pageSize),
|
|
13137
|
+
total,
|
|
13138
|
+
page: page2,
|
|
13139
|
+
pageSize,
|
|
13140
|
+
pages: Math.ceil(total / pageSize)
|
|
13141
|
+
});
|
|
13142
|
+
}).catch(next);
|
|
13143
|
+
};
|
|
13144
|
+
router.get(path, ...guards, handler);
|
|
13145
|
+
return router;
|
|
13146
|
+
}
|
|
12535
13147
|
var logger2 = new JSONLogger("tempest_express_sdk.admin.router");
|
|
13148
|
+
var JOB_STATUSES = [
|
|
13149
|
+
"queued",
|
|
13150
|
+
"running",
|
|
13151
|
+
"succeeded",
|
|
13152
|
+
"failed",
|
|
13153
|
+
"cancelled"
|
|
13154
|
+
];
|
|
13155
|
+
var TERMINAL_JOB_STATUSES = ["succeeded", "failed", "cancelled"];
|
|
13156
|
+
var TASK_PAGE_SIZE = 25;
|
|
13157
|
+
var LOG_SOURCES = ["all", "debug", "info", "warning", "error", "500"];
|
|
13158
|
+
var LOG_PAGE_SIZE = 50;
|
|
13159
|
+
var LOG_EXPORT_MAX = 500;
|
|
12536
13160
|
var INLINE_ROW_LIMIT = 50;
|
|
12537
13161
|
var AUTOCOMPLETE_LIMIT = 20;
|
|
12538
13162
|
var AUDIT_HISTORY_LIMIT = 50;
|
|
@@ -12564,6 +13188,16 @@ function makeAdminRouter(site, options) {
|
|
|
12564
13188
|
const theme = resolveAdminTheme(site.theme);
|
|
12565
13189
|
const showMetrics = options.showMetrics ?? true;
|
|
12566
13190
|
const exportMaxRows = options.exportMaxRows ?? 5e3;
|
|
13191
|
+
const systemNav = [];
|
|
13192
|
+
if (options.logDir !== void 0) {
|
|
13193
|
+
systemNav.push({ label: "Logs", url: `${prefix}/logs` });
|
|
13194
|
+
}
|
|
13195
|
+
if (options.sqlConsole !== void 0) {
|
|
13196
|
+
systemNav.push({ label: "SQL console", url: `${prefix}/sql` });
|
|
13197
|
+
}
|
|
13198
|
+
if (options.tasks !== void 0) {
|
|
13199
|
+
systemNav.push({ label: "Tasks", url: `${prefix}/tasks` });
|
|
13200
|
+
}
|
|
12567
13201
|
const maxUploadBytes = options.maxUploadBytes ?? 10 * 1024 * 1024;
|
|
12568
13202
|
const sessions = new AdminSessionStore({
|
|
12569
13203
|
secret: options.secretKey,
|
|
@@ -12585,6 +13219,7 @@ function makeAdminRouter(site, options) {
|
|
|
12585
13219
|
label: admin.verboseNamePlural(),
|
|
12586
13220
|
url: `${prefix}/m/${admin.slug()}`
|
|
12587
13221
|
})),
|
|
13222
|
+
navSystem: systemNav,
|
|
12588
13223
|
messages: flashFor(req)
|
|
12589
13224
|
});
|
|
12590
13225
|
const allows = async (principal, admin, action) => {
|
|
@@ -12795,6 +13430,296 @@ function makeAdminRouter(site, options) {
|
|
|
12795
13430
|
);
|
|
12796
13431
|
})
|
|
12797
13432
|
);
|
|
13433
|
+
if (options.logDir !== void 0) {
|
|
13434
|
+
const logDir = options.logDir;
|
|
13435
|
+
router.get(
|
|
13436
|
+
`${prefix}/logs`,
|
|
13437
|
+
guarded(async (req, res) => {
|
|
13438
|
+
const state = await authenticate(req, res);
|
|
13439
|
+
if (state === null) return;
|
|
13440
|
+
const { entries, source, search } = await readLogs(req, logDir);
|
|
13441
|
+
const page2 = Math.max(1, Number.parseInt(queryString(req.query.page), 10) || 1);
|
|
13442
|
+
const start = (page2 - 1) * LOG_PAGE_SIZE;
|
|
13443
|
+
const pages = Math.max(1, Math.ceil(entries.length / LOG_PAGE_SIZE));
|
|
13444
|
+
const exportUrl = (format) => `${prefix}/logs/export?${buildQuery({ source, q: search, format })}`;
|
|
13445
|
+
const pageUrl = (target) => `?${buildQuery({ source, q: search, page: target })}`;
|
|
13446
|
+
html(
|
|
13447
|
+
res,
|
|
13448
|
+
renderLogsPage(context(req, state.session, state.visible), {
|
|
13449
|
+
sources: LOG_SOURCES.map((value) => ({
|
|
13450
|
+
value,
|
|
13451
|
+
label: value === "500" ? "HTTP 500" : humanizeField(value),
|
|
13452
|
+
selected: value === source
|
|
13453
|
+
})),
|
|
13454
|
+
query: search,
|
|
13455
|
+
rows: entries.slice(start, start + LOG_PAGE_SIZE).map((entry) => ({
|
|
13456
|
+
level: entry.level,
|
|
13457
|
+
timestamp: entry.timestamp,
|
|
13458
|
+
logger: entry.logger,
|
|
13459
|
+
message: entry.message,
|
|
13460
|
+
stack: entry.stack,
|
|
13461
|
+
context: Object.entries(entry.context).map(([key, value]) => ({
|
|
13462
|
+
key,
|
|
13463
|
+
value: typeof value === "string" ? value : JSON.stringify(value)
|
|
13464
|
+
}))
|
|
13465
|
+
})),
|
|
13466
|
+
total: entries.length,
|
|
13467
|
+
page: page2,
|
|
13468
|
+
pages,
|
|
13469
|
+
prevUrl: page2 > 1 ? pageUrl(page2 - 1) : null,
|
|
13470
|
+
nextUrl: page2 < pages ? pageUrl(page2 + 1) : null,
|
|
13471
|
+
exportMarkdownUrl: exportUrl("md"),
|
|
13472
|
+
exportJsonUrl: exportUrl("json"),
|
|
13473
|
+
exportMax: LOG_EXPORT_MAX
|
|
13474
|
+
})
|
|
13475
|
+
);
|
|
13476
|
+
})
|
|
13477
|
+
);
|
|
13478
|
+
router.get(
|
|
13479
|
+
`${prefix}/logs/export`,
|
|
13480
|
+
guarded(async (req, res) => {
|
|
13481
|
+
const state = await authenticate(req, res);
|
|
13482
|
+
if (state === null) return;
|
|
13483
|
+
const format = queryString(req.query.format) === "json" ? "json" : "md";
|
|
13484
|
+
const { entries, source, search } = await readLogs(req, logDir);
|
|
13485
|
+
const window = entries.slice(0, LOG_EXPORT_MAX);
|
|
13486
|
+
const payload = format === "json" ? renderLogEntriesJson(window) : renderLogEntriesMarkdown(window, {
|
|
13487
|
+
source,
|
|
13488
|
+
query: search,
|
|
13489
|
+
total: entries.length
|
|
13490
|
+
});
|
|
13491
|
+
res.status(200).type(format === "json" ? "application/json" : "text/markdown; charset=utf-8").set("content-disposition", `attachment; filename="logs.${format}"`).send(payload);
|
|
13492
|
+
})
|
|
13493
|
+
);
|
|
13494
|
+
}
|
|
13495
|
+
if (options.sqlConsole !== void 0) {
|
|
13496
|
+
const console_ = options.sqlConsole;
|
|
13497
|
+
const policy = console_.policy ?? {};
|
|
13498
|
+
const dialect = console_.dialect ?? "postgresql";
|
|
13499
|
+
const capabilities = policy.capabilities ?? [SqlCapability.READ];
|
|
13500
|
+
const maxRows = policy.maxRows ?? 200;
|
|
13501
|
+
router.get(
|
|
13502
|
+
`${prefix}/sql`,
|
|
13503
|
+
guarded(async (req, res) => {
|
|
13504
|
+
const state = await authenticate(req, res);
|
|
13505
|
+
if (state === null) return;
|
|
13506
|
+
html(
|
|
13507
|
+
res,
|
|
13508
|
+
renderSqlPage(context(req, state.session, state.visible), {
|
|
13509
|
+
sql: "",
|
|
13510
|
+
capabilities: [...capabilities],
|
|
13511
|
+
error: null,
|
|
13512
|
+
columns: [],
|
|
13513
|
+
rows: [],
|
|
13514
|
+
rowCount: null,
|
|
13515
|
+
truncated: false,
|
|
13516
|
+
durationMs: null
|
|
13517
|
+
})
|
|
13518
|
+
);
|
|
13519
|
+
})
|
|
13520
|
+
);
|
|
13521
|
+
router.post(
|
|
13522
|
+
`${prefix}/sql`,
|
|
13523
|
+
guarded(async (req, res) => {
|
|
13524
|
+
const state = await authenticate(req, res);
|
|
13525
|
+
if (state === null) return;
|
|
13526
|
+
if (!checkCsrf(req, res, state)) return;
|
|
13527
|
+
const body = req.body;
|
|
13528
|
+
const sql5 = typeof body.sql === "string" ? body.sql : "";
|
|
13529
|
+
const principal = backend.displayName(state.principal);
|
|
13530
|
+
const render = (view) => {
|
|
13531
|
+
html(
|
|
13532
|
+
res,
|
|
13533
|
+
renderSqlPage(context(req, state.session, state.visible), {
|
|
13534
|
+
sql: sql5,
|
|
13535
|
+
capabilities: [...capabilities],
|
|
13536
|
+
...view
|
|
13537
|
+
}),
|
|
13538
|
+
view.error === null ? 200 : 400
|
|
13539
|
+
);
|
|
13540
|
+
};
|
|
13541
|
+
const parser = await loadSqlParser();
|
|
13542
|
+
const analysis = analyzeSql(sql5, dialect, parser);
|
|
13543
|
+
const verdict = checkSqlPolicy(analysis, policy);
|
|
13544
|
+
if (!verdict.allowed) {
|
|
13545
|
+
await audit(console_.onAudit, {
|
|
13546
|
+
sql: sql5,
|
|
13547
|
+
principal,
|
|
13548
|
+
allowed: false,
|
|
13549
|
+
reason: verdict.reason,
|
|
13550
|
+
analysis,
|
|
13551
|
+
durationMs: null,
|
|
13552
|
+
rowCount: null
|
|
13553
|
+
});
|
|
13554
|
+
render({
|
|
13555
|
+
error: verdict.reason,
|
|
13556
|
+
columns: [],
|
|
13557
|
+
rows: [],
|
|
13558
|
+
rowCount: null,
|
|
13559
|
+
truncated: false,
|
|
13560
|
+
durationMs: null
|
|
13561
|
+
});
|
|
13562
|
+
return;
|
|
13563
|
+
}
|
|
13564
|
+
const started = Date.now();
|
|
13565
|
+
let rows;
|
|
13566
|
+
try {
|
|
13567
|
+
rows = console_.run === void 0 ? await state.dbSession.raw(sql5).all() : await console_.run(sql5, state.dbSession);
|
|
13568
|
+
} catch (error) {
|
|
13569
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13570
|
+
await audit(console_.onAudit, {
|
|
13571
|
+
sql: sql5,
|
|
13572
|
+
principal,
|
|
13573
|
+
allowed: true,
|
|
13574
|
+
reason: message,
|
|
13575
|
+
analysis,
|
|
13576
|
+
durationMs: Date.now() - started,
|
|
13577
|
+
rowCount: null
|
|
13578
|
+
});
|
|
13579
|
+
render({
|
|
13580
|
+
error: message,
|
|
13581
|
+
columns: [],
|
|
13582
|
+
rows: [],
|
|
13583
|
+
rowCount: null,
|
|
13584
|
+
truncated: false,
|
|
13585
|
+
durationMs: null
|
|
13586
|
+
});
|
|
13587
|
+
return;
|
|
13588
|
+
}
|
|
13589
|
+
const durationMs = Date.now() - started;
|
|
13590
|
+
await audit(console_.onAudit, {
|
|
13591
|
+
sql: sql5,
|
|
13592
|
+
principal,
|
|
13593
|
+
allowed: true,
|
|
13594
|
+
reason: null,
|
|
13595
|
+
analysis,
|
|
13596
|
+
durationMs,
|
|
13597
|
+
rowCount: rows.length
|
|
13598
|
+
});
|
|
13599
|
+
const window = rows.slice(0, maxRows);
|
|
13600
|
+
const columns = window.length === 0 ? [] : Object.keys(window[0] ?? {});
|
|
13601
|
+
render({
|
|
13602
|
+
error: null,
|
|
13603
|
+
columns,
|
|
13604
|
+
rows: window.map(
|
|
13605
|
+
(row) => columns.map((column7) => formatCellValue(row[column7]))
|
|
13606
|
+
),
|
|
13607
|
+
rowCount: rows.length,
|
|
13608
|
+
truncated: rows.length > window.length,
|
|
13609
|
+
durationMs
|
|
13610
|
+
});
|
|
13611
|
+
})
|
|
13612
|
+
);
|
|
13613
|
+
}
|
|
13614
|
+
if (options.tasks !== void 0) {
|
|
13615
|
+
const tasks = options.tasks;
|
|
13616
|
+
router.get(
|
|
13617
|
+
`${prefix}/tasks`,
|
|
13618
|
+
guarded(async (req, res) => {
|
|
13619
|
+
const state = await authenticate(req, res);
|
|
13620
|
+
if (state === null) return;
|
|
13621
|
+
const inventory = tasks.manager === void 0 ? null : tasks.manager.inventory().map((entry) => ({
|
|
13622
|
+
name: entry.name,
|
|
13623
|
+
description: entry.description ?? "",
|
|
13624
|
+
schedule: entry.schedule ?? "\u2014"
|
|
13625
|
+
}));
|
|
13626
|
+
let runs = null;
|
|
13627
|
+
if (tasks.jobs !== void 0) {
|
|
13628
|
+
const store = tasks.jobs(state.dbSession);
|
|
13629
|
+
const page2 = Math.max(1, Number.parseInt(queryString(req.query.page), 10) || 1);
|
|
13630
|
+
const status = queryString(req.query.status);
|
|
13631
|
+
const name = queryString(req.query.task);
|
|
13632
|
+
const result = await store.list({
|
|
13633
|
+
page: page2,
|
|
13634
|
+
pageSize: TASK_PAGE_SIZE,
|
|
13635
|
+
...name === "" ? {} : { name },
|
|
13636
|
+
...JOB_STATUSES.includes(status) ? { status } : {}
|
|
13637
|
+
});
|
|
13638
|
+
const pageUrl = (target) => `?${buildQuery({ status, task: name, page: target })}`;
|
|
13639
|
+
runs = {
|
|
13640
|
+
rows: result.items.map((row) => ({
|
|
13641
|
+
id: String(row.id),
|
|
13642
|
+
name: String(row.name ?? ""),
|
|
13643
|
+
status: String(row.status ?? ""),
|
|
13644
|
+
startedAt: formatCellValue(row.startedAt),
|
|
13645
|
+
finishedAt: formatCellValue(row.finishedAt),
|
|
13646
|
+
attempts: formatCellValue(row.attempts),
|
|
13647
|
+
url: `${prefix}/tasks/${String(row.id)}`
|
|
13648
|
+
})),
|
|
13649
|
+
total: result.total,
|
|
13650
|
+
page: result.page,
|
|
13651
|
+
pages: result.pages,
|
|
13652
|
+
prevUrl: result.page > 1 ? pageUrl(result.page - 1) : null,
|
|
13653
|
+
nextUrl: result.page < result.pages ? pageUrl(result.page + 1) : null,
|
|
13654
|
+
statuses: ["", ...JOB_STATUSES].map((value) => ({
|
|
13655
|
+
value,
|
|
13656
|
+
label: value === "" ? "\u2014 any \u2014" : humanizeField(value),
|
|
13657
|
+
selected: value === status
|
|
13658
|
+
})),
|
|
13659
|
+
nameQuery: name
|
|
13660
|
+
};
|
|
13661
|
+
}
|
|
13662
|
+
html(
|
|
13663
|
+
res,
|
|
13664
|
+
renderTasksPage(context(req, state.session, state.visible), {
|
|
13665
|
+
inventory,
|
|
13666
|
+
runs
|
|
13667
|
+
})
|
|
13668
|
+
);
|
|
13669
|
+
})
|
|
13670
|
+
);
|
|
13671
|
+
if (tasks.jobs !== void 0) {
|
|
13672
|
+
const jobs = tasks.jobs;
|
|
13673
|
+
router.get(
|
|
13674
|
+
`${prefix}/tasks/:id`,
|
|
13675
|
+
guarded(async (req, res) => {
|
|
13676
|
+
const state = await authenticate(req, res);
|
|
13677
|
+
if (state === null) return;
|
|
13678
|
+
const row = await jobs(state.dbSession).get(String(req.params.id));
|
|
13679
|
+
if (row === null) {
|
|
13680
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
13681
|
+
return;
|
|
13682
|
+
}
|
|
13683
|
+
const status = String(row.status ?? "");
|
|
13684
|
+
html(
|
|
13685
|
+
res,
|
|
13686
|
+
renderTaskDetailPage(context(req, state.session, state.visible), {
|
|
13687
|
+
id: String(row.id),
|
|
13688
|
+
name: String(row.name ?? ""),
|
|
13689
|
+
status,
|
|
13690
|
+
fields: [
|
|
13691
|
+
{ label: "Created At", value: formatCellValue(row.createdAt) },
|
|
13692
|
+
{ label: "Started At", value: formatCellValue(row.startedAt) },
|
|
13693
|
+
{ label: "Finished At", value: formatCellValue(row.finishedAt) },
|
|
13694
|
+
{ label: "Attempts", value: formatCellValue(row.attempts) }
|
|
13695
|
+
],
|
|
13696
|
+
payload: jsonBlock(row.payload),
|
|
13697
|
+
result: jsonBlock(row.result),
|
|
13698
|
+
error: typeof row.error === "string" && row.error !== "" ? row.error : null,
|
|
13699
|
+
backUrl: `${prefix}/tasks`,
|
|
13700
|
+
cancelUrl: TERMINAL_JOB_STATUSES.includes(status) ? null : `${prefix}/tasks/${String(row.id)}/cancel`
|
|
13701
|
+
})
|
|
13702
|
+
);
|
|
13703
|
+
})
|
|
13704
|
+
);
|
|
13705
|
+
router.post(
|
|
13706
|
+
`${prefix}/tasks/:id/cancel`,
|
|
13707
|
+
guarded(async (req, res) => {
|
|
13708
|
+
const state = await authenticate(req, res);
|
|
13709
|
+
if (state === null) return;
|
|
13710
|
+
if (!checkCsrf(req, res, state)) return;
|
|
13711
|
+
const id = String(req.params.id);
|
|
13712
|
+
const cancelled = await jobs(state.dbSession).cancel(id);
|
|
13713
|
+
res.redirect(
|
|
13714
|
+
`${prefix}/tasks/${id}?${buildQuery({
|
|
13715
|
+
flash: cancelled ? "The run was asked to stop." : "That run had already finished.",
|
|
13716
|
+
level: cancelled ? "success" : "warning"
|
|
13717
|
+
})}`
|
|
13718
|
+
);
|
|
13719
|
+
})
|
|
13720
|
+
);
|
|
13721
|
+
}
|
|
13722
|
+
}
|
|
12798
13723
|
router.get(
|
|
12799
13724
|
`${prefix}/m/:slug`,
|
|
12800
13725
|
guarded(async (req, res) => {
|
|
@@ -13105,8 +14030,8 @@ function makeAdminRouter(site, options) {
|
|
|
13105
14030
|
res.status(404).json({ options: [] });
|
|
13106
14031
|
return;
|
|
13107
14032
|
}
|
|
13108
|
-
const
|
|
13109
|
-
const table =
|
|
14033
|
+
const column7 = adminColumns(admin.model)[field];
|
|
14034
|
+
const table = column7 === void 0 ? null : foreignKeyTable(column7);
|
|
13110
14035
|
const referenced = table === null ? null : site.get(table);
|
|
13111
14036
|
if (referenced === null) {
|
|
13112
14037
|
res.json({ options: [] });
|
|
@@ -13415,7 +14340,7 @@ function makeAdminRouter(site, options) {
|
|
|
13415
14340
|
formAction: "",
|
|
13416
14341
|
rows: visible.map((child) => ({
|
|
13417
14342
|
key: String(child[childAdmin?.identityField ?? "id"]),
|
|
13418
|
-
cells: columns.map((
|
|
14343
|
+
cells: columns.map((column7) => formatCellValue(child[column7])),
|
|
13419
14344
|
fields: [],
|
|
13420
14345
|
url: childAdmin === null ? null : `${prefix}/m/${inline.slug}/${String(child[childAdmin.identityField])}`
|
|
13421
14346
|
})),
|
|
@@ -13512,6 +14437,15 @@ function makeAdminRouter(site, options) {
|
|
|
13512
14437
|
const row = await admin.repository(dbSession).first({ [admin.identityField]: identity });
|
|
13513
14438
|
return row ?? null;
|
|
13514
14439
|
}
|
|
14440
|
+
async function readLogs(req, dir) {
|
|
14441
|
+
const requested = queryString(req.query.source);
|
|
14442
|
+
const source = LOG_SOURCES.includes(requested) ? requested : "all";
|
|
14443
|
+
const search = queryString(req.query.q);
|
|
14444
|
+
const raw = await readLogEntries(dir, source);
|
|
14445
|
+
const entries = filterLogEntries(raw.map(toLogEntry), search);
|
|
14446
|
+
entries.reverse();
|
|
14447
|
+
return { entries, source, search };
|
|
14448
|
+
}
|
|
13515
14449
|
function renderImport(req, state, admin, error, created, rowErrors) {
|
|
13516
14450
|
return renderImportPage(context(req, state.session, state.visible), {
|
|
13517
14451
|
title: admin.verboseNamePlural(),
|
|
@@ -13538,8 +14472,8 @@ function makeAdminRouter(site, options) {
|
|
|
13538
14472
|
for (const field of admin.uploadFields) {
|
|
13539
14473
|
const file = files.find((candidate) => candidate.field === field);
|
|
13540
14474
|
if (file === void 0) {
|
|
13541
|
-
const
|
|
13542
|
-
if (creating &&
|
|
14475
|
+
const column7 = columns[field];
|
|
14476
|
+
if (creating && column7 !== void 0 && !isColumnOptional(column7)) {
|
|
13543
14477
|
errors[field] = "This field is required.";
|
|
13544
14478
|
}
|
|
13545
14479
|
continue;
|
|
@@ -13571,14 +14505,14 @@ function makeAdminRouter(site, options) {
|
|
|
13571
14505
|
});
|
|
13572
14506
|
const displayed = admin.listDisplayNames();
|
|
13573
14507
|
const sort = {};
|
|
13574
|
-
for (const
|
|
13575
|
-
if (!(
|
|
13576
|
-
const active = (query.sortColumn ?? admin.orderKey) ===
|
|
14508
|
+
for (const column7 of displayed) {
|
|
14509
|
+
if (!(column7 in columns)) continue;
|
|
14510
|
+
const active = (query.sortColumn ?? admin.orderKey) === column7;
|
|
13577
14511
|
const nextAscending = active ? !query.ascending : true;
|
|
13578
|
-
sort[
|
|
14512
|
+
sort[column7] = {
|
|
13579
14513
|
url: `?${buildQuery({
|
|
13580
14514
|
...query.baseQuery,
|
|
13581
|
-
sort:
|
|
14515
|
+
sort: column7,
|
|
13582
14516
|
dir: nextAscending ? "asc" : "desc"
|
|
13583
14517
|
})}`,
|
|
13584
14518
|
active,
|
|
@@ -13599,7 +14533,7 @@ function makeAdminRouter(site, options) {
|
|
|
13599
14533
|
const identity = String(row[admin.identityField]);
|
|
13600
14534
|
return {
|
|
13601
14535
|
identity,
|
|
13602
|
-
cells: displayed.map((
|
|
14536
|
+
cells: displayed.map((column7) => formatCellValue(row[column7])),
|
|
13603
14537
|
url: `${prefix}/m/${admin.slug()}/${identity}`
|
|
13604
14538
|
};
|
|
13605
14539
|
}),
|
|
@@ -13645,9 +14579,9 @@ function makeAdminRouter(site, options) {
|
|
|
13645
14579
|
}
|
|
13646
14580
|
const filterViews = [];
|
|
13647
14581
|
for (const field of admin.listFilter) {
|
|
13648
|
-
const
|
|
13649
|
-
if (
|
|
13650
|
-
const spec = filterForColumn(
|
|
14582
|
+
const column7 = columns[field];
|
|
14583
|
+
if (column7 === void 0) continue;
|
|
14584
|
+
const spec = filterForColumn(column7);
|
|
13651
14585
|
if (spec.kind === "daterange") {
|
|
13652
14586
|
const from = queryString(req.query[`filter_${field}_from`]);
|
|
13653
14587
|
const to = queryString(req.query[`filter_${field}_to`]);
|
|
@@ -13666,12 +14600,12 @@ function makeAdminRouter(site, options) {
|
|
|
13666
14600
|
});
|
|
13667
14601
|
continue;
|
|
13668
14602
|
}
|
|
13669
|
-
const related = await relatedOptions(
|
|
14603
|
+
const related = await relatedOptions(column7, dbSession);
|
|
13670
14604
|
const options2 = related ?? spec.options;
|
|
13671
14605
|
const value = queryString(req.query[`filter_${field}`]);
|
|
13672
14606
|
if (value !== "") {
|
|
13673
14607
|
conditions.push({
|
|
13674
|
-
[field]:
|
|
14608
|
+
[field]: column7.type.kind === "boolean" ? value === "true" : value
|
|
13675
14609
|
});
|
|
13676
14610
|
}
|
|
13677
14611
|
filterViews.push({
|
|
@@ -13689,8 +14623,8 @@ function makeAdminRouter(site, options) {
|
|
|
13689
14623
|
});
|
|
13690
14624
|
}
|
|
13691
14625
|
const searchable = admin.searchFields.filter((field) => {
|
|
13692
|
-
const
|
|
13693
|
-
return
|
|
14626
|
+
const column7 = columns[field];
|
|
14627
|
+
return column7 !== void 0 && isSearchableColumn(column7);
|
|
13694
14628
|
});
|
|
13695
14629
|
if (search !== "" && searchable.length > 0) {
|
|
13696
14630
|
conditions.push(
|
|
@@ -13723,8 +14657,8 @@ function makeAdminRouter(site, options) {
|
|
|
13723
14657
|
lens: lens?.slug ?? ""
|
|
13724
14658
|
};
|
|
13725
14659
|
}
|
|
13726
|
-
async function relatedOptions(
|
|
13727
|
-
const table = foreignKeyTable(
|
|
14660
|
+
async function relatedOptions(column7, dbSession) {
|
|
14661
|
+
const table = foreignKeyTable(column7);
|
|
13728
14662
|
if (table === null) return null;
|
|
13729
14663
|
const referenced = site.get(table);
|
|
13730
14664
|
if (referenced === null) return null;
|
|
@@ -13739,9 +14673,9 @@ function makeAdminRouter(site, options) {
|
|
|
13739
14673
|
const options2 = {};
|
|
13740
14674
|
for (const field of Object.keys(foreignKeyFields(admin))) {
|
|
13741
14675
|
if (admin.autocompleteFields.includes(field)) continue;
|
|
13742
|
-
const
|
|
13743
|
-
if (
|
|
13744
|
-
const related = await relatedOptions(
|
|
14676
|
+
const column7 = columns[field];
|
|
14677
|
+
if (column7 === void 0) continue;
|
|
14678
|
+
const related = await relatedOptions(column7, dbSession);
|
|
13745
14679
|
if (related !== null) options2[field] = related;
|
|
13746
14680
|
}
|
|
13747
14681
|
return options2;
|
|
@@ -13760,8 +14694,8 @@ function makeAdminRouter(site, options) {
|
|
|
13760
14694
|
for (const field of admin.autocompleteFields) {
|
|
13761
14695
|
const value = row[field];
|
|
13762
14696
|
if (value === null || value === void 0 || value === "") continue;
|
|
13763
|
-
const
|
|
13764
|
-
const table =
|
|
14697
|
+
const column7 = columns[field];
|
|
14698
|
+
const table = column7 === void 0 ? null : foreignKeyTable(column7);
|
|
13765
14699
|
const referenced = table === null ? null : site.get(table);
|
|
13766
14700
|
if (referenced === null) continue;
|
|
13767
14701
|
const related = await referenced.repository(dbSession).first({ [referenced.identityField]: value });
|
|
@@ -13800,6 +14734,21 @@ function inlineFieldNames(childAdmin, inline) {
|
|
|
13800
14734
|
function inlineFields(childAdmin, names, key, values, errors) {
|
|
13801
14735
|
return buildFormFields(childAdmin, { values, errors }).filter((field) => names.includes(field.name)).map((field) => ({ ...field, name: `row.${key}.${field.name}` }));
|
|
13802
14736
|
}
|
|
14737
|
+
function jsonBlock(value) {
|
|
14738
|
+
if (value === null || value === void 0) return null;
|
|
14739
|
+
if (typeof value === "object" && Object.keys(value).length === 0) return null;
|
|
14740
|
+
return JSON.stringify(value, null, 2);
|
|
14741
|
+
}
|
|
14742
|
+
async function audit(hook, entry) {
|
|
14743
|
+
if (hook === void 0) return;
|
|
14744
|
+
try {
|
|
14745
|
+
await hook(entry);
|
|
14746
|
+
} catch (error) {
|
|
14747
|
+
logger2.error("Admin SQL audit hook failed", {
|
|
14748
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14749
|
+
});
|
|
14750
|
+
}
|
|
14751
|
+
}
|
|
13803
14752
|
function flagAllows(admin, action) {
|
|
13804
14753
|
if (action === AdminPermission.CREATE) return admin.canCreate;
|
|
13805
14754
|
if (action === AdminPermission.EDIT) return admin.canEdit;
|
|
@@ -13953,7 +14902,7 @@ function csvField(value) {
|
|
|
13953
14902
|
function toCsv(columns, rows) {
|
|
13954
14903
|
const lines = [columns.map(csvField).join(",")];
|
|
13955
14904
|
for (const row of rows) {
|
|
13956
|
-
lines.push(columns.map((
|
|
14905
|
+
lines.push(columns.map((column7) => csvField(exportValue(row[column7]))).join(","));
|
|
13957
14906
|
}
|
|
13958
14907
|
return `${lines.join("\r\n")}\r
|
|
13959
14908
|
`;
|
|
@@ -13961,7 +14910,7 @@ function toCsv(columns, rows) {
|
|
|
13961
14910
|
function toJson(columns, rows) {
|
|
13962
14911
|
return JSON.stringify(
|
|
13963
14912
|
rows.map(
|
|
13964
|
-
(row) => Object.fromEntries(columns.map((
|
|
14913
|
+
(row) => Object.fromEntries(columns.map((column7) => [column7, exportValue(row[column7])]))
|
|
13965
14914
|
),
|
|
13966
14915
|
null,
|
|
13967
14916
|
2
|
|
@@ -15886,59 +16835,6 @@ function makeToolSpecRouter(spec, options = {}) {
|
|
|
15886
16835
|
});
|
|
15887
16836
|
return router;
|
|
15888
16837
|
}
|
|
15889
|
-
function filesFor(dir, source) {
|
|
15890
|
-
if (source === "500") return [join(dir, HTTP_500_LOG_FILE)];
|
|
15891
|
-
if (source === "all") return Object.values(LEVEL_LOG_FILES).map((f) => join(dir, f));
|
|
15892
|
-
return [join(dir, LEVEL_LOG_FILES[source])];
|
|
15893
|
-
}
|
|
15894
|
-
async function readEntries(files) {
|
|
15895
|
-
const entries = [];
|
|
15896
|
-
for (const file of files) {
|
|
15897
|
-
let content;
|
|
15898
|
-
try {
|
|
15899
|
-
content = await readFile(file, "utf8");
|
|
15900
|
-
} catch {
|
|
15901
|
-
continue;
|
|
15902
|
-
}
|
|
15903
|
-
for (const line of content.split("\n")) {
|
|
15904
|
-
if (!line.trim()) continue;
|
|
15905
|
-
try {
|
|
15906
|
-
entries.push(JSON.parse(line));
|
|
15907
|
-
} catch {
|
|
15908
|
-
}
|
|
15909
|
-
}
|
|
15910
|
-
}
|
|
15911
|
-
return entries;
|
|
15912
|
-
}
|
|
15913
|
-
function makeLogsRouter(options) {
|
|
15914
|
-
const router = Router();
|
|
15915
|
-
const path = options.path ?? "/logs";
|
|
15916
|
-
const guards = options.guards ?? [];
|
|
15917
|
-
const handler = (req, res, next) => {
|
|
15918
|
-
const source = req.query.source ?? "all";
|
|
15919
|
-
const validSources = ["all", "debug", "info", "warning", "error", "500"];
|
|
15920
|
-
if (!validSources.includes(source)) {
|
|
15921
|
-
res.status(422).json({ detail: "Invalid log source", code: "VALIDATION_ERROR", details: {} });
|
|
15922
|
-
return;
|
|
15923
|
-
}
|
|
15924
|
-
const page2 = Math.max(1, Number(req.query.page) || 1);
|
|
15925
|
-
const pageSize = Math.min(500, Math.max(1, Number(req.query.pageSize) || 50));
|
|
15926
|
-
readEntries(filesFor(options.dir, source)).then((entries) => {
|
|
15927
|
-
entries.reverse();
|
|
15928
|
-
const total = entries.length;
|
|
15929
|
-
const start = (page2 - 1) * pageSize;
|
|
15930
|
-
res.json({
|
|
15931
|
-
items: entries.slice(start, start + pageSize),
|
|
15932
|
-
total,
|
|
15933
|
-
page: page2,
|
|
15934
|
-
pageSize,
|
|
15935
|
-
pages: Math.ceil(total / pageSize)
|
|
15936
|
-
});
|
|
15937
|
-
}).catch(next);
|
|
15938
|
-
};
|
|
15939
|
-
router.get(path, ...guards, handler);
|
|
15940
|
-
return router;
|
|
15941
|
-
}
|
|
15942
16838
|
function createTestDatabase(models) {
|
|
15943
16839
|
const driver = NodeSqliteDriver.open(":memory:");
|
|
15944
16840
|
for (const model of models) {
|
|
@@ -15970,6 +16866,6 @@ async function withTestDatabase(models, fn) {
|
|
|
15970
16866
|
}
|
|
15971
16867
|
}
|
|
15972
16868
|
|
|
15973
|
-
export { ADMIN_CSS, ActivationService, AdminJsonSite, AdminModel, AdminPermission, AdminSessionStore, AdminSite, AppException, AttemptThrottle, AuditAction, BaseAuditLogModel, BaseController, BaseModel, BaseOAuthClient, BaseOutboxModel, BaseService, BaseUserModel, BaseUserRefreshTokenModel, BaseUserTokenModel, CEP_PATTERN, CNPJ_PATTERN, CPF_PATTERN, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, CircuitOpenError, CompositeFeatureFlagBackend, ConflictException, DEFAULT_DOCS_FAVICON, DEFAULT_LOCALE, EmailProvider, EmailUtils, EnvFeatureFlagBackend, EventStream, ExpiredTokenException, FeatureFlags, ForbiddenException, GitHubOAuthClient, GoogleOAuthClient, GracefulShutdown, HTTPClient, HTTP_500_LOG_FILE, HTTP_500_MARKER, HttpMetrics, IDEMPOTENCY_HEADER, InvalidTokenException, JSONLogger, JWTUtils, LEVEL_LOG_FILES, LocalUploadStorage, MemoryBroker, MemoryCacheManager, MemoryFeatureFlagBackend, MemoryIdempotencyStore, MemoryRateLimitStore, MemorySessionStore, MemoryThrottleBackend, MessageCatalog, MessagingHub, MetricsUtils, MfaService, MultipartLimitError, NotFoundException, OAuthError, OIDCProvider, OutboxRelay, OutboxStatus, PHONE_BR_PATTERN, PasswordResetService, REDOC_CDN_URL, REQUEST_ID_HEADER, RabbitBroker, RedisCacheManager, RedisIdempotencyStore, RedisRateLimitStore, RedisSSEBroker, RedisSessionStore, Region, RetryPolicy, S3UploadStorage, SSEBroker, ServerSentEvent, SessionService, TOTPHelper, TaskManager, TelegramProvider, TenantScopedRepository, TooManyRequestsException, TwilioSmsProvider, UF, UnauthorizedException, UserAuthService, UserModelAuthBackend, UserTokenPurpose, ValidationException, WebPushDispatcher, WebPushError, WebPushGoneError, WebSocketHub, WebhookSignatureVerifier, WhatsAppProvider, activationSchema, addLogSink, adminAction, adminColumns, adminInline, adminLens, adminThemeCss, attachWebSocketHub, authResponseSchema, authSettingsShape, backupDatabase, bearerToken, bodySizeLimitMiddleware, broadcastText, buildContentDisposition, buildFormFields, buildPaginationLinkHeader, cached2 as cached, cepField, citiesByUf, cnpjField, coerceFlag, configureFileLogging, configureLogging, cpfField, cpfOrCnpjField, createApp, createOpenApiRegistry, createTestDatabase, createdByColumn, csrfMiddleware, csrfTokenMatches, cursorPaginationFilterSchema, cursorPaginationSchema, decodeCursor, defaultMessageCatalog, defineEnum, deletedAtColumn, diffSnapshots, emailSettingsShape, encodeCursor, envList, escapeHtml, filterForColumn, foreignKeyFields, foreignKeyLabel, foreignKeyTable, formatCellValue, formatFieldValue, generateCsrfToken, generateOAuthState, generateOpaqueToken, generateOpenApiDocument, getAuth, getClientIp, getConditions, getPaginationConditions, getRequestId, getState, groupInlineSubmission, hashOpaqueToken, humanizeField, idempotencyMiddleware, inboundMessageSchema, isColumnOptional, isMultipart, isSearchableColumn, isValidCep, isValidCity, isValidCnpj, isValidCpf, isValidCpfCnpj, isValidPhoneBr, isValidUf, jwtSettingsShape, keyByHeader, keyByIp, keyByJwtClaim, keyByJwtSubject, listStates, logEntrySchema, logSettingsShape, loginSchema, makeAdminJsonRouter, makeAdminRouter, makeAppExceptionHandler, makeAuthRouter, makeFlagGuard, makeHealthRouter, makeJwtAuthMiddleware, makeLogsRouter, makeMetricsRouter, makeSessionMiddleware, makeToolSpecRouter, makeTwilioWebhookRouter, makeUnhandledExceptionHandler, makeWhatsAppWebhookRouter, metricCard, mfaChallengeSchema, mfaCodeSchema, mfaEnrollResponseSchema, minioSettingsShape, modifyDict, mountOpenApiJson, mountRedoc, mountSwaggerUi, normalizeCep, normalizeCnpj, normalizeCpf, normalizeCpfCnpj, normalizePhoneBr, normalizeUf, notFoundHandler, onlyDigits, paginationFilterSchema, paginationSchema, parseAcceptLanguage, parseCookies, parseCsv, parseFormBody, parseMultipart, partitionTotal, passwordResetConfirmSchema, passwordResetRequestSchema, phoneBrField, prometheusMiddleware, rabbitmqSettingsShape, rateLimitMiddleware, redisSettingsShape, refreshSchema, registerExceptionHandlers, renderAuthResultPage, renderDashboardPage, renderDetailPage, renderFormPage, renderImportPage, renderLayout, renderListPage, renderLoginPage, renderMfaPage, renderPasswordResetFormPage, requestIdMiddleware, requestTracingMiddleware, requireRoles, resolveAdminTheme, resolveDownloadPath, resolveRedocBundle, runServer, runWithRequestContext, sendBytesDownload, sendFileDownload, sessionCookie, sessionSettingsShape, setRequestId, signupSchema, snapshot, sseResponse, statesByRegion, syncFilterSchema, syncPaginationSchema, tableNameFor, toUtc, tokenFromUrl, tokenPairSchema, tokenSettingsShape, trendDirection, trendPercent, ufField, updatedByColumn, uploadSettingsShape, userPublicSchema, utcnow, validateTwilioSignature, verifyOpaqueToken, webPushKeysSchema, webPushPayloadSchema, webPushSettingsShape, webPushSubscriptionSchema, webSocketSettingsShape, widgetForColumn, withTestDatabase, wrapWithSlowQueryLog, wsEnvelopeSchema };
|
|
16869
|
+
export { ADMIN_CSS, ActivationService, AdminJsonSite, AdminModel, AdminPermission, AdminSessionStore, AdminSite, AppException, AttemptThrottle, AuditAction, BaseAuditLogModel, BaseController, BaseJobModel, BaseModel, BaseOAuthClient, BaseOutboxModel, BaseService, BaseUserModel, BaseUserRefreshTokenModel, BaseUserTokenModel, CEP_PATTERN, CNPJ_PATTERN, CPF_PATTERN, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, CircuitOpenError, CompositeFeatureFlagBackend, ConflictException, DEFAULT_DOCS_FAVICON, DEFAULT_LOCALE, EmailProvider, EmailUtils, EnvFeatureFlagBackend, EventStream, ExpiredTokenException, FeatureFlags, ForbiddenException, GitHubOAuthClient, GoogleOAuthClient, GracefulShutdown, HTTPClient, HTTP_500_LOG_FILE, HTTP_500_MARKER, HttpMetrics, IDEMPOTENCY_HEADER, InvalidTokenException, JSONLogger, JWTUtils, JobStatus, JobStore, LEVEL_LOG_FILES, LocalUploadStorage, MemoryBroker, MemoryCacheManager, MemoryFeatureFlagBackend, MemoryIdempotencyStore, MemoryRateLimitStore, MemorySessionStore, MemoryThrottleBackend, MessageCatalog, MessagingHub, MetricsUtils, MfaService, MultipartLimitError, NotFoundException, OAuthError, OIDCProvider, OutboxRelay, OutboxStatus, PHONE_BR_PATTERN, PasswordResetService, REDOC_CDN_URL, REQUEST_ID_HEADER, RabbitBroker, RedisCacheManager, RedisIdempotencyStore, RedisRateLimitStore, RedisSSEBroker, RedisSessionStore, Region, RetryPolicy, S3UploadStorage, SSEBroker, ServerSentEvent, SessionService, SqlCapability, TOTPHelper, TaskManager, TelegramProvider, TenantScopedRepository, TooManyRequestsException, TwilioSmsProvider, UF, UnauthorizedException, UserAuthService, UserModelAuthBackend, UserTokenPurpose, ValidationException, WebPushDispatcher, WebPushError, WebPushGoneError, WebSocketHub, WebhookSignatureVerifier, WhatsAppProvider, activationSchema, addLogSink, adminAction, adminColumns, adminInline, adminLens, adminThemeCss, analyzeSql, attachWebSocketHub, authResponseSchema, authSettingsShape, backupDatabase, bearerToken, bodySizeLimitMiddleware, broadcastText, buildContentDisposition, buildFormFields, buildPaginationLinkHeader, cached2 as cached, cepField, checkSqlPolicy, citiesByUf, cnpjField, coerceFlag, configureFileLogging, configureLogging, cpfField, cpfOrCnpjField, createApp, createOpenApiRegistry, createTestDatabase, createdByColumn, csrfMiddleware, csrfTokenMatches, cursorPaginationFilterSchema, cursorPaginationSchema, decodeCursor, defaultMessageCatalog, defineEnum, deletedAtColumn, diffSnapshots, emailSettingsShape, encodeCursor, envList, escapeHtml, filterForColumn, filterLogEntries, foreignKeyFields, foreignKeyLabel, foreignKeyTable, formatCellValue, formatFieldValue, generateCsrfToken, generateOAuthState, generateOpaqueToken, generateOpenApiDocument, getAuth, getClientIp, getConditions, getPaginationConditions, getRequestId, getState, groupInlineSubmission, hashOpaqueToken, humanizeField, idempotencyMiddleware, inboundMessageSchema, isColumnOptional, isMultipart, isSearchableColumn, isValidCep, isValidCity, isValidCnpj, isValidCpf, isValidCpfCnpj, isValidPhoneBr, isValidUf, jwtSettingsShape, keyByHeader, keyByIp, keyByJwtClaim, keyByJwtSubject, listStates, loadSqlParser, logEntrySchema, logSettingsShape, loginSchema, makeAdminJsonRouter, makeAdminRouter, makeAppExceptionHandler, makeAuthRouter, makeFlagGuard, makeHealthRouter, makeJwtAuthMiddleware, makeLogsRouter, makeMetricsRouter, makeSessionMiddleware, makeToolSpecRouter, makeTwilioWebhookRouter, makeUnhandledExceptionHandler, makeWhatsAppWebhookRouter, metricCard, mfaChallengeSchema, mfaCodeSchema, mfaEnrollResponseSchema, minioSettingsShape, modifyDict, mountOpenApiJson, mountRedoc, mountSwaggerUi, normalizeCep, normalizeCnpj, normalizeCpf, normalizeCpfCnpj, normalizePhoneBr, normalizeUf, notFoundHandler, onlyDigits, paginationFilterSchema, paginationSchema, parseAcceptLanguage, parseCookies, parseCsv, parseFormBody, parseMultipart, partitionTotal, passwordResetConfirmSchema, passwordResetRequestSchema, phoneBrField, prometheusMiddleware, rabbitmqSettingsShape, rateLimitMiddleware, readLogEntries, redisSettingsShape, refreshSchema, registerExceptionHandlers, renderAuthResultPage, renderDashboardPage, renderDetailPage, renderFormPage, renderImportPage, renderLayout, renderListPage, renderLogEntriesJson, renderLogEntriesMarkdown, renderLoginPage, renderLogsPage, renderMfaPage, renderPasswordResetFormPage, renderSqlPage, renderTaskDetailPage, renderTasksPage, requestIdMiddleware, requestTracingMiddleware, requireRoles, resolveAdminTheme, resolveDownloadPath, resolveRedocBundle, runServer, runWithRequestContext, sendBytesDownload, sendFileDownload, sessionCookie, sessionSettingsShape, setRequestId, signupSchema, snapshot, sseResponse, statesByRegion, syncFilterSchema, syncPaginationSchema, tableNameFor, toLogEntry, toUtc, tokenFromUrl, tokenPairSchema, tokenSettingsShape, trendDirection, trendPercent, ufField, updatedByColumn, uploadSettingsShape, userPublicSchema, utcnow, validateTwilioSignature, verifyOpaqueToken, webPushKeysSchema, webPushPayloadSchema, webPushSettingsShape, webPushSubscriptionSchema, webSocketSettingsShape, widgetForColumn, withTestDatabase, wrapWithSlowQueryLog, wsEnvelopeSchema };
|
|
15974
16870
|
//# sourceMappingURL=index.js.map
|
|
15975
16871
|
//# sourceMappingURL=index.js.map
|