Let a super admin read the database from the app
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.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import quoteSqlIdentifier from "./quote-sql-identifier";
|
||||
import resolveTableKey from "./resolve-table-key";
|
||||
import type { DatabaseTableSummary } from "@/src/types";
|
||||
import type { TableCatalogEntry } from "./table-catalog-types";
|
||||
|
||||
type Params = {
|
||||
tables: TableCatalogEntry[];
|
||||
/**
|
||||
* How many bytes each table takes on disk, by name; empty when SQLite
|
||||
* can't measure it, which leaves the size column blank.
|
||||
*/
|
||||
sizes: Map<string, number>;
|
||||
};
|
||||
|
||||
type CountRow = { rows: number };
|
||||
|
||||
/**
|
||||
* The catalog as the browser lists it: every table with its record count,
|
||||
* its size on disk, its columns and what identifies its records.
|
||||
*
|
||||
* Each count is its own tiny statement rather than one aggregate, because
|
||||
* a virtual table has no rowid to build one from, and the whole read stays
|
||||
* in the low milliseconds for the install's fifty-odd tables.
|
||||
*/
|
||||
export default async function buildDatabaseTables({ tables, sizes }: Params): Promise<DatabaseTableSummary[]> {
|
||||
const summaries: DatabaseTableSummary[] = [];
|
||||
|
||||
for (const table of tables) {
|
||||
const size = sizes.get(table.name);
|
||||
|
||||
summaries.push({
|
||||
name: table.name,
|
||||
type: table.type,
|
||||
rows: await countRows({ table: table.name }),
|
||||
size_bytes: size === undefined ? null : size,
|
||||
columns: table.columns,
|
||||
key: resolveTableKey({ columns: table.columns, has_rowid: table.has_rowid }),
|
||||
});
|
||||
}
|
||||
|
||||
return summaries;
|
||||
}
|
||||
|
||||
async function countRows({ table }: { table: string }): Promise<number | null> {
|
||||
const res = await BunSQLite.sql<CountRow>({
|
||||
sql: `SELECT COUNT(*) AS rows FROM ${quoteSqlIdentifier({ name: table })}`,
|
||||
});
|
||||
|
||||
if (!res.success || !res.payload?.[0]) {
|
||||
/**
|
||||
* A table the module won't count (an index over a table whose
|
||||
* module is missing) is still a table; it just has no count.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
return Number(res.payload[0].rows) || 0;
|
||||
}
|
||||
Reference in New Issue
Block a user