@indiekitai/pg-dash 0.4.4 → 0.4.6
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 +50 -31
- package/dist/cli.js.map +1 -1
- package/dist/mcp.js +25 -11
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -300,30 +300,44 @@ SHOW shared_buffers;`,
|
|
|
300
300
|
}
|
|
301
301
|
try {
|
|
302
302
|
const r = await client.query(`
|
|
303
|
-
SELECT pid,
|
|
303
|
+
SELECT pid,
|
|
304
304
|
client_addr::text, application_name,
|
|
305
305
|
extract(epoch from now() - state_change)::int AS idle_seconds
|
|
306
306
|
FROM pg_stat_activity
|
|
307
|
-
WHERE state
|
|
307
|
+
WHERE state = 'idle in transaction'
|
|
308
308
|
AND now() - state_change > $1 * interval '1 minute'
|
|
309
309
|
AND pid != pg_backend_pid()
|
|
310
|
+
ORDER BY idle_seconds DESC
|
|
310
311
|
`, [longQueryThreshold]);
|
|
311
|
-
|
|
312
|
-
const
|
|
312
|
+
if (r.rows.length === 1) {
|
|
313
|
+
const row = r.rows[0];
|
|
313
314
|
issues.push({
|
|
314
|
-
id: `maint-idle-${row.pid}`,
|
|
315
|
-
severity:
|
|
315
|
+
id: `maint-idle-tx-${row.pid}`,
|
|
316
|
+
severity: "warning",
|
|
316
317
|
category: "maintenance",
|
|
317
|
-
title:
|
|
318
|
-
description: `PID ${row.pid} from ${row.client_addr || "local"} (${row.application_name || "unknown"}) has been
|
|
318
|
+
title: `Idle-in-transaction connection (PID ${row.pid})`,
|
|
319
|
+
description: `PID ${row.pid} from ${row.client_addr || "local"} (${row.application_name || "unknown"}) has been idle in transaction for ${Math.round(row.idle_seconds / 60)} minutes. This holds locks and blocks VACUUM.`,
|
|
319
320
|
fix: `SELECT pg_terminate_backend(${row.pid});`,
|
|
320
|
-
impact:
|
|
321
|
+
impact: "Idle-in-transaction connections hold locks and prevent VACUUM.",
|
|
322
|
+
effort: "quick"
|
|
323
|
+
});
|
|
324
|
+
} else if (r.rows.length > 1) {
|
|
325
|
+
const pids = r.rows.map((row) => row.pid);
|
|
326
|
+
const maxMin = Math.round(r.rows[0].idle_seconds / 60);
|
|
327
|
+
issues.push({
|
|
328
|
+
id: `maint-idle-tx-multi`,
|
|
329
|
+
severity: "warning",
|
|
330
|
+
category: "maintenance",
|
|
331
|
+
title: `${r.rows.length} idle-in-transaction connections (longest: ${maxMin}m)`,
|
|
332
|
+
description: `${r.rows.length} connections have been idle in transaction for over ${longQueryThreshold} minutes. These hold locks and prevent VACUUM from running.`,
|
|
333
|
+
fix: `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction' AND now() - state_change > interval '${longQueryThreshold} minutes';`,
|
|
334
|
+
impact: "Idle-in-transaction connections hold locks and prevent VACUUM.",
|
|
321
335
|
effort: "quick"
|
|
322
336
|
});
|
|
323
337
|
}
|
|
324
338
|
} catch (err) {
|
|
325
|
-
console.error("[advisor] Error checking idle connections:", err.message);
|
|
326
|
-
skipped.push("idle
|
|
339
|
+
console.error("[advisor] Error checking idle-in-transaction connections:", err.message);
|
|
340
|
+
skipped.push("idle-in-transaction: " + err.message);
|
|
327
341
|
}
|
|
328
342
|
try {
|
|
329
343
|
const r = await client.query(`
|
|
@@ -2741,7 +2755,7 @@ var RANGE_MAP = {
|
|
|
2741
2755
|
"7d": 7 * 24 * 60 * 60 * 1e3
|
|
2742
2756
|
};
|
|
2743
2757
|
function registerMetricsRoutes(app, store, collector) {
|
|
2744
|
-
app.get("/api/metrics", (c) => {
|
|
2758
|
+
app.get("/api/metrics", async (c) => {
|
|
2745
2759
|
try {
|
|
2746
2760
|
const metric = c.req.query("metric");
|
|
2747
2761
|
const range = c.req.query("range") || "1h";
|
|
@@ -2754,7 +2768,7 @@ function registerMetricsRoutes(app, store, collector) {
|
|
|
2754
2768
|
return c.json({ error: err.message }, 500);
|
|
2755
2769
|
}
|
|
2756
2770
|
});
|
|
2757
|
-
app.get("/api/metrics/latest", (_c) => {
|
|
2771
|
+
app.get("/api/metrics/latest", async (_c) => {
|
|
2758
2772
|
try {
|
|
2759
2773
|
const snapshot = collector.getLastSnapshot();
|
|
2760
2774
|
return _c.json(snapshot);
|
|
@@ -2845,7 +2859,7 @@ function registerAdvisorRoutes(app, pool, longQueryThreshold, store) {
|
|
|
2845
2859
|
return c.json({ error: err.message }, 500);
|
|
2846
2860
|
}
|
|
2847
2861
|
});
|
|
2848
|
-
app.get("/api/advisor/ignored", (c) => {
|
|
2862
|
+
app.get("/api/advisor/ignored", async (c) => {
|
|
2849
2863
|
try {
|
|
2850
2864
|
return c.json(getIgnoredIssues());
|
|
2851
2865
|
} catch (err) {
|
|
@@ -2863,7 +2877,7 @@ function registerAdvisorRoutes(app, pool, longQueryThreshold, store) {
|
|
|
2863
2877
|
return c.json({ error: err.message }, 500);
|
|
2864
2878
|
}
|
|
2865
2879
|
});
|
|
2866
|
-
app.delete("/api/advisor/ignore/:issueId", (c) => {
|
|
2880
|
+
app.delete("/api/advisor/ignore/:issueId", async (c) => {
|
|
2867
2881
|
try {
|
|
2868
2882
|
const issueId = c.req.param("issueId");
|
|
2869
2883
|
unignoreIssue(issueId);
|
|
@@ -2872,7 +2886,7 @@ function registerAdvisorRoutes(app, pool, longQueryThreshold, store) {
|
|
|
2872
2886
|
return c.json({ error: err.message }, 500);
|
|
2873
2887
|
}
|
|
2874
2888
|
});
|
|
2875
|
-
app.get("/api/advisor/history", (c) => {
|
|
2889
|
+
app.get("/api/advisor/history", async (c) => {
|
|
2876
2890
|
if (!store) return c.json([]);
|
|
2877
2891
|
try {
|
|
2878
2892
|
const range = c.req.query("range") || "24h";
|
|
@@ -2953,7 +2967,7 @@ function registerSchemaRoutes(app, pool, schemaTracker) {
|
|
|
2953
2967
|
return c.json({ error: err.message }, 500);
|
|
2954
2968
|
}
|
|
2955
2969
|
});
|
|
2956
|
-
app.get("/api/schema/history", (c) => {
|
|
2970
|
+
app.get("/api/schema/history", async (c) => {
|
|
2957
2971
|
try {
|
|
2958
2972
|
const limit = parseInt(c.req.query("limit") || "30");
|
|
2959
2973
|
return c.json(schemaTracker.getHistory(limit));
|
|
@@ -2961,7 +2975,7 @@ function registerSchemaRoutes(app, pool, schemaTracker) {
|
|
|
2961
2975
|
return c.json({ error: err.message }, 500);
|
|
2962
2976
|
}
|
|
2963
2977
|
});
|
|
2964
|
-
app.get("/api/schema/changes", (c) => {
|
|
2978
|
+
app.get("/api/schema/changes", async (c) => {
|
|
2965
2979
|
try {
|
|
2966
2980
|
const since = c.req.query("since");
|
|
2967
2981
|
return c.json(schemaTracker.getChanges(since ? parseInt(since) : void 0));
|
|
@@ -2969,14 +2983,14 @@ function registerSchemaRoutes(app, pool, schemaTracker) {
|
|
|
2969
2983
|
return c.json({ error: err.message }, 500);
|
|
2970
2984
|
}
|
|
2971
2985
|
});
|
|
2972
|
-
app.get("/api/schema/changes/latest", (c) => {
|
|
2986
|
+
app.get("/api/schema/changes/latest", async (c) => {
|
|
2973
2987
|
try {
|
|
2974
2988
|
return c.json(schemaTracker.getLatestChanges());
|
|
2975
2989
|
} catch (err) {
|
|
2976
2990
|
return c.json({ error: err.message }, 500);
|
|
2977
2991
|
}
|
|
2978
2992
|
});
|
|
2979
|
-
app.get("/api/schema/diff", (c) => {
|
|
2993
|
+
app.get("/api/schema/diff", async (c) => {
|
|
2980
2994
|
try {
|
|
2981
2995
|
const from = parseInt(c.req.query("from") || "0");
|
|
2982
2996
|
const to = parseInt(c.req.query("to") || "0");
|
|
@@ -3000,7 +3014,7 @@ function registerSchemaRoutes(app, pool, schemaTracker) {
|
|
|
3000
3014
|
|
|
3001
3015
|
// src/server/routes/alerts.ts
|
|
3002
3016
|
function registerAlertsRoutes(app, alertManager) {
|
|
3003
|
-
app.get("/api/alerts/rules", (c) => {
|
|
3017
|
+
app.get("/api/alerts/rules", async (c) => {
|
|
3004
3018
|
try {
|
|
3005
3019
|
return c.json(alertManager.getRules());
|
|
3006
3020
|
} catch (err) {
|
|
@@ -3027,7 +3041,7 @@ function registerAlertsRoutes(app, alertManager) {
|
|
|
3027
3041
|
return c.json({ error: err.message }, 500);
|
|
3028
3042
|
}
|
|
3029
3043
|
});
|
|
3030
|
-
app.delete("/api/alerts/rules/:id", (c) => {
|
|
3044
|
+
app.delete("/api/alerts/rules/:id", async (c) => {
|
|
3031
3045
|
try {
|
|
3032
3046
|
const id = parseInt(c.req.param("id"));
|
|
3033
3047
|
const ok = alertManager.deleteRule(id);
|
|
@@ -3037,7 +3051,7 @@ function registerAlertsRoutes(app, alertManager) {
|
|
|
3037
3051
|
return c.json({ error: err.message }, 500);
|
|
3038
3052
|
}
|
|
3039
3053
|
});
|
|
3040
|
-
app.get("/api/alerts/webhook-info", (c) => {
|
|
3054
|
+
app.get("/api/alerts/webhook-info", async (c) => {
|
|
3041
3055
|
try {
|
|
3042
3056
|
const url = alertManager.getWebhookUrl();
|
|
3043
3057
|
const type = alertManager.getWebhookType();
|
|
@@ -3055,7 +3069,7 @@ function registerAlertsRoutes(app, alertManager) {
|
|
|
3055
3069
|
return c.json({ error: err.message }, 500);
|
|
3056
3070
|
}
|
|
3057
3071
|
});
|
|
3058
|
-
app.get("/api/alerts/history", (c) => {
|
|
3072
|
+
app.get("/api/alerts/history", async (c) => {
|
|
3059
3073
|
try {
|
|
3060
3074
|
const limit = parseInt(c.req.query("limit") || "50");
|
|
3061
3075
|
return c.json(alertManager.getHistory(limit));
|
|
@@ -3335,8 +3349,13 @@ function registerDiskRoutes(app, pool, store) {
|
|
|
3335
3349
|
(SELECT setting FROM pg_settings WHERE name = 'data_directory') AS data_dir
|
|
3336
3350
|
`);
|
|
3337
3351
|
const { db_size, data_dir } = dbRes.rows[0];
|
|
3338
|
-
const tsRes = await client.query(`
|
|
3339
|
-
|
|
3352
|
+
const tsRes = await client.query(`
|
|
3353
|
+
SELECT spcname,
|
|
3354
|
+
CASE WHEN has_tablespace_privilege(spcname, 'CREATE')
|
|
3355
|
+
THEN pg_tablespace_size(oid) ELSE NULL END AS size
|
|
3356
|
+
FROM pg_tablespace
|
|
3357
|
+
`);
|
|
3358
|
+
const tablespaces = tsRes.rows.filter((r) => r.size !== null).map((r) => ({
|
|
3340
3359
|
name: r.spcname,
|
|
3341
3360
|
size: parseInt(r.size)
|
|
3342
3361
|
}));
|
|
@@ -3369,7 +3388,7 @@ function registerDiskRoutes(app, pool, store) {
|
|
|
3369
3388
|
return c.json({ error: err.message }, 500);
|
|
3370
3389
|
}
|
|
3371
3390
|
});
|
|
3372
|
-
app.get("/api/disk/prediction", (c) => {
|
|
3391
|
+
app.get("/api/disk/prediction", async (c) => {
|
|
3373
3392
|
try {
|
|
3374
3393
|
const days = parseInt(c.req.query("days") || "30");
|
|
3375
3394
|
const maxDisk = c.req.query("maxDisk") ? parseInt(c.req.query("maxDisk")) : void 0;
|
|
@@ -3379,7 +3398,7 @@ function registerDiskRoutes(app, pool, store) {
|
|
|
3379
3398
|
return c.json({ error: err.message }, 500);
|
|
3380
3399
|
}
|
|
3381
3400
|
});
|
|
3382
|
-
app.get("/api/disk/table-history/:table", (c) => {
|
|
3401
|
+
app.get("/api/disk/table-history/:table", async (c) => {
|
|
3383
3402
|
try {
|
|
3384
3403
|
const table = c.req.param("table");
|
|
3385
3404
|
const range = c.req.query("range") || "24h";
|
|
@@ -3391,7 +3410,7 @@ function registerDiskRoutes(app, pool, store) {
|
|
|
3391
3410
|
return c.json({ error: err.message }, 500);
|
|
3392
3411
|
}
|
|
3393
3412
|
});
|
|
3394
|
-
app.get("/api/disk/history", (c) => {
|
|
3413
|
+
app.get("/api/disk/history", async (c) => {
|
|
3395
3414
|
try {
|
|
3396
3415
|
const range = c.req.query("range") || "24h";
|
|
3397
3416
|
const rangeMs = RANGE_MAP3[range] || RANGE_MAP3["24h"];
|
|
@@ -3600,7 +3619,7 @@ var RANGE_MAP4 = {
|
|
|
3600
3619
|
"7d": 7 * 24 * 60 * 60 * 1e3
|
|
3601
3620
|
};
|
|
3602
3621
|
function registerQueryStatsRoutes(app, store) {
|
|
3603
|
-
app.get("/api/query-stats/top", (c) => {
|
|
3622
|
+
app.get("/api/query-stats/top", async (c) => {
|
|
3604
3623
|
try {
|
|
3605
3624
|
const range = c.req.query("range") || "1h";
|
|
3606
3625
|
const orderBy = c.req.query("orderBy") || "total_time";
|
|
@@ -3613,7 +3632,7 @@ function registerQueryStatsRoutes(app, store) {
|
|
|
3613
3632
|
return c.json({ error: err.message }, 500);
|
|
3614
3633
|
}
|
|
3615
3634
|
});
|
|
3616
|
-
app.get("/api/query-stats/trend/:queryid", (c) => {
|
|
3635
|
+
app.get("/api/query-stats/trend/:queryid", async (c) => {
|
|
3617
3636
|
try {
|
|
3618
3637
|
const queryid = c.req.param("queryid");
|
|
3619
3638
|
const range = c.req.query("range") || "1h";
|