@indiekitai/pg-dash 0.3.2 → 0.3.3
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/dist/cli.js +186 -9
- package/dist/cli.js.map +1 -1
- package/dist/mcp.js +24 -1
- package/dist/mcp.js.map +1 -1
- package/dist/ui/assets/index-D5LMag3w.css +1 -0
- package/dist/ui/assets/index-RQDs_hnz.js +33 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-BI4_c1SD.js +0 -33
- package/dist/ui/assets/index-F2MaHZFy.css +0 -1
package/dist/mcp.js
CHANGED
|
@@ -840,6 +840,28 @@ SELECT pg_reload_conf();`,
|
|
|
840
840
|
const ignoredSet = new Set(ignoredIds);
|
|
841
841
|
const activeIssues = issues.filter((i) => !ignoredSet.has(i.id));
|
|
842
842
|
const ignoredCount = issues.length - activeIssues.length;
|
|
843
|
+
const batchFixes = [];
|
|
844
|
+
const groups = /* @__PURE__ */ new Map();
|
|
845
|
+
for (const issue of activeIssues) {
|
|
846
|
+
const prefix = issue.id.replace(/-[^-]+$/, "");
|
|
847
|
+
if (!groups.has(prefix)) groups.set(prefix, []);
|
|
848
|
+
groups.get(prefix).push(issue);
|
|
849
|
+
}
|
|
850
|
+
const BATCH_TITLES = {
|
|
851
|
+
"schema-fk-no-idx": "Create all missing FK indexes",
|
|
852
|
+
"schema-unused-idx": "Drop all unused indexes",
|
|
853
|
+
"schema-no-pk": "Fix all tables missing primary keys",
|
|
854
|
+
"maint-vacuum": "VACUUM all overdue tables",
|
|
855
|
+
"maint-analyze": "ANALYZE all tables missing statistics",
|
|
856
|
+
"perf-bloated-idx": "REINDEX all bloated indexes",
|
|
857
|
+
"perf-bloat": "VACUUM FULL all bloated tables"
|
|
858
|
+
};
|
|
859
|
+
for (const [prefix, group] of groups) {
|
|
860
|
+
if (group.length <= 1) continue;
|
|
861
|
+
const title = BATCH_TITLES[prefix] || `Fix all ${group.length} ${prefix} issues`;
|
|
862
|
+
const sql = group.map((i) => i.fix.split("\n").filter((l) => !l.trim().startsWith("--")).join("\n").trim()).filter(Boolean).join(";\n") + ";";
|
|
863
|
+
batchFixes.push({ type: prefix, title: `${title} (${group.length})`, count: group.length, sql });
|
|
864
|
+
}
|
|
843
865
|
const score = computeAdvisorScore(activeIssues);
|
|
844
866
|
return {
|
|
845
867
|
score,
|
|
@@ -847,7 +869,8 @@ SELECT pg_reload_conf();`,
|
|
|
847
869
|
issues: activeIssues,
|
|
848
870
|
breakdown: computeBreakdown(activeIssues),
|
|
849
871
|
skipped,
|
|
850
|
-
ignoredCount
|
|
872
|
+
ignoredCount,
|
|
873
|
+
batchFixes
|
|
851
874
|
};
|
|
852
875
|
} finally {
|
|
853
876
|
client.release();
|
package/dist/mcp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp.ts","../src/server/queries/overview.ts","../src/server/queries/tables.ts","../src/server/queries/schema.ts","../src/server/queries/activity.ts","../src/server/advisor.ts"],"sourcesContent":["#!/usr/bin/env node\n// MCP Server for pg-dash — exposes PostgreSQL monitoring tools\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { Pool } from \"pg\";\nimport { z } from \"zod\";\nimport { getOverview } from \"./server/queries/overview.js\";\nimport { getTables } from \"./server/queries/tables.js\";\nimport { getSchemaTableDetail } from \"./server/queries/schema.js\";\nimport { getActivity } from \"./server/queries/activity.js\";\nimport { getAdvisorReport, isSafeFix } from \"./server/advisor.js\";\nimport Database from \"better-sqlite3\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs, { readFileSync } from \"node:fs\";\n\nconst pkg = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf-8\"));\n\nconst connString = process.argv[2] || process.env.PG_DASH_CONNECTION_STRING;\nif (!connString) {\n console.error(\"Usage: pg-dash-mcp <connection-string>\");\n console.error(\" or set PG_DASH_CONNECTION_STRING env var\");\n process.exit(1);\n}\n\nconst pool = new Pool({ connectionString: connString });\nconst longQueryThreshold = parseInt(process.env.PG_DASH_LONG_QUERY_THRESHOLD || \"5\", 10);\nconst dataDir = process.env.PG_DASH_DATA_DIR || path.join(os.homedir(), \".pg-dash\");\nfs.mkdirSync(dataDir, { recursive: true });\n\n// Open schema and alerts DBs (read-only for history queries)\nlet schemaDb: Database.Database | null = null;\nlet alertsDb: Database.Database | null = null;\ntry {\n const schemaPath = path.join(dataDir, \"schema.db\");\n if (fs.existsSync(schemaPath)) schemaDb = new Database(schemaPath, { readonly: true });\n} catch (err) { console.error(\"[mcp] Error:\", (err as Error).message); }\ntry {\n const alertsPath = path.join(dataDir, \"alerts.db\");\n if (fs.existsSync(alertsPath)) alertsDb = new Database(alertsPath, { readonly: true });\n} catch (err) { console.error(\"[mcp] Error:\", (err as Error).message); }\n\nconst server = new McpServer({ name: \"pg-dash\", version: pkg.version });\n\nserver.tool(\"pg_dash_overview\", \"Get database overview (version, uptime, size, connections)\", {}, async () => {\n try {\n const data = await getOverview(pool);\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_health\", \"Get health advisor report with score, grade, and issues\", {}, async () => {\n try {\n const data = await getAdvisorReport(pool, longQueryThreshold);\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_tables\", \"List all tables with sizes and row counts\", {}, async () => {\n try {\n const data = await getTables(pool);\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_table_detail\", \"Get detailed info about a specific table\", { table: z.string().describe(\"Table name (e.g. 'users' or 'public.users')\") }, async ({ table }) => {\n try {\n const data = await getSchemaTableDetail(pool, table);\n if (!data) return { content: [{ type: \"text\", text: \"Table not found\" }], isError: true };\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_activity\", \"Get current database activity (active queries, connections)\", {}, async () => {\n try {\n const data = await getActivity(pool);\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_schema_changes\", \"Get recent schema changes\", {}, async () => {\n try {\n if (!schemaDb) return { content: [{ type: \"text\", text: \"No schema tracking data available. Run pg-dash server first.\" }] };\n const changes = schemaDb.prepare(\"SELECT * FROM schema_changes ORDER BY timestamp DESC LIMIT 50\").all();\n return { content: [{ type: \"text\", text: JSON.stringify(changes, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_fix\", \"Execute a safe fix (VACUUM, ANALYZE, REINDEX, etc.)\", { sql: z.string().describe(\"SQL to execute (must be a safe operation)\") }, async ({ sql }) => {\n try {\n if (!isSafeFix(sql)) return { content: [{ type: \"text\", text: \"Operation not allowed. Only VACUUM, ANALYZE, REINDEX, CREATE/DROP INDEX CONCURRENTLY, pg_terminate_backend, pg_cancel_backend, and EXPLAIN ANALYZE are permitted.\" }], isError: true };\n const client = await pool.connect();\n try {\n const start = Date.now();\n const result = await client.query(sql);\n return { content: [{ type: \"text\", text: JSON.stringify({ ok: true, duration: Date.now() - start, rowCount: result.rowCount, rows: result.rows || [] }, null, 2) }] };\n } finally {\n client.release();\n }\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_alerts\", \"Get alert history\", {}, async () => {\n try {\n if (!alertsDb) return { content: [{ type: \"text\", text: \"No alerts data available. Run pg-dash server first.\" }] };\n const history = alertsDb.prepare(\"SELECT * FROM alert_history ORDER BY timestamp DESC LIMIT 50\").all();\n return { content: [{ type: \"text\", text: JSON.stringify(history, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","import type { Pool } from \"pg\";\n\nexport async function getOverview(pool: Pool) {\n const client = await pool.connect();\n try {\n const version = await client.query(\"SHOW server_version\");\n const uptime = await client.query(\n \"SELECT to_char(now() - pg_postmaster_start_time(), 'DD \\\"d\\\" HH24 \\\"h\\\" MI \\\"m\\\"') AS uptime\"\n );\n const dbSize = await client.query(\n \"SELECT pg_size_pretty(pg_database_size(current_database())) AS size\"\n );\n const dbCount = await client.query(\n \"SELECT count(*)::int AS count FROM pg_database WHERE NOT datistemplate\"\n );\n const connections = await client.query(`\n SELECT\n (SELECT count(*)::int FROM pg_stat_activity WHERE state = 'active') AS active,\n (SELECT count(*)::int FROM pg_stat_activity WHERE state = 'idle') AS idle,\n (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max\n `);\n\n return {\n version: version.rows[0].server_version,\n uptime: uptime.rows[0].uptime,\n dbSize: dbSize.rows[0].size,\n databaseCount: dbCount.rows[0].count,\n connections: connections.rows[0],\n };\n } finally {\n client.release();\n }\n}\n","import type { Pool } from \"pg\";\n\nexport async function getTables(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n schemaname AS schema,\n relname AS name,\n pg_size_pretty(pg_total_relation_size(relid)) AS total_size,\n pg_total_relation_size(relid) AS size_bytes,\n n_live_tup AS rows,\n n_dead_tup AS dead_tuples,\n CASE WHEN n_live_tup > 0 \n THEN round(n_dead_tup::numeric / n_live_tup * 100, 1) \n ELSE 0 END AS dead_pct\n FROM pg_stat_user_tables\n ORDER BY pg_total_relation_size(relid) DESC\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n","import type { Pool } from \"pg\";\n\nexport async function getSchemaTables(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n c.relname AS name,\n n.nspname AS schema,\n pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,\n pg_total_relation_size(c.oid) AS total_size_bytes,\n pg_size_pretty(pg_relation_size(c.oid)) AS table_size,\n pg_size_pretty(pg_total_relation_size(c.oid) - pg_relation_size(c.oid)) AS index_size,\n s.n_live_tup AS row_count,\n obj_description(c.oid) AS description\n FROM pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid\n LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid\n WHERE c.relkind = 'r' AND n.nspname NOT IN ('pg_catalog', 'information_schema')\n ORDER BY pg_total_relation_size(c.oid) DESC\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaTableDetail(pool: Pool, tableName: string) {\n const client = await pool.connect();\n try {\n // Parse schema.table or default to public\n const parts = tableName.split(\".\");\n const schema = parts.length > 1 ? parts[0] : \"public\";\n const name = parts.length > 1 ? parts[1] : parts[0];\n\n // Table info\n const tableInfo = await client.query(`\n SELECT\n c.relname AS name, n.nspname AS schema,\n pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,\n pg_size_pretty(pg_relation_size(c.oid)) AS table_size,\n pg_size_pretty(pg_total_relation_size(c.oid) - pg_relation_size(c.oid)) AS index_size,\n pg_size_pretty(pg_relation_size(c.reltoastrelid)) AS toast_size,\n s.n_live_tup AS row_count, s.n_dead_tup AS dead_tuples,\n s.last_vacuum, s.last_autovacuum, s.last_analyze, s.last_autoanalyze,\n s.seq_scan, s.idx_scan\n FROM pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid\n LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid\n WHERE c.relname = $1 AND n.nspname = $2 AND c.relkind = 'r'\n `, [name, schema]);\n\n if (tableInfo.rows.length === 0) return null;\n\n // Columns\n const columns = await client.query(`\n SELECT\n a.attname AS name,\n pg_catalog.format_type(a.atttypid, a.atttypmod) AS type,\n NOT a.attnotnull AS nullable,\n pg_get_expr(d.adbin, d.adrelid) AS default_value,\n col_description(a.attrelid, a.attnum) AS description\n FROM pg_attribute a\n LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum\n WHERE a.attrelid = (SELECT c.oid FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = $1 AND n.nspname = $2)\n AND a.attnum > 0 AND NOT a.attisdropped\n ORDER BY a.attnum\n `, [name, schema]);\n\n // Indexes\n const indexes = await client.query(`\n SELECT\n i.relname AS name,\n am.amname AS type,\n pg_size_pretty(pg_relation_size(i.oid)) AS size,\n pg_get_indexdef(idx.indexrelid) AS definition,\n idx.indisunique AS is_unique,\n idx.indisprimary AS is_primary,\n s.idx_scan, s.idx_tup_read, s.idx_tup_fetch\n FROM pg_index idx\n JOIN pg_class i ON idx.indexrelid = i.oid\n JOIN pg_class t ON idx.indrelid = t.oid\n JOIN pg_namespace n ON t.relnamespace = n.oid\n JOIN pg_am am ON i.relam = am.oid\n LEFT JOIN pg_stat_user_indexes s ON s.indexrelid = i.oid\n WHERE t.relname = $1 AND n.nspname = $2\n ORDER BY i.relname\n `, [name, schema]);\n\n // Constraints\n const constraints = await client.query(`\n SELECT\n conname AS name,\n CASE contype WHEN 'p' THEN 'PRIMARY KEY' WHEN 'f' THEN 'FOREIGN KEY'\n WHEN 'u' THEN 'UNIQUE' WHEN 'c' THEN 'CHECK' WHEN 'x' THEN 'EXCLUDE' END AS type,\n pg_get_constraintdef(oid) AS definition\n FROM pg_constraint\n WHERE conrelid = (SELECT c.oid FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = $1 AND n.nspname = $2)\n ORDER BY\n CASE contype WHEN 'p' THEN 1 WHEN 'u' THEN 2 WHEN 'f' THEN 3 WHEN 'c' THEN 4 ELSE 5 END\n `, [name, schema]);\n\n // Foreign keys (outgoing)\n const foreignKeys = await client.query(`\n SELECT\n conname AS name,\n a.attname AS column_name,\n confrelid::regclass::text AS referenced_table,\n af.attname AS referenced_column\n FROM pg_constraint c\n JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)\n JOIN pg_attribute af ON af.attrelid = c.confrelid AND af.attnum = ANY(c.confkey)\n WHERE c.contype = 'f'\n AND c.conrelid = (SELECT cl.oid FROM pg_class cl JOIN pg_namespace n ON cl.relnamespace = n.oid WHERE cl.relname = $1 AND n.nspname = $2)\n `, [name, schema]);\n\n // Sample data (first 10 rows)\n let sampleData: any[] = [];\n try {\n const sample = await client.query(\n `SELECT * FROM ${client.escapeIdentifier(schema)}.${client.escapeIdentifier(name)} LIMIT 10`\n );\n sampleData = sample.rows;\n } catch (err) { console.error(\"[schema] Error:\", (err as Error).message); }\n\n return {\n ...tableInfo.rows[0],\n columns: columns.rows,\n indexes: indexes.rows,\n constraints: constraints.rows,\n foreignKeys: foreignKeys.rows,\n sampleData,\n };\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaIndexes(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n n.nspname AS schema,\n t.relname AS table_name,\n i.relname AS name,\n am.amname AS type,\n pg_size_pretty(pg_relation_size(i.oid)) AS size,\n pg_relation_size(i.oid) AS size_bytes,\n pg_get_indexdef(idx.indexrelid) AS definition,\n idx.indisunique AS is_unique,\n idx.indisprimary AS is_primary,\n s.idx_scan, s.idx_tup_read, s.idx_tup_fetch\n FROM pg_index idx\n JOIN pg_class i ON idx.indexrelid = i.oid\n JOIN pg_class t ON idx.indrelid = t.oid\n JOIN pg_namespace n ON t.relnamespace = n.oid\n JOIN pg_am am ON i.relam = am.oid\n LEFT JOIN pg_stat_user_indexes s ON s.indexrelid = i.oid\n WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')\n ORDER BY pg_relation_size(i.oid) DESC\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaFunctions(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n n.nspname AS schema,\n p.proname AS name,\n pg_get_function_result(p.oid) AS return_type,\n pg_get_function_arguments(p.oid) AS arguments,\n l.lanname AS language,\n p.prosrc AS source,\n CASE p.prokind WHEN 'f' THEN 'function' WHEN 'p' THEN 'procedure' WHEN 'a' THEN 'aggregate' WHEN 'w' THEN 'window' END AS kind\n FROM pg_proc p\n JOIN pg_namespace n ON p.pronamespace = n.oid\n JOIN pg_language l ON p.prolang = l.oid\n WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')\n ORDER BY n.nspname, p.proname\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaExtensions(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT extname AS name, extversion AS installed_version,\n n.nspname AS schema, obj_description(e.oid) AS description\n FROM pg_extension e\n JOIN pg_namespace n ON e.extnamespace = n.oid\n ORDER BY extname\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaEnums(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n t.typname AS name,\n n.nspname AS schema,\n array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values\n FROM pg_type t\n JOIN pg_namespace n ON t.typnamespace = n.oid\n JOIN pg_enum e ON t.oid = e.enumtypid\n WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')\n GROUP BY t.typname, n.nspname\n ORDER BY t.typname\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n","import type { Pool } from \"pg\";\n\nexport interface Activity {\n pid: number;\n query: string;\n state: string;\n wait_event: string | null;\n wait_event_type: string | null;\n duration: string | null;\n client_addr: string | null;\n application_name: string;\n backend_start: string;\n}\n\nexport async function getActivity(pool: Pool): Promise<Activity[]> {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n pid,\n COALESCE(query, '') AS query,\n COALESCE(state, 'unknown') AS state,\n wait_event,\n wait_event_type,\n CASE WHEN state = 'active' THEN (now() - query_start)::text\n WHEN state = 'idle in transaction' THEN (now() - state_change)::text\n ELSE NULL END AS duration,\n client_addr::text,\n COALESCE(application_name, '') AS application_name,\n backend_start::text\n FROM pg_stat_activity\n WHERE pid != pg_backend_pid()\n AND state IS NOT NULL\n ORDER BY\n CASE state\n WHEN 'active' THEN 1\n WHEN 'idle in transaction' THEN 2\n ELSE 3\n END,\n query_start ASC NULLS LAST\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n","import type { Pool } from \"pg\";\n\nexport interface AdvisorIssue {\n id: string;\n severity: \"critical\" | \"warning\" | \"info\";\n category: \"performance\" | \"maintenance\" | \"schema\" | \"security\";\n title: string;\n description: string;\n fix: string;\n impact: string;\n effort: \"quick\" | \"moderate\" | \"involved\";\n}\n\nexport interface AdvisorResult {\n score: number;\n grade: string;\n issues: AdvisorIssue[];\n breakdown: Record<string, { score: number; grade: string; count: number }>;\n skipped: string[];\n ignoredCount: number;\n}\n\nconst SEVERITY_WEIGHT = { critical: 15, warning: 5, info: 1 } as const;\nconst MAX_DEDUCTION = { critical: 60, warning: 30, info: 10 } as const;\n\nexport function computeAdvisorScore(issues: AdvisorIssue[]): number {\n let score = 100;\n const deductions = { critical: 0, warning: 0, info: 0 };\n const counts = { critical: 0, warning: 0, info: 0 };\n for (const issue of issues) {\n counts[issue.severity]++;\n const n = counts[issue.severity];\n const weight = SEVERITY_WEIGHT[issue.severity];\n // Diminishing penalty: full for first 3, half for 4-10, quarter for 11+\n let penalty: number;\n if (n <= 3) penalty = weight;\n else if (n <= 10) penalty = weight * 0.5;\n else penalty = weight * 0.25;\n deductions[issue.severity] += penalty;\n }\n // Cap deductions per severity\n for (const sev of [\"critical\", \"warning\", \"info\"] as const) {\n score -= Math.min(deductions[sev], MAX_DEDUCTION[sev]);\n }\n return Math.max(0, Math.min(100, Math.round(score)));\n}\n\nexport function gradeFromScore(score: number): string {\n if (score >= 90) return \"A\";\n if (score >= 80) return \"B\";\n if (score >= 70) return \"C\";\n if (score >= 50) return \"D\";\n return \"F\";\n}\n\nfunction computeBreakdown(issues: AdvisorIssue[]): Record<string, { score: number; grade: string; count: number }> {\n const categories = [\"performance\", \"maintenance\", \"schema\", \"security\"] as const;\n const result: Record<string, { score: number; grade: string; count: number }> = {};\n for (const cat of categories) {\n const catIssues = issues.filter((i) => i.category === cat);\n const score = computeAdvisorScore(catIssues);\n result[cat] = { score, grade: gradeFromScore(score), count: catIssues.length };\n }\n return result;\n}\n\nexport async function getAdvisorReport(pool: Pool, longQueryThreshold = 5): Promise<AdvisorResult> {\n const client = await pool.connect();\n const issues: AdvisorIssue[] = [];\n const skipped: string[] = [];\n\n try {\n // Detect PG version for compatibility\n const versionResult = await client.query(\"SHOW server_version_num\");\n const pgVersion = parseInt(versionResult.rows[0].server_version_num);\n\n // ── Performance Advisors ───────────────────────────────────────\n\n // Missing indexes (high seq scans on large tables)\n try {\n const r = await client.query(`\n SELECT schemaname, relname, seq_scan, seq_tup_read, n_live_tup,\n pg_size_pretty(pg_total_relation_size(relid)) AS size\n FROM pg_stat_user_tables\n WHERE n_live_tup > 10000 AND seq_scan > 100\n ORDER BY seq_tup_read DESC LIMIT 10\n `);\n for (const row of r.rows) {\n issues.push({\n id: `perf-seq-scan-${row.schemaname}-${row.relname}`,\n severity: row.seq_scan > 1000 ? \"warning\" : \"info\",\n category: \"performance\",\n title: `High sequential scans on ${row.relname}`,\n description: `Table ${row.schemaname}.${row.relname} (${row.n_live_tup} rows, ${row.size}) has ${row.seq_scan} sequential scans reading ${Number(row.seq_tup_read).toLocaleString()} tuples. Consider adding indexes on frequently filtered columns.`,\n fix: `-- Identify commonly filtered columns and add indexes:\\n-- EXPLAIN ANALYZE SELECT * FROM ${row.schemaname}.${row.relname} WHERE <your_condition>;\\nCREATE INDEX CONCURRENTLY idx_${row.relname}_<column> ON ${row.schemaname}.${row.relname} (<column>);`,\n impact: \"Queries will continue to do full table scans, degrading performance as the table grows.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking seq scans:\", (err as Error).message); skipped.push(\"seq scans: \" + (err as Error).message);\n }\n\n // Bloated indexes (index size > 3x table size)\n try {\n const r = await client.query(`\n SELECT\n schemaname, relname, indexrelname,\n pg_relation_size(indexrelid) AS idx_size,\n pg_relation_size(relid) AS tbl_size,\n pg_size_pretty(pg_relation_size(indexrelid)) AS idx_size_pretty,\n pg_size_pretty(pg_relation_size(relid)) AS tbl_size_pretty\n FROM pg_stat_user_indexes\n WHERE pg_relation_size(indexrelid) > 1048576\n AND pg_relation_size(indexrelid) > pg_relation_size(relid) * 3\n ORDER BY pg_relation_size(indexrelid) DESC LIMIT 10\n `);\n for (const row of r.rows) {\n issues.push({\n id: `perf-bloated-idx-${row.indexrelname}`,\n severity: \"warning\",\n category: \"performance\",\n title: `Bloated index ${row.indexrelname}`,\n description: `Index ${row.indexrelname} on ${row.relname} is ${row.idx_size_pretty} but the table is only ${row.tbl_size_pretty}. The index may need rebuilding.`,\n fix: `REINDEX INDEX CONCURRENTLY ${row.schemaname}.${row.indexrelname};`,\n impact: \"Bloated indexes waste disk space and slow down queries that use them.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking bloated indexes:\", (err as Error).message); skipped.push(\"bloated indexes: \" + (err as Error).message);\n }\n\n // Table bloat (dead tuples > 10%)\n try {\n const r = await client.query(`\n SELECT schemaname, relname, n_dead_tup, n_live_tup,\n CASE WHEN n_live_tup > 0 THEN round(n_dead_tup::numeric / n_live_tup * 100, 1) ELSE 0 END AS dead_pct,\n pg_size_pretty(pg_total_relation_size(relid)) AS size\n FROM pg_stat_user_tables\n WHERE n_live_tup > 1000 AND n_dead_tup::float / GREATEST(n_live_tup, 1) > 0.1\n ORDER BY n_dead_tup DESC LIMIT 10\n `);\n for (const row of r.rows) {\n const pct = parseFloat(row.dead_pct);\n issues.push({\n id: `perf-bloat-${row.schemaname}-${row.relname}`,\n severity: pct > 30 ? \"critical\" : \"warning\",\n category: \"performance\",\n title: `Table bloat on ${row.relname} (${row.dead_pct}% dead)`,\n description: `${row.schemaname}.${row.relname} has ${Number(row.n_dead_tup).toLocaleString()} dead tuples (${row.dead_pct}% of ${Number(row.n_live_tup).toLocaleString()} live rows). Size: ${row.size}.`,\n fix: `VACUUM FULL ${row.schemaname}.${row.relname};`,\n impact: \"Dead tuples waste storage and degrade scan performance.\",\n effort: pct > 30 ? \"moderate\" : \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking table bloat:\", (err as Error).message); skipped.push(\"table bloat: \" + (err as Error).message);\n }\n\n // Cache efficiency per table\n try {\n const r = await client.query(`\n SELECT schemaname, relname,\n heap_blks_hit, heap_blks_read,\n CASE WHEN (heap_blks_hit + heap_blks_read) = 0 THEN 1\n ELSE heap_blks_hit::float / (heap_blks_hit + heap_blks_read) END AS ratio\n FROM pg_statio_user_tables\n WHERE (heap_blks_hit + heap_blks_read) > 100\n ORDER BY ratio ASC LIMIT 5\n `);\n for (const row of r.rows) {\n const ratio = parseFloat(row.ratio);\n if (ratio < 0.9) {\n issues.push({\n id: `perf-cache-${row.schemaname}-${row.relname}`,\n severity: ratio < 0.5 ? \"critical\" : \"warning\",\n category: \"performance\",\n title: `Poor cache hit ratio on ${row.relname}`,\n description: `Table ${row.schemaname}.${row.relname} has a cache hit ratio of ${(ratio * 100).toFixed(1)}%. Most reads are going to disk.`,\n fix: `-- Consider increasing shared_buffers or reducing working set:\\nSHOW shared_buffers;`,\n impact: \"Disk reads are orders of magnitude slower than memory reads.\",\n effort: \"involved\",\n });\n }\n }\n } catch (err) {\n console.error(\"[advisor] Error checking cache efficiency:\", (err as Error).message); skipped.push(\"cache efficiency: \" + (err as Error).message);\n }\n\n // Slow queries from pg_stat_statements\n try {\n const extCheck = await client.query(\"SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements'\");\n if (extCheck.rows.length > 0) {\n const r = await client.query(`\n SELECT query, calls, mean_exec_time, total_exec_time,\n round(mean_exec_time::numeric, 2) AS mean_ms,\n round(total_exec_time::numeric / 1000, 2) AS total_sec\n FROM pg_stat_statements\n WHERE query NOT LIKE '%pg_stat%' AND query NOT LIKE '%pg_catalog%'\n AND mean_exec_time > 100\n ORDER BY mean_exec_time DESC LIMIT 5\n `);\n for (const row of r.rows) {\n issues.push({\n id: `perf-slow-${row.query.slice(0, 30).replace(/\\W/g, \"_\")}`,\n severity: parseFloat(row.mean_ms) > 1000 ? \"warning\" : \"info\",\n category: \"performance\",\n title: `Slow query (avg ${row.mean_ms}ms)`,\n description: `Query averaging ${row.mean_ms}ms over ${row.calls} calls (total: ${row.total_sec}s): ${row.query.slice(0, 200)}`,\n fix: `EXPLAIN ANALYZE ${row.query.slice(0, 500)};`,\n impact: \"Slow queries degrade overall database responsiveness.\",\n effort: \"moderate\",\n });\n }\n }\n } catch (err) {\n console.error(\"[advisor] Error checking slow queries:\", (err as Error).message); skipped.push(\"slow queries: \" + (err as Error).message);\n }\n\n // ── Maintenance Advisors ───────────────────────────────────────\n\n // VACUUM overdue\n try {\n const r = await client.query(`\n SELECT schemaname, relname, last_vacuum, last_autovacuum, n_dead_tup\n FROM pg_stat_user_tables\n WHERE n_live_tup > 100\n AND (last_vacuum IS NULL AND last_autovacuum IS NULL\n OR GREATEST(last_vacuum, last_autovacuum) < now() - interval '7 days')\n ORDER BY n_dead_tup DESC LIMIT 15\n `);\n for (const row of r.rows) {\n const never = !row.last_vacuum && !row.last_autovacuum;\n issues.push({\n id: `maint-vacuum-${row.schemaname}-${row.relname}`,\n severity: never ? \"warning\" : \"info\",\n category: \"maintenance\",\n title: `VACUUM ${never ? \"never run\" : \"overdue\"} on ${row.relname}`,\n description: `${row.schemaname}.${row.relname} ${never ? \"has never been vacuumed\" : \"was last vacuumed over 7 days ago\"}. Dead tuples: ${Number(row.n_dead_tup).toLocaleString()}.`,\n fix: `VACUUM ANALYZE ${row.schemaname}.${row.relname};`,\n impact: \"Dead tuples accumulate, increasing table size and degrading query performance.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking vacuum overdue:\", (err as Error).message); skipped.push(\"vacuum overdue: \" + (err as Error).message);\n }\n\n // ANALYZE overdue\n try {\n const r = await client.query(`\n SELECT schemaname, relname\n FROM pg_stat_user_tables\n WHERE n_live_tup > 100\n AND last_analyze IS NULL AND last_autoanalyze IS NULL\n AND NOT EXISTS (\n SELECT 1 FROM pg_stat_user_tables t2\n WHERE t2.relname = pg_stat_user_tables.relname\n AND (t2.last_vacuum IS NULL AND t2.last_autovacuum IS NULL)\n )\n LIMIT 10\n `);\n for (const row of r.rows) {\n issues.push({\n id: `maint-analyze-${row.schemaname}-${row.relname}`,\n severity: \"info\",\n category: \"maintenance\",\n title: `ANALYZE never run on ${row.relname}`,\n description: `${row.schemaname}.${row.relname} has never been analyzed. The query planner may choose suboptimal plans.`,\n fix: `ANALYZE ${row.schemaname}.${row.relname};`,\n impact: \"Without statistics, the query planner makes poor estimates leading to slow queries.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking analyze overdue:\", (err as Error).message); skipped.push(\"analyze overdue: \" + (err as Error).message);\n }\n\n // Transaction ID wraparound risk\n try {\n const r = await client.query(`\n SELECT datname, age(datfrozenxid) AS xid_age\n FROM pg_database\n WHERE datname = current_database()\n `);\n for (const row of r.rows) {\n const age = parseInt(row.xid_age);\n if (age > 1_000_000_000) {\n issues.push({\n id: `maint-xid-wraparound`,\n severity: \"critical\",\n category: \"maintenance\",\n title: `Transaction ID wraparound risk`,\n description: `Database ${row.datname} has datfrozenxid age of ${age.toLocaleString()}. Wraparound occurs at ~2 billion.`,\n fix: `VACUUM FREEZE;`,\n impact: \"If wraparound occurs, PostgreSQL will shut down to prevent data loss.\",\n effort: \"involved\",\n });\n } else if (age > 500_000_000) {\n issues.push({\n id: `maint-xid-warning`,\n severity: \"warning\",\n category: \"maintenance\",\n title: `Transaction ID age is high`,\n description: `Database ${row.datname} has datfrozenxid age of ${age.toLocaleString()}.`,\n fix: `VACUUM FREEZE;`,\n impact: \"Approaching transaction ID wraparound threshold.\",\n effort: \"moderate\",\n });\n }\n }\n } catch (err) {\n console.error(\"[advisor] Error checking xid wraparound:\", (err as Error).message); skipped.push(\"xid wraparound: \" + (err as Error).message);\n }\n\n // Idle connections > 10 min\n try {\n const r = await client.query(`\n SELECT pid, state, now() - state_change AS idle_duration,\n client_addr::text, application_name,\n extract(epoch from now() - state_change)::int AS idle_seconds\n FROM pg_stat_activity\n WHERE state IN ('idle', 'idle in transaction')\n AND now() - state_change > $1 * interval '1 minute'\n AND pid != pg_backend_pid()\n `, [longQueryThreshold]);\n for (const row of r.rows) {\n const isIdleTx = row.state === \"idle in transaction\";\n issues.push({\n id: `maint-idle-${row.pid}`,\n severity: isIdleTx ? \"warning\" : \"info\",\n category: \"maintenance\",\n title: `${isIdleTx ? \"Idle in transaction\" : \"Idle connection\"} (PID ${row.pid})`,\n description: `PID ${row.pid} from ${row.client_addr || \"local\"} (${row.application_name || \"unknown\"}) has been ${row.state} for ${Math.round(row.idle_seconds / 60)} minutes.`,\n fix: `SELECT pg_terminate_backend(${row.pid});`,\n impact: isIdleTx ? \"Idle-in-transaction connections hold locks and prevent VACUUM.\" : \"Idle connections consume connection slots.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking idle connections:\", (err as Error).message); skipped.push(\"idle connections: \" + (err as Error).message);\n }\n\n // ── Schema Advisors ────────────────────────────────────────────\n\n // Missing primary keys\n try {\n const r = await client.query(`\n SELECT c.relname AS table_name, n.nspname AS schema\n FROM pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid\n WHERE c.relkind = 'r' AND n.nspname = 'public'\n AND NOT EXISTS (\n SELECT 1 FROM pg_constraint con WHERE con.conrelid = c.oid AND con.contype = 'p'\n )\n `);\n for (const row of r.rows) {\n issues.push({\n id: `schema-no-pk-${row.schema}-${row.table_name}`,\n severity: \"warning\",\n category: \"schema\",\n title: `Missing primary key on ${row.table_name}`,\n description: `Table ${row.schema}.${row.table_name} has no primary key. This can cause replication issues and makes row identification unreliable.`,\n fix: `ALTER TABLE ${row.schema}.${row.table_name} ADD PRIMARY KEY (<column>);`,\n impact: \"No primary key means no unique row identity, problematic for replication and ORMs.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking missing primary keys:\", (err as Error).message); skipped.push(\"missing primary keys: \" + (err as Error).message);\n }\n\n // Unused indexes (idx_scan = 0, size > 1MB)\n try {\n const r = await client.query(`\n SELECT schemaname, relname, indexrelname, idx_scan,\n pg_size_pretty(pg_relation_size(indexrelid)) AS idx_size,\n pg_relation_size(indexrelid) AS idx_bytes\n FROM pg_stat_user_indexes\n WHERE idx_scan = 0\n AND indexrelname NOT LIKE '%_pkey'\n AND pg_relation_size(indexrelid) > 1048576\n ORDER BY pg_relation_size(indexrelid) DESC LIMIT 10\n `);\n for (const row of r.rows) {\n issues.push({\n id: `schema-unused-idx-${row.indexrelname}`,\n severity: \"warning\",\n category: \"schema\",\n title: `Unused index ${row.indexrelname} (${row.idx_size})`,\n description: `Index ${row.indexrelname} on ${row.relname} has never been used (0 scans) and takes ${row.idx_size}.`,\n fix: `DROP INDEX CONCURRENTLY ${row.schemaname}.${row.indexrelname};`,\n impact: \"Unused indexes waste disk space and slow down writes.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking unused indexes:\", (err as Error).message); skipped.push(\"unused indexes: \" + (err as Error).message);\n }\n\n // Duplicate indexes\n try {\n const r = await client.query(`\n SELECT array_agg(idx.indexrelid::regclass::text) AS indexes,\n idx.indrelid::regclass::text AS table_name,\n pg_size_pretty(sum(pg_relation_size(idx.indexrelid))) AS total_size\n FROM pg_index idx\n GROUP BY idx.indrelid, idx.indkey\n HAVING count(*) > 1\n `);\n for (const row of r.rows) {\n issues.push({\n id: `schema-dup-idx-${row.table_name}-${row.indexes[0]}`,\n severity: \"warning\",\n category: \"schema\",\n title: `Duplicate indexes on ${row.table_name}`,\n description: `These indexes cover the same columns on ${row.table_name}: ${row.indexes.join(\", \")}. Total wasted space: ${row.total_size}.`,\n fix: `-- Keep one, drop the rest:\\nDROP INDEX CONCURRENTLY ${row.indexes.slice(1).join(\";\\nDROP INDEX CONCURRENTLY \")};`,\n impact: \"Duplicate indexes double the write overhead and waste disk space.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking duplicate indexes:\", (err as Error).message); skipped.push(\"duplicate indexes: \" + (err as Error).message);\n }\n\n // Missing foreign key indexes\n try {\n const r = await client.query(`\n SELECT\n conrelid::regclass::text AS table_name,\n a.attname AS column_name,\n confrelid::regclass::text AS referenced_table\n FROM pg_constraint c\n JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)\n WHERE c.contype = 'f'\n AND NOT EXISTS (\n SELECT 1 FROM pg_index i\n WHERE i.indrelid = c.conrelid\n AND a.attnum = ANY(i.indkey)\n )\n `);\n for (const row of r.rows) {\n issues.push({\n id: `schema-fk-no-idx-${row.table_name}-${row.column_name}`,\n severity: \"warning\",\n category: \"schema\",\n title: `Missing index on FK column ${row.table_name}.${row.column_name}`,\n description: `Foreign key column ${row.column_name} on ${row.table_name} (references ${row.referenced_table}) has no index. This causes slow JOINs and cascading deletes.`,\n fix: `CREATE INDEX CONCURRENTLY idx_${row.table_name.replace(/\\./g, \"_\")}_${row.column_name} ON ${row.table_name} (${row.column_name});`,\n impact: \"JOINs and cascading deletes on this FK will require full table scans.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking missing FK indexes:\", (err as Error).message); skipped.push(\"missing FK indexes: \" + (err as Error).message);\n }\n\n // ── Infrastructure Advisors ──────────────────────────────────────\n\n // Lock detection\n try {\n const r = await client.query(`\n SELECT blocked_locks.pid AS blocked_pid,\n blocking_locks.pid AS blocking_pid,\n blocked_activity.query AS blocked_query\n FROM pg_catalog.pg_locks blocked_locks\n JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype\n AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database\n AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation\n AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page\n AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple\n AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid\n AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid\n AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid\n AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid\n AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid\n AND blocking_locks.pid != blocked_locks.pid\n JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid\n WHERE NOT blocked_locks.granted\n `);\n for (const row of r.rows) {\n issues.push({\n id: `perf-lock-blocked-${row.blocked_pid}`,\n severity: \"warning\",\n category: \"performance\",\n title: `Blocked query (PID ${row.blocked_pid} blocked by PID ${row.blocking_pid})`,\n description: `PID ${row.blocked_pid} is waiting for a lock held by PID ${row.blocking_pid}. Query: ${(row.blocked_query || \"\").slice(0, 200)}`,\n fix: `SELECT pg_cancel_backend(${row.blocking_pid});`,\n impact: \"Blocked queries cause cascading delays and potential timeouts.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking locks:\", (err as Error).message); skipped.push(\"locks: \" + (err as Error).message);\n }\n\n // WAL/replication lag\n try {\n const r = await client.query(`\n SELECT CASE WHEN pg_is_in_recovery()\n THEN pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn())\n ELSE 0 END AS lag_bytes\n `);\n const lagBytes = parseInt(r.rows[0]?.lag_bytes ?? \"0\");\n if (lagBytes > 1048576) { // > 1MB\n issues.push({\n id: `perf-replication-lag`,\n severity: lagBytes > 104857600 ? \"critical\" : \"warning\",\n category: \"performance\",\n title: `Replication lag: ${(lagBytes / 1048576).toFixed(1)} MB`,\n description: `WAL replay is lagging by ${(lagBytes / 1048576).toFixed(1)} MB. This indicates the replica is falling behind.`,\n fix: `-- Check replication status:\\nSELECT * FROM pg_stat_replication;`,\n impact: \"High replication lag means the replica has stale data and failover may lose transactions.\",\n effort: \"involved\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking replication lag:\", (err as Error).message); skipped.push(\"replication lag: \" + (err as Error).message);\n }\n\n // Checkpoint frequency\n try {\n const checkpointView = pgVersion >= 170000 ? 'pg_stat_checkpointer' : 'pg_stat_bgwriter';\n const r = await client.query(`\n SELECT checkpoints_req, checkpoints_timed,\n CASE WHEN (checkpoints_req + checkpoints_timed) = 0 THEN 0\n ELSE round(checkpoints_req::numeric / (checkpoints_req + checkpoints_timed) * 100, 1) END AS req_pct\n FROM ${checkpointView}\n `);\n const reqPct = parseFloat(r.rows[0]?.req_pct ?? \"0\");\n if (reqPct > 50) {\n issues.push({\n id: `maint-checkpoint-frequency`,\n severity: reqPct > 80 ? \"warning\" : \"info\",\n category: \"maintenance\",\n title: `${reqPct}% of checkpoints are requested (not timed)`,\n description: `${r.rows[0]?.checkpoints_req} requested vs ${r.rows[0]?.checkpoints_timed} timed checkpoints. High requested checkpoints indicate checkpoint_completion_target or max_wal_size may need tuning.`,\n fix: `-- Increase max_wal_size:\\nALTER SYSTEM SET max_wal_size = '2GB';\\nSELECT pg_reload_conf();`,\n impact: \"Frequent requested checkpoints cause I/O spikes and degrade performance.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking checkpoint frequency:\", (err as Error).message); skipped.push(\"checkpoint frequency: \" + (err as Error).message);\n }\n\n // AutoVACUUM config check\n try {\n const r = await client.query(`SELECT setting FROM pg_settings WHERE name = 'autovacuum'`);\n if (r.rows[0]?.setting === \"off\") {\n issues.push({\n id: `maint-autovacuum-disabled`,\n severity: \"critical\",\n category: \"maintenance\",\n title: `Autovacuum is disabled`,\n description: `Autovacuum is turned off. Dead tuples will accumulate and transaction ID wraparound becomes a risk.`,\n fix: `ALTER SYSTEM SET autovacuum = on;\\nSELECT pg_reload_conf();`,\n impact: \"Without autovacuum, tables bloat indefinitely and risk transaction ID wraparound shutdown.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking autovacuum:\", (err as Error).message); skipped.push(\"autovacuum: \" + (err as Error).message);\n }\n\n // shared_buffers / work_mem check\n try {\n const sbRes = await client.query(`SELECT setting, unit FROM pg_settings WHERE name = 'shared_buffers'`);\n const memRes = await client.query(`\n SELECT (SELECT setting::bigint FROM pg_settings WHERE name = 'shared_buffers') *\n (SELECT setting::bigint FROM pg_settings WHERE name = 'block_size') AS shared_bytes\n `);\n const sharedBytes = parseInt(memRes.rows[0]?.shared_bytes ?? \"0\");\n // Get total RAM from OS via a simple query (pg doesn't expose this directly, but we can estimate)\n // We'll compare against a reasonable minimum: if shared_buffers < 128MB, warn\n if (sharedBytes > 0 && sharedBytes < 128 * 1024 * 1024) {\n issues.push({\n id: `perf-shared-buffers-low`,\n severity: \"warning\",\n category: \"performance\",\n title: `shared_buffers is only ${(sharedBytes / 1048576).toFixed(0)} MB`,\n description: `shared_buffers is set to ${sbRes.rows[0]?.setting}${sbRes.rows[0]?.unit || \"\"}. Recommended: ~25% of system RAM, typically at least 256MB for production.`,\n fix: `ALTER SYSTEM SET shared_buffers = '256MB';\\n-- Requires restart`,\n impact: \"Low shared_buffers means more disk I/O and poor cache hit ratios.\",\n effort: \"involved\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking shared_buffers:\", (err as Error).message); skipped.push(\"shared_buffers: \" + (err as Error).message);\n }\n\n try {\n const r = await client.query(`SELECT setting, unit FROM pg_settings WHERE name = 'work_mem'`);\n const workMemKB = parseInt(r.rows[0]?.setting ?? \"0\");\n if (workMemKB > 0 && workMemKB < 4096) { // < 4MB\n issues.push({\n id: `perf-work-mem-low`,\n severity: \"info\",\n category: \"performance\",\n title: `work_mem is only ${workMemKB < 1024 ? workMemKB + \"kB\" : (workMemKB / 1024).toFixed(0) + \"MB\"}`,\n description: `work_mem is ${r.rows[0]?.setting}${r.rows[0]?.unit || \"\"}. Low work_mem causes sorts and hash operations to spill to disk.`,\n fix: `ALTER SYSTEM SET work_mem = '16MB';\\nSELECT pg_reload_conf();`,\n impact: \"Operations that exceed work_mem use temporary disk files, which is much slower.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking work_mem:\", (err as Error).message); skipped.push(\"work_mem: \" + (err as Error).message);\n }\n\n // ── Security Advisors ──────────────────────────────────────────\n\n // Superuser connections from non-localhost\n try {\n const r = await client.query(`\n SELECT pid, usename, client_addr::text\n FROM pg_stat_activity\n WHERE usename IN (SELECT rolname FROM pg_roles WHERE rolsuper)\n AND client_addr IS NOT NULL\n AND client_addr::text NOT IN ('127.0.0.1', '::1')\n AND pid != pg_backend_pid()\n `);\n for (const row of r.rows) {\n issues.push({\n id: `sec-superuser-remote-${row.pid}`,\n severity: \"critical\",\n category: \"security\",\n title: `Superuser ${row.usename} connected from ${row.client_addr}`,\n description: `Superuser ${row.usename} has an active connection from non-localhost address ${row.client_addr}. This is a security risk.`,\n fix: `-- Restrict superuser access in pg_hba.conf to localhost only.\\n-- Then: SELECT pg_reload_conf();`,\n impact: \"Remote superuser access is a significant security vulnerability.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking superuser connections:\", (err as Error).message); skipped.push(\"superuser connections: \" + (err as Error).message);\n }\n\n // SSL disabled\n try {\n const r = await client.query(`SELECT setting FROM pg_settings WHERE name = 'ssl'`);\n if (r.rows[0]?.setting === \"off\") {\n issues.push({\n id: `sec-ssl-off`,\n severity: \"warning\",\n category: \"security\",\n title: `SSL is disabled`,\n description: `SSL is turned off. Database connections are not encrypted.`,\n fix: `-- Enable SSL in postgresql.conf:\\n-- ssl = on\\n-- ssl_cert_file = 'server.crt'\\n-- ssl_key_file = 'server.key'\\nSELECT pg_reload_conf();`,\n impact: \"Database traffic can be intercepted and read in transit.\",\n effort: \"involved\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking SSL check:\", (err as Error).message); skipped.push(\"SSL check: \" + (err as Error).message);\n }\n\n // Password authentication check (PG 15+)\n try {\n const r = await client.query(`\n SELECT type, database, user_name, auth_method\n FROM pg_hba_file_rules\n WHERE auth_method = 'trust' AND type != 'local'\n LIMIT 5\n `);\n for (const row of r.rows) {\n issues.push({\n id: `sec-trust-auth-${row.database}-${row.user_name}`,\n severity: \"critical\",\n category: \"security\",\n title: `Trust authentication for ${row.user_name}@${row.database}`,\n description: `HBA rule allows trust (no password) authentication for ${row.type} connections to ${row.database} as ${row.user_name}.`,\n fix: `-- Change auth_method from 'trust' to 'scram-sha-256' in pg_hba.conf\\n-- Then: SELECT pg_reload_conf();`,\n impact: \"Anyone can connect without a password.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking trust auth:\", (err as Error).message); skipped.push(\"trust auth: \" + (err as Error).message);\n } // pg_hba_file_rules not available pre-PG15\n\n // Filter out ignored issues\n const ignoredIds = getIgnoredIssues();\n const ignoredSet = new Set(ignoredIds);\n const activeIssues = issues.filter(i => !ignoredSet.has(i.id));\n const ignoredCount = issues.length - activeIssues.length;\n\n const score = computeAdvisorScore(activeIssues);\n return {\n score,\n grade: gradeFromScore(score),\n issues: activeIssues,\n breakdown: computeBreakdown(activeIssues),\n skipped,\n ignoredCount,\n };\n } finally {\n client.release();\n }\n}\n\n// ── Ignored Issues Management ──────────────────────────────────\n\nimport Database from \"better-sqlite3\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs\";\n\nlet _ignoredDb: ReturnType<typeof Database> | null = null;\n\nfunction getIgnoredDb(): ReturnType<typeof Database> {\n if (_ignoredDb) return _ignoredDb;\n const dataDir = process.env.PG_DASH_DATA_DIR || path.join(os.homedir(), \".pg-dash\");\n fs.mkdirSync(dataDir, { recursive: true });\n const dbPath = path.join(dataDir, \"alerts.db\");\n _ignoredDb = new Database(dbPath);\n _ignoredDb.pragma(\"journal_mode = WAL\");\n _ignoredDb.exec(\"CREATE TABLE IF NOT EXISTS ignored_issues (issue_id TEXT PRIMARY KEY, ignored_at INTEGER)\");\n return _ignoredDb;\n}\n\nexport function getIgnoredIssues(): string[] {\n try {\n const db = getIgnoredDb();\n return db.prepare(\"SELECT issue_id FROM ignored_issues\").all().map((r: any) => r.issue_id);\n } catch {\n return [];\n }\n}\n\nexport function ignoreIssue(issueId: string): void {\n const db = getIgnoredDb();\n db.prepare(\"INSERT OR REPLACE INTO ignored_issues (issue_id, ignored_at) VALUES (?, ?)\").run(issueId, Date.now());\n}\n\nexport function unignoreIssue(issueId: string): void {\n const db = getIgnoredDb();\n db.prepare(\"DELETE FROM ignored_issues WHERE issue_id = ?\").run(issueId);\n}\n\n// Allowed SQL operations for the fix endpoint\n\nexport function isSafeFix(sql: string): boolean {\n const trimmed = sql.trim();\n if (!trimmed) return false;\n\n // Reject multi-statement SQL (split on semicolons, ignore trailing)\n const statements = trimmed.replace(/;\\s*$/, \"\").split(\";\").map(s => s.trim()).filter(Boolean);\n if (statements.length !== 1) return false;\n\n const upper = statements[0].toUpperCase();\n\n // EXPLAIN ANALYZE — only allow if followed by SELECT\n if (upper.startsWith(\"EXPLAIN ANALYZE\")) {\n const afterExplain = upper.replace(/^EXPLAIN\\s+ANALYZE\\s+/, \"\").trimStart();\n return afterExplain.startsWith(\"SELECT\");\n }\n\n // Simple prefix allowlist for single statements\n const ALLOWED_PREFIXES = [\n \"VACUUM\",\n \"ANALYZE\",\n \"REINDEX\",\n \"CREATE INDEX CONCURRENTLY\",\n \"DROP INDEX CONCURRENTLY\",\n \"SELECT PG_TERMINATE_BACKEND(\",\n \"SELECT PG_CANCEL_BACKEND(\",\n ];\n\n return ALLOWED_PREFIXES.some((p) => upper.startsWith(p));\n}\n"],"mappings":"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","names":["pool","pool","pool","pool","pool","longQueryThreshold","dataDir","Database","path","os","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts","../src/server/queries/overview.ts","../src/server/queries/tables.ts","../src/server/queries/schema.ts","../src/server/queries/activity.ts","../src/server/advisor.ts"],"sourcesContent":["#!/usr/bin/env node\n// MCP Server for pg-dash — exposes PostgreSQL monitoring tools\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { Pool } from \"pg\";\nimport { z } from \"zod\";\nimport { getOverview } from \"./server/queries/overview.js\";\nimport { getTables } from \"./server/queries/tables.js\";\nimport { getSchemaTableDetail } from \"./server/queries/schema.js\";\nimport { getActivity } from \"./server/queries/activity.js\";\nimport { getAdvisorReport, isSafeFix } from \"./server/advisor.js\";\nimport Database from \"better-sqlite3\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs, { readFileSync } from \"node:fs\";\n\nconst pkg = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf-8\"));\n\nconst connString = process.argv[2] || process.env.PG_DASH_CONNECTION_STRING;\nif (!connString) {\n console.error(\"Usage: pg-dash-mcp <connection-string>\");\n console.error(\" or set PG_DASH_CONNECTION_STRING env var\");\n process.exit(1);\n}\n\nconst pool = new Pool({ connectionString: connString });\nconst longQueryThreshold = parseInt(process.env.PG_DASH_LONG_QUERY_THRESHOLD || \"5\", 10);\nconst dataDir = process.env.PG_DASH_DATA_DIR || path.join(os.homedir(), \".pg-dash\");\nfs.mkdirSync(dataDir, { recursive: true });\n\n// Open schema and alerts DBs (read-only for history queries)\nlet schemaDb: Database.Database | null = null;\nlet alertsDb: Database.Database | null = null;\ntry {\n const schemaPath = path.join(dataDir, \"schema.db\");\n if (fs.existsSync(schemaPath)) schemaDb = new Database(schemaPath, { readonly: true });\n} catch (err) { console.error(\"[mcp] Error:\", (err as Error).message); }\ntry {\n const alertsPath = path.join(dataDir, \"alerts.db\");\n if (fs.existsSync(alertsPath)) alertsDb = new Database(alertsPath, { readonly: true });\n} catch (err) { console.error(\"[mcp] Error:\", (err as Error).message); }\n\nconst server = new McpServer({ name: \"pg-dash\", version: pkg.version });\n\nserver.tool(\"pg_dash_overview\", \"Get database overview (version, uptime, size, connections)\", {}, async () => {\n try {\n const data = await getOverview(pool);\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_health\", \"Get health advisor report with score, grade, and issues\", {}, async () => {\n try {\n const data = await getAdvisorReport(pool, longQueryThreshold);\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_tables\", \"List all tables with sizes and row counts\", {}, async () => {\n try {\n const data = await getTables(pool);\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_table_detail\", \"Get detailed info about a specific table\", { table: z.string().describe(\"Table name (e.g. 'users' or 'public.users')\") }, async ({ table }) => {\n try {\n const data = await getSchemaTableDetail(pool, table);\n if (!data) return { content: [{ type: \"text\", text: \"Table not found\" }], isError: true };\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_activity\", \"Get current database activity (active queries, connections)\", {}, async () => {\n try {\n const data = await getActivity(pool);\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_schema_changes\", \"Get recent schema changes\", {}, async () => {\n try {\n if (!schemaDb) return { content: [{ type: \"text\", text: \"No schema tracking data available. Run pg-dash server first.\" }] };\n const changes = schemaDb.prepare(\"SELECT * FROM schema_changes ORDER BY timestamp DESC LIMIT 50\").all();\n return { content: [{ type: \"text\", text: JSON.stringify(changes, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_fix\", \"Execute a safe fix (VACUUM, ANALYZE, REINDEX, etc.)\", { sql: z.string().describe(\"SQL to execute (must be a safe operation)\") }, async ({ sql }) => {\n try {\n if (!isSafeFix(sql)) return { content: [{ type: \"text\", text: \"Operation not allowed. Only VACUUM, ANALYZE, REINDEX, CREATE/DROP INDEX CONCURRENTLY, pg_terminate_backend, pg_cancel_backend, and EXPLAIN ANALYZE are permitted.\" }], isError: true };\n const client = await pool.connect();\n try {\n const start = Date.now();\n const result = await client.query(sql);\n return { content: [{ type: \"text\", text: JSON.stringify({ ok: true, duration: Date.now() - start, rowCount: result.rowCount, rows: result.rows || [] }, null, 2) }] };\n } finally {\n client.release();\n }\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nserver.tool(\"pg_dash_alerts\", \"Get alert history\", {}, async () => {\n try {\n if (!alertsDb) return { content: [{ type: \"text\", text: \"No alerts data available. Run pg-dash server first.\" }] };\n const history = alertsDb.prepare(\"SELECT * FROM alert_history ORDER BY timestamp DESC LIMIT 50\").all();\n return { content: [{ type: \"text\", text: JSON.stringify(history, null, 2) }] };\n } catch (err: any) {\n return { content: [{ type: \"text\", text: `Error: ${err.message}` }], isError: true };\n }\n});\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","import type { Pool } from \"pg\";\n\nexport async function getOverview(pool: Pool) {\n const client = await pool.connect();\n try {\n const version = await client.query(\"SHOW server_version\");\n const uptime = await client.query(\n \"SELECT to_char(now() - pg_postmaster_start_time(), 'DD \\\"d\\\" HH24 \\\"h\\\" MI \\\"m\\\"') AS uptime\"\n );\n const dbSize = await client.query(\n \"SELECT pg_size_pretty(pg_database_size(current_database())) AS size\"\n );\n const dbCount = await client.query(\n \"SELECT count(*)::int AS count FROM pg_database WHERE NOT datistemplate\"\n );\n const connections = await client.query(`\n SELECT\n (SELECT count(*)::int FROM pg_stat_activity WHERE state = 'active') AS active,\n (SELECT count(*)::int FROM pg_stat_activity WHERE state = 'idle') AS idle,\n (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max\n `);\n\n return {\n version: version.rows[0].server_version,\n uptime: uptime.rows[0].uptime,\n dbSize: dbSize.rows[0].size,\n databaseCount: dbCount.rows[0].count,\n connections: connections.rows[0],\n };\n } finally {\n client.release();\n }\n}\n","import type { Pool } from \"pg\";\n\nexport async function getTables(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n schemaname AS schema,\n relname AS name,\n pg_size_pretty(pg_total_relation_size(relid)) AS total_size,\n pg_total_relation_size(relid) AS size_bytes,\n n_live_tup AS rows,\n n_dead_tup AS dead_tuples,\n CASE WHEN n_live_tup > 0 \n THEN round(n_dead_tup::numeric / n_live_tup * 100, 1) \n ELSE 0 END AS dead_pct\n FROM pg_stat_user_tables\n ORDER BY pg_total_relation_size(relid) DESC\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n","import type { Pool } from \"pg\";\n\nexport async function getSchemaTables(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n c.relname AS name,\n n.nspname AS schema,\n pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,\n pg_total_relation_size(c.oid) AS total_size_bytes,\n pg_size_pretty(pg_relation_size(c.oid)) AS table_size,\n pg_size_pretty(pg_total_relation_size(c.oid) - pg_relation_size(c.oid)) AS index_size,\n s.n_live_tup AS row_count,\n obj_description(c.oid) AS description\n FROM pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid\n LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid\n WHERE c.relkind = 'r' AND n.nspname NOT IN ('pg_catalog', 'information_schema')\n ORDER BY pg_total_relation_size(c.oid) DESC\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaTableDetail(pool: Pool, tableName: string) {\n const client = await pool.connect();\n try {\n // Parse schema.table or default to public\n const parts = tableName.split(\".\");\n const schema = parts.length > 1 ? parts[0] : \"public\";\n const name = parts.length > 1 ? parts[1] : parts[0];\n\n // Table info\n const tableInfo = await client.query(`\n SELECT\n c.relname AS name, n.nspname AS schema,\n pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,\n pg_size_pretty(pg_relation_size(c.oid)) AS table_size,\n pg_size_pretty(pg_total_relation_size(c.oid) - pg_relation_size(c.oid)) AS index_size,\n pg_size_pretty(pg_relation_size(c.reltoastrelid)) AS toast_size,\n s.n_live_tup AS row_count, s.n_dead_tup AS dead_tuples,\n s.last_vacuum, s.last_autovacuum, s.last_analyze, s.last_autoanalyze,\n s.seq_scan, s.idx_scan\n FROM pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid\n LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid\n WHERE c.relname = $1 AND n.nspname = $2 AND c.relkind = 'r'\n `, [name, schema]);\n\n if (tableInfo.rows.length === 0) return null;\n\n // Columns\n const columns = await client.query(`\n SELECT\n a.attname AS name,\n pg_catalog.format_type(a.atttypid, a.atttypmod) AS type,\n NOT a.attnotnull AS nullable,\n pg_get_expr(d.adbin, d.adrelid) AS default_value,\n col_description(a.attrelid, a.attnum) AS description\n FROM pg_attribute a\n LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum\n WHERE a.attrelid = (SELECT c.oid FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = $1 AND n.nspname = $2)\n AND a.attnum > 0 AND NOT a.attisdropped\n ORDER BY a.attnum\n `, [name, schema]);\n\n // Indexes\n const indexes = await client.query(`\n SELECT\n i.relname AS name,\n am.amname AS type,\n pg_size_pretty(pg_relation_size(i.oid)) AS size,\n pg_get_indexdef(idx.indexrelid) AS definition,\n idx.indisunique AS is_unique,\n idx.indisprimary AS is_primary,\n s.idx_scan, s.idx_tup_read, s.idx_tup_fetch\n FROM pg_index idx\n JOIN pg_class i ON idx.indexrelid = i.oid\n JOIN pg_class t ON idx.indrelid = t.oid\n JOIN pg_namespace n ON t.relnamespace = n.oid\n JOIN pg_am am ON i.relam = am.oid\n LEFT JOIN pg_stat_user_indexes s ON s.indexrelid = i.oid\n WHERE t.relname = $1 AND n.nspname = $2\n ORDER BY i.relname\n `, [name, schema]);\n\n // Constraints\n const constraints = await client.query(`\n SELECT\n conname AS name,\n CASE contype WHEN 'p' THEN 'PRIMARY KEY' WHEN 'f' THEN 'FOREIGN KEY'\n WHEN 'u' THEN 'UNIQUE' WHEN 'c' THEN 'CHECK' WHEN 'x' THEN 'EXCLUDE' END AS type,\n pg_get_constraintdef(oid) AS definition\n FROM pg_constraint\n WHERE conrelid = (SELECT c.oid FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid WHERE c.relname = $1 AND n.nspname = $2)\n ORDER BY\n CASE contype WHEN 'p' THEN 1 WHEN 'u' THEN 2 WHEN 'f' THEN 3 WHEN 'c' THEN 4 ELSE 5 END\n `, [name, schema]);\n\n // Foreign keys (outgoing)\n const foreignKeys = await client.query(`\n SELECT\n conname AS name,\n a.attname AS column_name,\n confrelid::regclass::text AS referenced_table,\n af.attname AS referenced_column\n FROM pg_constraint c\n JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)\n JOIN pg_attribute af ON af.attrelid = c.confrelid AND af.attnum = ANY(c.confkey)\n WHERE c.contype = 'f'\n AND c.conrelid = (SELECT cl.oid FROM pg_class cl JOIN pg_namespace n ON cl.relnamespace = n.oid WHERE cl.relname = $1 AND n.nspname = $2)\n `, [name, schema]);\n\n // Sample data (first 10 rows)\n let sampleData: any[] = [];\n try {\n const sample = await client.query(\n `SELECT * FROM ${client.escapeIdentifier(schema)}.${client.escapeIdentifier(name)} LIMIT 10`\n );\n sampleData = sample.rows;\n } catch (err) { console.error(\"[schema] Error:\", (err as Error).message); }\n\n return {\n ...tableInfo.rows[0],\n columns: columns.rows,\n indexes: indexes.rows,\n constraints: constraints.rows,\n foreignKeys: foreignKeys.rows,\n sampleData,\n };\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaIndexes(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n n.nspname AS schema,\n t.relname AS table_name,\n i.relname AS name,\n am.amname AS type,\n pg_size_pretty(pg_relation_size(i.oid)) AS size,\n pg_relation_size(i.oid) AS size_bytes,\n pg_get_indexdef(idx.indexrelid) AS definition,\n idx.indisunique AS is_unique,\n idx.indisprimary AS is_primary,\n s.idx_scan, s.idx_tup_read, s.idx_tup_fetch\n FROM pg_index idx\n JOIN pg_class i ON idx.indexrelid = i.oid\n JOIN pg_class t ON idx.indrelid = t.oid\n JOIN pg_namespace n ON t.relnamespace = n.oid\n JOIN pg_am am ON i.relam = am.oid\n LEFT JOIN pg_stat_user_indexes s ON s.indexrelid = i.oid\n WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')\n ORDER BY pg_relation_size(i.oid) DESC\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaFunctions(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n n.nspname AS schema,\n p.proname AS name,\n pg_get_function_result(p.oid) AS return_type,\n pg_get_function_arguments(p.oid) AS arguments,\n l.lanname AS language,\n p.prosrc AS source,\n CASE p.prokind WHEN 'f' THEN 'function' WHEN 'p' THEN 'procedure' WHEN 'a' THEN 'aggregate' WHEN 'w' THEN 'window' END AS kind\n FROM pg_proc p\n JOIN pg_namespace n ON p.pronamespace = n.oid\n JOIN pg_language l ON p.prolang = l.oid\n WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')\n ORDER BY n.nspname, p.proname\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaExtensions(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT extname AS name, extversion AS installed_version,\n n.nspname AS schema, obj_description(e.oid) AS description\n FROM pg_extension e\n JOIN pg_namespace n ON e.extnamespace = n.oid\n ORDER BY extname\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n\nexport async function getSchemaEnums(pool: Pool) {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n t.typname AS name,\n n.nspname AS schema,\n array_agg(e.enumlabel ORDER BY e.enumsortorder) AS values\n FROM pg_type t\n JOIN pg_namespace n ON t.typnamespace = n.oid\n JOIN pg_enum e ON t.oid = e.enumtypid\n WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')\n GROUP BY t.typname, n.nspname\n ORDER BY t.typname\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n","import type { Pool } from \"pg\";\n\nexport interface Activity {\n pid: number;\n query: string;\n state: string;\n wait_event: string | null;\n wait_event_type: string | null;\n duration: string | null;\n client_addr: string | null;\n application_name: string;\n backend_start: string;\n}\n\nexport async function getActivity(pool: Pool): Promise<Activity[]> {\n const client = await pool.connect();\n try {\n const r = await client.query(`\n SELECT\n pid,\n COALESCE(query, '') AS query,\n COALESCE(state, 'unknown') AS state,\n wait_event,\n wait_event_type,\n CASE WHEN state = 'active' THEN (now() - query_start)::text\n WHEN state = 'idle in transaction' THEN (now() - state_change)::text\n ELSE NULL END AS duration,\n client_addr::text,\n COALESCE(application_name, '') AS application_name,\n backend_start::text\n FROM pg_stat_activity\n WHERE pid != pg_backend_pid()\n AND state IS NOT NULL\n ORDER BY\n CASE state\n WHEN 'active' THEN 1\n WHEN 'idle in transaction' THEN 2\n ELSE 3\n END,\n query_start ASC NULLS LAST\n `);\n return r.rows;\n } finally {\n client.release();\n }\n}\n","import type { Pool } from \"pg\";\n\nexport interface AdvisorIssue {\n id: string;\n severity: \"critical\" | \"warning\" | \"info\";\n category: \"performance\" | \"maintenance\" | \"schema\" | \"security\";\n title: string;\n description: string;\n fix: string;\n impact: string;\n effort: \"quick\" | \"moderate\" | \"involved\";\n}\n\nexport interface BatchFix {\n type: string;\n title: string;\n count: number;\n sql: string;\n}\n\nexport interface AdvisorResult {\n score: number;\n grade: string;\n issues: AdvisorIssue[];\n breakdown: Record<string, { score: number; grade: string; count: number }>;\n skipped: string[];\n ignoredCount: number;\n batchFixes: BatchFix[];\n}\n\nconst SEVERITY_WEIGHT = { critical: 15, warning: 5, info: 1 } as const;\nconst MAX_DEDUCTION = { critical: 60, warning: 30, info: 10 } as const;\n\nexport function computeAdvisorScore(issues: AdvisorIssue[]): number {\n let score = 100;\n const deductions = { critical: 0, warning: 0, info: 0 };\n const counts = { critical: 0, warning: 0, info: 0 };\n for (const issue of issues) {\n counts[issue.severity]++;\n const n = counts[issue.severity];\n const weight = SEVERITY_WEIGHT[issue.severity];\n // Diminishing penalty: full for first 3, half for 4-10, quarter for 11+\n let penalty: number;\n if (n <= 3) penalty = weight;\n else if (n <= 10) penalty = weight * 0.5;\n else penalty = weight * 0.25;\n deductions[issue.severity] += penalty;\n }\n // Cap deductions per severity\n for (const sev of [\"critical\", \"warning\", \"info\"] as const) {\n score -= Math.min(deductions[sev], MAX_DEDUCTION[sev]);\n }\n return Math.max(0, Math.min(100, Math.round(score)));\n}\n\nexport function gradeFromScore(score: number): string {\n if (score >= 90) return \"A\";\n if (score >= 80) return \"B\";\n if (score >= 70) return \"C\";\n if (score >= 50) return \"D\";\n return \"F\";\n}\n\nfunction computeBreakdown(issues: AdvisorIssue[]): Record<string, { score: number; grade: string; count: number }> {\n const categories = [\"performance\", \"maintenance\", \"schema\", \"security\"] as const;\n const result: Record<string, { score: number; grade: string; count: number }> = {};\n for (const cat of categories) {\n const catIssues = issues.filter((i) => i.category === cat);\n const score = computeAdvisorScore(catIssues);\n result[cat] = { score, grade: gradeFromScore(score), count: catIssues.length };\n }\n return result;\n}\n\nexport async function getAdvisorReport(pool: Pool, longQueryThreshold = 5): Promise<AdvisorResult> {\n const client = await pool.connect();\n const issues: AdvisorIssue[] = [];\n const skipped: string[] = [];\n\n try {\n // Detect PG version for compatibility\n const versionResult = await client.query(\"SHOW server_version_num\");\n const pgVersion = parseInt(versionResult.rows[0].server_version_num);\n\n // ── Performance Advisors ───────────────────────────────────────\n\n // Missing indexes (high seq scans on large tables)\n try {\n const r = await client.query(`\n SELECT schemaname, relname, seq_scan, seq_tup_read, n_live_tup,\n pg_size_pretty(pg_total_relation_size(relid)) AS size\n FROM pg_stat_user_tables\n WHERE n_live_tup > 10000 AND seq_scan > 100\n ORDER BY seq_tup_read DESC LIMIT 10\n `);\n for (const row of r.rows) {\n issues.push({\n id: `perf-seq-scan-${row.schemaname}-${row.relname}`,\n severity: row.seq_scan > 1000 ? \"warning\" : \"info\",\n category: \"performance\",\n title: `High sequential scans on ${row.relname}`,\n description: `Table ${row.schemaname}.${row.relname} (${row.n_live_tup} rows, ${row.size}) has ${row.seq_scan} sequential scans reading ${Number(row.seq_tup_read).toLocaleString()} tuples. Consider adding indexes on frequently filtered columns.`,\n fix: `-- Identify commonly filtered columns and add indexes:\\n-- EXPLAIN ANALYZE SELECT * FROM ${row.schemaname}.${row.relname} WHERE <your_condition>;\\nCREATE INDEX CONCURRENTLY idx_${row.relname}_<column> ON ${row.schemaname}.${row.relname} (<column>);`,\n impact: \"Queries will continue to do full table scans, degrading performance as the table grows.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking seq scans:\", (err as Error).message); skipped.push(\"seq scans: \" + (err as Error).message);\n }\n\n // Bloated indexes (index size > 3x table size)\n try {\n const r = await client.query(`\n SELECT\n schemaname, relname, indexrelname,\n pg_relation_size(indexrelid) AS idx_size,\n pg_relation_size(relid) AS tbl_size,\n pg_size_pretty(pg_relation_size(indexrelid)) AS idx_size_pretty,\n pg_size_pretty(pg_relation_size(relid)) AS tbl_size_pretty\n FROM pg_stat_user_indexes\n WHERE pg_relation_size(indexrelid) > 1048576\n AND pg_relation_size(indexrelid) > pg_relation_size(relid) * 3\n ORDER BY pg_relation_size(indexrelid) DESC LIMIT 10\n `);\n for (const row of r.rows) {\n issues.push({\n id: `perf-bloated-idx-${row.indexrelname}`,\n severity: \"warning\",\n category: \"performance\",\n title: `Bloated index ${row.indexrelname}`,\n description: `Index ${row.indexrelname} on ${row.relname} is ${row.idx_size_pretty} but the table is only ${row.tbl_size_pretty}. The index may need rebuilding.`,\n fix: `REINDEX INDEX CONCURRENTLY ${row.schemaname}.${row.indexrelname};`,\n impact: \"Bloated indexes waste disk space and slow down queries that use them.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking bloated indexes:\", (err as Error).message); skipped.push(\"bloated indexes: \" + (err as Error).message);\n }\n\n // Table bloat (dead tuples > 10%)\n try {\n const r = await client.query(`\n SELECT schemaname, relname, n_dead_tup, n_live_tup,\n CASE WHEN n_live_tup > 0 THEN round(n_dead_tup::numeric / n_live_tup * 100, 1) ELSE 0 END AS dead_pct,\n pg_size_pretty(pg_total_relation_size(relid)) AS size\n FROM pg_stat_user_tables\n WHERE n_live_tup > 1000 AND n_dead_tup::float / GREATEST(n_live_tup, 1) > 0.1\n ORDER BY n_dead_tup DESC LIMIT 10\n `);\n for (const row of r.rows) {\n const pct = parseFloat(row.dead_pct);\n issues.push({\n id: `perf-bloat-${row.schemaname}-${row.relname}`,\n severity: pct > 30 ? \"critical\" : \"warning\",\n category: \"performance\",\n title: `Table bloat on ${row.relname} (${row.dead_pct}% dead)`,\n description: `${row.schemaname}.${row.relname} has ${Number(row.n_dead_tup).toLocaleString()} dead tuples (${row.dead_pct}% of ${Number(row.n_live_tup).toLocaleString()} live rows). Size: ${row.size}.`,\n fix: `VACUUM FULL ${row.schemaname}.${row.relname};`,\n impact: \"Dead tuples waste storage and degrade scan performance.\",\n effort: pct > 30 ? \"moderate\" : \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking table bloat:\", (err as Error).message); skipped.push(\"table bloat: \" + (err as Error).message);\n }\n\n // Cache efficiency per table\n try {\n const r = await client.query(`\n SELECT schemaname, relname,\n heap_blks_hit, heap_blks_read,\n CASE WHEN (heap_blks_hit + heap_blks_read) = 0 THEN 1\n ELSE heap_blks_hit::float / (heap_blks_hit + heap_blks_read) END AS ratio\n FROM pg_statio_user_tables\n WHERE (heap_blks_hit + heap_blks_read) > 100\n ORDER BY ratio ASC LIMIT 5\n `);\n for (const row of r.rows) {\n const ratio = parseFloat(row.ratio);\n if (ratio < 0.9) {\n issues.push({\n id: `perf-cache-${row.schemaname}-${row.relname}`,\n severity: ratio < 0.5 ? \"critical\" : \"warning\",\n category: \"performance\",\n title: `Poor cache hit ratio on ${row.relname}`,\n description: `Table ${row.schemaname}.${row.relname} has a cache hit ratio of ${(ratio * 100).toFixed(1)}%. Most reads are going to disk.`,\n fix: `-- Consider increasing shared_buffers or reducing working set:\\nSHOW shared_buffers;`,\n impact: \"Disk reads are orders of magnitude slower than memory reads.\",\n effort: \"involved\",\n });\n }\n }\n } catch (err) {\n console.error(\"[advisor] Error checking cache efficiency:\", (err as Error).message); skipped.push(\"cache efficiency: \" + (err as Error).message);\n }\n\n // Slow queries from pg_stat_statements\n try {\n const extCheck = await client.query(\"SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements'\");\n if (extCheck.rows.length > 0) {\n const r = await client.query(`\n SELECT query, calls, mean_exec_time, total_exec_time,\n round(mean_exec_time::numeric, 2) AS mean_ms,\n round(total_exec_time::numeric / 1000, 2) AS total_sec\n FROM pg_stat_statements\n WHERE query NOT LIKE '%pg_stat%' AND query NOT LIKE '%pg_catalog%'\n AND mean_exec_time > 100\n ORDER BY mean_exec_time DESC LIMIT 5\n `);\n for (const row of r.rows) {\n issues.push({\n id: `perf-slow-${row.query.slice(0, 30).replace(/\\W/g, \"_\")}`,\n severity: parseFloat(row.mean_ms) > 1000 ? \"warning\" : \"info\",\n category: \"performance\",\n title: `Slow query (avg ${row.mean_ms}ms)`,\n description: `Query averaging ${row.mean_ms}ms over ${row.calls} calls (total: ${row.total_sec}s): ${row.query.slice(0, 200)}`,\n fix: `EXPLAIN ANALYZE ${row.query.slice(0, 500)};`,\n impact: \"Slow queries degrade overall database responsiveness.\",\n effort: \"moderate\",\n });\n }\n }\n } catch (err) {\n console.error(\"[advisor] Error checking slow queries:\", (err as Error).message); skipped.push(\"slow queries: \" + (err as Error).message);\n }\n\n // ── Maintenance Advisors ───────────────────────────────────────\n\n // VACUUM overdue\n try {\n const r = await client.query(`\n SELECT schemaname, relname, last_vacuum, last_autovacuum, n_dead_tup\n FROM pg_stat_user_tables\n WHERE n_live_tup > 100\n AND (last_vacuum IS NULL AND last_autovacuum IS NULL\n OR GREATEST(last_vacuum, last_autovacuum) < now() - interval '7 days')\n ORDER BY n_dead_tup DESC LIMIT 15\n `);\n for (const row of r.rows) {\n const never = !row.last_vacuum && !row.last_autovacuum;\n issues.push({\n id: `maint-vacuum-${row.schemaname}-${row.relname}`,\n severity: never ? \"warning\" : \"info\",\n category: \"maintenance\",\n title: `VACUUM ${never ? \"never run\" : \"overdue\"} on ${row.relname}`,\n description: `${row.schemaname}.${row.relname} ${never ? \"has never been vacuumed\" : \"was last vacuumed over 7 days ago\"}. Dead tuples: ${Number(row.n_dead_tup).toLocaleString()}.`,\n fix: `VACUUM ANALYZE ${row.schemaname}.${row.relname};`,\n impact: \"Dead tuples accumulate, increasing table size and degrading query performance.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking vacuum overdue:\", (err as Error).message); skipped.push(\"vacuum overdue: \" + (err as Error).message);\n }\n\n // ANALYZE overdue\n try {\n const r = await client.query(`\n SELECT schemaname, relname\n FROM pg_stat_user_tables\n WHERE n_live_tup > 100\n AND last_analyze IS NULL AND last_autoanalyze IS NULL\n AND NOT EXISTS (\n SELECT 1 FROM pg_stat_user_tables t2\n WHERE t2.relname = pg_stat_user_tables.relname\n AND (t2.last_vacuum IS NULL AND t2.last_autovacuum IS NULL)\n )\n LIMIT 10\n `);\n for (const row of r.rows) {\n issues.push({\n id: `maint-analyze-${row.schemaname}-${row.relname}`,\n severity: \"info\",\n category: \"maintenance\",\n title: `ANALYZE never run on ${row.relname}`,\n description: `${row.schemaname}.${row.relname} has never been analyzed. The query planner may choose suboptimal plans.`,\n fix: `ANALYZE ${row.schemaname}.${row.relname};`,\n impact: \"Without statistics, the query planner makes poor estimates leading to slow queries.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking analyze overdue:\", (err as Error).message); skipped.push(\"analyze overdue: \" + (err as Error).message);\n }\n\n // Transaction ID wraparound risk\n try {\n const r = await client.query(`\n SELECT datname, age(datfrozenxid) AS xid_age\n FROM pg_database\n WHERE datname = current_database()\n `);\n for (const row of r.rows) {\n const age = parseInt(row.xid_age);\n if (age > 1_000_000_000) {\n issues.push({\n id: `maint-xid-wraparound`,\n severity: \"critical\",\n category: \"maintenance\",\n title: `Transaction ID wraparound risk`,\n description: `Database ${row.datname} has datfrozenxid age of ${age.toLocaleString()}. Wraparound occurs at ~2 billion.`,\n fix: `VACUUM FREEZE;`,\n impact: \"If wraparound occurs, PostgreSQL will shut down to prevent data loss.\",\n effort: \"involved\",\n });\n } else if (age > 500_000_000) {\n issues.push({\n id: `maint-xid-warning`,\n severity: \"warning\",\n category: \"maintenance\",\n title: `Transaction ID age is high`,\n description: `Database ${row.datname} has datfrozenxid age of ${age.toLocaleString()}.`,\n fix: `VACUUM FREEZE;`,\n impact: \"Approaching transaction ID wraparound threshold.\",\n effort: \"moderate\",\n });\n }\n }\n } catch (err) {\n console.error(\"[advisor] Error checking xid wraparound:\", (err as Error).message); skipped.push(\"xid wraparound: \" + (err as Error).message);\n }\n\n // Idle connections > 10 min\n try {\n const r = await client.query(`\n SELECT pid, state, now() - state_change AS idle_duration,\n client_addr::text, application_name,\n extract(epoch from now() - state_change)::int AS idle_seconds\n FROM pg_stat_activity\n WHERE state IN ('idle', 'idle in transaction')\n AND now() - state_change > $1 * interval '1 minute'\n AND pid != pg_backend_pid()\n `, [longQueryThreshold]);\n for (const row of r.rows) {\n const isIdleTx = row.state === \"idle in transaction\";\n issues.push({\n id: `maint-idle-${row.pid}`,\n severity: isIdleTx ? \"warning\" : \"info\",\n category: \"maintenance\",\n title: `${isIdleTx ? \"Idle in transaction\" : \"Idle connection\"} (PID ${row.pid})`,\n description: `PID ${row.pid} from ${row.client_addr || \"local\"} (${row.application_name || \"unknown\"}) has been ${row.state} for ${Math.round(row.idle_seconds / 60)} minutes.`,\n fix: `SELECT pg_terminate_backend(${row.pid});`,\n impact: isIdleTx ? \"Idle-in-transaction connections hold locks and prevent VACUUM.\" : \"Idle connections consume connection slots.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking idle connections:\", (err as Error).message); skipped.push(\"idle connections: \" + (err as Error).message);\n }\n\n // ── Schema Advisors ────────────────────────────────────────────\n\n // Missing primary keys\n try {\n const r = await client.query(`\n SELECT c.relname AS table_name, n.nspname AS schema\n FROM pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid\n WHERE c.relkind = 'r' AND n.nspname = 'public'\n AND NOT EXISTS (\n SELECT 1 FROM pg_constraint con WHERE con.conrelid = c.oid AND con.contype = 'p'\n )\n `);\n for (const row of r.rows) {\n issues.push({\n id: `schema-no-pk-${row.schema}-${row.table_name}`,\n severity: \"warning\",\n category: \"schema\",\n title: `Missing primary key on ${row.table_name}`,\n description: `Table ${row.schema}.${row.table_name} has no primary key. This can cause replication issues and makes row identification unreliable.`,\n fix: `ALTER TABLE ${row.schema}.${row.table_name} ADD PRIMARY KEY (<column>);`,\n impact: \"No primary key means no unique row identity, problematic for replication and ORMs.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking missing primary keys:\", (err as Error).message); skipped.push(\"missing primary keys: \" + (err as Error).message);\n }\n\n // Unused indexes (idx_scan = 0, size > 1MB)\n try {\n const r = await client.query(`\n SELECT schemaname, relname, indexrelname, idx_scan,\n pg_size_pretty(pg_relation_size(indexrelid)) AS idx_size,\n pg_relation_size(indexrelid) AS idx_bytes\n FROM pg_stat_user_indexes\n WHERE idx_scan = 0\n AND indexrelname NOT LIKE '%_pkey'\n AND pg_relation_size(indexrelid) > 1048576\n ORDER BY pg_relation_size(indexrelid) DESC LIMIT 10\n `);\n for (const row of r.rows) {\n issues.push({\n id: `schema-unused-idx-${row.indexrelname}`,\n severity: \"warning\",\n category: \"schema\",\n title: `Unused index ${row.indexrelname} (${row.idx_size})`,\n description: `Index ${row.indexrelname} on ${row.relname} has never been used (0 scans) and takes ${row.idx_size}.`,\n fix: `DROP INDEX CONCURRENTLY ${row.schemaname}.${row.indexrelname};`,\n impact: \"Unused indexes waste disk space and slow down writes.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking unused indexes:\", (err as Error).message); skipped.push(\"unused indexes: \" + (err as Error).message);\n }\n\n // Duplicate indexes\n try {\n const r = await client.query(`\n SELECT array_agg(idx.indexrelid::regclass::text) AS indexes,\n idx.indrelid::regclass::text AS table_name,\n pg_size_pretty(sum(pg_relation_size(idx.indexrelid))) AS total_size\n FROM pg_index idx\n GROUP BY idx.indrelid, idx.indkey\n HAVING count(*) > 1\n `);\n for (const row of r.rows) {\n issues.push({\n id: `schema-dup-idx-${row.table_name}-${row.indexes[0]}`,\n severity: \"warning\",\n category: \"schema\",\n title: `Duplicate indexes on ${row.table_name}`,\n description: `These indexes cover the same columns on ${row.table_name}: ${row.indexes.join(\", \")}. Total wasted space: ${row.total_size}.`,\n fix: `-- Keep one, drop the rest:\\nDROP INDEX CONCURRENTLY ${row.indexes.slice(1).join(\";\\nDROP INDEX CONCURRENTLY \")};`,\n impact: \"Duplicate indexes double the write overhead and waste disk space.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking duplicate indexes:\", (err as Error).message); skipped.push(\"duplicate indexes: \" + (err as Error).message);\n }\n\n // Missing foreign key indexes\n try {\n const r = await client.query(`\n SELECT\n conrelid::regclass::text AS table_name,\n a.attname AS column_name,\n confrelid::regclass::text AS referenced_table\n FROM pg_constraint c\n JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)\n WHERE c.contype = 'f'\n AND NOT EXISTS (\n SELECT 1 FROM pg_index i\n WHERE i.indrelid = c.conrelid\n AND a.attnum = ANY(i.indkey)\n )\n `);\n for (const row of r.rows) {\n issues.push({\n id: `schema-fk-no-idx-${row.table_name}-${row.column_name}`,\n severity: \"warning\",\n category: \"schema\",\n title: `Missing index on FK column ${row.table_name}.${row.column_name}`,\n description: `Foreign key column ${row.column_name} on ${row.table_name} (references ${row.referenced_table}) has no index. This causes slow JOINs and cascading deletes.`,\n fix: `CREATE INDEX CONCURRENTLY idx_${row.table_name.replace(/\\./g, \"_\")}_${row.column_name} ON ${row.table_name} (${row.column_name});`,\n impact: \"JOINs and cascading deletes on this FK will require full table scans.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking missing FK indexes:\", (err as Error).message); skipped.push(\"missing FK indexes: \" + (err as Error).message);\n }\n\n // ── Infrastructure Advisors ──────────────────────────────────────\n\n // Lock detection\n try {\n const r = await client.query(`\n SELECT blocked_locks.pid AS blocked_pid,\n blocking_locks.pid AS blocking_pid,\n blocked_activity.query AS blocked_query\n FROM pg_catalog.pg_locks blocked_locks\n JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype\n AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database\n AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation\n AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page\n AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple\n AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid\n AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid\n AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid\n AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid\n AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid\n AND blocking_locks.pid != blocked_locks.pid\n JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid\n WHERE NOT blocked_locks.granted\n `);\n for (const row of r.rows) {\n issues.push({\n id: `perf-lock-blocked-${row.blocked_pid}`,\n severity: \"warning\",\n category: \"performance\",\n title: `Blocked query (PID ${row.blocked_pid} blocked by PID ${row.blocking_pid})`,\n description: `PID ${row.blocked_pid} is waiting for a lock held by PID ${row.blocking_pid}. Query: ${(row.blocked_query || \"\").slice(0, 200)}`,\n fix: `SELECT pg_cancel_backend(${row.blocking_pid});`,\n impact: \"Blocked queries cause cascading delays and potential timeouts.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking locks:\", (err as Error).message); skipped.push(\"locks: \" + (err as Error).message);\n }\n\n // WAL/replication lag\n try {\n const r = await client.query(`\n SELECT CASE WHEN pg_is_in_recovery()\n THEN pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn())\n ELSE 0 END AS lag_bytes\n `);\n const lagBytes = parseInt(r.rows[0]?.lag_bytes ?? \"0\");\n if (lagBytes > 1048576) { // > 1MB\n issues.push({\n id: `perf-replication-lag`,\n severity: lagBytes > 104857600 ? \"critical\" : \"warning\",\n category: \"performance\",\n title: `Replication lag: ${(lagBytes / 1048576).toFixed(1)} MB`,\n description: `WAL replay is lagging by ${(lagBytes / 1048576).toFixed(1)} MB. This indicates the replica is falling behind.`,\n fix: `-- Check replication status:\\nSELECT * FROM pg_stat_replication;`,\n impact: \"High replication lag means the replica has stale data and failover may lose transactions.\",\n effort: \"involved\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking replication lag:\", (err as Error).message); skipped.push(\"replication lag: \" + (err as Error).message);\n }\n\n // Checkpoint frequency\n try {\n const checkpointView = pgVersion >= 170000 ? 'pg_stat_checkpointer' : 'pg_stat_bgwriter';\n const r = await client.query(`\n SELECT checkpoints_req, checkpoints_timed,\n CASE WHEN (checkpoints_req + checkpoints_timed) = 0 THEN 0\n ELSE round(checkpoints_req::numeric / (checkpoints_req + checkpoints_timed) * 100, 1) END AS req_pct\n FROM ${checkpointView}\n `);\n const reqPct = parseFloat(r.rows[0]?.req_pct ?? \"0\");\n if (reqPct > 50) {\n issues.push({\n id: `maint-checkpoint-frequency`,\n severity: reqPct > 80 ? \"warning\" : \"info\",\n category: \"maintenance\",\n title: `${reqPct}% of checkpoints are requested (not timed)`,\n description: `${r.rows[0]?.checkpoints_req} requested vs ${r.rows[0]?.checkpoints_timed} timed checkpoints. High requested checkpoints indicate checkpoint_completion_target or max_wal_size may need tuning.`,\n fix: `-- Increase max_wal_size:\\nALTER SYSTEM SET max_wal_size = '2GB';\\nSELECT pg_reload_conf();`,\n impact: \"Frequent requested checkpoints cause I/O spikes and degrade performance.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking checkpoint frequency:\", (err as Error).message); skipped.push(\"checkpoint frequency: \" + (err as Error).message);\n }\n\n // AutoVACUUM config check\n try {\n const r = await client.query(`SELECT setting FROM pg_settings WHERE name = 'autovacuum'`);\n if (r.rows[0]?.setting === \"off\") {\n issues.push({\n id: `maint-autovacuum-disabled`,\n severity: \"critical\",\n category: \"maintenance\",\n title: `Autovacuum is disabled`,\n description: `Autovacuum is turned off. Dead tuples will accumulate and transaction ID wraparound becomes a risk.`,\n fix: `ALTER SYSTEM SET autovacuum = on;\\nSELECT pg_reload_conf();`,\n impact: \"Without autovacuum, tables bloat indefinitely and risk transaction ID wraparound shutdown.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking autovacuum:\", (err as Error).message); skipped.push(\"autovacuum: \" + (err as Error).message);\n }\n\n // shared_buffers / work_mem check\n try {\n const sbRes = await client.query(`SELECT setting, unit FROM pg_settings WHERE name = 'shared_buffers'`);\n const memRes = await client.query(`\n SELECT (SELECT setting::bigint FROM pg_settings WHERE name = 'shared_buffers') *\n (SELECT setting::bigint FROM pg_settings WHERE name = 'block_size') AS shared_bytes\n `);\n const sharedBytes = parseInt(memRes.rows[0]?.shared_bytes ?? \"0\");\n // Get total RAM from OS via a simple query (pg doesn't expose this directly, but we can estimate)\n // We'll compare against a reasonable minimum: if shared_buffers < 128MB, warn\n if (sharedBytes > 0 && sharedBytes < 128 * 1024 * 1024) {\n issues.push({\n id: `perf-shared-buffers-low`,\n severity: \"warning\",\n category: \"performance\",\n title: `shared_buffers is only ${(sharedBytes / 1048576).toFixed(0)} MB`,\n description: `shared_buffers is set to ${sbRes.rows[0]?.setting}${sbRes.rows[0]?.unit || \"\"}. Recommended: ~25% of system RAM, typically at least 256MB for production.`,\n fix: `ALTER SYSTEM SET shared_buffers = '256MB';\\n-- Requires restart`,\n impact: \"Low shared_buffers means more disk I/O and poor cache hit ratios.\",\n effort: \"involved\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking shared_buffers:\", (err as Error).message); skipped.push(\"shared_buffers: \" + (err as Error).message);\n }\n\n try {\n const r = await client.query(`SELECT setting, unit FROM pg_settings WHERE name = 'work_mem'`);\n const workMemKB = parseInt(r.rows[0]?.setting ?? \"0\");\n if (workMemKB > 0 && workMemKB < 4096) { // < 4MB\n issues.push({\n id: `perf-work-mem-low`,\n severity: \"info\",\n category: \"performance\",\n title: `work_mem is only ${workMemKB < 1024 ? workMemKB + \"kB\" : (workMemKB / 1024).toFixed(0) + \"MB\"}`,\n description: `work_mem is ${r.rows[0]?.setting}${r.rows[0]?.unit || \"\"}. Low work_mem causes sorts and hash operations to spill to disk.`,\n fix: `ALTER SYSTEM SET work_mem = '16MB';\\nSELECT pg_reload_conf();`,\n impact: \"Operations that exceed work_mem use temporary disk files, which is much slower.\",\n effort: \"quick\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking work_mem:\", (err as Error).message); skipped.push(\"work_mem: \" + (err as Error).message);\n }\n\n // ── Security Advisors ──────────────────────────────────────────\n\n // Superuser connections from non-localhost\n try {\n const r = await client.query(`\n SELECT pid, usename, client_addr::text\n FROM pg_stat_activity\n WHERE usename IN (SELECT rolname FROM pg_roles WHERE rolsuper)\n AND client_addr IS NOT NULL\n AND client_addr::text NOT IN ('127.0.0.1', '::1')\n AND pid != pg_backend_pid()\n `);\n for (const row of r.rows) {\n issues.push({\n id: `sec-superuser-remote-${row.pid}`,\n severity: \"critical\",\n category: \"security\",\n title: `Superuser ${row.usename} connected from ${row.client_addr}`,\n description: `Superuser ${row.usename} has an active connection from non-localhost address ${row.client_addr}. This is a security risk.`,\n fix: `-- Restrict superuser access in pg_hba.conf to localhost only.\\n-- Then: SELECT pg_reload_conf();`,\n impact: \"Remote superuser access is a significant security vulnerability.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking superuser connections:\", (err as Error).message); skipped.push(\"superuser connections: \" + (err as Error).message);\n }\n\n // SSL disabled\n try {\n const r = await client.query(`SELECT setting FROM pg_settings WHERE name = 'ssl'`);\n if (r.rows[0]?.setting === \"off\") {\n issues.push({\n id: `sec-ssl-off`,\n severity: \"warning\",\n category: \"security\",\n title: `SSL is disabled`,\n description: `SSL is turned off. Database connections are not encrypted.`,\n fix: `-- Enable SSL in postgresql.conf:\\n-- ssl = on\\n-- ssl_cert_file = 'server.crt'\\n-- ssl_key_file = 'server.key'\\nSELECT pg_reload_conf();`,\n impact: \"Database traffic can be intercepted and read in transit.\",\n effort: \"involved\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking SSL check:\", (err as Error).message); skipped.push(\"SSL check: \" + (err as Error).message);\n }\n\n // Password authentication check (PG 15+)\n try {\n const r = await client.query(`\n SELECT type, database, user_name, auth_method\n FROM pg_hba_file_rules\n WHERE auth_method = 'trust' AND type != 'local'\n LIMIT 5\n `);\n for (const row of r.rows) {\n issues.push({\n id: `sec-trust-auth-${row.database}-${row.user_name}`,\n severity: \"critical\",\n category: \"security\",\n title: `Trust authentication for ${row.user_name}@${row.database}`,\n description: `HBA rule allows trust (no password) authentication for ${row.type} connections to ${row.database} as ${row.user_name}.`,\n fix: `-- Change auth_method from 'trust' to 'scram-sha-256' in pg_hba.conf\\n-- Then: SELECT pg_reload_conf();`,\n impact: \"Anyone can connect without a password.\",\n effort: \"moderate\",\n });\n }\n } catch (err) {\n console.error(\"[advisor] Error checking trust auth:\", (err as Error).message); skipped.push(\"trust auth: \" + (err as Error).message);\n } // pg_hba_file_rules not available pre-PG15\n\n // Filter out ignored issues\n const ignoredIds = getIgnoredIssues();\n const ignoredSet = new Set(ignoredIds);\n const activeIssues = issues.filter(i => !ignoredSet.has(i.id));\n const ignoredCount = issues.length - activeIssues.length;\n\n // Generate batch fixes for groups of same-type issues\n const batchFixes: BatchFix[] = [];\n const groups = new Map<string, AdvisorIssue[]>();\n for (const issue of activeIssues) {\n // Group by id prefix (everything before the last dash-separated segment with variable data)\n const prefix = issue.id.replace(/-[^-]+$/, \"\");\n if (!groups.has(prefix)) groups.set(prefix, []);\n groups.get(prefix)!.push(issue);\n }\n const BATCH_TITLES: Record<string, string> = {\n \"schema-fk-no-idx\": \"Create all missing FK indexes\",\n \"schema-unused-idx\": \"Drop all unused indexes\",\n \"schema-no-pk\": \"Fix all tables missing primary keys\",\n \"maint-vacuum\": \"VACUUM all overdue tables\",\n \"maint-analyze\": \"ANALYZE all tables missing statistics\",\n \"perf-bloated-idx\": \"REINDEX all bloated indexes\",\n \"perf-bloat\": \"VACUUM FULL all bloated tables\",\n };\n for (const [prefix, group] of groups) {\n if (group.length <= 1) continue;\n const title = BATCH_TITLES[prefix] || `Fix all ${group.length} ${prefix} issues`;\n const sql = group.map(i => i.fix.split(\"\\n\").filter(l => !l.trim().startsWith(\"--\")).join(\"\\n\").trim()).filter(Boolean).join(\";\\n\") + \";\";\n batchFixes.push({ type: prefix, title: `${title} (${group.length})`, count: group.length, sql });\n }\n\n const score = computeAdvisorScore(activeIssues);\n return {\n score,\n grade: gradeFromScore(score),\n issues: activeIssues,\n breakdown: computeBreakdown(activeIssues),\n skipped,\n ignoredCount,\n batchFixes,\n };\n } finally {\n client.release();\n }\n}\n\n// ── Ignored Issues Management ──────────────────────────────────\n\nimport Database from \"better-sqlite3\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs\";\n\nlet _ignoredDb: ReturnType<typeof Database> | null = null;\n\nfunction getIgnoredDb(): ReturnType<typeof Database> {\n if (_ignoredDb) return _ignoredDb;\n const dataDir = process.env.PG_DASH_DATA_DIR || path.join(os.homedir(), \".pg-dash\");\n fs.mkdirSync(dataDir, { recursive: true });\n const dbPath = path.join(dataDir, \"alerts.db\");\n _ignoredDb = new Database(dbPath);\n _ignoredDb.pragma(\"journal_mode = WAL\");\n _ignoredDb.exec(\"CREATE TABLE IF NOT EXISTS ignored_issues (issue_id TEXT PRIMARY KEY, ignored_at INTEGER)\");\n return _ignoredDb;\n}\n\nexport function getIgnoredIssues(): string[] {\n try {\n const db = getIgnoredDb();\n return db.prepare(\"SELECT issue_id FROM ignored_issues\").all().map((r: any) => r.issue_id);\n } catch {\n return [];\n }\n}\n\nexport function ignoreIssue(issueId: string): void {\n const db = getIgnoredDb();\n db.prepare(\"INSERT OR REPLACE INTO ignored_issues (issue_id, ignored_at) VALUES (?, ?)\").run(issueId, Date.now());\n}\n\nexport function unignoreIssue(issueId: string): void {\n const db = getIgnoredDb();\n db.prepare(\"DELETE FROM ignored_issues WHERE issue_id = ?\").run(issueId);\n}\n\n// Allowed SQL operations for the fix endpoint\n\nexport function isSafeFix(sql: string): boolean {\n const trimmed = sql.trim();\n if (!trimmed) return false;\n\n // Reject multi-statement SQL (split on semicolons, ignore trailing)\n const statements = trimmed.replace(/;\\s*$/, \"\").split(\";\").map(s => s.trim()).filter(Boolean);\n if (statements.length !== 1) return false;\n\n const upper = statements[0].toUpperCase();\n\n // EXPLAIN ANALYZE — only allow if followed by SELECT\n if (upper.startsWith(\"EXPLAIN ANALYZE\")) {\n const afterExplain = upper.replace(/^EXPLAIN\\s+ANALYZE\\s+/, \"\").trimStart();\n return afterExplain.startsWith(\"SELECT\");\n }\n\n // Simple prefix allowlist for single statements\n const ALLOWED_PREFIXES = [\n \"VACUUM\",\n \"ANALYZE\",\n \"REINDEX\",\n \"CREATE INDEX CONCURRENTLY\",\n \"DROP INDEX CONCURRENTLY\",\n \"SELECT PG_TERMINATE_BACKEND(\",\n \"SELECT PG_CANCEL_BACKEND(\",\n ];\n\n return ALLOWED_PREFIXES.some((p) => upper.startsWith(p));\n}\n"],"mappings":"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","names":["pool","pool","pool","pool","pool","longQueryThreshold","dataDir","Database","path","os","fs"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-200:oklch(92.5% .084 155.995);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-teal-300:oklch(85.5% .138 181.071);--color-teal-900:oklch(38.6% .063 188.416);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-900:oklch(35.9% .144 278.697);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-900:oklch(38.1% .176 304.987);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-4xl:56rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-black:900;--tracking-wide:.025em;--radius-lg:.5rem;--radius-xl:.75rem;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.top-0\.5{top:calc(var(--spacing) * .5)}.right-0{right:calc(var(--spacing) * 0)}.right-4{right:calc(var(--spacing) * 4)}.bottom-4{bottom:calc(var(--spacing) * 4)}.left-0\.5{left:calc(var(--spacing) * .5)}.left-5{left:calc(var(--spacing) * 5)}.z-40{z-index:40}.z-50{z-index:50}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-20{margin-top:calc(var(--spacing) * 20)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-6{margin-left:calc(var(--spacing) * 6)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-block{display:inline-block}.table{display:table}.h-2{height:calc(var(--spacing) * 2)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-8{height:calc(var(--spacing) * 8)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-32{height:calc(var(--spacing) * 32)}.h-40{height:calc(var(--spacing) * 40)}.h-48{height:calc(var(--spacing) * 48)}.h-64{height:calc(var(--spacing) * 64)}.h-72{height:calc(var(--spacing) * 72)}.h-\[calc\(100vh-16rem\)\]{height:calc(100vh - 16rem)}.max-h-24{max-height:calc(var(--spacing) * 24)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[85vh\]{max-height:85vh}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing) * 2)}.w-4{width:calc(var(--spacing) * 4)}.w-10{width:calc(var(--spacing) * 10)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-40{width:calc(var(--spacing) * 40)}.w-48{width:calc(var(--spacing) * 48)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[120px\]{min-width:120px}.min-w-\[300px\]{min-width:300px}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.border-gray-800\/50{border-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.border-gray-800\/50{border-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.border-green-400{border-color:var(--color-green-400)}.border-green-800{border-color:var(--color-green-800)}.border-orange-400{border-color:var(--color-orange-400)}.border-orange-800{border-color:var(--color-orange-800)}.border-red-400{border-color:var(--color-red-400)}.border-red-700{border-color:var(--color-red-700)}.border-red-800{border-color:var(--color-red-800)}.border-yellow-400{border-color:var(--color-yellow-400)}.border-yellow-800{border-color:var(--color-yellow-800)}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.bg-black\/70{background-color:#000000b3}@supports (color:color-mix(in lab,red,red)){.bg-black\/70{background-color:color-mix(in oklab,var(--color-black) 70%,transparent)}}.bg-blue-800{background-color:var(--color-blue-800)}.bg-blue-900{background-color:var(--color-blue-900)}.bg-blue-900\/30{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.bg-blue-900\/30{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-950{background-color:var(--color-gray-950)}.bg-green-400{background-color:var(--color-green-400)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-600{background-color:var(--color-green-600)}.bg-green-700{background-color:var(--color-green-700)}.bg-green-800{background-color:var(--color-green-800)}.bg-green-900{background-color:var(--color-green-900)}.bg-green-900\/20{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.bg-green-900\/20{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.bg-green-900\/30{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.bg-green-900\/30{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-indigo-600\/30{background-color:#4f39f64d}@supports (color:color-mix(in lab,red,red)){.bg-indigo-600\/30{background-color:color-mix(in oklab,var(--color-indigo-600) 30%,transparent)}}.bg-indigo-900{background-color:var(--color-indigo-900)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-900\/30{background-color:#7e2a0c4d}@supports (color:color-mix(in lab,red,red)){.bg-orange-900\/30{background-color:color-mix(in oklab,var(--color-orange-900) 30%,transparent)}}.bg-purple-900{background-color:var(--color-purple-900)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-red-700{background-color:var(--color-red-700)}.bg-red-800{background-color:var(--color-red-800)}.bg-red-900{background-color:var(--color-red-900)}.bg-red-900\/20{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.bg-red-900\/20{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.bg-teal-900{background-color:var(--color-teal-900)}.bg-white{background-color:var(--color-white)}.bg-yellow-500{background-color:var(--color-yellow-500)}.bg-yellow-900{background-color:var(--color-yellow-900)}.bg-yellow-900\/20{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.bg-yellow-900\/20{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.bg-yellow-900\/30{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.bg-yellow-900\/30{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-200{color:var(--color-blue-200)}.text-blue-300{color:var(--color-blue-300)}.text-blue-400{color:var(--color-blue-400)}.text-cyan-400{color:var(--color-cyan-400)}.text-gray-100{color:var(--color-gray-100)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-green-200{color:var(--color-green-200)}.text-green-400{color:var(--color-green-400)}.text-indigo-300{color:var(--color-indigo-300)}.text-orange-400{color:var(--color-orange-400)}.text-purple-300{color:var(--color-purple-300)}.text-purple-400{color:var(--color-purple-400)}.text-red-200{color:var(--color-red-200)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-teal-300{color:var(--color-teal-300)}.text-white{color:var(--color-white)}.text-yellow-300{color:var(--color-yellow-300)}.text-yellow-400{color:var(--color-yellow-400)}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.opacity-50{opacity:.5}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-red-500{--tw-ring-color:var(--color-red-500)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-gray-800\/30:hover{background-color:#1e29394d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-gray-800\/30:hover{background-color:color-mix(in oklab,var(--color-gray-800) 30%,transparent)}}.hover\:bg-green-600:hover{background-color:var(--color-green-600)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:text-blue-300:hover{color:var(--color-blue-300)}.hover\:text-gray-200:hover{color:var(--color-gray-200)}.hover\:text-gray-300:hover{color:var(--color-gray-300)}.hover\:text-red-300:hover{color:var(--color-red-300)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@keyframes pulse{50%{opacity:.5}}
|