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.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) {
|
|
@@ -9461,6 +9659,199 @@ function partitionTotal(partition) {
|
|
|
9461
9659
|
return partition.segments.reduce((total, segment) => total + segment.value, 0);
|
|
9462
9660
|
}
|
|
9463
9661
|
|
|
9662
|
+
// src/admin/logs.ts
|
|
9663
|
+
var LIFTED = /* @__PURE__ */ new Set(["level", "logger", "message", "timestamp", "stack"]);
|
|
9664
|
+
function toLogEntry(raw) {
|
|
9665
|
+
const context = {};
|
|
9666
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
9667
|
+
if (LIFTED.has(key) || value === null || value === void 0) continue;
|
|
9668
|
+
context[key] = value;
|
|
9669
|
+
}
|
|
9670
|
+
return {
|
|
9671
|
+
level: typeof raw.level === "string" ? raw.level : "info",
|
|
9672
|
+
logger: typeof raw.logger === "string" ? raw.logger : "",
|
|
9673
|
+
message: typeof raw.message === "string" ? raw.message : "",
|
|
9674
|
+
timestamp: typeof raw.timestamp === "string" ? raw.timestamp : "",
|
|
9675
|
+
stack: typeof raw.stack === "string" ? raw.stack : null,
|
|
9676
|
+
context,
|
|
9677
|
+
raw
|
|
9678
|
+
};
|
|
9679
|
+
}
|
|
9680
|
+
function filterLogEntries(entries, term) {
|
|
9681
|
+
const needle = term.trim().toLowerCase();
|
|
9682
|
+
if (needle === "") return entries;
|
|
9683
|
+
return entries.filter(
|
|
9684
|
+
(entry) => `${entry.message} ${entry.logger} ${entry.stack ?? ""}`.toLowerCase().includes(needle)
|
|
9685
|
+
);
|
|
9686
|
+
}
|
|
9687
|
+
function renderLogEntriesMarkdown(entries, options) {
|
|
9688
|
+
const lines = [
|
|
9689
|
+
"# Application logs",
|
|
9690
|
+
"",
|
|
9691
|
+
`- **Source:** \`${options.source}\``,
|
|
9692
|
+
`- **Search:** ${options.query === "" ? "_none_" : `\`${options.query}\``}`,
|
|
9693
|
+
`- **Exported:** ${entries.length} of ${options.total} matching record(s)`,
|
|
9694
|
+
""
|
|
9695
|
+
];
|
|
9696
|
+
if (entries.length < options.total) {
|
|
9697
|
+
lines.push(
|
|
9698
|
+
`> Truncated: the export is capped, so ${options.total - entries.length} older matching record(s) are not included.`,
|
|
9699
|
+
""
|
|
9700
|
+
);
|
|
9701
|
+
}
|
|
9702
|
+
for (const entry of entries) {
|
|
9703
|
+
lines.push(
|
|
9704
|
+
`## ${entry.level.toUpperCase()} \u2014 ${entry.message || "(no message)"}`,
|
|
9705
|
+
"",
|
|
9706
|
+
`- **When:** ${entry.timestamp || "unknown"}`,
|
|
9707
|
+
`- **Logger:** ${entry.logger || "unknown"}`
|
|
9708
|
+
);
|
|
9709
|
+
for (const [key, value] of Object.entries(entry.context)) {
|
|
9710
|
+
lines.push(
|
|
9711
|
+
`- **${key}:** ${typeof value === "string" ? value : JSON.stringify(value)}`
|
|
9712
|
+
);
|
|
9713
|
+
}
|
|
9714
|
+
lines.push("");
|
|
9715
|
+
if (entry.stack !== null) {
|
|
9716
|
+
lines.push("```text", entry.stack, "```", "");
|
|
9717
|
+
}
|
|
9718
|
+
}
|
|
9719
|
+
return lines.join("\n");
|
|
9720
|
+
}
|
|
9721
|
+
function renderLogEntriesJson(entries) {
|
|
9722
|
+
return JSON.stringify(
|
|
9723
|
+
entries.map((entry) => entry.raw),
|
|
9724
|
+
null,
|
|
9725
|
+
2
|
|
9726
|
+
);
|
|
9727
|
+
}
|
|
9728
|
+
|
|
9729
|
+
// src/admin/sqlConsole.ts
|
|
9730
|
+
var SqlCapability = {
|
|
9731
|
+
/** `SELECT`, `WITH … SELECT`, `EXPLAIN`, `SHOW`. */
|
|
9732
|
+
READ: "read",
|
|
9733
|
+
/** Adds rows. */
|
|
9734
|
+
INSERT: "insert",
|
|
9735
|
+
/** Changes rows. */
|
|
9736
|
+
UPDATE: "update",
|
|
9737
|
+
/** Removes rows. */
|
|
9738
|
+
DELETE: "delete",
|
|
9739
|
+
/** `CREATE` / `ALTER` / `COMMENT`. */
|
|
9740
|
+
DDL: "ddl",
|
|
9741
|
+
/** `DROP` and `TRUNCATE`: irreversible structure loss. */
|
|
9742
|
+
DROP: "drop",
|
|
9743
|
+
/**
|
|
9744
|
+
* `GRANT` / `REVOKE` / `SET`, and anything the analyser cannot classify.
|
|
9745
|
+
* Unknown statements land here on purpose, so a construct nobody anticipated
|
|
9746
|
+
* needs the most privileged capability rather than the least.
|
|
9747
|
+
*/
|
|
9748
|
+
ADMIN: "admin"
|
|
9749
|
+
};
|
|
9750
|
+
var STATEMENT_CAPABILITIES = {
|
|
9751
|
+
select: SqlCapability.READ,
|
|
9752
|
+
with: SqlCapability.READ,
|
|
9753
|
+
explain: SqlCapability.READ,
|
|
9754
|
+
show: SqlCapability.READ,
|
|
9755
|
+
desc: SqlCapability.READ,
|
|
9756
|
+
describe: SqlCapability.READ,
|
|
9757
|
+
insert: SqlCapability.INSERT,
|
|
9758
|
+
replace: SqlCapability.INSERT,
|
|
9759
|
+
update: SqlCapability.UPDATE,
|
|
9760
|
+
delete: SqlCapability.DELETE,
|
|
9761
|
+
create: SqlCapability.DDL,
|
|
9762
|
+
alter: SqlCapability.DDL,
|
|
9763
|
+
comment: SqlCapability.DDL,
|
|
9764
|
+
rename: SqlCapability.DDL,
|
|
9765
|
+
drop: SqlCapability.DROP,
|
|
9766
|
+
truncate: SqlCapability.DROP
|
|
9767
|
+
};
|
|
9768
|
+
var PARSER_HINT = "The admin SQL console needs the optional peer `node-sql-parser`. Install it with: npm install node-sql-parser";
|
|
9769
|
+
async function loadSqlParser() {
|
|
9770
|
+
try {
|
|
9771
|
+
const module = await import('node-sql-parser');
|
|
9772
|
+
const Parser = module.Parser ?? module.default?.Parser;
|
|
9773
|
+
if (Parser === void 0) throw new Error(PARSER_HINT);
|
|
9774
|
+
return new Parser();
|
|
9775
|
+
} catch {
|
|
9776
|
+
throw new Error(PARSER_HINT);
|
|
9777
|
+
}
|
|
9778
|
+
}
|
|
9779
|
+
function analyzeSql(sql5, dialect, parser) {
|
|
9780
|
+
const capabilities = /* @__PURE__ */ new Set();
|
|
9781
|
+
const tables = /* @__PURE__ */ new Set();
|
|
9782
|
+
let statements = 0;
|
|
9783
|
+
let parsed = true;
|
|
9784
|
+
let unscopedWrite = false;
|
|
9785
|
+
try {
|
|
9786
|
+
const ast = parser.astify(sql5, { database: dialect });
|
|
9787
|
+
const list = Array.isArray(ast) ? ast : [ast];
|
|
9788
|
+
statements = list.length;
|
|
9789
|
+
for (const statement of list) {
|
|
9790
|
+
const type = String(statement.type ?? "").toLowerCase();
|
|
9791
|
+
capabilities.add(STATEMENT_CAPABILITIES[type] ?? SqlCapability.ADMIN);
|
|
9792
|
+
if ((type === "update" || type === "delete") && !statement.where) {
|
|
9793
|
+
unscopedWrite = true;
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
} catch {
|
|
9797
|
+
parsed = false;
|
|
9798
|
+
statements = 1;
|
|
9799
|
+
capabilities.add(SqlCapability.ADMIN);
|
|
9800
|
+
}
|
|
9801
|
+
try {
|
|
9802
|
+
for (const entry of parser.tableList(sql5, { database: dialect })) {
|
|
9803
|
+
const name = entry.split("::").pop();
|
|
9804
|
+
if (name !== void 0 && name !== "null") tables.add(name.toLowerCase());
|
|
9805
|
+
}
|
|
9806
|
+
} catch {
|
|
9807
|
+
}
|
|
9808
|
+
return {
|
|
9809
|
+
statements,
|
|
9810
|
+
capabilities: [...capabilities],
|
|
9811
|
+
tables: [...tables],
|
|
9812
|
+
parsed,
|
|
9813
|
+
unscopedWrite
|
|
9814
|
+
};
|
|
9815
|
+
}
|
|
9816
|
+
function checkSqlPolicy(analysis, policy) {
|
|
9817
|
+
const granted = new Set(policy.capabilities ?? [SqlCapability.READ]);
|
|
9818
|
+
for (const capability of analysis.capabilities) {
|
|
9819
|
+
if (!granted.has(capability)) {
|
|
9820
|
+
return {
|
|
9821
|
+
allowed: false,
|
|
9822
|
+
reason: analysis.parsed ? `This console may not run ${capability} statements.` : "The statement could not be parsed, so it needs the admin capability."
|
|
9823
|
+
};
|
|
9824
|
+
}
|
|
9825
|
+
}
|
|
9826
|
+
if ((policy.requireWhereOnWrites ?? true) && analysis.unscopedWrite) {
|
|
9827
|
+
return {
|
|
9828
|
+
allowed: false,
|
|
9829
|
+
reason: "An UPDATE or DELETE without a WHERE clause is refused."
|
|
9830
|
+
};
|
|
9831
|
+
}
|
|
9832
|
+
const denied = new Set((policy.denyTables ?? []).map((name) => name.toLowerCase()));
|
|
9833
|
+
for (const table of analysis.tables) {
|
|
9834
|
+
if (denied.has(table)) {
|
|
9835
|
+
return { allowed: false, reason: `Table "${table}" is not available here.` };
|
|
9836
|
+
}
|
|
9837
|
+
}
|
|
9838
|
+
if (policy.allowTables !== void 0) {
|
|
9839
|
+
const allowed = new Set(policy.allowTables.map((name) => name.toLowerCase()));
|
|
9840
|
+
if (analysis.tables.length === 0) {
|
|
9841
|
+
return {
|
|
9842
|
+
allowed: false,
|
|
9843
|
+
reason: "This console only runs statements naming an allowed table."
|
|
9844
|
+
};
|
|
9845
|
+
}
|
|
9846
|
+
for (const table of analysis.tables) {
|
|
9847
|
+
if (!allowed.has(table)) {
|
|
9848
|
+
return { allowed: false, reason: `Table "${table}" is not on the allow list.` };
|
|
9849
|
+
}
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9852
|
+
return { allowed: true, reason: null };
|
|
9853
|
+
}
|
|
9854
|
+
|
|
9464
9855
|
// src/admin/inlines.ts
|
|
9465
9856
|
function adminInline(options) {
|
|
9466
9857
|
const slug = options.model.tablename;
|
|
@@ -9601,8 +9992,8 @@ function humanizeField(name) {
|
|
|
9601
9992
|
function adminColumns(model) {
|
|
9602
9993
|
return tempestDbJs.columnsOf(model);
|
|
9603
9994
|
}
|
|
9604
|
-
function widgetForColumn(
|
|
9605
|
-
const { kind, meta } =
|
|
9995
|
+
function widgetForColumn(column7) {
|
|
9996
|
+
const { kind, meta } = column7.type;
|
|
9606
9997
|
const plain = (widget) => ({
|
|
9607
9998
|
widget,
|
|
9608
9999
|
step: null,
|
|
@@ -9646,11 +10037,11 @@ function widgetForColumn(column6) {
|
|
|
9646
10037
|
return plain("text");
|
|
9647
10038
|
}
|
|
9648
10039
|
}
|
|
9649
|
-
function isColumnOptional(
|
|
9650
|
-
return !
|
|
10040
|
+
function isColumnOptional(column7) {
|
|
10041
|
+
return !column7.flags.notNull || column7.flags.hasDefault || column7.flags.primaryKey;
|
|
9651
10042
|
}
|
|
9652
|
-
function filterForColumn(
|
|
9653
|
-
const { kind, meta } =
|
|
10043
|
+
function filterForColumn(column7) {
|
|
10044
|
+
const { kind, meta } = column7.type;
|
|
9654
10045
|
if (kind === "boolean") {
|
|
9655
10046
|
return {
|
|
9656
10047
|
kind: "select",
|
|
@@ -9674,11 +10065,11 @@ function filterForColumn(column6) {
|
|
|
9674
10065
|
}
|
|
9675
10066
|
return { kind: "text", options: [] };
|
|
9676
10067
|
}
|
|
9677
|
-
function foreignKeyTable(
|
|
9678
|
-
return
|
|
10068
|
+
function foreignKeyTable(column7) {
|
|
10069
|
+
return column7.reference?.table ?? null;
|
|
9679
10070
|
}
|
|
9680
|
-
function isSearchableColumn(
|
|
9681
|
-
const { kind } =
|
|
10071
|
+
function isSearchableColumn(column7) {
|
|
10072
|
+
const { kind } = column7.type;
|
|
9682
10073
|
return kind === "varchar" || kind === "text" || kind === "char";
|
|
9683
10074
|
}
|
|
9684
10075
|
|
|
@@ -9960,8 +10351,8 @@ function formatFieldValue(widget, value) {
|
|
|
9960
10351
|
}
|
|
9961
10352
|
return String(value);
|
|
9962
10353
|
}
|
|
9963
|
-
function literalDefault(
|
|
9964
|
-
const fallback =
|
|
10354
|
+
function literalDefault(column7) {
|
|
10355
|
+
const fallback = column7.defaultValue;
|
|
9965
10356
|
if (fallback === null || fallback.kind !== "literal") return void 0;
|
|
9966
10357
|
return fallback.value;
|
|
9967
10358
|
}
|
|
@@ -9974,19 +10365,19 @@ function buildFormFields(admin, options = {}) {
|
|
|
9974
10365
|
const autocompleteLabels = options.autocompleteLabels ?? {};
|
|
9975
10366
|
const uploads = new Set(admin.uploadFields);
|
|
9976
10367
|
return admin.editableFieldNames().flatMap((name) => {
|
|
9977
|
-
const
|
|
9978
|
-
if (
|
|
10368
|
+
const column7 = columns[name];
|
|
10369
|
+
if (column7 === void 0) return [];
|
|
9979
10370
|
const related = foreignKeys[name];
|
|
9980
10371
|
const autocompleteUrl = autocompleteUrls[name];
|
|
9981
|
-
const spec = uploads.has(name) ? { widget: "file", step: null, options: [] } : autocompleteUrl !== void 0 ? { widget: "autocomplete", step: null, options: [] } : related === void 0 ? widgetForColumn(
|
|
9982
|
-
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);
|
|
9983
10374
|
return [
|
|
9984
10375
|
{
|
|
9985
10376
|
name,
|
|
9986
10377
|
label: humanizeField(name),
|
|
9987
10378
|
widget: spec.widget,
|
|
9988
10379
|
value: spec.widget === "checkbox" ? "" : formatFieldValue(spec.widget, raw),
|
|
9989
|
-
required: !isColumnOptional(
|
|
10380
|
+
required: !isColumnOptional(column7),
|
|
9990
10381
|
checked: spec.widget === "checkbox" && toBoolean(raw),
|
|
9991
10382
|
step: spec.step,
|
|
9992
10383
|
options: spec.options,
|
|
@@ -10003,8 +10394,8 @@ function toBoolean(value) {
|
|
|
10003
10394
|
if (typeof value !== "string") return false;
|
|
10004
10395
|
return ["true", "on", "yes", "1"].includes(value.trim().toLowerCase());
|
|
10005
10396
|
}
|
|
10006
|
-
function coerceValue(
|
|
10007
|
-
const { kind, meta } =
|
|
10397
|
+
function coerceValue(column7, widget, raw) {
|
|
10398
|
+
const { kind, meta } = column7.type;
|
|
10008
10399
|
switch (widget) {
|
|
10009
10400
|
case "number": {
|
|
10010
10401
|
const parsed = Number(raw);
|
|
@@ -10048,10 +10439,10 @@ function parseFormBody(admin, body, options = {}) {
|
|
|
10048
10439
|
const only = options.only === void 0 ? null : new Set(options.only);
|
|
10049
10440
|
for (const name of admin.editableFieldNames()) {
|
|
10050
10441
|
if (only !== null && !only.has(name)) continue;
|
|
10051
|
-
const
|
|
10052
|
-
if (
|
|
10442
|
+
const column7 = columns[name];
|
|
10443
|
+
if (column7 === void 0) continue;
|
|
10053
10444
|
if (uploads.has(name)) continue;
|
|
10054
|
-
const { widget } = widgetForColumn(
|
|
10445
|
+
const { widget } = widgetForColumn(column7);
|
|
10055
10446
|
if (widget === "checkbox") {
|
|
10056
10447
|
data[name] = toBoolean(body[name]);
|
|
10057
10448
|
continue;
|
|
@@ -10059,16 +10450,16 @@ function parseFormBody(admin, body, options = {}) {
|
|
|
10059
10450
|
const submitted = body[name];
|
|
10060
10451
|
const raw = typeof submitted === "string" ? submitted.trim() : "";
|
|
10061
10452
|
if (raw === "") {
|
|
10062
|
-
if (!isColumnOptional(
|
|
10453
|
+
if (!isColumnOptional(column7)) {
|
|
10063
10454
|
errors[name] = "This field is required.";
|
|
10064
10455
|
continue;
|
|
10065
10456
|
}
|
|
10066
|
-
if (
|
|
10457
|
+
if (column7.flags.hasDefault && !(name in body)) continue;
|
|
10067
10458
|
data[name] = null;
|
|
10068
10459
|
continue;
|
|
10069
10460
|
}
|
|
10070
10461
|
try {
|
|
10071
|
-
data[name] = coerceValue(
|
|
10462
|
+
data[name] = coerceValue(column7, widget, raw);
|
|
10072
10463
|
} catch (error) {
|
|
10073
10464
|
errors[name] = error instanceof Error ? error.message : "Invalid value.";
|
|
10074
10465
|
}
|
|
@@ -10086,9 +10477,9 @@ function foreignKeyFields(admin) {
|
|
|
10086
10477
|
const columns = adminColumns(admin.model);
|
|
10087
10478
|
const out = {};
|
|
10088
10479
|
for (const name of admin.editableFieldNames()) {
|
|
10089
|
-
const
|
|
10090
|
-
if (
|
|
10091
|
-
const table = foreignKeyTable(
|
|
10480
|
+
const column7 = columns[name];
|
|
10481
|
+
if (column7 === void 0) continue;
|
|
10482
|
+
const table = foreignKeyTable(column7);
|
|
10092
10483
|
if (table !== null) out[name] = table;
|
|
10093
10484
|
}
|
|
10094
10485
|
return out;
|
|
@@ -10474,7 +10865,14 @@ var AdminSite = class {
|
|
|
10474
10865
|
};
|
|
10475
10866
|
|
|
10476
10867
|
// src/admin/styles.ts
|
|
10477
|
-
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 {
|
|
10478
10876
|
--tempest-bg: #0f172a;
|
|
10479
10877
|
--tempest-bg-soft: #1e293b;
|
|
10480
10878
|
--tempest-bg-row: #f8fafc;
|
|
@@ -12033,6 +12431,7 @@ a.tempest-admin-list__new:hover {
|
|
|
12033
12431
|
}
|
|
12034
12432
|
}
|
|
12035
12433
|
`;
|
|
12434
|
+
var ADMIN_CSS = `${ADMIN_CSS_BASE}${ADMIN_CSS_EXTRA}`;
|
|
12036
12435
|
|
|
12037
12436
|
// src/admin/theme.ts
|
|
12038
12437
|
var FORBIDDEN_CHARS = ["<", ">", "{", "}", '"'];
|
|
@@ -12112,7 +12511,7 @@ function escapeHtml(value) {
|
|
|
12112
12511
|
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
12113
12512
|
}
|
|
12114
12513
|
function renderLayout(context, title, body) {
|
|
12115
|
-
const { site, theme, prefix, session, currentPath, navModels, messages } = context;
|
|
12514
|
+
const { site, theme, prefix, session, currentPath, navModels, navSystem, messages } = context;
|
|
12116
12515
|
const authed = session !== null;
|
|
12117
12516
|
const indexUrl = `${prefix}/`;
|
|
12118
12517
|
const navLink = (url, label, active) => `<a href="${escapeHtml(url)}" class="tempest-admin-sidebar__link${active ? " tempest-admin-sidebar__link--active" : ""}">${escapeHtml(label)}</a>`;
|
|
@@ -12122,6 +12521,9 @@ function renderLayout(context, title, body) {
|
|
|
12122
12521
|
${navLink(indexUrl, "Dashboard", currentPath === indexUrl)}
|
|
12123
12522
|
${navModels.length > 0 ? `<span class="tempest-admin-sidebar__heading">Models</span>${navModels.map(
|
|
12124
12523
|
(entry) => navLink(entry.url, entry.label, currentPath.startsWith(entry.url))
|
|
12524
|
+
).join("")}` : ""}
|
|
12525
|
+
${navSystem.length > 0 ? `<span class="tempest-admin-sidebar__heading">System</span>${navSystem.map(
|
|
12526
|
+
(entry) => navLink(entry.url, entry.label, currentPath.startsWith(entry.url))
|
|
12125
12527
|
).join("")}` : ""}
|
|
12126
12528
|
</nav>
|
|
12127
12529
|
</aside>` : "";
|
|
@@ -12290,11 +12692,11 @@ function renderFilter(filter) {
|
|
|
12290
12692
|
function renderListPage(context, view) {
|
|
12291
12693
|
const bulk = view.bulkActions.length > 0 && context.session !== null;
|
|
12292
12694
|
const checkColumn = bulk ? 1 : 0;
|
|
12293
|
-
const headers = view.columns.map((
|
|
12294
|
-
const state = view.sort[
|
|
12295
|
-
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>`;
|
|
12296
12698
|
const arrow = state.active ? state.ascending ? "\u25B2" : "\u25BC" : "\u2195";
|
|
12297
|
-
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>`;
|
|
12298
12700
|
}).join("");
|
|
12299
12701
|
const rows = view.rows.length > 0 ? view.rows.map((row) => {
|
|
12300
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>` : "";
|
|
@@ -12435,7 +12837,7 @@ function renderInline(inline, csrfToken, error) {
|
|
|
12435
12837
|
if (inline.rows.length === 0) {
|
|
12436
12838
|
return `<section class="tempest-admin-inline">${heading}<p><em>No related records.</em></p></section>`;
|
|
12437
12839
|
}
|
|
12438
|
-
const head2 = `<tr>${inline.columns.map((
|
|
12840
|
+
const head2 = `<tr>${inline.columns.map((column7) => `<th>${escapeHtml(column7)}</th>`).join("")}<th></th></tr>`;
|
|
12439
12841
|
const body = inline.rows.map(
|
|
12440
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>`
|
|
12441
12843
|
).join("");
|
|
@@ -12450,7 +12852,7 @@ function renderInline(inline, csrfToken, error) {
|
|
|
12450
12852
|
${more}
|
|
12451
12853
|
</section>`;
|
|
12452
12854
|
}
|
|
12453
|
-
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>`;
|
|
12454
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>`;
|
|
12455
12857
|
const rows = inline.rows.map((row) => renderRow(row, false)).join("");
|
|
12456
12858
|
const blank = inline.newRow === null ? "" : renderRow(inline.newRow, true);
|
|
@@ -12472,11 +12874,11 @@ function renderInline(inline, csrfToken, error) {
|
|
|
12472
12874
|
${more}
|
|
12473
12875
|
</section>`;
|
|
12474
12876
|
}
|
|
12475
|
-
function renderAuditPanel(
|
|
12476
|
-
const rows =
|
|
12877
|
+
function renderAuditPanel(audit2) {
|
|
12878
|
+
const rows = audit2.fields.map(
|
|
12477
12879
|
(field) => `<dt>${escapeHtml(field.label)}</dt><dd>${field.value === "" ? "<em>\u2014</em>" : escapeHtml(field.value)}</dd>`
|
|
12478
12880
|
).join("");
|
|
12479
|
-
const history =
|
|
12881
|
+
const history = audit2.history.length > 0 ? `<ol class="tempest-admin-history">${audit2.history.map((entry) => {
|
|
12480
12882
|
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(
|
|
12481
12883
|
(change) => `<tr><td>${escapeHtml(change.field)}</td><td>${escapeHtml(change.before)}</td><td>${escapeHtml(change.after)}</td></tr>`
|
|
12482
12884
|
).join("")}</tbody></table>` : "<p><em>No field changes recorded.</em></p>";
|
|
@@ -12574,6 +12976,160 @@ function renderFormPage(context, view) {
|
|
|
12574
12976
|
</section>`;
|
|
12575
12977
|
return renderLayout(context, `${heading} \xB7 ${context.site.title}`, body);
|
|
12576
12978
|
}
|
|
12979
|
+
function renderLogsPage(context, view) {
|
|
12980
|
+
const options = view.sources.map(
|
|
12981
|
+
(source) => `<option value="${escapeHtml(source.value)}"${source.selected ? " selected" : ""}>${escapeHtml(source.label)}</option>`
|
|
12982
|
+
).join("");
|
|
12983
|
+
const rows = view.rows.length > 0 ? view.rows.map((row) => {
|
|
12984
|
+
const context_ = row.context.map(
|
|
12985
|
+
(entry) => `<span class="tempest-admin-logs__meta"><b>${escapeHtml(entry.key)}</b>: ${escapeHtml(entry.value)}</span>`
|
|
12986
|
+
).join(" ");
|
|
12987
|
+
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>`;
|
|
12988
|
+
return `<tr>
|
|
12989
|
+
<td data-label="Level"><span class="tempest-log-badge tempest-log-badge--${escapeHtml(row.level)}">${escapeHtml(row.level)}</span></td>
|
|
12990
|
+
<td data-label="When">${escapeHtml(row.timestamp)}</td>
|
|
12991
|
+
<td data-label="Logger">${escapeHtml(row.logger)}</td>
|
|
12992
|
+
<td data-label="Message">${message}</td>
|
|
12993
|
+
</tr>`;
|
|
12994
|
+
}).join("") : `<tr><td colspan="4">No log records yet. Point <code>configureFileLogging</code> at the same directory to populate this page.</td></tr>`;
|
|
12995
|
+
const body = `<section class="tempest-admin-logs">
|
|
12996
|
+
<header class="tempest-admin-list__header">
|
|
12997
|
+
<h1>Logs</h1>
|
|
12998
|
+
<p>${escapeHtml(view.total)} record${view.total === 1 ? "" : "s"}.</p>
|
|
12999
|
+
</header>
|
|
13000
|
+
<div class="tempest-admin-list__toolbar">
|
|
13001
|
+
<form method="get" class="tempest-admin-list__filters">
|
|
13002
|
+
<label><span>Source</span><select name="source">${options}</select></label>
|
|
13003
|
+
<input type="search" name="q" value="${escapeHtml(view.query)}" placeholder="Search\u2026" aria-label="Search">
|
|
13004
|
+
<button type="submit">Apply</button>
|
|
13005
|
+
</form>
|
|
13006
|
+
<div class="tempest-admin-list__actions">
|
|
13007
|
+
<a href="${escapeHtml(view.exportMarkdownUrl)}">Export Markdown</a>
|
|
13008
|
+
<a href="${escapeHtml(view.exportJsonUrl)}">Export JSON</a>
|
|
13009
|
+
</div>
|
|
13010
|
+
</div>
|
|
13011
|
+
<div class="tempest-admin-table-wrap">
|
|
13012
|
+
<table class="tempest-admin-list__table">
|
|
13013
|
+
<thead><tr><th>Level</th><th>When</th><th>Logger</th><th>Message</th></tr></thead>
|
|
13014
|
+
<tbody>${rows}</tbody>
|
|
13015
|
+
</table>
|
|
13016
|
+
</div>
|
|
13017
|
+
${view.pages > 1 ? `<nav class="tempest-admin-list__pagination" aria-label="Pagination">
|
|
13018
|
+
${view.prevUrl !== null ? `<a href="${escapeHtml(view.prevUrl)}">\u2190 Prev</a>` : ""}
|
|
13019
|
+
<span>Page ${escapeHtml(view.page)} of ${escapeHtml(view.pages)}</span>
|
|
13020
|
+
${view.nextUrl !== null ? `<a href="${escapeHtml(view.nextUrl)}">Next \u2192</a>` : ""}
|
|
13021
|
+
</nav>` : ""}
|
|
13022
|
+
<p class="tempest-admin-form__hint">Exports carry at most ${escapeHtml(view.exportMax)} records, newest first, honouring the filters above.</p>
|
|
13023
|
+
</section>`;
|
|
13024
|
+
return renderLayout(context, `Logs \xB7 ${context.site.title}`, body);
|
|
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
|
+
}
|
|
13100
|
+
function renderSqlPage(context, view) {
|
|
13101
|
+
if (context.session === null) throw new Error("The SQL console requires a session");
|
|
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>
|
|
13103
|
+
<div class="tempest-admin-table-wrap">
|
|
13104
|
+
<table class="tempest-admin-list__table">
|
|
13105
|
+
<thead><tr>${view.columns.map((column7) => `<th>${escapeHtml(column7)}</th>`).join("")}</tr></thead>
|
|
13106
|
+
<tbody>${view.rows.map(
|
|
13107
|
+
(row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`
|
|
13108
|
+
).join("")}</tbody>
|
|
13109
|
+
</table>
|
|
13110
|
+
</div>`;
|
|
13111
|
+
const body = `<section class="tempest-admin-sql">
|
|
13112
|
+
<header class="tempest-admin-list__header">
|
|
13113
|
+
<h1>SQL console</h1>
|
|
13114
|
+
<p>Capabilities: <code>${escapeHtml(view.capabilities.join(", "))}</code></p>
|
|
13115
|
+
</header>
|
|
13116
|
+
${view.error !== null ? `<p class="tempest-admin-form__error">${escapeHtml(view.error)}</p>` : ""}
|
|
13117
|
+
<form method="post" action="${escapeHtml(`${context.prefix}/sql`)}" class="tempest-admin-form__form">
|
|
13118
|
+
<input type="hidden" name="csrf_token" value="${escapeHtml(context.session.csrfToken)}">
|
|
13119
|
+
<div class="tempest-admin-form__field">
|
|
13120
|
+
<label>
|
|
13121
|
+
<span>Statement</span>
|
|
13122
|
+
<textarea name="sql" rows="6" spellcheck="false" required>${escapeHtml(view.sql)}</textarea>
|
|
13123
|
+
</label>
|
|
13124
|
+
</div>
|
|
13125
|
+
<div class="tempest-admin-form__actions">
|
|
13126
|
+
<button type="submit">Run</button>
|
|
13127
|
+
</div>
|
|
13128
|
+
</form>
|
|
13129
|
+
${result}
|
|
13130
|
+
</section>`;
|
|
13131
|
+
return renderLayout(context, `SQL console \xB7 ${context.site.title}`, body);
|
|
13132
|
+
}
|
|
12577
13133
|
function renderImportPage(context, view) {
|
|
12578
13134
|
if (context.session === null) throw new Error("The import page requires a session");
|
|
12579
13135
|
const summary = view.created === null ? "" : `<p class="tempest-admin-import__summary">Created ${escapeHtml(view.created)} record${view.created === 1 ? "" : "s"}.</p>`;
|
|
@@ -12659,7 +13215,75 @@ var AUTOCOMPLETE_SCRIPT = `<script>
|
|
|
12659
13215
|
});
|
|
12660
13216
|
})();
|
|
12661
13217
|
</script>`;
|
|
13218
|
+
function filesFor(dir, source) {
|
|
13219
|
+
if (source === "500") return [path.join(dir, HTTP_500_LOG_FILE)];
|
|
13220
|
+
if (source === "all") return Object.values(LEVEL_LOG_FILES).map((f) => path.join(dir, f));
|
|
13221
|
+
return [path.join(dir, LEVEL_LOG_FILES[source])];
|
|
13222
|
+
}
|
|
13223
|
+
async function readLogEntries(dir, source) {
|
|
13224
|
+
return await readEntries(filesFor(dir, source));
|
|
13225
|
+
}
|
|
13226
|
+
async function readEntries(files) {
|
|
13227
|
+
const entries = [];
|
|
13228
|
+
for (const file of files) {
|
|
13229
|
+
let content;
|
|
13230
|
+
try {
|
|
13231
|
+
content = await promises.readFile(file, "utf8");
|
|
13232
|
+
} catch {
|
|
13233
|
+
continue;
|
|
13234
|
+
}
|
|
13235
|
+
for (const line of content.split("\n")) {
|
|
13236
|
+
if (!line.trim()) continue;
|
|
13237
|
+
try {
|
|
13238
|
+
entries.push(JSON.parse(line));
|
|
13239
|
+
} catch {
|
|
13240
|
+
}
|
|
13241
|
+
}
|
|
13242
|
+
}
|
|
13243
|
+
return entries;
|
|
13244
|
+
}
|
|
13245
|
+
function makeLogsRouter(options) {
|
|
13246
|
+
const router = express3.Router();
|
|
13247
|
+
const path = options.path ?? "/logs";
|
|
13248
|
+
const guards = options.guards ?? [];
|
|
13249
|
+
const handler = (req, res, next) => {
|
|
13250
|
+
const source = req.query.source ?? "all";
|
|
13251
|
+
const validSources = ["all", "debug", "info", "warning", "error", "500"];
|
|
13252
|
+
if (!validSources.includes(source)) {
|
|
13253
|
+
res.status(422).json({ detail: "Invalid log source", code: "VALIDATION_ERROR", details: {} });
|
|
13254
|
+
return;
|
|
13255
|
+
}
|
|
13256
|
+
const page2 = Math.max(1, Number(req.query.page) || 1);
|
|
13257
|
+
const pageSize = Math.min(500, Math.max(1, Number(req.query.pageSize) || 50));
|
|
13258
|
+
readEntries(filesFor(options.dir, source)).then((entries) => {
|
|
13259
|
+
entries.reverse();
|
|
13260
|
+
const total = entries.length;
|
|
13261
|
+
const start = (page2 - 1) * pageSize;
|
|
13262
|
+
res.json({
|
|
13263
|
+
items: entries.slice(start, start + pageSize),
|
|
13264
|
+
total,
|
|
13265
|
+
page: page2,
|
|
13266
|
+
pageSize,
|
|
13267
|
+
pages: Math.ceil(total / pageSize)
|
|
13268
|
+
});
|
|
13269
|
+
}).catch(next);
|
|
13270
|
+
};
|
|
13271
|
+
router.get(path, ...guards, handler);
|
|
13272
|
+
return router;
|
|
13273
|
+
}
|
|
12662
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;
|
|
13284
|
+
var LOG_SOURCES = ["all", "debug", "info", "warning", "error", "500"];
|
|
13285
|
+
var LOG_PAGE_SIZE = 50;
|
|
13286
|
+
var LOG_EXPORT_MAX = 500;
|
|
12663
13287
|
var INLINE_ROW_LIMIT = 50;
|
|
12664
13288
|
var AUTOCOMPLETE_LIMIT = 20;
|
|
12665
13289
|
var AUDIT_HISTORY_LIMIT = 50;
|
|
@@ -12691,6 +13315,16 @@ function makeAdminRouter(site, options) {
|
|
|
12691
13315
|
const theme = resolveAdminTheme(site.theme);
|
|
12692
13316
|
const showMetrics = options.showMetrics ?? true;
|
|
12693
13317
|
const exportMaxRows = options.exportMaxRows ?? 5e3;
|
|
13318
|
+
const systemNav = [];
|
|
13319
|
+
if (options.logDir !== void 0) {
|
|
13320
|
+
systemNav.push({ label: "Logs", url: `${prefix}/logs` });
|
|
13321
|
+
}
|
|
13322
|
+
if (options.sqlConsole !== void 0) {
|
|
13323
|
+
systemNav.push({ label: "SQL console", url: `${prefix}/sql` });
|
|
13324
|
+
}
|
|
13325
|
+
if (options.tasks !== void 0) {
|
|
13326
|
+
systemNav.push({ label: "Tasks", url: `${prefix}/tasks` });
|
|
13327
|
+
}
|
|
12694
13328
|
const maxUploadBytes = options.maxUploadBytes ?? 10 * 1024 * 1024;
|
|
12695
13329
|
const sessions = new AdminSessionStore({
|
|
12696
13330
|
secret: options.secretKey,
|
|
@@ -12712,6 +13346,7 @@ function makeAdminRouter(site, options) {
|
|
|
12712
13346
|
label: admin.verboseNamePlural(),
|
|
12713
13347
|
url: `${prefix}/m/${admin.slug()}`
|
|
12714
13348
|
})),
|
|
13349
|
+
navSystem: systemNav,
|
|
12715
13350
|
messages: flashFor(req)
|
|
12716
13351
|
});
|
|
12717
13352
|
const allows = async (principal, admin, action) => {
|
|
@@ -12922,6 +13557,296 @@ function makeAdminRouter(site, options) {
|
|
|
12922
13557
|
);
|
|
12923
13558
|
})
|
|
12924
13559
|
);
|
|
13560
|
+
if (options.logDir !== void 0) {
|
|
13561
|
+
const logDir = options.logDir;
|
|
13562
|
+
router.get(
|
|
13563
|
+
`${prefix}/logs`,
|
|
13564
|
+
guarded(async (req, res) => {
|
|
13565
|
+
const state = await authenticate(req, res);
|
|
13566
|
+
if (state === null) return;
|
|
13567
|
+
const { entries, source, search } = await readLogs(req, logDir);
|
|
13568
|
+
const page2 = Math.max(1, Number.parseInt(queryString(req.query.page), 10) || 1);
|
|
13569
|
+
const start = (page2 - 1) * LOG_PAGE_SIZE;
|
|
13570
|
+
const pages = Math.max(1, Math.ceil(entries.length / LOG_PAGE_SIZE));
|
|
13571
|
+
const exportUrl = (format) => `${prefix}/logs/export?${buildQuery({ source, q: search, format })}`;
|
|
13572
|
+
const pageUrl = (target) => `?${buildQuery({ source, q: search, page: target })}`;
|
|
13573
|
+
html(
|
|
13574
|
+
res,
|
|
13575
|
+
renderLogsPage(context(req, state.session, state.visible), {
|
|
13576
|
+
sources: LOG_SOURCES.map((value) => ({
|
|
13577
|
+
value,
|
|
13578
|
+
label: value === "500" ? "HTTP 500" : humanizeField(value),
|
|
13579
|
+
selected: value === source
|
|
13580
|
+
})),
|
|
13581
|
+
query: search,
|
|
13582
|
+
rows: entries.slice(start, start + LOG_PAGE_SIZE).map((entry) => ({
|
|
13583
|
+
level: entry.level,
|
|
13584
|
+
timestamp: entry.timestamp,
|
|
13585
|
+
logger: entry.logger,
|
|
13586
|
+
message: entry.message,
|
|
13587
|
+
stack: entry.stack,
|
|
13588
|
+
context: Object.entries(entry.context).map(([key, value]) => ({
|
|
13589
|
+
key,
|
|
13590
|
+
value: typeof value === "string" ? value : JSON.stringify(value)
|
|
13591
|
+
}))
|
|
13592
|
+
})),
|
|
13593
|
+
total: entries.length,
|
|
13594
|
+
page: page2,
|
|
13595
|
+
pages,
|
|
13596
|
+
prevUrl: page2 > 1 ? pageUrl(page2 - 1) : null,
|
|
13597
|
+
nextUrl: page2 < pages ? pageUrl(page2 + 1) : null,
|
|
13598
|
+
exportMarkdownUrl: exportUrl("md"),
|
|
13599
|
+
exportJsonUrl: exportUrl("json"),
|
|
13600
|
+
exportMax: LOG_EXPORT_MAX
|
|
13601
|
+
})
|
|
13602
|
+
);
|
|
13603
|
+
})
|
|
13604
|
+
);
|
|
13605
|
+
router.get(
|
|
13606
|
+
`${prefix}/logs/export`,
|
|
13607
|
+
guarded(async (req, res) => {
|
|
13608
|
+
const state = await authenticate(req, res);
|
|
13609
|
+
if (state === null) return;
|
|
13610
|
+
const format = queryString(req.query.format) === "json" ? "json" : "md";
|
|
13611
|
+
const { entries, source, search } = await readLogs(req, logDir);
|
|
13612
|
+
const window = entries.slice(0, LOG_EXPORT_MAX);
|
|
13613
|
+
const payload = format === "json" ? renderLogEntriesJson(window) : renderLogEntriesMarkdown(window, {
|
|
13614
|
+
source,
|
|
13615
|
+
query: search,
|
|
13616
|
+
total: entries.length
|
|
13617
|
+
});
|
|
13618
|
+
res.status(200).type(format === "json" ? "application/json" : "text/markdown; charset=utf-8").set("content-disposition", `attachment; filename="logs.${format}"`).send(payload);
|
|
13619
|
+
})
|
|
13620
|
+
);
|
|
13621
|
+
}
|
|
13622
|
+
if (options.sqlConsole !== void 0) {
|
|
13623
|
+
const console_ = options.sqlConsole;
|
|
13624
|
+
const policy = console_.policy ?? {};
|
|
13625
|
+
const dialect = console_.dialect ?? "postgresql";
|
|
13626
|
+
const capabilities = policy.capabilities ?? [SqlCapability.READ];
|
|
13627
|
+
const maxRows = policy.maxRows ?? 200;
|
|
13628
|
+
router.get(
|
|
13629
|
+
`${prefix}/sql`,
|
|
13630
|
+
guarded(async (req, res) => {
|
|
13631
|
+
const state = await authenticate(req, res);
|
|
13632
|
+
if (state === null) return;
|
|
13633
|
+
html(
|
|
13634
|
+
res,
|
|
13635
|
+
renderSqlPage(context(req, state.session, state.visible), {
|
|
13636
|
+
sql: "",
|
|
13637
|
+
capabilities: [...capabilities],
|
|
13638
|
+
error: null,
|
|
13639
|
+
columns: [],
|
|
13640
|
+
rows: [],
|
|
13641
|
+
rowCount: null,
|
|
13642
|
+
truncated: false,
|
|
13643
|
+
durationMs: null
|
|
13644
|
+
})
|
|
13645
|
+
);
|
|
13646
|
+
})
|
|
13647
|
+
);
|
|
13648
|
+
router.post(
|
|
13649
|
+
`${prefix}/sql`,
|
|
13650
|
+
guarded(async (req, res) => {
|
|
13651
|
+
const state = await authenticate(req, res);
|
|
13652
|
+
if (state === null) return;
|
|
13653
|
+
if (!checkCsrf(req, res, state)) return;
|
|
13654
|
+
const body = req.body;
|
|
13655
|
+
const sql5 = typeof body.sql === "string" ? body.sql : "";
|
|
13656
|
+
const principal = backend.displayName(state.principal);
|
|
13657
|
+
const render = (view) => {
|
|
13658
|
+
html(
|
|
13659
|
+
res,
|
|
13660
|
+
renderSqlPage(context(req, state.session, state.visible), {
|
|
13661
|
+
sql: sql5,
|
|
13662
|
+
capabilities: [...capabilities],
|
|
13663
|
+
...view
|
|
13664
|
+
}),
|
|
13665
|
+
view.error === null ? 200 : 400
|
|
13666
|
+
);
|
|
13667
|
+
};
|
|
13668
|
+
const parser = await loadSqlParser();
|
|
13669
|
+
const analysis = analyzeSql(sql5, dialect, parser);
|
|
13670
|
+
const verdict = checkSqlPolicy(analysis, policy);
|
|
13671
|
+
if (!verdict.allowed) {
|
|
13672
|
+
await audit(console_.onAudit, {
|
|
13673
|
+
sql: sql5,
|
|
13674
|
+
principal,
|
|
13675
|
+
allowed: false,
|
|
13676
|
+
reason: verdict.reason,
|
|
13677
|
+
analysis,
|
|
13678
|
+
durationMs: null,
|
|
13679
|
+
rowCount: null
|
|
13680
|
+
});
|
|
13681
|
+
render({
|
|
13682
|
+
error: verdict.reason,
|
|
13683
|
+
columns: [],
|
|
13684
|
+
rows: [],
|
|
13685
|
+
rowCount: null,
|
|
13686
|
+
truncated: false,
|
|
13687
|
+
durationMs: null
|
|
13688
|
+
});
|
|
13689
|
+
return;
|
|
13690
|
+
}
|
|
13691
|
+
const started = Date.now();
|
|
13692
|
+
let rows;
|
|
13693
|
+
try {
|
|
13694
|
+
rows = console_.run === void 0 ? await state.dbSession.raw(sql5).all() : await console_.run(sql5, state.dbSession);
|
|
13695
|
+
} catch (error) {
|
|
13696
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13697
|
+
await audit(console_.onAudit, {
|
|
13698
|
+
sql: sql5,
|
|
13699
|
+
principal,
|
|
13700
|
+
allowed: true,
|
|
13701
|
+
reason: message,
|
|
13702
|
+
analysis,
|
|
13703
|
+
durationMs: Date.now() - started,
|
|
13704
|
+
rowCount: null
|
|
13705
|
+
});
|
|
13706
|
+
render({
|
|
13707
|
+
error: message,
|
|
13708
|
+
columns: [],
|
|
13709
|
+
rows: [],
|
|
13710
|
+
rowCount: null,
|
|
13711
|
+
truncated: false,
|
|
13712
|
+
durationMs: null
|
|
13713
|
+
});
|
|
13714
|
+
return;
|
|
13715
|
+
}
|
|
13716
|
+
const durationMs = Date.now() - started;
|
|
13717
|
+
await audit(console_.onAudit, {
|
|
13718
|
+
sql: sql5,
|
|
13719
|
+
principal,
|
|
13720
|
+
allowed: true,
|
|
13721
|
+
reason: null,
|
|
13722
|
+
analysis,
|
|
13723
|
+
durationMs,
|
|
13724
|
+
rowCount: rows.length
|
|
13725
|
+
});
|
|
13726
|
+
const window = rows.slice(0, maxRows);
|
|
13727
|
+
const columns = window.length === 0 ? [] : Object.keys(window[0] ?? {});
|
|
13728
|
+
render({
|
|
13729
|
+
error: null,
|
|
13730
|
+
columns,
|
|
13731
|
+
rows: window.map(
|
|
13732
|
+
(row) => columns.map((column7) => formatCellValue(row[column7]))
|
|
13733
|
+
),
|
|
13734
|
+
rowCount: rows.length,
|
|
13735
|
+
truncated: rows.length > window.length,
|
|
13736
|
+
durationMs
|
|
13737
|
+
});
|
|
13738
|
+
})
|
|
13739
|
+
);
|
|
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
|
+
}
|
|
12925
13850
|
router.get(
|
|
12926
13851
|
`${prefix}/m/:slug`,
|
|
12927
13852
|
guarded(async (req, res) => {
|
|
@@ -13232,8 +14157,8 @@ function makeAdminRouter(site, options) {
|
|
|
13232
14157
|
res.status(404).json({ options: [] });
|
|
13233
14158
|
return;
|
|
13234
14159
|
}
|
|
13235
|
-
const
|
|
13236
|
-
const table =
|
|
14160
|
+
const column7 = adminColumns(admin.model)[field];
|
|
14161
|
+
const table = column7 === void 0 ? null : foreignKeyTable(column7);
|
|
13237
14162
|
const referenced = table === null ? null : site.get(table);
|
|
13238
14163
|
if (referenced === null) {
|
|
13239
14164
|
res.json({ options: [] });
|
|
@@ -13542,7 +14467,7 @@ function makeAdminRouter(site, options) {
|
|
|
13542
14467
|
formAction: "",
|
|
13543
14468
|
rows: visible.map((child) => ({
|
|
13544
14469
|
key: String(child[childAdmin?.identityField ?? "id"]),
|
|
13545
|
-
cells: columns.map((
|
|
14470
|
+
cells: columns.map((column7) => formatCellValue(child[column7])),
|
|
13546
14471
|
fields: [],
|
|
13547
14472
|
url: childAdmin === null ? null : `${prefix}/m/${inline.slug}/${String(child[childAdmin.identityField])}`
|
|
13548
14473
|
})),
|
|
@@ -13639,6 +14564,15 @@ function makeAdminRouter(site, options) {
|
|
|
13639
14564
|
const row = await admin.repository(dbSession).first({ [admin.identityField]: identity });
|
|
13640
14565
|
return row ?? null;
|
|
13641
14566
|
}
|
|
14567
|
+
async function readLogs(req, dir) {
|
|
14568
|
+
const requested = queryString(req.query.source);
|
|
14569
|
+
const source = LOG_SOURCES.includes(requested) ? requested : "all";
|
|
14570
|
+
const search = queryString(req.query.q);
|
|
14571
|
+
const raw = await readLogEntries(dir, source);
|
|
14572
|
+
const entries = filterLogEntries(raw.map(toLogEntry), search);
|
|
14573
|
+
entries.reverse();
|
|
14574
|
+
return { entries, source, search };
|
|
14575
|
+
}
|
|
13642
14576
|
function renderImport(req, state, admin, error, created, rowErrors) {
|
|
13643
14577
|
return renderImportPage(context(req, state.session, state.visible), {
|
|
13644
14578
|
title: admin.verboseNamePlural(),
|
|
@@ -13665,8 +14599,8 @@ function makeAdminRouter(site, options) {
|
|
|
13665
14599
|
for (const field of admin.uploadFields) {
|
|
13666
14600
|
const file = files.find((candidate) => candidate.field === field);
|
|
13667
14601
|
if (file === void 0) {
|
|
13668
|
-
const
|
|
13669
|
-
if (creating &&
|
|
14602
|
+
const column7 = columns[field];
|
|
14603
|
+
if (creating && column7 !== void 0 && !isColumnOptional(column7)) {
|
|
13670
14604
|
errors[field] = "This field is required.";
|
|
13671
14605
|
}
|
|
13672
14606
|
continue;
|
|
@@ -13698,14 +14632,14 @@ function makeAdminRouter(site, options) {
|
|
|
13698
14632
|
});
|
|
13699
14633
|
const displayed = admin.listDisplayNames();
|
|
13700
14634
|
const sort = {};
|
|
13701
|
-
for (const
|
|
13702
|
-
if (!(
|
|
13703
|
-
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;
|
|
13704
14638
|
const nextAscending = active ? !query.ascending : true;
|
|
13705
|
-
sort[
|
|
14639
|
+
sort[column7] = {
|
|
13706
14640
|
url: `?${buildQuery({
|
|
13707
14641
|
...query.baseQuery,
|
|
13708
|
-
sort:
|
|
14642
|
+
sort: column7,
|
|
13709
14643
|
dir: nextAscending ? "asc" : "desc"
|
|
13710
14644
|
})}`,
|
|
13711
14645
|
active,
|
|
@@ -13726,7 +14660,7 @@ function makeAdminRouter(site, options) {
|
|
|
13726
14660
|
const identity = String(row[admin.identityField]);
|
|
13727
14661
|
return {
|
|
13728
14662
|
identity,
|
|
13729
|
-
cells: displayed.map((
|
|
14663
|
+
cells: displayed.map((column7) => formatCellValue(row[column7])),
|
|
13730
14664
|
url: `${prefix}/m/${admin.slug()}/${identity}`
|
|
13731
14665
|
};
|
|
13732
14666
|
}),
|
|
@@ -13772,9 +14706,9 @@ function makeAdminRouter(site, options) {
|
|
|
13772
14706
|
}
|
|
13773
14707
|
const filterViews = [];
|
|
13774
14708
|
for (const field of admin.listFilter) {
|
|
13775
|
-
const
|
|
13776
|
-
if (
|
|
13777
|
-
const spec = filterForColumn(
|
|
14709
|
+
const column7 = columns[field];
|
|
14710
|
+
if (column7 === void 0) continue;
|
|
14711
|
+
const spec = filterForColumn(column7);
|
|
13778
14712
|
if (spec.kind === "daterange") {
|
|
13779
14713
|
const from = queryString(req.query[`filter_${field}_from`]);
|
|
13780
14714
|
const to = queryString(req.query[`filter_${field}_to`]);
|
|
@@ -13793,12 +14727,12 @@ function makeAdminRouter(site, options) {
|
|
|
13793
14727
|
});
|
|
13794
14728
|
continue;
|
|
13795
14729
|
}
|
|
13796
|
-
const related = await relatedOptions(
|
|
14730
|
+
const related = await relatedOptions(column7, dbSession);
|
|
13797
14731
|
const options2 = related ?? spec.options;
|
|
13798
14732
|
const value = queryString(req.query[`filter_${field}`]);
|
|
13799
14733
|
if (value !== "") {
|
|
13800
14734
|
conditions.push({
|
|
13801
|
-
[field]:
|
|
14735
|
+
[field]: column7.type.kind === "boolean" ? value === "true" : value
|
|
13802
14736
|
});
|
|
13803
14737
|
}
|
|
13804
14738
|
filterViews.push({
|
|
@@ -13816,8 +14750,8 @@ function makeAdminRouter(site, options) {
|
|
|
13816
14750
|
});
|
|
13817
14751
|
}
|
|
13818
14752
|
const searchable = admin.searchFields.filter((field) => {
|
|
13819
|
-
const
|
|
13820
|
-
return
|
|
14753
|
+
const column7 = columns[field];
|
|
14754
|
+
return column7 !== void 0 && isSearchableColumn(column7);
|
|
13821
14755
|
});
|
|
13822
14756
|
if (search !== "" && searchable.length > 0) {
|
|
13823
14757
|
conditions.push(
|
|
@@ -13850,8 +14784,8 @@ function makeAdminRouter(site, options) {
|
|
|
13850
14784
|
lens: lens?.slug ?? ""
|
|
13851
14785
|
};
|
|
13852
14786
|
}
|
|
13853
|
-
async function relatedOptions(
|
|
13854
|
-
const table = foreignKeyTable(
|
|
14787
|
+
async function relatedOptions(column7, dbSession) {
|
|
14788
|
+
const table = foreignKeyTable(column7);
|
|
13855
14789
|
if (table === null) return null;
|
|
13856
14790
|
const referenced = site.get(table);
|
|
13857
14791
|
if (referenced === null) return null;
|
|
@@ -13866,9 +14800,9 @@ function makeAdminRouter(site, options) {
|
|
|
13866
14800
|
const options2 = {};
|
|
13867
14801
|
for (const field of Object.keys(foreignKeyFields(admin))) {
|
|
13868
14802
|
if (admin.autocompleteFields.includes(field)) continue;
|
|
13869
|
-
const
|
|
13870
|
-
if (
|
|
13871
|
-
const related = await relatedOptions(
|
|
14803
|
+
const column7 = columns[field];
|
|
14804
|
+
if (column7 === void 0) continue;
|
|
14805
|
+
const related = await relatedOptions(column7, dbSession);
|
|
13872
14806
|
if (related !== null) options2[field] = related;
|
|
13873
14807
|
}
|
|
13874
14808
|
return options2;
|
|
@@ -13887,8 +14821,8 @@ function makeAdminRouter(site, options) {
|
|
|
13887
14821
|
for (const field of admin.autocompleteFields) {
|
|
13888
14822
|
const value = row[field];
|
|
13889
14823
|
if (value === null || value === void 0 || value === "") continue;
|
|
13890
|
-
const
|
|
13891
|
-
const table =
|
|
14824
|
+
const column7 = columns[field];
|
|
14825
|
+
const table = column7 === void 0 ? null : foreignKeyTable(column7);
|
|
13892
14826
|
const referenced = table === null ? null : site.get(table);
|
|
13893
14827
|
if (referenced === null) continue;
|
|
13894
14828
|
const related = await referenced.repository(dbSession).first({ [referenced.identityField]: value });
|
|
@@ -13927,6 +14861,21 @@ function inlineFieldNames(childAdmin, inline) {
|
|
|
13927
14861
|
function inlineFields(childAdmin, names, key, values, errors) {
|
|
13928
14862
|
return buildFormFields(childAdmin, { values, errors }).filter((field) => names.includes(field.name)).map((field) => ({ ...field, name: `row.${key}.${field.name}` }));
|
|
13929
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
|
+
}
|
|
14869
|
+
async function audit(hook, entry) {
|
|
14870
|
+
if (hook === void 0) return;
|
|
14871
|
+
try {
|
|
14872
|
+
await hook(entry);
|
|
14873
|
+
} catch (error) {
|
|
14874
|
+
logger2.error("Admin SQL audit hook failed", {
|
|
14875
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14876
|
+
});
|
|
14877
|
+
}
|
|
14878
|
+
}
|
|
13930
14879
|
function flagAllows(admin, action) {
|
|
13931
14880
|
if (action === AdminPermission.CREATE) return admin.canCreate;
|
|
13932
14881
|
if (action === AdminPermission.EDIT) return admin.canEdit;
|
|
@@ -14080,7 +15029,7 @@ function csvField(value) {
|
|
|
14080
15029
|
function toCsv(columns, rows) {
|
|
14081
15030
|
const lines = [columns.map(csvField).join(",")];
|
|
14082
15031
|
for (const row of rows) {
|
|
14083
|
-
lines.push(columns.map((
|
|
15032
|
+
lines.push(columns.map((column7) => csvField(exportValue(row[column7]))).join(","));
|
|
14084
15033
|
}
|
|
14085
15034
|
return `${lines.join("\r\n")}\r
|
|
14086
15035
|
`;
|
|
@@ -14088,7 +15037,7 @@ function toCsv(columns, rows) {
|
|
|
14088
15037
|
function toJson(columns, rows) {
|
|
14089
15038
|
return JSON.stringify(
|
|
14090
15039
|
rows.map(
|
|
14091
|
-
(row) => Object.fromEntries(columns.map((
|
|
15040
|
+
(row) => Object.fromEntries(columns.map((column7) => [column7, exportValue(row[column7])]))
|
|
14092
15041
|
),
|
|
14093
15042
|
null,
|
|
14094
15043
|
2
|
|
@@ -16013,59 +16962,6 @@ function makeToolSpecRouter(spec, options = {}) {
|
|
|
16013
16962
|
});
|
|
16014
16963
|
return router;
|
|
16015
16964
|
}
|
|
16016
|
-
function filesFor(dir, source) {
|
|
16017
|
-
if (source === "500") return [path.join(dir, HTTP_500_LOG_FILE)];
|
|
16018
|
-
if (source === "all") return Object.values(LEVEL_LOG_FILES).map((f) => path.join(dir, f));
|
|
16019
|
-
return [path.join(dir, LEVEL_LOG_FILES[source])];
|
|
16020
|
-
}
|
|
16021
|
-
async function readEntries(files) {
|
|
16022
|
-
const entries = [];
|
|
16023
|
-
for (const file of files) {
|
|
16024
|
-
let content;
|
|
16025
|
-
try {
|
|
16026
|
-
content = await promises.readFile(file, "utf8");
|
|
16027
|
-
} catch {
|
|
16028
|
-
continue;
|
|
16029
|
-
}
|
|
16030
|
-
for (const line of content.split("\n")) {
|
|
16031
|
-
if (!line.trim()) continue;
|
|
16032
|
-
try {
|
|
16033
|
-
entries.push(JSON.parse(line));
|
|
16034
|
-
} catch {
|
|
16035
|
-
}
|
|
16036
|
-
}
|
|
16037
|
-
}
|
|
16038
|
-
return entries;
|
|
16039
|
-
}
|
|
16040
|
-
function makeLogsRouter(options) {
|
|
16041
|
-
const router = express3.Router();
|
|
16042
|
-
const path = options.path ?? "/logs";
|
|
16043
|
-
const guards = options.guards ?? [];
|
|
16044
|
-
const handler = (req, res, next) => {
|
|
16045
|
-
const source = req.query.source ?? "all";
|
|
16046
|
-
const validSources = ["all", "debug", "info", "warning", "error", "500"];
|
|
16047
|
-
if (!validSources.includes(source)) {
|
|
16048
|
-
res.status(422).json({ detail: "Invalid log source", code: "VALIDATION_ERROR", details: {} });
|
|
16049
|
-
return;
|
|
16050
|
-
}
|
|
16051
|
-
const page2 = Math.max(1, Number(req.query.page) || 1);
|
|
16052
|
-
const pageSize = Math.min(500, Math.max(1, Number(req.query.pageSize) || 50));
|
|
16053
|
-
readEntries(filesFor(options.dir, source)).then((entries) => {
|
|
16054
|
-
entries.reverse();
|
|
16055
|
-
const total = entries.length;
|
|
16056
|
-
const start = (page2 - 1) * pageSize;
|
|
16057
|
-
res.json({
|
|
16058
|
-
items: entries.slice(start, start + pageSize),
|
|
16059
|
-
total,
|
|
16060
|
-
page: page2,
|
|
16061
|
-
pageSize,
|
|
16062
|
-
pages: Math.ceil(total / pageSize)
|
|
16063
|
-
});
|
|
16064
|
-
}).catch(next);
|
|
16065
|
-
};
|
|
16066
|
-
router.get(path, ...guards, handler);
|
|
16067
|
-
return router;
|
|
16068
|
-
}
|
|
16069
16965
|
function createTestDatabase(models) {
|
|
16070
16966
|
const driver = tempestDbJs.NodeSqliteDriver.open(":memory:");
|
|
16071
16967
|
for (const model of models) {
|
|
@@ -16098,7 +16994,7 @@ async function withTestDatabase(models, fn) {
|
|
|
16098
16994
|
}
|
|
16099
16995
|
|
|
16100
16996
|
// src/version.ts
|
|
16101
|
-
var VERSION = "0.
|
|
16997
|
+
var VERSION = "0.30.0";
|
|
16102
16998
|
|
|
16103
16999
|
Object.defineProperty(exports, "OpenAPIRegistry", {
|
|
16104
17000
|
enumerable: true,
|
|
@@ -16264,6 +17160,7 @@ exports.AttemptThrottle = AttemptThrottle;
|
|
|
16264
17160
|
exports.AuditAction = AuditAction;
|
|
16265
17161
|
exports.BaseAuditLogModel = BaseAuditLogModel;
|
|
16266
17162
|
exports.BaseController = BaseController;
|
|
17163
|
+
exports.BaseJobModel = BaseJobModel;
|
|
16267
17164
|
exports.BaseModel = BaseModel;
|
|
16268
17165
|
exports.BaseOAuthClient = BaseOAuthClient;
|
|
16269
17166
|
exports.BaseOutboxModel = BaseOutboxModel;
|
|
@@ -16299,6 +17196,8 @@ exports.IDEMPOTENCY_HEADER = IDEMPOTENCY_HEADER;
|
|
|
16299
17196
|
exports.InvalidTokenException = InvalidTokenException;
|
|
16300
17197
|
exports.JSONLogger = JSONLogger;
|
|
16301
17198
|
exports.JWTUtils = JWTUtils;
|
|
17199
|
+
exports.JobStatus = JobStatus;
|
|
17200
|
+
exports.JobStore = JobStore;
|
|
16302
17201
|
exports.LEVEL_LOG_FILES = LEVEL_LOG_FILES;
|
|
16303
17202
|
exports.LocalUploadStorage = LocalUploadStorage;
|
|
16304
17203
|
exports.MemoryBroker = MemoryBroker;
|
|
@@ -16335,6 +17234,7 @@ exports.S3UploadStorage = S3UploadStorage;
|
|
|
16335
17234
|
exports.SSEBroker = SSEBroker;
|
|
16336
17235
|
exports.ServerSentEvent = ServerSentEvent;
|
|
16337
17236
|
exports.SessionService = SessionService;
|
|
17237
|
+
exports.SqlCapability = SqlCapability;
|
|
16338
17238
|
exports.TOTPHelper = TOTPHelper;
|
|
16339
17239
|
exports.TaskManager = TaskManager;
|
|
16340
17240
|
exports.TelegramProvider = TelegramProvider;
|
|
@@ -16361,6 +17261,7 @@ exports.adminColumns = adminColumns;
|
|
|
16361
17261
|
exports.adminInline = adminInline;
|
|
16362
17262
|
exports.adminLens = adminLens;
|
|
16363
17263
|
exports.adminThemeCss = adminThemeCss;
|
|
17264
|
+
exports.analyzeSql = analyzeSql;
|
|
16364
17265
|
exports.attachWebSocketHub = attachWebSocketHub;
|
|
16365
17266
|
exports.authResponseSchema = authResponseSchema;
|
|
16366
17267
|
exports.authSettingsShape = authSettingsShape;
|
|
@@ -16377,6 +17278,7 @@ exports.buildPaginationLinkHeader = buildPaginationLinkHeader;
|
|
|
16377
17278
|
exports.cached = cached3;
|
|
16378
17279
|
exports.centsField = centsField;
|
|
16379
17280
|
exports.cepField = cepField;
|
|
17281
|
+
exports.checkSqlPolicy = checkSqlPolicy;
|
|
16380
17282
|
exports.citiesByUf = citiesByUf;
|
|
16381
17283
|
exports.cnpjField = cnpjField;
|
|
16382
17284
|
exports.coerceFlag = coerceFlag;
|
|
@@ -16405,6 +17307,7 @@ exports.envBoolean = looseBoolean;
|
|
|
16405
17307
|
exports.envList = envList;
|
|
16406
17308
|
exports.escapeHtml = escapeHtml;
|
|
16407
17309
|
exports.filterForColumn = filterForColumn;
|
|
17310
|
+
exports.filterLogEntries = filterLogEntries;
|
|
16408
17311
|
exports.foreignKeyFields = foreignKeyFields;
|
|
16409
17312
|
exports.foreignKeyLabel = foreignKeyLabel;
|
|
16410
17313
|
exports.foreignKeyTable = foreignKeyTable;
|
|
@@ -16444,6 +17347,7 @@ exports.keyByJwtSubject = keyByJwtSubject;
|
|
|
16444
17347
|
exports.latitudeField = latitudeField;
|
|
16445
17348
|
exports.listStates = listStates;
|
|
16446
17349
|
exports.loadSettings = loadSettings;
|
|
17350
|
+
exports.loadSqlParser = loadSqlParser;
|
|
16447
17351
|
exports.logEntrySchema = logEntrySchema;
|
|
16448
17352
|
exports.logSettingsShape = logSettingsShape;
|
|
16449
17353
|
exports.loginSchema = loginSchema;
|
|
@@ -16504,6 +17408,7 @@ exports.rabbitmqSettingsShape = rabbitmqSettingsShape;
|
|
|
16504
17408
|
exports.rateLimitMiddleware = rateLimitMiddleware;
|
|
16505
17409
|
exports.ratingField = ratingField;
|
|
16506
17410
|
exports.ratioField = ratioField;
|
|
17411
|
+
exports.readLogEntries = readLogEntries;
|
|
16507
17412
|
exports.redisSettingsShape = redisSettingsShape;
|
|
16508
17413
|
exports.refreshSchema = refreshSchema;
|
|
16509
17414
|
exports.registerExceptionHandlers = registerExceptionHandlers;
|
|
@@ -16514,9 +17419,15 @@ exports.renderFormPage = renderFormPage;
|
|
|
16514
17419
|
exports.renderImportPage = renderImportPage;
|
|
16515
17420
|
exports.renderLayout = renderLayout;
|
|
16516
17421
|
exports.renderListPage = renderListPage;
|
|
17422
|
+
exports.renderLogEntriesJson = renderLogEntriesJson;
|
|
17423
|
+
exports.renderLogEntriesMarkdown = renderLogEntriesMarkdown;
|
|
16517
17424
|
exports.renderLoginPage = renderLoginPage;
|
|
17425
|
+
exports.renderLogsPage = renderLogsPage;
|
|
16518
17426
|
exports.renderMfaPage = renderMfaPage;
|
|
16519
17427
|
exports.renderPasswordResetFormPage = renderPasswordResetFormPage;
|
|
17428
|
+
exports.renderSqlPage = renderSqlPage;
|
|
17429
|
+
exports.renderTaskDetailPage = renderTaskDetailPage;
|
|
17430
|
+
exports.renderTasksPage = renderTasksPage;
|
|
16520
17431
|
exports.requestIdMiddleware = requestIdMiddleware;
|
|
16521
17432
|
exports.requestTracingMiddleware = requestTracingMiddleware;
|
|
16522
17433
|
exports.requireRoles = requireRoles;
|
|
@@ -16540,6 +17451,7 @@ exports.syncFilterSchema = syncFilterSchema;
|
|
|
16540
17451
|
exports.syncPaginationSchema = syncPaginationSchema;
|
|
16541
17452
|
exports.tableNameFor = tableNameFor;
|
|
16542
17453
|
exports.toDict = toDict;
|
|
17454
|
+
exports.toLogEntry = toLogEntry;
|
|
16543
17455
|
exports.toUtc = toUtc;
|
|
16544
17456
|
exports.tokenFromUrl = tokenFromUrl;
|
|
16545
17457
|
exports.tokenPairSchema = tokenPairSchema;
|