@calltelemetry/cli 0.7.10 → 0.9.2

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.
Files changed (87) hide show
  1. package/dist/commands/appliance.d.ts.map +1 -1
  2. package/dist/commands/appliance.js +16 -1
  3. package/dist/commands/appliance.js.map +1 -1
  4. package/dist/commands/db.d.ts.map +1 -1
  5. package/dist/commands/db.js +74 -1
  6. package/dist/commands/db.js.map +1 -1
  7. package/dist/commands/update.d.ts +5 -0
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +149 -4
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/index.js +2 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/bundle.d.ts +12 -0
  14. package/dist/lib/bundle.d.ts.map +1 -1
  15. package/dist/lib/bundle.js +60 -3
  16. package/dist/lib/bundle.js.map +1 -1
  17. package/dist/lib/compose.d.ts +2 -0
  18. package/dist/lib/compose.d.ts.map +1 -1
  19. package/dist/lib/compose.js +4 -0
  20. package/dist/lib/compose.js.map +1 -1
  21. package/dist/lib/db-backup.d.ts.map +1 -1
  22. package/dist/lib/db-backup.js +5 -2
  23. package/dist/lib/db-backup.js.map +1 -1
  24. package/dist/lib/db-probe.d.ts +75 -0
  25. package/dist/lib/db-probe.d.ts.map +1 -0
  26. package/dist/lib/db-probe.js +149 -0
  27. package/dist/lib/db-probe.js.map +1 -0
  28. package/dist/lib/hardware.d.ts +31 -0
  29. package/dist/lib/hardware.d.ts.map +1 -1
  30. package/dist/lib/hardware.js +75 -1
  31. package/dist/lib/hardware.js.map +1 -1
  32. package/dist/lib/offline-steps.d.ts.map +1 -1
  33. package/dist/lib/offline-steps.js +5 -0
  34. package/dist/lib/offline-steps.js.map +1 -1
  35. package/dist/lib/prefs.d.ts +39 -0
  36. package/dist/lib/prefs.d.ts.map +1 -1
  37. package/dist/lib/prefs.js +0 -0
  38. package/dist/lib/prefs.js.map +1 -1
  39. package/dist/lib/update-steps.d.ts.map +1 -1
  40. package/dist/lib/update-steps.js +19 -3
  41. package/dist/lib/update-steps.js.map +1 -1
  42. package/dist/lib/update.d.ts +12 -2
  43. package/dist/lib/update.d.ts.map +1 -1
  44. package/dist/lib/update.js +105 -6
  45. package/dist/lib/update.js.map +1 -1
  46. package/dist/lib/version.d.ts +1 -1
  47. package/dist/lib/version.d.ts.map +1 -1
  48. package/dist/lib/version.js +1 -1
  49. package/dist/lib/version.js.map +1 -1
  50. package/dist/shell/commands/appliance-hardware.d.ts +1 -0
  51. package/dist/shell/commands/appliance-hardware.d.ts.map +1 -1
  52. package/dist/shell/commands/appliance-hardware.js +17 -1
  53. package/dist/shell/commands/appliance-hardware.js.map +1 -1
  54. package/dist/shell/commands/db.d.ts +1 -0
  55. package/dist/shell/commands/db.d.ts.map +1 -1
  56. package/dist/shell/commands/db.js +115 -0
  57. package/dist/shell/commands/db.js.map +1 -1
  58. package/dist/shell/commands/registry.d.ts.map +1 -1
  59. package/dist/shell/commands/registry.js +27 -0
  60. package/dist/shell/commands/registry.js.map +1 -1
  61. package/dist/shell/commands/sql-monitor.d.ts +35 -0
  62. package/dist/shell/commands/sql-monitor.d.ts.map +1 -0
  63. package/dist/shell/commands/sql-monitor.js +228 -0
  64. package/dist/shell/commands/sql-monitor.js.map +1 -0
  65. package/dist/ui/views/DbCompactView.d.ts.map +1 -1
  66. package/dist/ui/views/DbCompactView.js +7 -5
  67. package/dist/ui/views/DbCompactView.js.map +1 -1
  68. package/dist/ui/views/DbSizeView.d.ts.map +1 -1
  69. package/dist/ui/views/DbSizeView.js +5 -9
  70. package/dist/ui/views/DbSizeView.js.map +1 -1
  71. package/dist/ui/views/DbStatusView.d.ts.map +1 -1
  72. package/dist/ui/views/DbStatusView.js +8 -10
  73. package/dist/ui/views/DbStatusView.js.map +1 -1
  74. package/dist/ui/views/DbTablesView.d.ts.map +1 -1
  75. package/dist/ui/views/DbTablesView.js +8 -12
  76. package/dist/ui/views/DbTablesView.js.map +1 -1
  77. package/dist/ui/views/MainMenu.d.ts.map +1 -1
  78. package/dist/ui/views/MainMenu.js +19 -3
  79. package/dist/ui/views/MainMenu.js.map +1 -1
  80. package/dist/ui/views/MigrateHistoryView.d.ts.map +1 -1
  81. package/dist/ui/views/MigrateHistoryView.js +8 -9
  82. package/dist/ui/views/MigrateHistoryView.js.map +1 -1
  83. package/dist/ui/views/SystemHardwareCheckView.d.ts +6 -0
  84. package/dist/ui/views/SystemHardwareCheckView.d.ts.map +1 -0
  85. package/dist/ui/views/SystemHardwareCheckView.js +16 -0
  86. package/dist/ui/views/SystemHardwareCheckView.js.map +1 -0
  87. package/package.json +4 -4
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Shell command handlers for the `sql-monitor` command group.
3
+ *
4
+ * Toggles the opt-in PgHero web SQL-health dashboard. PgHero ships as the
5
+ * compose service `pghero`, gated behind the compose profile `sql-monitor`
6
+ * via `.env` (COMPOSE_PROFILES). Off by default — complements the
7
+ * point-in-time pg-bench-audit tool.
8
+ *
9
+ * Mirrors the appliance `ova/cli.sh` `sql_monitor_cmd` behaviour:
10
+ * enable | disable | status | password (alias show-password) | rotate-password
11
+ */
12
+ import { randomBytes } from 'node:crypto';
13
+ import { readFileSync } from 'node:fs';
14
+ import { readEnvKeys, updateEnvKeys } from '../../lib/env.js';
15
+ import { composeUp, composePs, compose } from '../../lib/compose.js';
16
+ import { exec } from '../../lib/exec.js';
17
+ import { statusLine } from '../formatter.js';
18
+ // ── Constants ────────────────────────────────────────────────────────────────
19
+ const PROFILE = 'sql-monitor';
20
+ const SERVICE = 'pghero';
21
+ const DEFAULT_USERNAME = 'admin';
22
+ const DEFAULT_PORT = '8080';
23
+ // ── Profile helpers ──────────────────────────────────────────────────────────
24
+ /** Split COMPOSE_PROFILES into a clean, non-empty list. */
25
+ export function parseProfiles(raw) {
26
+ return (raw ?? '')
27
+ .split(',')
28
+ .map((p) => p.trim())
29
+ .filter((p) => p.length > 0);
30
+ }
31
+ /** Whether the sql-monitor profile is present in a COMPOSE_PROFILES value. */
32
+ export function isEnabled(profilesRaw) {
33
+ return parseProfiles(profilesRaw).includes(PROFILE);
34
+ }
35
+ /** Add sql-monitor to a profile list without duplicating it. */
36
+ export function addProfile(profilesRaw) {
37
+ const profiles = parseProfiles(profilesRaw);
38
+ if (!profiles.includes(PROFILE))
39
+ profiles.push(PROFILE);
40
+ return profiles.join(',');
41
+ }
42
+ /** Remove sql-monitor from a profile list. */
43
+ export function removeProfile(profilesRaw) {
44
+ return parseProfiles(profilesRaw)
45
+ .filter((p) => p !== PROFILE)
46
+ .join(',');
47
+ }
48
+ // ── Secret generation ────────────────────────────────────────────────────────
49
+ /** Generate a 32-hex-char basic-auth password (16 random bytes). */
50
+ export function generatePassword() {
51
+ return randomBytes(16).toString('hex');
52
+ }
53
+ // ── Host address resolution ──────────────────────────────────────────────────
54
+ /**
55
+ * Resolve the LAN address an operator would type into a browser.
56
+ * Prefers /etc/network-environment DEFAULT_IPV4 (last occurrence, matching
57
+ * cli.sh `grep … | tail -1`), falls back to the first `hostname -I` address,
58
+ * then localhost.
59
+ */
60
+ export async function resolveHostAddr() {
61
+ // 1. /etc/network-environment DEFAULT_IPV4 (last-wins)
62
+ try {
63
+ const content = readFileSync('/etc/network-environment', 'utf-8');
64
+ const matches = [...content.matchAll(/^DEFAULT_IPV4=(.+)$/gm)];
65
+ if (matches.length > 0) {
66
+ const value = matches[matches.length - 1][1].trim();
67
+ if (value)
68
+ return value;
69
+ }
70
+ }
71
+ catch {
72
+ // file absent in dev/test — fall through
73
+ }
74
+ // 2. first `hostname -I` address
75
+ try {
76
+ const { stdout } = await exec('hostname', ['-I']);
77
+ const first = String(stdout).trim().split(/\s+/).filter(Boolean)[0];
78
+ if (first)
79
+ return first;
80
+ }
81
+ catch {
82
+ // hostname unavailable — fall through
83
+ }
84
+ // 3. localhost
85
+ return 'localhost';
86
+ }
87
+ // ── Env mutation helpers (testable) ──────────────────────────────────────────
88
+ /**
89
+ * Ensure a PGHERO_PASSWORD exists. Generates one (and a default username) only
90
+ * when absent, or always when `rotate` is true. Returns the password in effect
91
+ * and whether a new one was written.
92
+ */
93
+ export async function ensurePassword(rotate = false) {
94
+ const env = readEnvKeys();
95
+ const existing = env.PGHERO_PASSWORD;
96
+ if (!rotate && existing) {
97
+ return { password: existing, generated: false };
98
+ }
99
+ const password = generatePassword();
100
+ const updates = { PGHERO_PASSWORD: password };
101
+ // No-clobber default username.
102
+ if (!env.PGHERO_USERNAME) {
103
+ updates.PGHERO_USERNAME = DEFAULT_USERNAME;
104
+ }
105
+ await updateEnvKeys(updates);
106
+ return { password, generated: true };
107
+ }
108
+ // ── Access block ─────────────────────────────────────────────────────────────
109
+ async function printAccessBlock() {
110
+ const env = readEnvKeys();
111
+ const addr = await resolveHostAddr();
112
+ const port = env.PGHERO_PORT || DEFAULT_PORT;
113
+ const user = env.PGHERO_USERNAME || DEFAULT_USERNAME;
114
+ const pass = env.PGHERO_PASSWORD ?? '';
115
+ console.log('');
116
+ console.log(' PgHero SQL monitor — web dashboard');
117
+ console.log(' ─────────────────────────────────');
118
+ console.log(` URL: http://${addr}:${port}/`);
119
+ console.log(` Username: ${user}`);
120
+ console.log(` Password: ${pass}`);
121
+ console.log('');
122
+ console.log(statusLine('Basic-auth only, served over HTTP on the LAN. Disable when finished:', 'warn', 'sql-monitor disable'));
123
+ }
124
+ // ── Command handlers ─────────────────────────────────────────────────────────
125
+ export async function sqlMonitorEnable(_args, _opts, _ctx) {
126
+ try {
127
+ await ensurePassword(false);
128
+ const env = readEnvKeys();
129
+ const next = addProfile(env.COMPOSE_PROFILES);
130
+ if (next !== (env.COMPOSE_PROFILES ?? '')) {
131
+ await updateEnvKeys({ COMPOSE_PROFILES: next });
132
+ }
133
+ console.log(statusLine('SQL monitor (PgHero) enabled — starting service', 'ok'));
134
+ await composeUp(['-d', SERVICE]);
135
+ await printAccessBlock();
136
+ }
137
+ catch (err) {
138
+ const msg = err instanceof Error ? err.message : String(err);
139
+ console.log(statusLine(`Failed to enable SQL monitor: ${msg}`, 'fail'));
140
+ }
141
+ }
142
+ export async function sqlMonitorDisable(_args, _opts, _ctx) {
143
+ try {
144
+ const env = readEnvKeys();
145
+ const next = removeProfile(env.COMPOSE_PROFILES);
146
+ await updateEnvKeys({ COMPOSE_PROFILES: next });
147
+ console.log(statusLine('SQL monitor disabled — stopping PgHero', 'ok'));
148
+ // Stop and remove the pghero container. `--remove-orphans` does not evict
149
+ // profile-gated services, so target it explicitly via rm.
150
+ await compose(['rm', '-fs', SERVICE]);
151
+ console.log(statusLine('PgHero stopped. PGHERO_PASSWORD is kept in .env for next enable', 'info', 'rotate with: sql-monitor rotate-password'));
152
+ }
153
+ catch (err) {
154
+ const msg = err instanceof Error ? err.message : String(err);
155
+ console.log(statusLine(`Failed to disable SQL monitor: ${msg}`, 'fail'));
156
+ }
157
+ }
158
+ export async function sqlMonitorStatus(_args, _opts, _ctx) {
159
+ const env = readEnvKeys();
160
+ if (isEnabled(env.COMPOSE_PROFILES)) {
161
+ console.log(statusLine('SQL monitor (PgHero): enabled', 'ok'));
162
+ try {
163
+ const out = await composePs(SERVICE);
164
+ if (out)
165
+ console.log(out);
166
+ }
167
+ catch {
168
+ // compose unavailable — status string already printed
169
+ }
170
+ }
171
+ else {
172
+ console.log(statusLine('SQL monitor (PgHero): disabled', 'info'));
173
+ }
174
+ }
175
+ export async function sqlMonitorPassword(_args, _opts, _ctx) {
176
+ const env = readEnvKeys();
177
+ if (!isEnabled(env.COMPOSE_PROFILES)) {
178
+ console.log(statusLine('SQL monitor is disabled — showing saved credentials anyway', 'warn', 'PgHero is not running; start it with: sql-monitor enable'));
179
+ }
180
+ if (!env.PGHERO_PASSWORD) {
181
+ console.log(statusLine('No PGHERO_PASSWORD set yet', 'warn', 'run: sql-monitor enable'));
182
+ return;
183
+ }
184
+ await printAccessBlock();
185
+ }
186
+ export async function sqlMonitorRotatePassword(_args, _opts, _ctx) {
187
+ try {
188
+ await ensurePassword(true);
189
+ if (isEnabled(readEnvKeys().COMPOSE_PROFILES)) {
190
+ console.log(statusLine('Restarting PgHero with the new password', 'info'));
191
+ await composeUp(['-d', SERVICE]);
192
+ }
193
+ await printAccessBlock();
194
+ }
195
+ catch (err) {
196
+ const msg = err instanceof Error ? err.message : String(err);
197
+ console.log(statusLine(`Failed to rotate password: ${msg}`, 'fail'));
198
+ }
199
+ }
200
+ // ── Wire handlers into command tree ──────────────────────────────────────────
201
+ export function wireSqlMonitorHandlers(node) {
202
+ if (!node.children)
203
+ return;
204
+ for (const child of node.children) {
205
+ switch (child.name) {
206
+ case 'enable':
207
+ child.handler = sqlMonitorEnable;
208
+ break;
209
+ case 'disable':
210
+ child.handler = sqlMonitorDisable;
211
+ break;
212
+ case 'status':
213
+ child.handler = sqlMonitorStatus;
214
+ break;
215
+ case 'password':
216
+ case 'show-password':
217
+ child.handler = sqlMonitorPassword;
218
+ break;
219
+ case 'rotate-password':
220
+ child.handler = sqlMonitorRotatePassword;
221
+ break;
222
+ }
223
+ }
224
+ // Group with no bare handler defaults to `status` for convenience.
225
+ if (!node.handler)
226
+ node.handler = sqlMonitorStatus;
227
+ }
228
+ //# sourceMappingURL=sql-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-monitor.js","sourceRoot":"","sources":["../../../src/shell/commands/sql-monitor.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DbCompactView.d.ts","sourceRoot":"","sources":["../../../src/ui/views/DbCompactView.tsx"],"names":[],"mappings":"AAMA,UAAU,kBAAkB;IAC1B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE,kBAAkB,2CAgC3D"}
1
+ {"version":3,"file":"DbCompactView.d.ts","sourceRoot":"","sources":["../../../src/ui/views/DbCompactView.tsx"],"names":[],"mappings":"AAKA,UAAU,kBAAkB;IAC1B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE,kBAAkB,2CAiC3D"}
@@ -1,16 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect } from 'react';
3
- import { composeExec } from '../../lib/compose.js';
4
- import { DB_USER, DB_NAME, PG_ENV } from '../../lib/db.js';
3
+ import { probeDatabaseReady, runBoundedQuery } from '../../lib/db-probe.js';
5
4
  import { AppShell, Section, Spinner, StatusLine } from '../components/index.js';
