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.
36 lines
982 B
TypeScript
36 lines
982 B
TypeScript
import type { TableCatalogEntry, TableCatalogRow } from "./table-catalog-types";
|
|
|
|
type Params = {
|
|
rows: TableCatalogRow[];
|
|
};
|
|
|
|
/**
|
|
* Groups the catalog's rows per table, keeping the statement's order, so
|
|
* the list of tables and every table's columns come back in one read.
|
|
*/
|
|
export default function groupTableCatalog({ rows }: Params): TableCatalogEntry[] {
|
|
const entries: TableCatalogEntry[] = [];
|
|
|
|
for (const row of rows) {
|
|
let entry = entries.at(-1);
|
|
|
|
if (!entry || entry.name != row.table_name) {
|
|
entry = {
|
|
name: row.table_name,
|
|
type: row.table_type,
|
|
has_rowid: row.table_type == "table" && !row.without_rowid,
|
|
columns: [],
|
|
};
|
|
entries.push(entry);
|
|
}
|
|
|
|
entry.columns.push({
|
|
name: row.column_name,
|
|
type: row.column_type || "",
|
|
pk: Boolean(row.column_pk),
|
|
});
|
|
}
|
|
|
|
return entries;
|
|
}
|