A new super admin settings page, /admin/settings/database, lists every table with its record count and size, and one page of the open table's records newest key first. Values are cut inside SQL (400 characters, with a flag saying so) and a record opens whole in a modal, so a table of long texts reads without shipping it all. A search matches every column's text, word by word or as a phrase, which is the one thing that also works on a virtual table. Nothing in it writes: the catalog, the records and the single record are all reads, GET only.
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import { metaColumnAlias, RecordKeyAlias } from "./build-records-sql";
|
|
import toDatabaseCell from "./to-database-cell";
|
|
import type { DatabaseRecord, DatabaseTableColumn } from "@/src/types";
|
|
|
|
type Params = {
|
|
/**
|
|
* The row as SQLite returned it, its meta columns included.
|
|
*/
|
|
row: Record<string, unknown>;
|
|
columns: DatabaseTableColumn[];
|
|
max_chars: number;
|
|
};
|
|
|
|
/**
|
|
* One row of a records query as a record the browser can draw: its key,
|
|
* and every column's value as a cell. The meta columns the query added
|
|
* (`typeof` and `length` per column) are read and dropped here, so they
|
|
* never reach the client.
|
|
*/
|
|
export default function buildRecordRow({ row, columns, max_chars }: Params): DatabaseRecord {
|
|
const cells: Record<string, DatabaseRecord["cells"][string]> = {};
|
|
|
|
for (const column of columns) {
|
|
cells[column.name] = toDatabaseCell({
|
|
value: row[column.name],
|
|
type: String(row[metaColumnAlias({ name: column.name, kind: "type" })] ?? "null"),
|
|
length: Number(row[metaColumnAlias({ name: column.name, kind: "length" })] ?? 0),
|
|
max_chars,
|
|
});
|
|
}
|
|
|
|
const key = row[RecordKeyAlias];
|
|
|
|
return {
|
|
key:
|
|
typeof key == "string" || typeof key == "number"
|
|
? key
|
|
: key === null || key === undefined
|
|
? null
|
|
: String(key),
|
|
cells,
|
|
};
|
|
}
|