6
5
  import { useAsyncTask } from '../hooks/index.js';
7
6
  export function DbCompactView({ onBack }) {
7
+ // Readiness is bounded + classified. VACUUM FULL legitimately runs for
8
+ // minutes, so it opts OUT of the timeouts (all 0) — we keep only the
9
+ // classified-failure reporting, not a kill.
8
10
  const readyTask = useAsyncTask(async () => {
9
- await composeExec('db', ['pg_isready', '-U', DB_USER, '-d', DB_NAME], { pipe: true });
11
+ await probeDatabaseReady();
10
12
  return true;
11
13
  });
12
14
  const vacuumTask = useAsyncTask(async () => {
13
- await composeExec('db', ['psql', '-U', DB_USER, '-d', DB_NAME, '-c', 'VACUUM FULL;'], { env: PG_ENV, pipe: true });
15
+ await runBoundedQuery('VACUUM FULL;', { timeoutMs: 0, statementTimeoutMs: 0, lockTimeoutMs: 0 });
14
16
  return true;
15
17
  }, false); // Don't auto-run, wait for readiness
16
18
  // Chain: run vacuum after ready
@@ -19,6 +21,6 @@ export function DbCompactView({ onBack }) {
19
21
  vacuumTask.run();
20
22
  }, [readyTask.status]);
