tempest-express-sdk 0.27.0 → 0.29.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 +1 -1
- package/dist/{chunk-ADF7OHRH.js → chunk-7T4MZJZT.js} +3 -3
- package/dist/{chunk-ADF7OHRH.js.map → chunk-7T4MZJZT.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 +882 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +479 -2
- package/dist/index.d.ts +479 -2
- package/dist/index.js +871 -60
- 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-7T4MZJZT.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-7T4MZJZT.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';
|
|
@@ -9334,6 +9334,216 @@ function partitionTotal(partition) {
|
|
|
9334
9334
|
return partition.segments.reduce((total, segment) => total + segment.value, 0);
|
|
9335
9335
|
}
|
|
9336
9336
|
|
|
9337
|
+
// src/admin/logs.ts
|
|
9338
|
+
var LIFTED = /* @__PURE__ */ new Set(["level", "logger", "message", "timestamp", "stack"]);
|
|
9339
|
+
function toLogEntry(raw) {
|
|
9340
|
+
const context = {};
|
|
9341
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
9342
|
+
if (LIFTED.has(key) || value === null || value === void 0) continue;
|
|
9343
|
+
context[key] = value;
|
|
9344
|
+
}
|
|
9345
|
+
return {
|
|
9346
|
+
level: typeof raw.level === "string" ? raw.level : "info",
|
|
9347
|
+
logger: typeof raw.logger === "string" ? raw.logger : "",
|
|
9348
|
+
message: typeof raw.message === "string" ? raw.message : "",
|
|
9349
|
+
timestamp: typeof raw.timestamp === "string" ? raw.timestamp : "",
|
|
9350
|
+
stack: typeof raw.stack === "string" ? raw.stack : null,
|
|
9351
|
+
context,
|
|
9352
|
+
raw
|
|
9353
|
+
};
|
|
9354
|
+
}
|
|
9355
|
+
function filterLogEntries(entries, term) {
|
|
9356
|
+
const needle = term.trim().toLowerCase();
|
|
9357
|
+
if (needle === "") return entries;
|
|
9358
|
+
return entries.filter(
|
|
9359
|
+
(entry) => `${entry.message} ${entry.logger} ${entry.stack ?? ""}`.toLowerCase().includes(needle)
|
|
9360
|
+
);
|
|
9361
|
+
}
|
|
9362
|
+
function renderLogEntriesMarkdown(entries, options) {
|
|
9363
|
+
const lines = [
|
|
9364
|
+
"# Application logs",
|
|
9365
|
+
"",
|
|
9366
|
+
`- **Source:** \`${options.source}\``,
|
|
9367
|
+
`- **Search:** ${options.query === "" ? "_none_" : `\`${options.query}\``}`,
|
|
9368
|
+
`- **Exported:** ${entries.length} of ${options.total} matching record(s)`,
|
|
9369
|
+
""
|
|
9370
|
+
];
|
|
9371
|
+
if (entries.length < options.total) {
|
|
9372
|
+
lines.push(
|
|
9373
|
+
`> Truncated: the export is capped, so ${options.total - entries.length} older matching record(s) are not included.`,
|
|
9374
|
+
""
|
|
9375
|
+
);
|
|
9376
|
+
}
|
|
9377
|
+
for (const entry of entries) {
|
|
9378
|
+
lines.push(
|
|
9379
|
+
`## ${entry.level.toUpperCase()} \u2014 ${entry.message || "(no message)"}`,
|
|
9380
|
+
"",
|
|
9381
|
+
`- **When:** ${entry.timestamp || "unknown"}`,
|
|
9382
|
+
`- **Logger:** ${entry.logger || "unknown"}`
|
|
9383
|
+
);
|
|
9384
|
+
for (const [key, value] of Object.entries(entry.context)) {
|
|
9385
|
+
lines.push(
|
|
9386
|
+
`- **${key}:** ${typeof value === "string" ? value : JSON.stringify(value)}`
|
|
9387
|
+
);
|
|
9388
|
+
}
|
|
9389
|
+
lines.push("");
|
|
9390
|
+
if (entry.stack !== null) {
|
|
9391
|
+
lines.push("```text", entry.stack, "```", "");
|
|
9392
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
return lines.join("\n");
|
|
9395
|
+
}
|
|
9396
|
+
function renderLogEntriesJson(entries) {
|
|
9397
|
+
return JSON.stringify(
|
|
9398
|
+
entries.map((entry) => entry.raw),
|
|
9399
|
+
null,
|
|
9400
|
+
2
|
|
9401
|
+
);
|
|
9402
|
+
}
|
|
9403
|
+
|
|
9404
|
+
// src/admin/sqlConsole.ts
|
|
9405
|
+
var SqlCapability = {
|
|
9406
|
+
/** `SELECT`, `WITH … SELECT`, `EXPLAIN`, `SHOW`. */
|
|
9407
|
+
READ: "read",
|
|
9408
|
+
/** Adds rows. */
|
|
9409
|
+
INSERT: "insert",
|
|
9410
|
+
/** Changes rows. */
|
|
9411
|
+
UPDATE: "update",
|
|
9412
|
+
/** Removes rows. */
|
|
9413
|
+
DELETE: "delete",
|
|
9414
|
+
/** `CREATE` / `ALTER` / `COMMENT`. */
|
|
9415
|
+
DDL: "ddl",
|
|
9416
|
+
/** `DROP` and `TRUNCATE`: irreversible structure loss. */
|
|
9417
|
+
DROP: "drop",
|
|
9418
|
+
/**
|
|
9419
|
+
* `GRANT` / `REVOKE` / `SET`, and anything the analyser cannot classify.
|
|
9420
|
+
* Unknown statements land here on purpose, so a construct nobody anticipated
|
|
9421
|
+
* needs the most privileged capability rather than the least.
|
|
9422
|
+
*/
|
|
9423
|
+
ADMIN: "admin"
|
|
9424
|
+
};
|
|
9425
|
+
var STATEMENT_CAPABILITIES = {
|
|
9426
|
+
select: SqlCapability.READ,
|
|
9427
|
+
with: SqlCapability.READ,
|
|
9428
|
+
explain: SqlCapability.READ,
|
|
9429
|
+
show: SqlCapability.READ,
|
|
9430
|
+
desc: SqlCapability.READ,
|
|
9431
|
+
describe: SqlCapability.READ,
|
|
9432
|
+
insert: SqlCapability.INSERT,
|
|
9433
|
+
replace: SqlCapability.INSERT,
|
|
9434
|
+
update: SqlCapability.UPDATE,
|
|
9435
|
+
delete: SqlCapability.DELETE,
|
|
9436
|
+
create: SqlCapability.DDL,
|
|
9437
|
+
alter: SqlCapability.DDL,
|
|
9438
|
+
comment: SqlCapability.DDL,
|
|
9439
|
+
rename: SqlCapability.DDL,
|
|
9440
|
+
drop: SqlCapability.DROP,
|
|
9441
|
+
truncate: SqlCapability.DROP
|
|
9442
|
+
};
|
|
9443
|
+
var PARSER_HINT = "The admin SQL console needs the optional peer `node-sql-parser`. Install it with: npm install node-sql-parser";
|
|
9444
|
+
async function loadSqlParser() {
|
|
9445
|
+
try {
|
|
9446
|
+
const module = await import('node-sql-parser');
|
|
9447
|
+
const Parser = module.Parser ?? module.default?.Parser;
|
|
9448
|
+
if (Parser === void 0) throw new Error(PARSER_HINT);
|
|
9449
|
+
return new Parser();
|
|
9450
|
+
} catch {
|
|
9451
|
+
throw new Error(PARSER_HINT);
|
|
9452
|
+
}
|
|
9453
|
+
}
|
|
9454
|
+
function analyzeSql(sql5, dialect, parser) {
|
|
9455
|
+
const capabilities = /* @__PURE__ */ new Set();
|
|
9456
|
+
const tables = /* @__PURE__ */ new Set();
|
|
9457
|
+
let statements = 0;
|
|
9458
|
+
let parsed = true;
|
|
9459
|
+
let unscopedWrite = false;
|
|
9460
|
+
try {
|
|
9461
|
+
const ast = parser.astify(sql5, { database: dialect });
|
|
9462
|
+
const list = Array.isArray(ast) ? ast : [ast];
|
|
9463
|
+
statements = list.length;
|
|
9464
|
+
for (const statement of list) {
|
|
9465
|
+
const type = String(statement.type ?? "").toLowerCase();
|
|
9466
|
+
capabilities.add(STATEMENT_CAPABILITIES[type] ?? SqlCapability.ADMIN);
|
|
9467
|
+
if ((type === "update" || type === "delete") && !statement.where) {
|
|
9468
|
+
unscopedWrite = true;
|
|
9469
|
+
}
|
|
9470
|
+
}
|
|
9471
|
+
} catch {
|
|
9472
|
+
parsed = false;
|
|
9473
|
+
statements = 1;
|
|
9474
|
+
capabilities.add(SqlCapability.ADMIN);
|
|
9475
|
+
}
|
|
9476
|
+
try {
|
|
9477
|
+
for (const entry of parser.tableList(sql5, { database: dialect })) {
|
|
9478
|
+
const name = entry.split("::").pop();
|
|
9479
|
+
if (name !== void 0 && name !== "null") tables.add(name.toLowerCase());
|
|
9480
|
+
}
|
|
9481
|
+
} catch {
|
|
9482
|
+
}
|
|
9483
|
+
return {
|
|
9484
|
+
statements,
|
|
9485
|
+
capabilities: [...capabilities],
|
|
9486
|
+
tables: [...tables],
|
|
9487
|
+
parsed,
|
|
9488
|
+
unscopedWrite
|
|
9489
|
+
};
|
|
9490
|
+
}
|
|
9491
|
+
function checkSqlPolicy(analysis, policy) {
|
|
9492
|
+
const granted = new Set(policy.capabilities ?? [SqlCapability.READ]);
|
|
9493
|
+
for (const capability of analysis.capabilities) {
|
|
9494
|
+
if (!granted.has(capability)) {
|
|
9495
|
+
return {
|
|
9496
|
+
allowed: false,
|
|
9497
|
+
reason: analysis.parsed ? `This console may not run ${capability} statements.` : "The statement could not be parsed, so it needs the admin capability."
|
|
9498
|
+
};
|
|
9499
|
+
}
|
|
9500
|
+
}
|
|
9501
|
+
if ((policy.requireWhereOnWrites ?? true) && analysis.unscopedWrite) {
|
|
9502
|
+
return {
|
|
9503
|
+
allowed: false,
|
|
9504
|
+
reason: "An UPDATE or DELETE without a WHERE clause is refused."
|
|
9505
|
+
};
|
|
9506
|
+
}
|
|
9507
|
+
const denied = new Set((policy.denyTables ?? []).map((name) => name.toLowerCase()));
|
|
9508
|
+
for (const table of analysis.tables) {
|
|
9509
|
+
if (denied.has(table)) {
|
|
9510
|
+
return { allowed: false, reason: `Table "${table}" is not available here.` };
|
|
9511
|
+
}
|
|
9512
|
+
}
|
|
9513
|
+
if (policy.allowTables !== void 0) {
|
|
9514
|
+
const allowed = new Set(policy.allowTables.map((name) => name.toLowerCase()));
|
|
9515
|
+
if (analysis.tables.length === 0) {
|
|
9516
|
+
return {
|
|
9517
|
+
allowed: false,
|
|
9518
|
+
reason: "This console only runs statements naming an allowed table."
|
|
9519
|
+
};
|
|
9520
|
+
}
|
|
9521
|
+
for (const table of analysis.tables) {
|
|
9522
|
+
if (!allowed.has(table)) {
|
|
9523
|
+
return { allowed: false, reason: `Table "${table}" is not on the allow list.` };
|
|
9524
|
+
}
|
|
9525
|
+
}
|
|
9526
|
+
}
|
|
9527
|
+
return { allowed: true, reason: null };
|
|
9528
|
+
}
|
|
9529
|
+
|
|
9530
|
+
// src/admin/inlines.ts
|
|
9531
|
+
function adminInline(options) {
|
|
9532
|
+
const slug = options.model.tablename;
|
|
9533
|
+
if (typeof slug !== "string" || slug === "") {
|
|
9534
|
+
throw new Error("adminInline requires a concrete model with a tablename");
|
|
9535
|
+
}
|
|
9536
|
+
return {
|
|
9537
|
+
model: options.model,
|
|
9538
|
+
slug,
|
|
9539
|
+
fkField: options.fkField,
|
|
9540
|
+
listDisplay: options.listDisplay === void 0 ? null : [...options.listDisplay],
|
|
9541
|
+
label: options.label ?? null,
|
|
9542
|
+
editable: options.editable ?? false,
|
|
9543
|
+
canDelete: options.canDelete ?? false
|
|
9544
|
+
};
|
|
9545
|
+
}
|
|
9546
|
+
|
|
9337
9547
|
// src/admin/lenses.ts
|
|
9338
9548
|
function slugify(name) {
|
|
9339
9549
|
return name.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "lens";
|
|
@@ -9589,6 +9799,8 @@ var AdminModel = class {
|
|
|
9589
9799
|
canImport;
|
|
9590
9800
|
/** Foreign-key columns rendered as a typed search box. */
|
|
9591
9801
|
autocompleteFields;
|
|
9802
|
+
/** Related child models listed on the detail view. */
|
|
9803
|
+
inlines;
|
|
9592
9804
|
actions = /* @__PURE__ */ new Map();
|
|
9593
9805
|
slugOverride;
|
|
9594
9806
|
listDisplayOverride;
|
|
@@ -9620,6 +9832,7 @@ var AdminModel = class {
|
|
|
9620
9832
|
this.uploadStorage = options.uploadStorage ?? null;
|
|
9621
9833
|
this.canImport = options.canImport ?? false;
|
|
9622
9834
|
this.autocompleteFields = [...options.autocompleteFields ?? []];
|
|
9835
|
+
this.inlines = [...options.inlines ?? []];
|
|
9623
9836
|
if (this.uploadFields.length > 0 && this.uploadStorage === null) {
|
|
9624
9837
|
throw new Error(
|
|
9625
9838
|
`AdminModel(${this.model.tablename}).uploadFields requires an uploadStorage (e.g. LocalUploadStorage / S3UploadStorage) \u2014 without one there is nowhere to write the file.`
|
|
@@ -9898,7 +10111,9 @@ function parseFormBody(admin, body, options = {}) {
|
|
|
9898
10111
|
const data = {};
|
|
9899
10112
|
const errors = {};
|
|
9900
10113
|
const uploads = options.uploadsAsText === true ? /* @__PURE__ */ new Set() : new Set(admin.uploadFields);
|
|
10114
|
+
const only = options.only === void 0 ? null : new Set(options.only);
|
|
9901
10115
|
for (const name of admin.editableFieldNames()) {
|
|
10116
|
+
if (only !== null && !only.has(name)) continue;
|
|
9902
10117
|
const column6 = columns[name];
|
|
9903
10118
|
if (column6 === void 0) continue;
|
|
9904
10119
|
if (uploads.has(name)) continue;
|
|
@@ -11963,7 +12178,7 @@ function escapeHtml(value) {
|
|
|
11963
12178
|
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
11964
12179
|
}
|
|
11965
12180
|
function renderLayout(context, title, body) {
|
|
11966
|
-
const { site, theme, prefix, session, currentPath, navModels, messages } = context;
|
|
12181
|
+
const { site, theme, prefix, session, currentPath, navModels, navSystem, messages } = context;
|
|
11967
12182
|
const authed = session !== null;
|
|
11968
12183
|
const indexUrl = `${prefix}/`;
|
|
11969
12184
|
const navLink = (url, label, active) => `<a href="${escapeHtml(url)}" class="tempest-admin-sidebar__link${active ? " tempest-admin-sidebar__link--active" : ""}">${escapeHtml(label)}</a>`;
|
|
@@ -11973,6 +12188,9 @@ function renderLayout(context, title, body) {
|
|
|
11973
12188
|
${navLink(indexUrl, "Dashboard", currentPath === indexUrl)}
|
|
11974
12189
|
${navModels.length > 0 ? `<span class="tempest-admin-sidebar__heading">Models</span>${navModels.map(
|
|
11975
12190
|
(entry) => navLink(entry.url, entry.label, currentPath.startsWith(entry.url))
|
|
12191
|
+
).join("")}` : ""}
|
|
12192
|
+
${navSystem.length > 0 ? `<span class="tempest-admin-sidebar__heading">System</span>${navSystem.map(
|
|
12193
|
+
(entry) => navLink(entry.url, entry.label, currentPath.startsWith(entry.url))
|
|
11976
12194
|
).join("")}` : ""}
|
|
11977
12195
|
</nav>
|
|
11978
12196
|
</aside>` : "";
|
|
@@ -12232,15 +12450,102 @@ function renderDetailPage(context, view) {
|
|
|
12232
12450
|
</div>
|
|
12233
12451
|
</header>
|
|
12234
12452
|
<dl class="tempest-admin-detail__fields">${fields}</dl>
|
|
12453
|
+
${view.inlines.map((inline) => renderInline(inline, context.session?.csrfToken ?? "", view.inlineError)).join("")}
|
|
12235
12454
|
${auditPanel}
|
|
12236
12455
|
</section>`;
|
|
12237
12456
|
return renderLayout(context, `${view.title} \xB7 ${view.identity}`, body);
|
|
12238
12457
|
}
|
|
12239
|
-
function
|
|
12240
|
-
const
|
|
12458
|
+
function renderInlineCell(field) {
|
|
12459
|
+
const required = field.required ? " required" : "";
|
|
12460
|
+
const name = escapeHtml(field.name);
|
|
12461
|
+
const value = escapeHtml(field.value);
|
|
12462
|
+
let control;
|
|
12463
|
+
switch (field.widget) {
|
|
12464
|
+
case "checkbox":
|
|
12465
|
+
control = `<input type="checkbox" name="${name}" value="true"${field.checked ? " checked" : ""}>`;
|
|
12466
|
+
break;
|
|
12467
|
+
case "textarea":
|
|
12468
|
+
case "json":
|
|
12469
|
+
control = `<textarea name="${name}" rows="2"${required}>${value}</textarea>`;
|
|
12470
|
+
break;
|
|
12471
|
+
case "select": {
|
|
12472
|
+
const blank = field.required ? "" : '<option value="">\u2014 none \u2014</option>';
|
|
12473
|
+
const options = field.options.map(
|
|
12474
|
+
(option) => `<option value="${escapeHtml(option.value)}"${option.value === field.value ? " selected" : ""}>${escapeHtml(option.label)}</option>`
|
|
12475
|
+
).join("");
|
|
12476
|
+
control = `<select name="${name}"${required}>${blank}${options}</select>`;
|
|
12477
|
+
break;
|
|
12478
|
+
}
|
|
12479
|
+
case "number":
|
|
12480
|
+
control = `<input type="number" name="${name}" value="${value}"${field.step !== null ? ` step="${escapeHtml(field.step)}"` : ""}${required}>`;
|
|
12481
|
+
break;
|
|
12482
|
+
case "datetime":
|
|
12483
|
+
control = `<input type="datetime-local" name="${name}" value="${value}"${required}>`;
|
|
12484
|
+
break;
|
|
12485
|
+
case "date":
|
|
12486
|
+
control = `<input type="date" name="${name}" value="${value}"${required}>`;
|
|
12487
|
+
break;
|
|
12488
|
+
case "time":
|
|
12489
|
+
control = `<input type="time" name="${name}" value="${value}"${required}>`;
|
|
12490
|
+
break;
|
|
12491
|
+
default:
|
|
12492
|
+
control = `<input type="text" name="${name}" value="${value}"${required}>`;
|
|
12493
|
+
}
|
|
12494
|
+
const error = field.error !== null ? `<small class="tempest-admin-form__field-error">${escapeHtml(field.error)}</small>` : "";
|
|
12495
|
+
return `${control}${error}`;
|
|
12496
|
+
}
|
|
12497
|
+
function renderInline(inline, csrfToken, error) {
|
|
12498
|
+
const heading = `<header class="tempest-admin-inline__header">
|
|
12499
|
+
<h2>${escapeHtml(inline.label)}${inline.total > 0 ? ` <span class="tempest-admin-inline__count">(${escapeHtml(inline.total)})</span>` : ""}</h2>
|
|
12500
|
+
${inline.addUrl !== null ? `<a class="tempest-admin-btn" href="${escapeHtml(inline.addUrl)}">Add</a>` : ""}
|
|
12501
|
+
</header>`;
|
|
12502
|
+
const more = inline.truncated ? `<p class="tempest-admin-inline__more"><em>Showing the first ${escapeHtml(inline.rows.length)} of ${escapeHtml(inline.total)}.</em></p>` : "";
|
|
12503
|
+
if (!inline.editable) {
|
|
12504
|
+
if (inline.rows.length === 0) {
|
|
12505
|
+
return `<section class="tempest-admin-inline">${heading}<p><em>No related records.</em></p></section>`;
|
|
12506
|
+
}
|
|
12507
|
+
const head2 = `<tr>${inline.columns.map((column6) => `<th>${escapeHtml(column6)}</th>`).join("")}<th></th></tr>`;
|
|
12508
|
+
const body = inline.rows.map(
|
|
12509
|
+
(row) => `<tr>${row.cells.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}<td>${row.url === null ? "" : `<a href="${escapeHtml(row.url)}">View</a>`}</td></tr>`
|
|
12510
|
+
).join("");
|
|
12511
|
+
return `<section class="tempest-admin-inline">
|
|
12512
|
+
${heading}
|
|
12513
|
+
<div class="tempest-admin-inline__scroll">
|
|
12514
|
+
<table class="tempest-admin-inline__table">
|
|
12515
|
+
<thead>${head2}</thead>
|
|
12516
|
+
<tbody>${body}</tbody>
|
|
12517
|
+
</table>
|
|
12518
|
+
</div>
|
|
12519
|
+
${more}
|
|
12520
|
+
</section>`;
|
|
12521
|
+
}
|
|
12522
|
+
const head = `<tr>${inline.columns.map((column6) => `<th>${escapeHtml(column6)}</th>`).join("")}${inline.canDelete ? "<th>Delete</th>" : ""}</tr>`;
|
|
12523
|
+
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>`;
|
|
12524
|
+
const rows = inline.rows.map((row) => renderRow(row, false)).join("");
|
|
12525
|
+
const blank = inline.newRow === null ? "" : renderRow(inline.newRow, true);
|
|
12526
|
+
return `<section class="tempest-admin-inline">
|
|
12527
|
+
${heading}
|
|
12528
|
+
${error !== null ? `<p class="tempest-admin-form__error">${escapeHtml(error)}</p>` : ""}
|
|
12529
|
+
<form method="post" action="${escapeHtml(inline.formAction)}" class="tempest-admin-inline__form">
|
|
12530
|
+
<input type="hidden" name="csrf_token" value="${escapeHtml(csrfToken)}">
|
|
12531
|
+
<div class="tempest-admin-inline__scroll">
|
|
12532
|
+
<table class="tempest-admin-inline__table">
|
|
12533
|
+
<thead>${head}</thead>
|
|
12534
|
+
<tbody>${rows}${blank}</tbody>
|
|
12535
|
+
</table>
|
|
12536
|
+
</div>
|
|
12537
|
+
<div class="tempest-admin-form__actions">
|
|
12538
|
+
<button type="submit">Save ${escapeHtml(inline.label)}</button>
|
|
12539
|
+
</div>
|
|
12540
|
+
</form>
|
|
12541
|
+
${more}
|
|
12542
|
+
</section>`;
|
|
12543
|
+
}
|
|
12544
|
+
function renderAuditPanel(audit2) {
|
|
12545
|
+
const rows = audit2.fields.map(
|
|
12241
12546
|
(field) => `<dt>${escapeHtml(field.label)}</dt><dd>${field.value === "" ? "<em>\u2014</em>" : escapeHtml(field.value)}</dd>`
|
|
12242
12547
|
).join("");
|
|
12243
|
-
const history =
|
|
12548
|
+
const history = audit2.history.length > 0 ? `<ol class="tempest-admin-history">${audit2.history.map((entry) => {
|
|
12244
12549
|
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(
|
|
12245
12550
|
(change) => `<tr><td>${escapeHtml(change.field)}</td><td>${escapeHtml(change.before)}</td><td>${escapeHtml(change.after)}</td></tr>`
|
|
12246
12551
|
).join("")}</tbody></table>` : "<p><em>No field changes recorded.</em></p>";
|
|
@@ -12338,6 +12643,86 @@ function renderFormPage(context, view) {
|
|
|
12338
12643
|
</section>`;
|
|
12339
12644
|
return renderLayout(context, `${heading} \xB7 ${context.site.title}`, body);
|
|
12340
12645
|
}
|
|
12646
|
+
function renderLogsPage(context, view) {
|
|
12647
|
+
const options = view.sources.map(
|
|
12648
|
+
(source) => `<option value="${escapeHtml(source.value)}"${source.selected ? " selected" : ""}>${escapeHtml(source.label)}</option>`
|
|
12649
|
+
).join("");
|
|
12650
|
+
const rows = view.rows.length > 0 ? view.rows.map((row) => {
|
|
12651
|
+
const context_ = row.context.map(
|
|
12652
|
+
(entry) => `<span class="tempest-admin-logs__meta"><b>${escapeHtml(entry.key)}</b>: ${escapeHtml(entry.value)}</span>`
|
|
12653
|
+
).join(" ");
|
|
12654
|
+
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>`;
|
|
12655
|
+
return `<tr>
|
|
12656
|
+
<td data-label="Level"><span class="tempest-log-badge tempest-log-badge--${escapeHtml(row.level)}">${escapeHtml(row.level)}</span></td>
|
|
12657
|
+
<td data-label="When">${escapeHtml(row.timestamp)}</td>
|
|
12658
|
+
<td data-label="Logger">${escapeHtml(row.logger)}</td>
|
|
12659
|
+
<td data-label="Message">${message}</td>
|
|
12660
|
+
</tr>`;
|
|
12661
|
+
}).join("") : `<tr><td colspan="4">No log records yet. Point <code>configureFileLogging</code> at the same directory to populate this page.</td></tr>`;
|
|
12662
|
+
const body = `<section class="tempest-admin-logs">
|
|
12663
|
+
<header class="tempest-admin-list__header">
|
|
12664
|
+
<h1>Logs</h1>
|
|
12665
|
+
<p>${escapeHtml(view.total)} record${view.total === 1 ? "" : "s"}.</p>
|
|
12666
|
+
</header>
|
|
12667
|
+
<div class="tempest-admin-list__toolbar">
|
|
12668
|
+
<form method="get" class="tempest-admin-list__filters">
|
|
12669
|
+
<label><span>Source</span><select name="source">${options}</select></label>
|
|
12670
|
+
<input type="search" name="q" value="${escapeHtml(view.query)}" placeholder="Search\u2026" aria-label="Search">
|
|
12671
|
+
<button type="submit">Apply</button>
|
|
12672
|
+
</form>
|
|
12673
|
+
<div class="tempest-admin-list__actions">
|
|
12674
|
+
<a href="${escapeHtml(view.exportMarkdownUrl)}">Export Markdown</a>
|
|
12675
|
+
<a href="${escapeHtml(view.exportJsonUrl)}">Export JSON</a>
|
|
12676
|
+
</div>
|
|
12677
|
+
</div>
|
|
12678
|
+
<div class="tempest-admin-table-wrap">
|
|
12679
|
+
<table class="tempest-admin-list__table">
|
|
12680
|
+
<thead><tr><th>Level</th><th>When</th><th>Logger</th><th>Message</th></tr></thead>
|
|
12681
|
+
<tbody>${rows}</tbody>
|
|
12682
|
+
</table>
|
|
12683
|
+
</div>
|
|
12684
|
+
${view.pages > 1 ? `<nav class="tempest-admin-list__pagination" aria-label="Pagination">
|
|
12685
|
+
${view.prevUrl !== null ? `<a href="${escapeHtml(view.prevUrl)}">\u2190 Prev</a>` : ""}
|
|
12686
|
+
<span>Page ${escapeHtml(view.page)} of ${escapeHtml(view.pages)}</span>
|
|
12687
|
+
${view.nextUrl !== null ? `<a href="${escapeHtml(view.nextUrl)}">Next \u2192</a>` : ""}
|
|
12688
|
+
</nav>` : ""}
|
|
12689
|
+
<p class="tempest-admin-form__hint">Exports carry at most ${escapeHtml(view.exportMax)} records, newest first, honouring the filters above.</p>
|
|
12690
|
+
</section>`;
|
|
12691
|
+
return renderLayout(context, `Logs \xB7 ${context.site.title}`, body);
|
|
12692
|
+
}
|
|
12693
|
+
function renderSqlPage(context, view) {
|
|
12694
|
+
if (context.session === null) throw new Error("The SQL console requires a session");
|
|
12695
|
+
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>
|
|
12696
|
+
<div class="tempest-admin-table-wrap">
|
|
12697
|
+
<table class="tempest-admin-list__table">
|
|
12698
|
+
<thead><tr>${view.columns.map((column6) => `<th>${escapeHtml(column6)}</th>`).join("")}</tr></thead>
|
|
12699
|
+
<tbody>${view.rows.map(
|
|
12700
|
+
(row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`
|
|
12701
|
+
).join("")}</tbody>
|
|
12702
|
+
</table>
|
|
12703
|
+
</div>`;
|
|
12704
|
+
const body = `<section class="tempest-admin-sql">
|
|
12705
|
+
<header class="tempest-admin-list__header">
|
|
12706
|
+
<h1>SQL console</h1>
|
|
12707
|
+
<p>Capabilities: <code>${escapeHtml(view.capabilities.join(", "))}</code></p>
|
|
12708
|
+
</header>
|
|
12709
|
+
${view.error !== null ? `<p class="tempest-admin-form__error">${escapeHtml(view.error)}</p>` : ""}
|
|
12710
|
+
<form method="post" action="${escapeHtml(`${context.prefix}/sql`)}" class="tempest-admin-form__form">
|
|
12711
|
+
<input type="hidden" name="csrf_token" value="${escapeHtml(context.session.csrfToken)}">
|
|
12712
|
+
<div class="tempest-admin-form__field">
|
|
12713
|
+
<label>
|
|
12714
|
+
<span>Statement</span>
|
|
12715
|
+
<textarea name="sql" rows="6" spellcheck="false" required>${escapeHtml(view.sql)}</textarea>
|
|
12716
|
+
</label>
|
|
12717
|
+
</div>
|
|
12718
|
+
<div class="tempest-admin-form__actions">
|
|
12719
|
+
<button type="submit">Run</button>
|
|
12720
|
+
</div>
|
|
12721
|
+
</form>
|
|
12722
|
+
${result}
|
|
12723
|
+
</section>`;
|
|
12724
|
+
return renderLayout(context, `SQL console \xB7 ${context.site.title}`, body);
|
|
12725
|
+
}
|
|
12341
12726
|
function renderImportPage(context, view) {
|
|
12342
12727
|
if (context.session === null) throw new Error("The import page requires a session");
|
|
12343
12728
|
const summary = view.created === null ? "" : `<p class="tempest-admin-import__summary">Created ${escapeHtml(view.created)} record${view.created === 1 ? "" : "s"}.</p>`;
|
|
@@ -12423,7 +12808,67 @@ var AUTOCOMPLETE_SCRIPT = `<script>
|
|
|
12423
12808
|
});
|
|
12424
12809
|
})();
|
|
12425
12810
|
</script>`;
|
|
12811
|
+
function filesFor(dir, source) {
|
|
12812
|
+
if (source === "500") return [join(dir, HTTP_500_LOG_FILE)];
|
|
12813
|
+
if (source === "all") return Object.values(LEVEL_LOG_FILES).map((f) => join(dir, f));
|
|
12814
|
+
return [join(dir, LEVEL_LOG_FILES[source])];
|
|
12815
|
+
}
|
|
12816
|
+
async function readLogEntries(dir, source) {
|
|
12817
|
+
return await readEntries(filesFor(dir, source));
|
|
12818
|
+
}
|
|
12819
|
+
async function readEntries(files) {
|
|
12820
|
+
const entries = [];
|
|
12821
|
+
for (const file of files) {
|
|
12822
|
+
let content;
|
|
12823
|
+
try {
|
|
12824
|
+
content = await readFile(file, "utf8");
|
|
12825
|
+
} catch {
|
|
12826
|
+
continue;
|
|
12827
|
+
}
|
|
12828
|
+
for (const line of content.split("\n")) {
|
|
12829
|
+
if (!line.trim()) continue;
|
|
12830
|
+
try {
|
|
12831
|
+
entries.push(JSON.parse(line));
|
|
12832
|
+
} catch {
|
|
12833
|
+
}
|
|
12834
|
+
}
|
|
12835
|
+
}
|
|
12836
|
+
return entries;
|
|
12837
|
+
}
|
|
12838
|
+
function makeLogsRouter(options) {
|
|
12839
|
+
const router = Router();
|
|
12840
|
+
const path = options.path ?? "/logs";
|
|
12841
|
+
const guards = options.guards ?? [];
|
|
12842
|
+
const handler = (req, res, next) => {
|
|
12843
|
+
const source = req.query.source ?? "all";
|
|
12844
|
+
const validSources = ["all", "debug", "info", "warning", "error", "500"];
|
|
12845
|
+
if (!validSources.includes(source)) {
|
|
12846
|
+
res.status(422).json({ detail: "Invalid log source", code: "VALIDATION_ERROR", details: {} });
|
|
12847
|
+
return;
|
|
12848
|
+
}
|
|
12849
|
+
const page2 = Math.max(1, Number(req.query.page) || 1);
|
|
12850
|
+
const pageSize = Math.min(500, Math.max(1, Number(req.query.pageSize) || 50));
|
|
12851
|
+
readEntries(filesFor(options.dir, source)).then((entries) => {
|
|
12852
|
+
entries.reverse();
|
|
12853
|
+
const total = entries.length;
|
|
12854
|
+
const start = (page2 - 1) * pageSize;
|
|
12855
|
+
res.json({
|
|
12856
|
+
items: entries.slice(start, start + pageSize),
|
|
12857
|
+
total,
|
|
12858
|
+
page: page2,
|
|
12859
|
+
pageSize,
|
|
12860
|
+
pages: Math.ceil(total / pageSize)
|
|
12861
|
+
});
|
|
12862
|
+
}).catch(next);
|
|
12863
|
+
};
|
|
12864
|
+
router.get(path, ...guards, handler);
|
|
12865
|
+
return router;
|
|
12866
|
+
}
|
|
12426
12867
|
var logger2 = new JSONLogger("tempest_express_sdk.admin.router");
|
|
12868
|
+
var LOG_SOURCES = ["all", "debug", "info", "warning", "error", "500"];
|
|
12869
|
+
var LOG_PAGE_SIZE = 50;
|
|
12870
|
+
var LOG_EXPORT_MAX = 500;
|
|
12871
|
+
var INLINE_ROW_LIMIT = 50;
|
|
12427
12872
|
var AUTOCOMPLETE_LIMIT = 20;
|
|
12428
12873
|
var AUDIT_HISTORY_LIMIT = 50;
|
|
12429
12874
|
var FK_OPTION_CAP = 1e3;
|
|
@@ -12454,6 +12899,13 @@ function makeAdminRouter(site, options) {
|
|
|
12454
12899
|
const theme = resolveAdminTheme(site.theme);
|
|
12455
12900
|
const showMetrics = options.showMetrics ?? true;
|
|
12456
12901
|
const exportMaxRows = options.exportMaxRows ?? 5e3;
|
|
12902
|
+
const systemNav = [];
|
|
12903
|
+
if (options.logDir !== void 0) {
|
|
12904
|
+
systemNav.push({ label: "Logs", url: `${prefix}/logs` });
|
|
12905
|
+
}
|
|
12906
|
+
if (options.sqlConsole !== void 0) {
|
|
12907
|
+
systemNav.push({ label: "SQL console", url: `${prefix}/sql` });
|
|
12908
|
+
}
|
|
12457
12909
|
const maxUploadBytes = options.maxUploadBytes ?? 10 * 1024 * 1024;
|
|
12458
12910
|
const sessions = new AdminSessionStore({
|
|
12459
12911
|
secret: options.secretKey,
|
|
@@ -12475,6 +12927,7 @@ function makeAdminRouter(site, options) {
|
|
|
12475
12927
|
label: admin.verboseNamePlural(),
|
|
12476
12928
|
url: `${prefix}/m/${admin.slug()}`
|
|
12477
12929
|
})),
|
|
12930
|
+
navSystem: systemNav,
|
|
12478
12931
|
messages: flashFor(req)
|
|
12479
12932
|
});
|
|
12480
12933
|
const allows = async (principal, admin, action) => {
|
|
@@ -12685,6 +13138,187 @@ function makeAdminRouter(site, options) {
|
|
|
12685
13138
|
);
|
|
12686
13139
|
})
|
|
12687
13140
|
);
|
|
13141
|
+
if (options.logDir !== void 0) {
|
|
13142
|
+
const logDir = options.logDir;
|
|
13143
|
+
router.get(
|
|
13144
|
+
`${prefix}/logs`,
|
|
13145
|
+
guarded(async (req, res) => {
|
|
13146
|
+
const state = await authenticate(req, res);
|
|
13147
|
+
if (state === null) return;
|
|
13148
|
+
const { entries, source, search } = await readLogs(req, logDir);
|
|
13149
|
+
const page2 = Math.max(1, Number.parseInt(queryString(req.query.page), 10) || 1);
|
|
13150
|
+
const start = (page2 - 1) * LOG_PAGE_SIZE;
|
|
13151
|
+
const pages = Math.max(1, Math.ceil(entries.length / LOG_PAGE_SIZE));
|
|
13152
|
+
const exportUrl = (format) => `${prefix}/logs/export?${buildQuery({ source, q: search, format })}`;
|
|
13153
|
+
const pageUrl = (target) => `?${buildQuery({ source, q: search, page: target })}`;
|
|
13154
|
+
html(
|
|
13155
|
+
res,
|
|
13156
|
+
renderLogsPage(context(req, state.session, state.visible), {
|
|
13157
|
+
sources: LOG_SOURCES.map((value) => ({
|
|
13158
|
+
value,
|
|
13159
|
+
label: value === "500" ? "HTTP 500" : humanizeField(value),
|
|
13160
|
+
selected: value === source
|
|
13161
|
+
})),
|
|
13162
|
+
query: search,
|
|
13163
|
+
rows: entries.slice(start, start + LOG_PAGE_SIZE).map((entry) => ({
|
|
13164
|
+
level: entry.level,
|
|
13165
|
+
timestamp: entry.timestamp,
|
|
13166
|
+
logger: entry.logger,
|
|
13167
|
+
message: entry.message,
|
|
13168
|
+
stack: entry.stack,
|
|
13169
|
+
context: Object.entries(entry.context).map(([key, value]) => ({
|
|
13170
|
+
key,
|
|
13171
|
+
value: typeof value === "string" ? value : JSON.stringify(value)
|
|
13172
|
+
}))
|
|
13173
|
+
})),
|
|
13174
|
+
total: entries.length,
|
|
13175
|
+
page: page2,
|
|
13176
|
+
pages,
|
|
13177
|
+
prevUrl: page2 > 1 ? pageUrl(page2 - 1) : null,
|
|
13178
|
+
nextUrl: page2 < pages ? pageUrl(page2 + 1) : null,
|
|
13179
|
+
exportMarkdownUrl: exportUrl("md"),
|
|
13180
|
+
exportJsonUrl: exportUrl("json"),
|
|
13181
|
+
exportMax: LOG_EXPORT_MAX
|
|
13182
|
+
})
|
|
13183
|
+
);
|
|
13184
|
+
})
|
|
13185
|
+
);
|
|
13186
|
+
router.get(
|
|
13187
|
+
`${prefix}/logs/export`,
|
|
13188
|
+
guarded(async (req, res) => {
|
|
13189
|
+
const state = await authenticate(req, res);
|
|
13190
|
+
if (state === null) return;
|
|
13191
|
+
const format = queryString(req.query.format) === "json" ? "json" : "md";
|
|
13192
|
+
const { entries, source, search } = await readLogs(req, logDir);
|
|
13193
|
+
const window = entries.slice(0, LOG_EXPORT_MAX);
|
|
13194
|
+
const payload = format === "json" ? renderLogEntriesJson(window) : renderLogEntriesMarkdown(window, {
|
|
13195
|
+
source,
|
|
13196
|
+
query: search,
|
|
13197
|
+
total: entries.length
|
|
13198
|
+
});
|
|
13199
|
+
res.status(200).type(format === "json" ? "application/json" : "text/markdown; charset=utf-8").set("content-disposition", `attachment; filename="logs.${format}"`).send(payload);
|
|
13200
|
+
})
|
|
13201
|
+
);
|
|
13202
|
+
}
|
|
13203
|
+
if (options.sqlConsole !== void 0) {
|
|
13204
|
+
const console_ = options.sqlConsole;
|
|
13205
|
+
const policy = console_.policy ?? {};
|
|
13206
|
+
const dialect = console_.dialect ?? "postgresql";
|
|
13207
|
+
const capabilities = policy.capabilities ?? [SqlCapability.READ];
|
|
13208
|
+
const maxRows = policy.maxRows ?? 200;
|
|
13209
|
+
router.get(
|
|
13210
|
+
`${prefix}/sql`,
|
|
13211
|
+
guarded(async (req, res) => {
|
|
13212
|
+
const state = await authenticate(req, res);
|
|
13213
|
+
if (state === null) return;
|
|
13214
|
+
html(
|
|
13215
|
+
res,
|
|
13216
|
+
renderSqlPage(context(req, state.session, state.visible), {
|
|
13217
|
+
sql: "",
|
|
13218
|
+
capabilities: [...capabilities],
|
|
13219
|
+
error: null,
|
|
13220
|
+
columns: [],
|
|
13221
|
+
rows: [],
|
|
13222
|
+
rowCount: null,
|
|
13223
|
+
truncated: false,
|
|
13224
|
+
durationMs: null
|
|
13225
|
+
})
|
|
13226
|
+
);
|
|
13227
|
+
})
|
|
13228
|
+
);
|
|
13229
|
+
router.post(
|
|
13230
|
+
`${prefix}/sql`,
|
|
13231
|
+
guarded(async (req, res) => {
|
|
13232
|
+
const state = await authenticate(req, res);
|
|
13233
|
+
if (state === null) return;
|
|
13234
|
+
if (!checkCsrf(req, res, state)) return;
|
|
13235
|
+
const body = req.body;
|
|
13236
|
+
const sql5 = typeof body.sql === "string" ? body.sql : "";
|
|
13237
|
+
const principal = backend.displayName(state.principal);
|
|
13238
|
+
const render = (view) => {
|
|
13239
|
+
html(
|
|
13240
|
+
res,
|
|
13241
|
+
renderSqlPage(context(req, state.session, state.visible), {
|
|
13242
|
+
sql: sql5,
|
|
13243
|
+
capabilities: [...capabilities],
|
|
13244
|
+
...view
|
|
13245
|
+
}),
|
|
13246
|
+
view.error === null ? 200 : 400
|
|
13247
|
+
);
|
|
13248
|
+
};
|
|
13249
|
+
const parser = await loadSqlParser();
|
|
13250
|
+
const analysis = analyzeSql(sql5, dialect, parser);
|
|
13251
|
+
const verdict = checkSqlPolicy(analysis, policy);
|
|
13252
|
+
if (!verdict.allowed) {
|
|
13253
|
+
await audit(console_.onAudit, {
|
|
13254
|
+
sql: sql5,
|
|
13255
|
+
principal,
|
|
13256
|
+
allowed: false,
|
|
13257
|
+
reason: verdict.reason,
|
|
13258
|
+
analysis,
|
|
13259
|
+
durationMs: null,
|
|
13260
|
+
rowCount: null
|
|
13261
|
+
});
|
|
13262
|
+
render({
|
|
13263
|
+
error: verdict.reason,
|
|
13264
|
+
columns: [],
|
|
13265
|
+
rows: [],
|
|
13266
|
+
rowCount: null,
|
|
13267
|
+
truncated: false,
|
|
13268
|
+
durationMs: null
|
|
13269
|
+
});
|
|
13270
|
+
return;
|
|
13271
|
+
}
|
|
13272
|
+
const started = Date.now();
|
|
13273
|
+
let rows;
|
|
13274
|
+
try {
|
|
13275
|
+
rows = console_.run === void 0 ? await state.dbSession.raw(sql5).all() : await console_.run(sql5, state.dbSession);
|
|
13276
|
+
} catch (error) {
|
|
13277
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13278
|
+
await audit(console_.onAudit, {
|
|
13279
|
+
sql: sql5,
|
|
13280
|
+
principal,
|
|
13281
|
+
allowed: true,
|
|
13282
|
+
reason: message,
|
|
13283
|
+
analysis,
|
|
13284
|
+
durationMs: Date.now() - started,
|
|
13285
|
+
rowCount: null
|
|
13286
|
+
});
|
|
13287
|
+
render({
|
|
13288
|
+
error: message,
|
|
13289
|
+
columns: [],
|
|
13290
|
+
rows: [],
|
|
13291
|
+
rowCount: null,
|
|
13292
|
+
truncated: false,
|
|
13293
|
+
durationMs: null
|
|
13294
|
+
});
|
|
13295
|
+
return;
|
|
13296
|
+
}
|
|
13297
|
+
const durationMs = Date.now() - started;
|
|
13298
|
+
await audit(console_.onAudit, {
|
|
13299
|
+
sql: sql5,
|
|
13300
|
+
principal,
|
|
13301
|
+
allowed: true,
|
|
13302
|
+
reason: null,
|
|
13303
|
+
analysis,
|
|
13304
|
+
durationMs,
|
|
13305
|
+
rowCount: rows.length
|
|
13306
|
+
});
|
|
13307
|
+
const window = rows.slice(0, maxRows);
|
|
13308
|
+
const columns = window.length === 0 ? [] : Object.keys(window[0] ?? {});
|
|
13309
|
+
render({
|
|
13310
|
+
error: null,
|
|
13311
|
+
columns,
|
|
13312
|
+
rows: window.map(
|
|
13313
|
+
(row) => columns.map((column6) => formatCellValue(row[column6]))
|
|
13314
|
+
),
|
|
13315
|
+
rowCount: rows.length,
|
|
13316
|
+
truncated: rows.length > window.length,
|
|
13317
|
+
durationMs
|
|
13318
|
+
});
|
|
13319
|
+
})
|
|
13320
|
+
);
|
|
13321
|
+
}
|
|
12688
13322
|
router.get(
|
|
12689
13323
|
`${prefix}/m/:slug`,
|
|
12690
13324
|
guarded(async (req, res) => {
|
|
@@ -13040,6 +13674,8 @@ function makeAdminRouter(site, options) {
|
|
|
13040
13674
|
title: admin.verboseName(),
|
|
13041
13675
|
identity,
|
|
13042
13676
|
audit: await buildAuditView(admin, row, state.dbSession),
|
|
13677
|
+
inlines: await buildInlines(admin, row, state, identity),
|
|
13678
|
+
inlineError: null,
|
|
13043
13679
|
fields: admin.detailFieldNames().map((name) => ({ label: name, value: formatCellValue(row[name]) })),
|
|
13044
13680
|
backUrl: `${prefix}/m/${admin.slug()}`,
|
|
13045
13681
|
editUrl: await allows(state.principal, admin, AdminPermission.EDIT) ? `${prefix}/m/${admin.slug()}/${identity}/edit` : null,
|
|
@@ -13160,6 +13796,103 @@ function makeAdminRouter(site, options) {
|
|
|
13160
13796
|
res.redirect(`${prefix}/m/${admin.slug()}/${identity}?ok=updated`);
|
|
13161
13797
|
})
|
|
13162
13798
|
);
|
|
13799
|
+
router.post(
|
|
13800
|
+
`${prefix}/m/:slug/:identity/inlines/:child`,
|
|
13801
|
+
guarded(async (req, res) => {
|
|
13802
|
+
const state = await authenticate(req, res);
|
|
13803
|
+
if (state === null) return;
|
|
13804
|
+
const admin = await resolveAdmin(req, res, state);
|
|
13805
|
+
if (admin === null) return;
|
|
13806
|
+
if (!checkCsrf(req, res, state)) return;
|
|
13807
|
+
const childSlug = String(req.params.child);
|
|
13808
|
+
const inline = admin.inlines.find(
|
|
13809
|
+
(entry) => entry.slug === childSlug && entry.editable
|
|
13810
|
+
);
|
|
13811
|
+
const childAdmin = inline === void 0 ? null : site.get(childSlug);
|
|
13812
|
+
if (inline === void 0 || childAdmin === null || !await allows(state.principal, childAdmin, AdminPermission.EDIT)) {
|
|
13813
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
13814
|
+
return;
|
|
13815
|
+
}
|
|
13816
|
+
const identity = String(req.params.identity);
|
|
13817
|
+
const parent = await findRow(admin, state.dbSession, identity);
|
|
13818
|
+
if (parent === null) {
|
|
13819
|
+
html(res, renderNotFound(context(req, state.session, state.visible)), 404);
|
|
13820
|
+
return;
|
|
13821
|
+
}
|
|
13822
|
+
const parentId = parent[admin.identityField];
|
|
13823
|
+
const { rows: grouped, deletions } = groupInlineSubmission(
|
|
13824
|
+
req.body
|
|
13825
|
+
);
|
|
13826
|
+
const names = inlineFieldNames(childAdmin, inline);
|
|
13827
|
+
const childRepo = childAdmin.repository(state.dbSession);
|
|
13828
|
+
const actorId = backend.principalId(state.principal);
|
|
13829
|
+
const canDelete = inline.canDelete && await allows(state.principal, childAdmin, AdminPermission.DELETE);
|
|
13830
|
+
const failed = [];
|
|
13831
|
+
let formError = null;
|
|
13832
|
+
const owned = async (key) => await childRepo.first({
|
|
13833
|
+
[childAdmin.identityField]: key,
|
|
13834
|
+
[inline.fkField]: parentId
|
|
13835
|
+
});
|
|
13836
|
+
for (const [key, values] of Object.entries(grouped)) {
|
|
13837
|
+
const isNew = key.startsWith("new");
|
|
13838
|
+
if (!isNew && canDelete && deletions.has(key)) {
|
|
13839
|
+
if (await owned(key) !== null) {
|
|
13840
|
+
await childRepo.delete({ [childAdmin.identityField]: key });
|
|
13841
|
+
}
|
|
13842
|
+
continue;
|
|
13843
|
+
}
|
|
13844
|
+
if (isNew && Object.values(values).every((value) => value.trim() === "")) {
|
|
13845
|
+
continue;
|
|
13846
|
+
}
|
|
13847
|
+
const parsed = parseFormBody(childAdmin, values, { only: names });
|
|
13848
|
+
if (Object.keys(parsed.errors).length > 0) {
|
|
13849
|
+
failed.push({ key, values, errors: parsed.errors });
|
|
13850
|
+
continue;
|
|
13851
|
+
}
|
|
13852
|
+
try {
|
|
13853
|
+
if (isNew) {
|
|
13854
|
+
stampActor(childAdmin, parsed.data, actorId, true);
|
|
13855
|
+
await childRepo.create({
|
|
13856
|
+
...parsed.data,
|
|
13857
|
+
[inline.fkField]: parentId
|
|
13858
|
+
});
|
|
13859
|
+
} else {
|
|
13860
|
+
if (await owned(key) === null) continue;
|
|
13861
|
+
stampActor(childAdmin, parsed.data, actorId, false);
|
|
13862
|
+
await childRepo.update(
|
|
13863
|
+
{ [childAdmin.identityField]: key },
|
|
13864
|
+
parsed.data
|
|
13865
|
+
);
|
|
13866
|
+
}
|
|
13867
|
+
} catch (error) {
|
|
13868
|
+
formError = describeWriteFailure(childAdmin, error);
|
|
13869
|
+
failed.push({ key, values, errors: {} });
|
|
13870
|
+
}
|
|
13871
|
+
}
|
|
13872
|
+
if (failed.length > 0) {
|
|
13873
|
+
const fresh = await findRow(admin, state.dbSession, identity) ?? parent;
|
|
13874
|
+
html(
|
|
13875
|
+
res,
|
|
13876
|
+
renderDetailPage(context(req, state.session, state.visible), {
|
|
13877
|
+
title: admin.verboseName(),
|
|
13878
|
+
identity,
|
|
13879
|
+
audit: await buildAuditView(admin, fresh, state.dbSession),
|
|
13880
|
+
inlines: await buildInlines(admin, fresh, state, identity, {
|
|
13881
|
+
[childSlug]: failed
|
|
13882
|
+
}),
|
|
13883
|
+
inlineError: formError ?? "Some inline rows could not be saved.",
|
|
13884
|
+
fields: admin.detailFieldNames().map((name) => ({ label: name, value: formatCellValue(fresh[name]) })),
|
|
13885
|
+
backUrl: `${prefix}/m/${admin.slug()}`,
|
|
13886
|
+
editUrl: await allows(state.principal, admin, AdminPermission.EDIT) ? `${prefix}/m/${admin.slug()}/${identity}/edit` : null,
|
|
13887
|
+
deleteUrl: await allows(state.principal, admin, AdminPermission.DELETE) ? `${prefix}/m/${admin.slug()}/${identity}/delete` : null
|
|
13888
|
+
}),
|
|
13889
|
+
400
|
|
13890
|
+
);
|
|
13891
|
+
return;
|
|
13892
|
+
}
|
|
13893
|
+
res.redirect(`${prefix}/m/${admin.slug()}/${identity}?ok=updated`);
|
|
13894
|
+
})
|
|
13895
|
+
);
|
|
13163
13896
|
router.post(
|
|
13164
13897
|
`${prefix}/m/:slug/:identity/delete`,
|
|
13165
13898
|
guarded(async (req, res) => {
|
|
@@ -13181,6 +13914,89 @@ function makeAdminRouter(site, options) {
|
|
|
13181
13914
|
const principal = await backend.loadPrincipal(dbSession, String(actor));
|
|
13182
13915
|
return principal === null ? String(actor) : backend.displayName(principal);
|
|
13183
13916
|
}
|
|
13917
|
+
async function buildInlines(admin, parent, state, identity, overrides = {}) {
|
|
13918
|
+
const parentId = parent[admin.identityField];
|
|
13919
|
+
const blocks = [];
|
|
13920
|
+
for (const inline of admin.inlines) {
|
|
13921
|
+
const childAdmin = site.get(inline.slug);
|
|
13922
|
+
const repository = childAdmin === null ? new BaseRepository(inline.model, state.dbSession) : childAdmin.repository(state.dbSession);
|
|
13923
|
+
const children = await repository.list({
|
|
13924
|
+
[inline.fkField]: parentId
|
|
13925
|
+
});
|
|
13926
|
+
const columns = inline.listDisplay ?? childAdmin?.listDisplayNames() ?? Object.keys(adminColumns(inline.model));
|
|
13927
|
+
const label = inline.label ?? childAdmin?.verboseNamePlural() ?? humanizeField(inline.slug);
|
|
13928
|
+
const visible = children.slice(0, INLINE_ROW_LIMIT);
|
|
13929
|
+
const editable = inline.editable && childAdmin !== null && await allows(state.principal, childAdmin, AdminPermission.EDIT);
|
|
13930
|
+
const addUrl = childAdmin !== null && await allows(state.principal, childAdmin, AdminPermission.CREATE) ? `${prefix}/m/${inline.slug}/new` : null;
|
|
13931
|
+
if (!editable || childAdmin === null) {
|
|
13932
|
+
blocks.push({
|
|
13933
|
+
label,
|
|
13934
|
+
total: children.length,
|
|
13935
|
+
columns,
|
|
13936
|
+
editable: false,
|
|
13937
|
+
canDelete: false,
|
|
13938
|
+
addUrl,
|
|
13939
|
+
formAction: "",
|
|
13940
|
+
rows: visible.map((child) => ({
|
|
13941
|
+
key: String(child[childAdmin?.identityField ?? "id"]),
|
|
13942
|
+
cells: columns.map((column6) => formatCellValue(child[column6])),
|
|
13943
|
+
fields: [],
|
|
13944
|
+
url: childAdmin === null ? null : `${prefix}/m/${inline.slug}/${String(child[childAdmin.identityField])}`
|
|
13945
|
+
})),
|
|
13946
|
+
newRow: null,
|
|
13947
|
+
truncated: children.length > visible.length
|
|
13948
|
+
});
|
|
13949
|
+
continue;
|
|
13950
|
+
}
|
|
13951
|
+
const names = inlineFieldNames(childAdmin, inline);
|
|
13952
|
+
const submitted = overrides[inline.slug];
|
|
13953
|
+
const rows = [];
|
|
13954
|
+
if (submitted !== void 0) {
|
|
13955
|
+
for (const entry of submitted) {
|
|
13956
|
+
rows.push({
|
|
13957
|
+
key: entry.key,
|
|
13958
|
+
cells: [],
|
|
13959
|
+
fields: inlineFields(
|
|
13960
|
+
childAdmin,
|
|
13961
|
+
names,
|
|
13962
|
+
entry.key,
|
|
13963
|
+
entry.values,
|
|
13964
|
+
entry.errors
|
|
13965
|
+
),
|
|
13966
|
+
url: null
|
|
13967
|
+
});
|
|
13968
|
+
}
|
|
13969
|
+
} else {
|
|
13970
|
+
for (const child of visible) {
|
|
13971
|
+
const key = String(child[childAdmin.identityField]);
|
|
13972
|
+
rows.push({
|
|
13973
|
+
key,
|
|
13974
|
+
cells: [],
|
|
13975
|
+
fields: inlineFields(childAdmin, names, key, child, {}),
|
|
13976
|
+
url: `${prefix}/m/${inline.slug}/${key}`
|
|
13977
|
+
});
|
|
13978
|
+
}
|
|
13979
|
+
}
|
|
13980
|
+
blocks.push({
|
|
13981
|
+
label,
|
|
13982
|
+
total: children.length,
|
|
13983
|
+
columns: names.map(humanizeField),
|
|
13984
|
+
editable: true,
|
|
13985
|
+
canDelete: inline.canDelete && childAdmin.canDelete,
|
|
13986
|
+
addUrl,
|
|
13987
|
+
formAction: `${prefix}/m/${admin.slug()}/${identity}/inlines/${inline.slug}`,
|
|
13988
|
+
rows,
|
|
13989
|
+
newRow: {
|
|
13990
|
+
key: "new1",
|
|
13991
|
+
cells: [],
|
|
13992
|
+
fields: inlineFields(childAdmin, names, "new1", {}, {}),
|
|
13993
|
+
url: null
|
|
13994
|
+
},
|
|
13995
|
+
truncated: children.length > visible.length
|
|
13996
|
+
});
|
|
13997
|
+
}
|
|
13998
|
+
return blocks;
|
|
13999
|
+
}
|
|
13184
14000
|
async function buildAuditView(admin, row, dbSession) {
|
|
13185
14001
|
const fields = [];
|
|
13186
14002
|
for (const name of admin.auditFieldNames()) {
|
|
@@ -13220,6 +14036,15 @@ function makeAdminRouter(site, options) {
|
|
|
13220
14036
|
const row = await admin.repository(dbSession).first({ [admin.identityField]: identity });
|
|
13221
14037
|
return row ?? null;
|
|
13222
14038
|
}
|
|
14039
|
+
async function readLogs(req, dir) {
|
|
14040
|
+
const requested = queryString(req.query.source);
|
|
14041
|
+
const source = LOG_SOURCES.includes(requested) ? requested : "all";
|
|
14042
|
+
const search = queryString(req.query.q);
|
|
14043
|
+
const raw = await readLogEntries(dir, source);
|
|
14044
|
+
const entries = filterLogEntries(raw.map(toLogEntry), search);
|
|
14045
|
+
entries.reverse();
|
|
14046
|
+
return { entries, source, search };
|
|
14047
|
+
}
|
|
13223
14048
|
function renderImport(req, state, admin, error, created, rowErrors) {
|
|
13224
14049
|
return renderImportPage(context(req, state.session, state.visible), {
|
|
13225
14050
|
title: admin.verboseNamePlural(),
|
|
@@ -13479,6 +14304,45 @@ function makeAdminRouter(site, options) {
|
|
|
13479
14304
|
}
|
|
13480
14305
|
return router;
|
|
13481
14306
|
}
|
|
14307
|
+
function groupInlineSubmission(body) {
|
|
14308
|
+
const rows = {};
|
|
14309
|
+
const deletions = /* @__PURE__ */ new Set();
|
|
14310
|
+
for (const [key, raw] of Object.entries(body)) {
|
|
14311
|
+
if (!key.startsWith("row.")) continue;
|
|
14312
|
+
const parts = key.split(".");
|
|
14313
|
+
if (parts.length !== 3) continue;
|
|
14314
|
+
const [, rowKey, field] = parts;
|
|
14315
|
+
const value = typeof raw === "string" ? raw : "";
|
|
14316
|
+
if (field === "__delete") {
|
|
14317
|
+
if (!["", "false", "off", "0", "no"].includes(value.trim().toLowerCase())) {
|
|
14318
|
+
deletions.add(rowKey);
|
|
14319
|
+
}
|
|
14320
|
+
continue;
|
|
14321
|
+
}
|
|
14322
|
+
const row = rows[rowKey] ?? {};
|
|
14323
|
+
row[field] = value;
|
|
14324
|
+
rows[rowKey] = row;
|
|
14325
|
+
}
|
|
14326
|
+
return { rows, deletions };
|
|
14327
|
+
}
|
|
14328
|
+
function inlineFieldNames(childAdmin, inline) {
|
|
14329
|
+
return childAdmin.editableFieldNames().filter(
|
|
14330
|
+
(name) => name !== inline.fkField && !childAdmin.uploadFields.includes(name) && !childAdmin.autocompleteFields.includes(name)
|
|
14331
|
+
);
|
|
14332
|
+
}
|
|
14333
|
+
function inlineFields(childAdmin, names, key, values, errors) {
|
|
14334
|
+
return buildFormFields(childAdmin, { values, errors }).filter((field) => names.includes(field.name)).map((field) => ({ ...field, name: `row.${key}.${field.name}` }));
|
|
14335
|
+
}
|
|
14336
|
+
async function audit(hook, entry) {
|
|
14337
|
+
if (hook === void 0) return;
|
|
14338
|
+
try {
|
|
14339
|
+
await hook(entry);
|
|
14340
|
+
} catch (error) {
|
|
14341
|
+
logger2.error("Admin SQL audit hook failed", {
|
|
14342
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14343
|
+
});
|
|
14344
|
+
}
|
|
14345
|
+
}
|
|
13482
14346
|
function flagAllows(admin, action) {
|
|
13483
14347
|
if (action === AdminPermission.CREATE) return admin.canCreate;
|
|
13484
14348
|
if (action === AdminPermission.EDIT) return admin.canEdit;
|
|
@@ -15565,59 +16429,6 @@ function makeToolSpecRouter(spec, options = {}) {
|
|
|
15565
16429
|
});
|
|
15566
16430
|
return router;
|
|
15567
16431
|
}
|
|
15568
|
-
function filesFor(dir, source) {
|
|
15569
|
-
if (source === "500") return [join(dir, HTTP_500_LOG_FILE)];
|
|
15570
|
-
if (source === "all") return Object.values(LEVEL_LOG_FILES).map((f) => join(dir, f));
|
|
15571
|
-
return [join(dir, LEVEL_LOG_FILES[source])];
|
|
15572
|
-
}
|
|
15573
|
-
async function readEntries(files) {
|
|
15574
|
-
const entries = [];
|
|
15575
|
-
for (const file of files) {
|
|
15576
|
-
let content;
|
|
15577
|
-
try {
|
|
15578
|
-
content = await readFile(file, "utf8");
|
|
15579
|
-
} catch {
|
|
15580
|
-
continue;
|
|
15581
|
-
}
|
|
15582
|
-
for (const line of content.split("\n")) {
|
|
15583
|
-
if (!line.trim()) continue;
|
|
15584
|
-
try {
|
|
15585
|
-
entries.push(JSON.parse(line));
|
|
15586
|
-
} catch {
|
|
15587
|
-
}
|
|
15588
|
-
}
|
|
15589
|
-
}
|
|
15590
|
-
return entries;
|
|
15591
|
-
}
|
|
15592
|
-
function makeLogsRouter(options) {
|
|
15593
|
-
const router = Router();
|
|
15594
|
-
const path = options.path ?? "/logs";
|
|
15595
|
-
const guards = options.guards ?? [];
|
|
15596
|
-
const handler = (req, res, next) => {
|
|
15597
|
-
const source = req.query.source ?? "all";
|
|
15598
|
-
const validSources = ["all", "debug", "info", "warning", "error", "500"];
|
|
15599
|
-
if (!validSources.includes(source)) {
|
|
15600
|
-
res.status(422).json({ detail: "Invalid log source", code: "VALIDATION_ERROR", details: {} });
|
|
15601
|
-
return;
|
|
15602
|
-
}
|
|
15603
|
-
const page2 = Math.max(1, Number(req.query.page) || 1);
|
|
15604
|
-
const pageSize = Math.min(500, Math.max(1, Number(req.query.pageSize) || 50));
|
|
15605
|
-
readEntries(filesFor(options.dir, source)).then((entries) => {
|
|
15606
|
-
entries.reverse();
|
|
15607
|
-
const total = entries.length;
|
|
15608
|
-
const start = (page2 - 1) * pageSize;
|
|
15609
|
-
res.json({
|
|
15610
|
-
items: entries.slice(start, start + pageSize),
|
|
15611
|
-
total,
|
|
15612
|
-
page: page2,
|
|
15613
|
-
pageSize,
|
|
15614
|
-
pages: Math.ceil(total / pageSize)
|
|
15615
|
-
});
|
|
15616
|
-
}).catch(next);
|
|
15617
|
-
};
|
|
15618
|
-
router.get(path, ...guards, handler);
|
|
15619
|
-
return router;
|
|
15620
|
-
}
|
|
15621
16432
|
function createTestDatabase(models) {
|
|
15622
16433
|
const driver = NodeSqliteDriver.open(":memory:");
|
|
15623
16434
|
for (const model of models) {
|
|
@@ -15649,6 +16460,6 @@ async function withTestDatabase(models, fn) {
|
|
|
15649
16460
|
}
|
|
15650
16461
|
}
|
|
15651
16462
|
|
|
15652
|
-
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, 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, 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 };
|
|
16463
|
+
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, 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, 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 };
|
|
15653
16464
|
//# sourceMappingURL=index.js.map
|
|
15654
16465
|
//# sourceMappingURL=index.js.map
|