21
23
  const allDone = readyTask.status === 'error' || vacuumTask.status === 'done' || vacuumTask.status === 'error';
22
- return (_jsx(AppShell, { command: "ct db compact", onBack: onBack, isComplete: allDone, children: _jsxs(Section, { title: "Database Compaction", children: [readyTask.status === 'pending' && _jsx(Spinner, { label: "Checking database..." }), readyTask.status === 'error' && _jsx(StatusLine, { status: "fail", label: "Database not ready" }), readyTask.status === 'done' && vacuumTask.status === 'pending' && _jsx(Spinner, { label: "Running VACUUM FULL (this may take several minutes)..." }), vacuumTask.status === 'error' && _jsx(StatusLine, { status: "fail", label: "Database vacuum failed" }), vacuumTask.status === 'done' && _jsx(StatusLine, { status: "ok", label: "Database vacuum completed successfully" })] }) }));
24
+ return (_jsx(AppShell, { command: "ct db compact", onBack: onBack, isComplete: allDone, children: _jsxs(Section, { title: "Database Compaction", children: [readyTask.status === 'pending' && _jsx(Spinner, { label: "Checking database..." }), readyTask.status === 'error' && _jsx(StatusLine, { status: "fail", label: `Database not ready — ${readyTask.error?.message ?? 'unknown error'}` }), readyTask.status === 'done' && vacuumTask.status === 'pending' && _jsx(Spinner, { label: "Running VACUUM FULL (this may take several minutes)..." }), vacuumTask.status === 'error' && _jsx(StatusLine, { status: "fail", label: `Database vacuum failed — ${vacuumTask.error?.message ?? 'unknown error'}` }), vacuumTask.status === 'done' && _jsx(StatusLine, { status: "ok", label: "Database vacuum completed successfully" })] }) }));
23
25
  }
24
26
  //# sourceMappingURL=DbCompactView.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DbCompactView.js","sourceRoot":"","sources":["../../../src/ui/views/DbCompactView.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAMjD,MAAM,UAAU,aAAa,CAAC,EAAE,MAAM,EAAsB;IAE1D,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,IAAI,EAAE;QACxC,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,IAAI,EAAE;QACzC,MAAM,WAAW,CAAC,IAAI,EACpB,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,CAAC,EAC5D,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,qCAAqC;IAEhD,gCAAgC;IAChC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,CAAC,MAAM,KAAK,MAAM;YAAE,UAAU,CAAC,GAAG,EAAE,CAAC;IACpD,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAEvB,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,KAAK,OAAO,IAAI,UAAU,CAAC,MAAM,KAAK,MAAM,IAAI,UAAU,CAAC,MAAM,KAAK,OAAO,CAAC;IAE9G,OAAO,CACL,KAAC,QAAQ,IAAC,OAAO,EAAC,eAAe,EAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,YACnE,MAAC,OAAO,IAAC,KAAK,EAAC,qBAAqB,aACjC,SAAS,CAAC,MAAM,KAAK,SAAS,IAAI,KAAC,OAAO,IAAC,KAAK,EAAC,sBAAsB,GAAG,EAC1E,SAAS,CAAC,MAAM,KAAK,OAAO,IAAI,KAAC,UAAU,IAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAC,oBAAoB,GAAG,EACvF,SAAS,CAAC,MAAM,KAAK,MAAM,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,IAAI,KAAC,OAAO,IAAC,KAAK,EAAC,wDAAwD,GAAG,EAC5I,UAAU,CAAC,MAAM,KAAK,OAAO,IAAI,KAAC,UAAU,IAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAC,wBAAwB,GAAG,EAC5F,UAAU,CAAC,MAAM,KAAK,MAAM,IAAI,KAAC,UAAU,IAAC,MAAM,EAAC,IAAI,EAAC,KAAK,EAAC,wCAAwC,GAAG,IAClG,GACD,CACZ,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"DbCompactView.js","sourceRoot":"","sources":["../../../src/ui/views/DbCompactView.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAMjD,MAAM,UAAU,aAAa,CAAC,EAAE,MAAM,EAAsB;IAE1D,uEAAuE;IACvE,qEAAqE;IACrE,4CAA4C;IAC5C,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,IAAI,EAAE;QACxC,MAAM,kBAAkB,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,IAAI,EAAE;QACzC,MAAM,eAAe,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC;QACjG,OAAO,IAAI,CAAC;IACd,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,qCAAqC;IAEhD,gCAAgC;IAChC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,CAAC,MAAM,KAAK,MAAM;YAAE,UAAU,CAAC,GAAG,EAAE,CAAC;IACpD,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAEvB,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,KAAK,OAAO,IAAI,UAAU,CAAC,MAAM,KAAK,MAAM,IAAI,UAAU,CAAC,MAAM,KAAK,OAAO,CAAC;IAE9G,OAAO,CACL,KAAC,QAAQ,IAAC,OAAO,EAAC,eAAe,EAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,YACnE,MAAC,OAAO,IAAC,KAAK,EAAC,qBAAqB,aACjC,SAAS,CAAC,MAAM,KAAK,SAAS,IAAI,KAAC,OAAO,IAAC,KAAK,EAAC,sBAAsB,GAAG,EAC1E,SAAS,CAAC,MAAM,KAAK,OAAO,IAAI,KAAC,UAAU,IAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAE,wBAAwB,SAAS,CAAC,KAAK,EAAE,OAAO,IAAI,eAAe,EAAE,GAAI,EAC1I,SAAS,CAAC,MAAM,KAAK,MAAM,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,IAAI,KAAC,OAAO,IAAC,KAAK,EAAC,wDAAwD,GAAG,EAC5I,UAAU,CAAC,MAAM,KAAK,OAAO,IAAI,KAAC,UAAU,IAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAE,4BAA4B,UAAU,CAAC,KAAK,EAAE,OAAO,IAAI,eAAe,EAAE,GAAI,EAChJ,UAAU,CAAC,MAAM,KAAK,MAAM,IAAI,KAAC,UAAU,IAAC,MAAM,EAAC,IAAI,EAAC,KAAK,EAAC,wCAAwC,GAAG,IAClG,GACD,CACZ,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"DbSizeView.d.ts","sourceRoot":"","sources":["../../../src/ui/views/DbSizeView.tsx"],"names":[],"mappings":"AAOA,UAAU,eAAe;IACvB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,eAAe,2CAwBrD"}
1
+ {"version":3,"file":"DbSizeView.d.ts","sourceRoot":"","sources":["../../../src/ui/views/DbSizeView.tsx"],"names":[],"mappings":"AAKA,UAAU,eAAe;IACvB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,eAAe,2CAyBrD"}
@@ -1,16 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { composeExec } from '../../lib/compose.js';
3
- import { DB_USER, DB_NAME, PG_ENV } from '../../lib/db.js';
4
2
  import { AppShell, Section, Spinner, StatusLine, KeyboardHint } from '../components/index.js';
5
3
  import { useAsyncTask } from '../hooks/index.js';
6
- import { parsePsqlScalar } from '../../utils/parsePsql.js';
4
+ import { measureDatabaseSize } from '../../lib/db-probe.js';
7
5
  export function DbSizeView({ onBack }) {
8
- const sizeTask = useAsyncTask(async () => {
9
- const result = await composeExec('db', ['psql', '-U', DB_USER, '-d', DB_NAME, '-c',
10
- `SELECT pg_size_pretty(pg_database_size('${DB_NAME}')) AS database_size;`], { env: PG_ENV, pipe: true });
11
- return parsePsqlScalar((result.stdout ?? '').trim());
12
- });
6
+ // Hardened probe: bounded by timeouts and reports WHY on failure (auth
7
+ // mismatch, container down, lock contention, timeout) instead of a guess.
8
+ const sizeTask = useAsyncTask(async () => measureDatabaseSize());
13
9
  const sizeDone = sizeTask.status === 'done' || sizeTask.status === 'error';
14
- return (_jsxs(AppShell, { command: "ct db size", onBack: onBack, isComplete: sizeDone, children: [_jsxs(Section, { title: "Database Size", children: [sizeTask.status === 'pending' && _jsx(Spinner, { label: "Querying database size..." }), sizeTask.status === 'error' && _jsx(StatusLine, { status: "fail", label: "Could not query database size" }), sizeTask.status === 'done' && sizeTask.data && (_jsx(StatusLine, { status: "ok", label: `Database size: ${sizeTask.data}` }))] }), _jsx(KeyboardHint, { actions: [{ key: 'q', label: 'Back' }] })] }));
10
+ return (_jsxs(AppShell, { command: "ct db size", onBack: onBack, isComplete: sizeDone, children: [_jsxs(Section, { title: "Database Size", children: [sizeTask.status === 'pending' && _jsx(Spinner, { label: "Querying database size..." }), sizeTask.status === 'error' && (_jsx(StatusLine, { status: "fail", label: `Could not query database size — ${sizeTask.error?.message ?? 'unknown error'}` })), sizeTask.status === 'done' && sizeTask.data && (_jsx(StatusLine, { status: "ok", label: `Database size: ${sizeTask.data.pretty}` }))] }), _jsx(KeyboardHint, { actions: [{ key: 'q', label: 'Back' }] })] }));
15
11
  }
16
12
  //# sourceMappingURL=DbSizeView.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DbSizeView.js","sourceRoot":"","sources":["../../../src/ui/views/DbSizeView.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC9F,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAM3D,MAAM,UAAU,UAAU,CAAC,EAAE,MAAM,EAAmB;IAEpD,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,IAAI,EAAE;QACvC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EACnC,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI;YAC1C,2CAA2C,OAAO,uBAAuB,CAAC,EAC3E,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/B,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC;IAE3E,OAAO,CACL,MAAC,QAAQ,IAAC,OAAO,EAAC,YAAY,EAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,aACjE,MAAC,OAAO,IAAC,KAAK,EAAC,eAAe,aAC3B,QAAQ,CAAC,MAAM,KAAK,SAAS,IAAI,KAAC,OAAO,IAAC,KAAK,EAAC,2BAA2B,GAAG,EAC9E,QAAQ,CAAC,MAAM,KAAK,OAAO,IAAI,KAAC,UAAU,IAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAC,+BAA+B,GAAG,EACjG,QAAQ,CAAC,MAAM,KAAK,MAAM,IAAI,QAAQ,CAAC,IAAI,IAAI,CAC9C,KAAC,UAAU,IAAC,MAAM,EAAC,IAAI,EAAC,KAAK,EAAE,kBAAkB,QAAQ,CAAC,IAAI,EAAE,GAAI,CACrE,IACO,EACV,KAAC,YAAY,IAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,GAAI,IAC/C,CACZ,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"DbSizeView.js","sourceRoot":"","sources":["../../../src/ui/views/DbSizeView.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC9F,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAM5D,MAAM,UAAU,UAAU,CAAC,EAAE,MAAM,EAAmB;IAEpD,uEAAuE;IACvE,0EAA0E;IAC1E,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,IAAI,EAAE,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAEjE,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC;IAE3E,OAAO,CACL,MAAC,QAAQ,IAAC,OAAO,EAAC,YAAY,EAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,aACjE,MAAC,OAAO,IAAC,KAAK,EAAC,eAAe,aAC3B,QAAQ,CAAC,MAAM,KAAK,SAAS,IAAI,KAAC,OAAO,IAAC,KAAK,EAAC,2BAA2B,GAAG,EAC9E,QAAQ,CAAC,MAAM,KAAK,OAAO,IAAI,CAC9B,KAAC,UAAU,IACT,MAAM,EAAC,MAAM,EACb,KAAK,EAAE,mCAAmC,QAAQ,CAAC,KAAK,EAAE,OAAO,IAAI,eAAe,EAAE,GACtF,CACH,EACA,QAAQ,CAAC,MAAM,KAAK,MAAM,IAAI,QAAQ,CAAC,IAAI,IAAI,CAC9C,KAAC,UAAU,IAAC,MAAM,EAAC,IAAI,EAAC,KAAK,EAAE,kBAAkB,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,GAAI,CAC5E,IACO,EACV,KAAC,YAAY,IAAC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,GAAI,IAC/C,CACZ,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"DbStatusView.d.ts","sourceRoot":"","sources":["../../../src/ui/views/DbStatusView.tsx"],"names":[],"mappings":"AAWA,UAAU,iBAAiB;IACzB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA4FzD"}
1
+ {"version":3,"file":"DbStatusView.d.ts","sourceRoot":"","sources":["../../../src/ui/views/DbStatusView.tsx"],"names":[],"mappings":"AAUA,UAAU,iBAAiB;IACzB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CAyFzD"}
@@ -2,22 +2,20 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { existsSync, readdirSync, statSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { Box, Text } from 'ink';
5
- import { composeExec } from '../../lib/compose.js';
6
- import { DB_USER, DB_NAME, PG_ENV } from '../../lib/db.js';
5
+ import { measureDatabaseSize, probeDatabaseReady } from '../../lib/db-probe.js';
7
6
  import { getPaths } from '../../lib/paths.js';
8
7
  import { fetchPostgresPerformance } from '../../lib/postgres-performance.js';
9
8
  import { AppShell, Section, StatusLine, Spinner, KeyValue, PostgresPerformanceLines } from '../components/index.js';
10
9
  import { useAsyncTask } from '../hooks/index.js';
11
10
  export function DbStatusView({ onBack }) {
11
+ // Hardened probes: bounded by timeouts and report WHY on failure (auth,
12
+ // not-running, lock, timeout) instead of a generic message.
12
13
  const readyTask = useAsyncTask(async () => {
13
- await composeExec('db', ['pg_isready', '-U', DB_USER, '-d', DB_NAME], { pipe: true });
14
- return true;
15
- });
16
- const sizeTask = useAsyncTask(async () => {
17
- const result = await composeExec('db', ['psql', '-U', DB_USER, '-d', DB_NAME, '-t', '-c',
18
- `SELECT pg_size_pretty(pg_database_size('${DB_NAME}')) AS database_size;`], { env: PG_ENV, pipe: true });
19
- return (result.stdout ?? '').trim();
14
+ // Resolves on success, rejects with a classified DbProbeError on failure;
15
+ // the view keys off readyTask.status / readyTask.error.
16
+ await probeDatabaseReady();
20
17
  });
18
+ const sizeTask = useAsyncTask(async () => measureDatabaseSize());
21
19
  const backupsTask = useAsyncTask(async () => {
22
20
  const { dbDumpsDir } = getPaths();
23
21
  if (!existsSync(dbDumpsDir))
@@ -32,7 +30,7 @@ export function DbStatusView({ onBack }) {
32
30
  });
33
31
  const performanceTask = useAsyncTask(fetchPostgresPerformance);
34
32
  const allDone = [readyTask, sizeTask, backupsTask, performanceTask].every(t => t.status === 'done' || t.status === 'error');
35
- return (_jsxs(AppShell, { command: "ct db status", onBack: onBack, isComplete: allDone, children: [_jsxs(Section, { title: "Database Connection", children: [readyTask.status === 'pending' && _jsx(Spinner, { label: "Checking database..." }), readyTask.status === 'error' && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: "fail", label: "Database not accepting connections" }), readyTask.error && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: "red", children: readyTask.error.stderr?.trim() || readyTask.error.message }) }))] })), readyTask.status === 'done' && _jsx(StatusLine, { status: "ok", label: "Database: accepting connections" })] }), _jsxs(Section, { title: "Database Size", children: [sizeTask.status === 'pending' && _jsx(Spinner, { label: "Querying size..." }), sizeTask.status === 'error' && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: "fail", label: "Could not query database size" }), sizeTask.error && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: "red", children: sizeTask.error.stderr?.trim() || sizeTask.error.message }) }))] })), sizeTask.status === 'done' && _jsx(StatusLine, { status: "info", label: `Database size: ${sizeTask.data}` })] }), _jsxs(Section, { title: "PostgreSQL Performance", children: [performanceTask.status === 'pending' && _jsx(Spinner, { label: "Querying timing metrics..." }), performanceTask.status === 'error' && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: "warn", label: "Could not query timing metrics" }), performanceTask.error && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: "yellow", children: performanceTask.error.stderr?.trim() || performanceTask.error.message }) }))] })), performanceTask.status === 'done' && performanceTask.data && (_jsx(PostgresPerformanceLines, { performance: performanceTask.data }))] }), _jsxs(Section, { title: "Available Backups", children: [backupsTask.status === 'pending' && _jsx(Spinner, { label: "Listing backups..." }), backupsTask.status === 'error' && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: "fail", label: "Could not list backups" }), backupsTask.error && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: "red", children: backupsTask.error.stderr?.trim() || backupsTask.error.message }) }))] })), backupsTask.status === 'done' && backupsTask.data && (backupsTask.data.length === 0
33
+ return (_jsxs(AppShell, { command: "ct db status", onBack: onBack, isComplete: allDone, children: [_jsxs(Section, { title: "Database Connection", children: [readyTask.status === 'pending' && _jsx(Spinner, { label: "Checking database..." }), readyTask.status === 'error' && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: "fail", label: "Database not accepting connections" }), readyTask.error && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: "red", children: readyTask.error.stderr?.trim() || readyTask.error.message }) }))] })), readyTask.status === 'done' && _jsx(StatusLine, { status: "ok", label: "Database: accepting connections" })] }), _jsxs(Section, { title: "Database Size", children: [sizeTask.status === 'pending' && _jsx(Spinner, { label: "Querying size..." }), sizeTask.status === 'error' && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: "fail", label: "Could not query database size" }), sizeTask.error && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: "red", children: sizeTask.error.stderr?.trim() || sizeTask.error.message }) }))] })), sizeTask.status === 'done' && sizeTask.data && _jsx(StatusLine, { status: "info", label: `Database size: ${sizeTask.data.pretty}` })] }), _jsxs(Section, { title: "PostgreSQL Performance", children: [performanceTask.status === 'pending' && _jsx(Spinner, { label: "Querying timing metrics..." }), performanceTask.status === 'error' && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: "warn", label: "Could not query timing metrics" }), performanceTask.error && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: "yellow", children: performanceTask.error.stderr?.trim() || performanceTask.error.message }) }))] })), performanceTask.status === 'done' && performanceTask.data && (_jsx(PostgresPerformanceLines, { performance: performanceTask.data }))] }), _jsxs(Section, { title: "Available Backups", children: [backupsTask.status === 'pending' && _jsx(Spinner, { label: "Listing backups..." }), backupsTask.status === 'error' && (_jsxs(_Fragment, { children: [_jsx(StatusLine, { status: "fail", label: "Could not list backups" }), backupsTask.error && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: "red", children: backupsTask.error.stderr?.trim() || backupsTask.error.message }) }))] })), backupsTask.status === 'done' && backupsTask.data && (backupsTask.data.length === 0
36
34
  ? _jsx(StatusLine, { status: "info", label: "No backups found" })
37
35
  : _jsx(KeyValue, { items: backupsTask.data }))] })] }));
38
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DbStatusView.js","sourceRoot":"","sources":["../../../src/ui/views/DbStatusView.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DbStatusView.js","sourceRoot":"","sources":["../../../src/ui/views/DbStatusView.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DbTablesView.d.ts","sourceRoot":"","sources":["../../../src/ui/views/DbTablesView.tsx"],"names":[],"mappings":"AAOA,UAAU,iBAAiB;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA6C/D"}
1
+ {"version":3,"file":"DbTablesView.d.ts","sourceRoot":"","sources":["../../../src/ui/views/DbTablesView.tsx"],"names":[],"mappings":"AAMA,UAAU,iBAAiB;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CAsC/D"}
@@ -1,27 +1,23 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { composeExec } from '../../lib/compose.js';
3
- import { DB_USER, DB_NAME, PG_ENV } from '../../lib/db.js';
2
+ import { measureDatabaseSize, runBoundedQuery } from '../../lib/db-probe.js';
4
3
  import { AppShell, Section, DataTable, Spinner, StatusLine, KeyboardHint } from '../components/index.js';
5
4
  import { useAsyncTask } from '../hooks/index.js';
6
- import { parsePsqlTable, parsePsqlScalar } from '../../utils/parsePsql.js';
5
+ import { parsePsqlTable } from '../../utils/parsePsql.js';
7
6
  export function DbTablesView({ name, onBack }) {
7
+ // Bounded + classified probes: a slow query can't hang the view, and a
8
+ // failure reports WHY (auth / not-running / lock / timeout).
8
9
  const tablesTask = useAsyncTask(async () => {
9
10
  const safeName = name ? name.replace(/'/g, "''") : '';
10
11
  const whereClause = safeName
11
12
  ? `AND c.relname LIKE '%${safeName}%'`
12
13
  : '';
13
14
  const query = `SELECT table_name, row_count, pg_size_pretty(total_bytes) AS total_size, pg_size_pretty(index_bytes) AS index_size, pg_size_pretty(table_bytes) AS table_size FROM (SELECT c.relname AS table_name, c.reltuples::BIGINT AS row_count, pg_total_relation_size(c.oid) AS total_bytes, pg_indexes_size(c.oid) AS index_bytes, pg_relation_size(c.oid) AS table_bytes FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relkind = 'r' ${whereClause}) t ORDER BY total_bytes DESC;`;
14
- const result = await composeExec('db', ['psql', '-U', DB_USER, '-d', DB_NAME, '-c', query], { env: PG_ENV, pipe: true });
15
- return parsePsqlTable((result.stdout ?? '').trim());
16
- });
17
- const sizeTask = useAsyncTask(async () => {
18
- const result = await composeExec('db', ['psql', '-U', DB_USER, '-d', DB_NAME, '-c',
19
- `SELECT pg_size_pretty(pg_database_size('${DB_NAME}')) AS database_size;`], { env: PG_ENV, pipe: true });
20
- return parsePsqlScalar((result.stdout ?? '').trim());
15
+ return parsePsqlTable(await runBoundedQuery(query));
21
16
  });
17
+ const sizeTask = useAsyncTask(async () => measureDatabaseSize());
22
18
  const allDone = [tablesTask, sizeTask].every(t => t.status === 'done' || t.status === 'error');
23
- return (_jsxs(AppShell, { command: name ? `ct db tables ${name}` : 'ct db tables', onBack: onBack, isComplete: allDone, children: [_jsxs(Section, { title: name ? `Table Sizes: ${name}` : 'All Table Sizes', children: [tablesTask.status === 'pending' && _jsx(Spinner, { label: "Querying table sizes..." }), tablesTask.status === 'error' && _jsx(StatusLine, { status: "fail", label: "Query failed" }), tablesTask.status === 'done' && tablesTask.data && (tablesTask.data.rows.length > 0
19
+ return (_jsxs(AppShell, { command: name ? `ct db tables ${name}` : 'ct db tables', onBack: onBack, isComplete: allDone, children: [_jsxs(Section, { title: name ? `Table Sizes: ${name}` : 'All Table Sizes', children: [tablesTask.status === 'pending' && _jsx(Spinner, { label: "Querying table sizes..." }), tablesTask.status === 'error' && _jsx(StatusLine, { status: "fail", label: `Query failed — ${tablesTask.error?.message ?? 'unknown error'}` }), tablesTask.status === 'done' && tablesTask.data && (tablesTask.data.rows.length > 0
24
20
  ? _jsx(DataTable, { columns: tablesTask.data.columns, rows: tablesTask.data.rows })
25
- : _jsx(StatusLine, { status: "ok", label: "No tables found" }))] }), _jsxs(Section, { title: "Database Total Size", children: [sizeTask.status === 'pending' && _jsx(Spinner, { label: "Querying size..." }), sizeTask.status === 'error' && _jsx(StatusLine, { status: "fail", label: "Query failed" }), sizeTask.status === 'done' && sizeTask.data && (_jsx(StatusLine, { status: "ok", label: `Database size: ${sizeTask.data}` }))] }), _jsx(KeyboardHint, { actions: [{ key: 'q', label: 'Back' }] })] }));
21
+ : _jsx(StatusLine, { status: "ok", label: "No tables found" }))] }), _jsxs(Section, { title: "Database Total Size", children: [sizeTask.status === 'pending' && _jsx(Spinner, { label: "Querying size..." }), sizeTask.status === 'error' && _jsx(StatusLine, { status: "fail", label: `Query failed — ${sizeTask.error?.message ?? 'unknown error'}` }), sizeTask.status === 'done' && sizeTask.data && (_jsx(StatusLine, { status: "ok", label: `Database size: ${sizeTask.data.pretty}` }))] }), _jsx(KeyboardHint, { actions: [{ key: 'q', label: 'Back' }] })] }));
26
22
  }
27
23
  //# sourceMappingURL=DbTablesView.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DbTablesView.js","sourceRoot":"","sources":["../../../src/ui/views/DbTablesView.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DbTablesView.js","sourceRoot":"","sources":["../../../src/ui/views/DbTablesView.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"MainMenu.d.ts","sourceRoot":"","sources":["../../../src/ui/views/MainMenu.tsx"],"names":[],"mappings":"AA+SA,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,aAAa,2CAstBlF"}
1
+ {"version":3,"file":"MainMenu.d.ts","sourceRoot":"","sources":["../../../src/ui/views/MainMenu.tsx"],"names":[],"mappings":"AAmTA,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,aAAa,2CA4uBlF"}
@@ -4,8 +4,8 @@ import { Box, Text, useInput, useStdout } from 'ink';
4
4
  import { existsSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
6
  import { execa } from 'execa';
7
- import { ActionBar, AppShell, DashboardBlock, MenuListRow, PanelState, LogLevelBadge, SplitPane, SplitPaneCard, StatusListRow } from '../components/index.js';
8
- import { usePolling, useLogLevel, useListCursor } from '../hooks/index.js';
7
+ import { ActionBar, AppShell, DashboardBlock, MenuListRow, PanelState, LogLevelBadge, SplitPane, SplitPaneCard, StatusBox, StatusListRow } from '../components/index.js';
8
+ import { useAsyncTask, usePolling, useLogLevel, useListCursor } from '../hooks/index.js';
9
9
  import { getServiceList } from '../../lib/services.js';
10
10
  import { getAppHealth } from '../../lib/rpc.js';
11
11
  import { healthIcon, healthColor, extractUptime } from '../utils/serviceDisplay.js';
@@ -66,7 +66,9 @@ import { K8sDeployView } from './K8sDeployView.js';
66
66
  import { SetupWizardView } from './SetupWizardView.js';
67
67
  import { PowerActionView } from './PowerActionView.js';
68
68
  import { DiskHardwareCheckView } from './DiskHardwareCheckView.js';
69
+ import { SystemHardwareCheckView } from './SystemHardwareCheckView.js';
69
70
  import { resetBackendCache } from '../../lib/deployment.js';
71
+ import { validateApplianceHardware } from '../../lib/hardware.js';
70
72
  /* ------------------------------------------------------------------ */
71
73
  /* Menu definition */
72
74
  /* ------------------------------------------------------------------ */
@@ -188,6 +190,7 @@ const MENU_ITEMS = [
188
190
  value: 'appliance-menu',
189
191
  children: [
190
192
  { label: 'Status', description: 'Migration summary and appliance info', value: 'appliance status' },
193
+ { label: 'System Hardware Check', description: 'Validate CPU, RAM, and disk requirements', value: 'appliance hardware-check system' },
191
194
  { label: 'Disk Hardware Check', description: 'fio benchmark with live charts and sparklines', value: 'appliance hardware-check disk' },
192
195
  { label: 'Migrations', description: 'Show all migrations and their status', value: 'appliance migrations' },
193
196
  { label: 'Run Migrations', description: 'Run pending appliance migrations', value: 'appliance migrate' },
@@ -245,6 +248,7 @@ const VIEW_MAP = {
245
248
  'loadtest': (onBack) => _jsx(LoadTestView, { onBack: onBack }),
246
249
  'loadtest xcc': (onBack) => _jsx(XccLoadTestView, { onBack: onBack }),
247
250
  'appliance hardware-check disk': (onBack) => _jsx(DiskHardwareCheckView, { onBack: onBack }),
251
+ 'appliance hardware-check system': (onBack) => _jsx(SystemHardwareCheckView, { onBack: onBack }),
248
252
  'offline list': (onBack) => _jsx(OfflineListView, { onBack: onBack }),
249
253
  'offline fetch': (onBack) => _jsx(OfflineFetchView, { onBack: onBack }),
250
254
  'offline download': (onBack) => _jsx(OfflineDownloadView, { onBack: onBack }),
@@ -304,6 +308,7 @@ export function MainMenu({ version, firstRun, onCliCommand, onExit }) {
304
308
  }, [isK8s]);
305
309
  const { data: services, stop: stopSvcPolling, start: startSvcPolling } = usePolling(getServiceList, 5000, false);
306
310
  const { data: appHealth, stop: stopHealth, start: startHealth } = usePolling(getAppHealth, 15000, false);
311
+ const hardwareTask = useAsyncTask(validateApplianceHardware, true);
307
312
  // Cert expiry warning (passive, non-blocking)
308
313
  const [certDaysLeft, setCertDaysLeft] = useState(null);
309
314
  useEffect(() => {
@@ -736,7 +741,18 @@ export function MainMenu({ version, firstRun, onCliCommand, onExit }) {
736
741
  lines: ['Select a menu item to preview it.'],
737
742
  };
738
743
  })();
739
- return (_jsxs(AppShell, { title: `CallTelemetry CLI v${version}`, logLevel: logLevel, backKeys: false, footerHint: "", children: [_jsx(Box, { paddingLeft: 1, marginBottom: 1, children: _jsx(Text, { color: "cyan", children: LOGO }) }), appHealth === undefined ? (_jsx(PanelState, { state: "loading", message: "Checking app health..." })) : (_jsxs(DashboardBlock, { title: "App", marginBottom: 0, meta: _jsxs(_Fragment, { children: [_jsxs(Text, { color: appHealth.overall === 'healthy' ? 'green' : appHealth.overall === 'degraded' ? 'yellow' : 'red', children: [appHealth.overall === 'healthy' ? ' ●' : appHealth.overall === 'degraded' ? ' ▲' : ' ✘', ` ${appHealth.overall}`] }), (() => {
744
+ return (_jsxs(AppShell, { title: `CallTelemetry CLI v${version}`, logLevel: logLevel, backKeys: false, footerHint: "", children: [_jsx(Box, { paddingLeft: 1, marginBottom: 1, children: _jsx(Text, { color: "cyan", children: LOGO }) }), hardwareTask.status === 'done' && hardwareTask.data && !hardwareTask.data.ok && (_jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsx(StatusBox, { variant: "warning", title: "Hardware validation failed", lines: [
745
+ hardwareTask.data.cpu.ok
746
+ ? `CPU ok: ${hardwareTask.data.cpu.model} (${hardwareTask.data.cpu.arch}, ${hardwareTask.data.cpu.cores} cores)`
747
+ : `CPU mismatch: ${hardwareTask.data.cpu.missingFlags.length > 0 ? `missing ${hardwareTask.data.cpu.missingFlags.join(', ')}` : `${hardwareTask.data.cpu.cores} cores detected`}`,
748
+ hardwareTask.data.ram.ok
749
+ ? `RAM ok: ${hardwareTask.data.ram.totalMb}MB`
750
+ : `RAM low: ${hardwareTask.data.ram.totalMb}MB detected, ${hardwareTask.data.ram.requiredMb}MB required`,
751
+ hardwareTask.data.disk.ok
752
+ ? `Disk ok: ${hardwareTask.data.disk.freePercent}% free`
753
+ : `Disk low: ${hardwareTask.data.disk.freePercent}% free, ${hardwareTask.data.disk.requiredFreePercent}% required`,
754
+ 'Updates are blocked until hardware requirements pass.',
755
+ ] }) })), appHealth === undefined ? (_jsx(PanelState, { state: "loading", message: "Checking app health..." })) : (_jsxs(DashboardBlock, { title: "App", marginBottom: 0, meta: _jsxs(_Fragment, { children: [_jsxs(Text, { color: appHealth.overall === 'healthy' ? 'green' : appHealth.overall === 'degraded' ? 'yellow' : 'red', children: [appHealth.overall === 'healthy' ? ' ●' : appHealth.overall === 'degraded' ? ' ▲' : ' ✘', ` ${appHealth.overall}`] }), (() => {
740
756
  const webSvc = serviceList.find(s => s.service === 'web');
741
757
  const uptime = webSvc ? extractUptime(webSvc.status) : '';
742
758
  return uptime && uptime !== 'exited' ? (_jsxs(Text, { dimColor: true, children: [' ', "Up ", uptime] })) : null;