clawsql 0.2.1 → 0.2.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/README.md +25 -5
- package/dist/bin/clawsql.js +0 -0
- package/dist/cli/agent/handler.d.ts +13 -31
- package/dist/cli/agent/handler.d.ts.map +1 -1
- package/dist/cli/agent/handler.js +111 -149
- package/dist/cli/agent/handler.js.map +1 -1
- package/dist/cli/agent/index.d.ts +2 -1
- package/dist/cli/agent/index.d.ts.map +1 -1
- package/dist/cli/agent/index.js +7 -1
- package/dist/cli/agent/index.js.map +1 -1
- package/dist/cli/agent/openclaw-integration.d.ts +92 -25
- package/dist/cli/agent/openclaw-integration.d.ts.map +1 -1
- package/dist/cli/agent/openclaw-integration.js +312 -195
- package/dist/cli/agent/openclaw-integration.js.map +1 -1
- package/dist/cli/commands/cleanup.d.ts.map +1 -1
- package/dist/cli/commands/cleanup.js +26 -15
- package/dist/cli/commands/cleanup.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +0 -4
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +232 -581
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/openclaw.d.ts +9 -0
- package/dist/cli/commands/openclaw.d.ts.map +1 -0
- package/dist/cli/commands/openclaw.js +236 -0
- package/dist/cli/commands/openclaw.js.map +1 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +264 -8
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +7 -34
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/repl.js +1 -1
- package/dist/cli/repl.js.map +1 -1
- package/dist/cli/utils/ai-config.d.ts +32 -0
- package/dist/cli/utils/ai-config.d.ts.map +1 -0
- package/dist/cli/utils/ai-config.js +78 -0
- package/dist/cli/utils/ai-config.js.map +1 -0
- package/dist/cli/utils/docker-files.d.ts.map +1 -1
- package/dist/cli/utils/docker-files.js +2 -0
- package/dist/cli/utils/docker-files.js.map +1 -1
- package/docker/openclaw/entrypoint.sh +102 -0
- package/docker/openclaw/openclaw.json +4 -1
- package/docker/orchestrator/orchestrator-schema.sql +837 -0
- package/docker-compose.yml +22 -14
- package/init/metadata.sql +14 -2
- package/package.json +1 -1
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* ClawSQL CLI - Doctor Command
|
|
4
4
|
*
|
|
5
5
|
* Diagnoses system health and suggests fixes for common issues.
|
|
6
|
-
* Similar to `brew doctor` or `npm doctor`.
|
|
7
6
|
*/
|
|
8
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
8
|
exports.doctorCommand = void 0;
|
|
@@ -11,189 +10,128 @@ const components_js_1 = require("../ui/components.js");
|
|
|
11
10
|
const child_process_1 = require("child_process");
|
|
12
11
|
const openclaw_integration_js_1 = require("../agent/openclaw-integration.js");
|
|
13
12
|
const docker_prereq_js_1 = require("../utils/docker-prereq.js");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const ai_config_js_1 = require("../utils/ai-config.js");
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Command Definition
|
|
16
|
+
// ============================================================================
|
|
17
17
|
exports.doctorCommand = {
|
|
18
18
|
name: 'doctor',
|
|
19
19
|
description: 'Diagnose system issues and suggest fixes',
|
|
20
20
|
usage: '/doctor [--fix]',
|
|
21
|
-
handler: async (
|
|
22
|
-
|
|
23
|
-
const shouldFix = args.includes('--fix');
|
|
24
|
-
console.log(formatter.header('ClawSQL Doctor'));
|
|
21
|
+
handler: async (_args, ctx) => {
|
|
22
|
+
console.log(ctx.formatter.header('ClawSQL Doctor'));
|
|
25
23
|
console.log(components_js_1.theme.muted('Running diagnostics...\n'));
|
|
26
24
|
const results = [];
|
|
27
|
-
// Run all diagnostic checks
|
|
28
25
|
await runDiagnostics(ctx, results);
|
|
29
|
-
// Display results
|
|
30
26
|
displayResults(results);
|
|
31
|
-
|
|
32
|
-
const errors = results.filter(r => r.severity === 'error');
|
|
33
|
-
const warnings = results.filter(r => r.severity === 'warning');
|
|
34
|
-
console.log();
|
|
35
|
-
if (errors.length === 0 && warnings.length === 0) {
|
|
36
|
-
console.log(components_js_1.theme.success(`${components_js_1.indicators.check} All systems healthy!`));
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
console.log(components_js_1.theme.warning(`Found ${errors.length} error(s) and ${warnings.length} warning(s)`));
|
|
40
|
-
if (!shouldFix && (errors.length > 0 || warnings.length > 0)) {
|
|
41
|
-
console.log(components_js_1.theme.muted('\nSome issues may have automatic fixes available.'));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
27
|
+
printSummary(results);
|
|
44
28
|
},
|
|
45
29
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Diagnostic Runner
|
|
32
|
+
// ============================================================================
|
|
49
33
|
async function runDiagnostics(ctx, results) {
|
|
50
|
-
// Platform checks
|
|
51
34
|
const runtime = await checkContainerRuntime(results);
|
|
52
|
-
await
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
await Promise.all([
|
|
36
|
+
checkDockerImages(runtime, results),
|
|
37
|
+
checkClawSQLAPI(ctx, results),
|
|
38
|
+
checkOrchestrator(ctx, results),
|
|
39
|
+
checkProxySQL(ctx, results),
|
|
40
|
+
checkPrometheus(ctx, results),
|
|
41
|
+
checkOpenClaw(ctx, results),
|
|
42
|
+
]);
|
|
59
43
|
checkConfiguration(ctx, results);
|
|
60
|
-
// MySQL checks
|
|
61
|
-
await checkMySQLInstances(ctx, results);
|
|
62
|
-
await checkReplicationTopology(ctx, results);
|
|
63
|
-
// Sync check
|
|
64
|
-
await checkProxySQLSync(ctx, results);
|
|
65
44
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Individual Checks
|
|
47
|
+
// ============================================================================
|
|
69
48
|
async function checkContainerRuntime(results) {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
for (const runtime of runtimes) {
|
|
73
|
-
try {
|
|
74
|
-
const result = await execCommand([runtime, 'info'], true);
|
|
75
|
-
if (result.success) {
|
|
76
|
-
foundRuntime = runtime;
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// Continue to next runtime
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (foundRuntime) {
|
|
85
|
-
results.push({
|
|
86
|
-
name: 'Container Runtime',
|
|
87
|
-
severity: 'ok',
|
|
88
|
-
message: `${foundRuntime} is installed and running`,
|
|
89
|
-
});
|
|
90
|
-
// Check if containers are running
|
|
91
|
-
try {
|
|
92
|
-
const psResult = await execCommand([foundRuntime, 'ps', '--filter', 'name=clawsql', '--filter', 'name=orchestrator',
|
|
93
|
-
'--filter', 'name=proxysql', '-q'], true);
|
|
94
|
-
const containerCount = psResult.stdout.trim().split('\n').filter(Boolean).length;
|
|
95
|
-
if (containerCount === 0) {
|
|
96
|
-
results.push({
|
|
97
|
-
name: 'Platform Containers',
|
|
98
|
-
severity: 'warning',
|
|
99
|
-
message: 'No ClawSQL containers are running',
|
|
100
|
-
fix: 'Start the platform with: /start',
|
|
101
|
-
fixCommand: '/start',
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
results.push({
|
|
106
|
-
name: 'Platform Containers',
|
|
107
|
-
severity: 'ok',
|
|
108
|
-
message: `${containerCount} container(s) running`,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
results.push({
|
|
114
|
-
name: 'Platform Containers',
|
|
115
|
-
severity: 'warning',
|
|
116
|
-
message: 'Could not check container status',
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
49
|
+
const runtime = await (0, docker_prereq_js_1.detectRuntime)();
|
|
50
|
+
if (!runtime) {
|
|
121
51
|
results.push({
|
|
122
52
|
name: 'Container Runtime',
|
|
123
53
|
severity: 'error',
|
|
124
54
|
message: 'No container runtime found (docker or podman required)',
|
|
125
55
|
fix: 'Install Docker from https://docs.docker.com/get-docker/',
|
|
126
56
|
});
|
|
57
|
+
return null;
|
|
127
58
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
59
|
+
results.push({
|
|
60
|
+
name: 'Container Runtime',
|
|
61
|
+
severity: 'ok',
|
|
62
|
+
message: `${runtime} is installed and running`,
|
|
63
|
+
});
|
|
64
|
+
const psResult = await execCommand([runtime, 'ps', '--filter', 'name=clawsql', '--filter', 'name=orchestrator', '--filter', 'name=proxysql', '-q'], true);
|
|
65
|
+
const containerCount = psResult.stdout.trim().split('\n').filter(Boolean).length;
|
|
66
|
+
if (containerCount === 0) {
|
|
67
|
+
results.push({
|
|
68
|
+
name: 'Platform Containers',
|
|
69
|
+
severity: 'warning',
|
|
70
|
+
message: 'No ClawSQL containers are running',
|
|
71
|
+
fix: 'Start the platform with: /start',
|
|
72
|
+
fixCommand: '/start',
|
|
73
|
+
});
|
|
136
74
|
}
|
|
137
|
-
|
|
138
|
-
if (imageStatus.installed.length === imageStatus.total) {
|
|
75
|
+
else {
|
|
139
76
|
results.push({
|
|
140
|
-
name: '
|
|
77
|
+
name: 'Platform Containers',
|
|
141
78
|
severity: 'ok',
|
|
142
|
-
message:
|
|
79
|
+
message: `${containerCount} container(s) running`,
|
|
143
80
|
});
|
|
144
81
|
}
|
|
145
|
-
|
|
82
|
+
return runtime;
|
|
83
|
+
}
|
|
84
|
+
async function checkDockerImages(runtime, results) {
|
|
85
|
+
if (!runtime)
|
|
86
|
+
return;
|
|
87
|
+
const status = await (0, docker_prereq_js_1.checkImagesInstalled)(runtime);
|
|
88
|
+
if (status.installed.length === status.total) {
|
|
89
|
+
results.push({ name: 'Docker Images', severity: 'ok', message: `All ${status.total} images installed` });
|
|
90
|
+
}
|
|
91
|
+
else if (status.installed.length === 0) {
|
|
146
92
|
results.push({
|
|
147
93
|
name: 'Docker Images',
|
|
148
94
|
severity: 'error',
|
|
149
95
|
message: 'No Docker images installed',
|
|
150
|
-
detail: 'Required images have not been pulled',
|
|
151
96
|
fix: 'Pull images with: /install',
|
|
152
97
|
fixCommand: '/install',
|
|
153
98
|
});
|
|
154
99
|
}
|
|
155
100
|
else {
|
|
101
|
+
const missing = status.missing.slice(0, 3).map(i => i.split('/').pop()).join(', ');
|
|
156
102
|
results.push({
|
|
157
103
|
name: 'Docker Images',
|
|
158
104
|
severity: 'warning',
|
|
159
|
-
message: `${
|
|
160
|
-
detail: `Missing: ${
|
|
105
|
+
message: `${status.installed.length}/${status.total} images installed`,
|
|
106
|
+
detail: `Missing: ${missing}${status.missing.length > 3 ? '...' : ''}`,
|
|
161
107
|
fix: 'Pull missing images with: /install',
|
|
162
108
|
fixCommand: '/install',
|
|
163
109
|
});
|
|
164
110
|
}
|
|
165
111
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Check ClawSQL API health
|
|
168
|
-
*/
|
|
169
112
|
async function checkClawSQLAPI(ctx, results) {
|
|
170
113
|
try {
|
|
171
114
|
const response = await fetch(`http://localhost:${ctx.settings.api.port}/health`, {
|
|
172
115
|
signal: AbortSignal.timeout(5000),
|
|
173
116
|
});
|
|
174
|
-
if (response.ok) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
severity: 'error',
|
|
187
|
-
message: `API returned status: ${data.status}`,
|
|
188
|
-
fix: 'Check logs: docker logs clawsql',
|
|
189
|
-
});
|
|
190
|
-
}
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
results.push({
|
|
119
|
+
name: 'ClawSQL API',
|
|
120
|
+
severity: 'error',
|
|
121
|
+
message: `API returned HTTP ${response.status}`,
|
|
122
|
+
fix: 'Check logs: docker logs clawsql',
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
if (data.status === 'healthy') {
|
|
128
|
+
results.push({ name: 'ClawSQL API', severity: 'ok', message: `Running on port ${ctx.settings.api.port}` });
|
|
191
129
|
}
|
|
192
130
|
else {
|
|
193
131
|
results.push({
|
|
194
132
|
name: 'ClawSQL API',
|
|
195
133
|
severity: 'error',
|
|
196
|
-
message: `API returned
|
|
134
|
+
message: `API returned status: ${data.status}`,
|
|
197
135
|
fix: 'Check logs: docker logs clawsql',
|
|
198
136
|
});
|
|
199
137
|
}
|
|
@@ -209,54 +147,20 @@ async function checkClawSQLAPI(ctx, results) {
|
|
|
209
147
|
});
|
|
210
148
|
}
|
|
211
149
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Check Orchestrator health
|
|
214
|
-
*/
|
|
215
150
|
async function checkOrchestrator(ctx, results) {
|
|
151
|
+
// Health check
|
|
216
152
|
try {
|
|
217
153
|
const isHealthy = await ctx.orchestrator.healthCheck();
|
|
218
|
-
if (isHealthy) {
|
|
219
|
-
results.push({
|
|
220
|
-
name: 'Orchestrator',
|
|
221
|
-
severity: 'ok',
|
|
222
|
-
message: `Running at ${ctx.settings.orchestrator.url}`,
|
|
223
|
-
});
|
|
224
|
-
// Check if instances are discovered
|
|
225
|
-
try {
|
|
226
|
-
const clusters = await ctx.orchestrator.getClusters();
|
|
227
|
-
if (clusters.length === 0) {
|
|
228
|
-
results.push({
|
|
229
|
-
name: 'MySQL Topology',
|
|
230
|
-
severity: 'warning',
|
|
231
|
-
message: 'No MySQL instances discovered in Orchestrator',
|
|
232
|
-
fix: 'Register instances with: /instances register <host>',
|
|
233
|
-
fixCommand: '/instances register <mysql-host>',
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
results.push({
|
|
238
|
-
name: 'MySQL Topology',
|
|
239
|
-
severity: 'ok',
|
|
240
|
-
message: `${clusters.length} cluster(s) discovered`,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
results.push({
|
|
246
|
-
name: 'MySQL Topology',
|
|
247
|
-
severity: 'warning',
|
|
248
|
-
message: 'Could not retrieve topology information',
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
154
|
+
if (!isHealthy) {
|
|
253
155
|
results.push({
|
|
254
156
|
name: 'Orchestrator',
|
|
255
157
|
severity: 'error',
|
|
256
158
|
message: 'Health check failed',
|
|
257
159
|
fix: 'Check Orchestrator container: docker logs orchestrator',
|
|
258
160
|
});
|
|
161
|
+
return;
|
|
259
162
|
}
|
|
163
|
+
results.push({ name: 'Orchestrator', severity: 'ok', message: `Running at ${ctx.settings.orchestrator.url}` });
|
|
260
164
|
}
|
|
261
165
|
catch {
|
|
262
166
|
results.push({
|
|
@@ -264,92 +168,63 @@ async function checkOrchestrator(ctx, results) {
|
|
|
264
168
|
severity: 'error',
|
|
265
169
|
message: 'Cannot connect to Orchestrator',
|
|
266
170
|
detail: `Expected at ${ctx.settings.orchestrator.url}`,
|
|
267
|
-
fix: 'Ensure
|
|
171
|
+
fix: 'Ensure container is running: docker ps | grep orchestrator',
|
|
268
172
|
});
|
|
269
173
|
}
|
|
270
174
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Check ProxySQL health
|
|
273
|
-
*/
|
|
274
175
|
async function checkProxySQL(ctx, results) {
|
|
275
176
|
try {
|
|
276
177
|
await ctx.proxysql.connect();
|
|
277
|
-
results.push({
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
178
|
+
results.push({ name: 'ProxySQL', severity: 'ok', message: `Admin interface on port ${ctx.settings.proxysql.adminPort}` });
|
|
179
|
+
const servers = await ctx.proxysql.getServers();
|
|
180
|
+
if (servers.length === 0) {
|
|
181
|
+
results.push({
|
|
182
|
+
name: 'ProxySQL Servers',
|
|
183
|
+
severity: 'warning',
|
|
184
|
+
message: 'No MySQL servers configured',
|
|
185
|
+
fix: 'Register instances, then sync with: /clusters sync',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const online = servers.filter(s => s.status === 'ONLINE').length;
|
|
190
|
+
if (online === servers.length) {
|
|
191
|
+
results.push({ name: 'ProxySQL Servers', severity: 'ok', message: `${online}/${servers.length} servers online` });
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
286
194
|
results.push({
|
|
287
195
|
name: 'ProxySQL Servers',
|
|
288
196
|
severity: 'warning',
|
|
289
|
-
message:
|
|
290
|
-
fix: '
|
|
197
|
+
message: `${online}/${servers.length} servers online`,
|
|
198
|
+
fix: 'Check MySQL server connectivity and credentials',
|
|
291
199
|
});
|
|
292
200
|
}
|
|
293
|
-
else {
|
|
294
|
-
const onlineServers = servers.filter(s => s.status === 'ONLINE');
|
|
295
|
-
if (onlineServers.length === servers.length) {
|
|
296
|
-
results.push({
|
|
297
|
-
name: 'ProxySQL Servers',
|
|
298
|
-
severity: 'ok',
|
|
299
|
-
message: `${onlineServers.length}/${servers.length} servers online`,
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
results.push({
|
|
304
|
-
name: 'ProxySQL Servers',
|
|
305
|
-
severity: 'warning',
|
|
306
|
-
message: `${onlineServers.length}/${servers.length} servers online`,
|
|
307
|
-
fix: 'Check MySQL server connectivity and credentials',
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
results.push({
|
|
314
|
-
name: 'ProxySQL Servers',
|
|
315
|
-
severity: 'warning',
|
|
316
|
-
message: 'Could not retrieve server configuration',
|
|
317
|
-
});
|
|
318
201
|
}
|
|
319
202
|
await ctx.proxysql.close();
|
|
320
203
|
}
|
|
321
204
|
catch (error) {
|
|
322
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
323
205
|
results.push({
|
|
324
206
|
name: 'ProxySQL',
|
|
325
207
|
severity: 'error',
|
|
326
208
|
message: 'Cannot connect to ProxySQL admin interface',
|
|
327
|
-
detail: message,
|
|
328
|
-
fix: 'Ensure
|
|
209
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
210
|
+
fix: 'Ensure container is running: docker ps | grep proxysql',
|
|
329
211
|
});
|
|
330
212
|
}
|
|
331
213
|
}
|
|
332
|
-
/**
|
|
333
|
-
* Check Prometheus health
|
|
334
|
-
*/
|
|
335
214
|
async function checkPrometheus(ctx, results) {
|
|
336
215
|
try {
|
|
337
216
|
const response = await fetch(`${ctx.settings.prometheus.url}/-/healthy`, {
|
|
338
217
|
signal: AbortSignal.timeout(5000),
|
|
339
218
|
});
|
|
340
219
|
if (response.ok) {
|
|
341
|
-
results.push({
|
|
342
|
-
name: 'Prometheus',
|
|
343
|
-
severity: 'ok',
|
|
344
|
-
message: `Running at ${ctx.settings.prometheus.url}`,
|
|
345
|
-
});
|
|
220
|
+
results.push({ name: 'Prometheus', severity: 'ok', message: `Running at ${ctx.settings.prometheus.url}` });
|
|
346
221
|
}
|
|
347
222
|
else {
|
|
348
223
|
results.push({
|
|
349
224
|
name: 'Prometheus',
|
|
350
225
|
severity: 'warning',
|
|
351
226
|
message: `Health check returned status ${response.status}`,
|
|
352
|
-
fix: 'Check
|
|
227
|
+
fix: 'Check container: docker logs prometheus',
|
|
353
228
|
});
|
|
354
229
|
}
|
|
355
230
|
}
|
|
@@ -363,109 +238,130 @@ async function checkPrometheus(ctx, results) {
|
|
|
363
238
|
});
|
|
364
239
|
}
|
|
365
240
|
}
|
|
366
|
-
/**
|
|
367
|
-
* Check OpenClaw AI Gateway health
|
|
368
|
-
*/
|
|
369
241
|
async function checkOpenClaw(_ctx, results) {
|
|
370
242
|
try {
|
|
371
|
-
const status = await (0, openclaw_integration_js_1.
|
|
372
|
-
if (status.available) {
|
|
373
|
-
// Check
|
|
374
|
-
const
|
|
375
|
-
if (
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
severity: 'ok',
|
|
380
|
-
message: 'Running in Docker container (ws://localhost:18789)',
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
else if (status.isLocal) {
|
|
243
|
+
const status = await (0, openclaw_integration_js_1.getDetailedOpenClawStatus)();
|
|
244
|
+
if (!status.available) {
|
|
245
|
+
// Check if container exists but stopped
|
|
246
|
+
const runtime = await (0, docker_prereq_js_1.detectRuntime)();
|
|
247
|
+
if (runtime) {
|
|
248
|
+
const psResult = await execCommand([runtime, 'ps', '-a', '--filter', 'name=openclaw', '--format', '{{.Status}}'], true);
|
|
249
|
+
const containerStatus = psResult.stdout.trim();
|
|
250
|
+
if (containerStatus && !containerStatus.toLowerCase().includes('up')) {
|
|
384
251
|
results.push({
|
|
385
252
|
name: 'OpenClaw Gateway',
|
|
386
|
-
severity: '
|
|
387
|
-
message: '
|
|
253
|
+
severity: 'warning',
|
|
254
|
+
message: 'OpenClaw container exists but is not running',
|
|
255
|
+
detail: `Status: ${containerStatus}`,
|
|
256
|
+
fix: 'Restart the platform with: /start',
|
|
257
|
+
fixCommand: '/start',
|
|
388
258
|
});
|
|
259
|
+
return;
|
|
389
260
|
}
|
|
390
261
|
}
|
|
262
|
+
results.push({
|
|
263
|
+
name: 'OpenClaw Gateway',
|
|
264
|
+
severity: 'warning',
|
|
265
|
+
message: 'OpenClaw gateway is not available',
|
|
266
|
+
detail: 'AI features will not work without OpenClaw',
|
|
267
|
+
fix: 'Start with: /start',
|
|
268
|
+
fixCommand: '/start',
|
|
269
|
+
});
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (!status.gatewayHealthy) {
|
|
273
|
+
results.push({
|
|
274
|
+
name: 'OpenClaw Gateway',
|
|
275
|
+
severity: 'warning',
|
|
276
|
+
message: 'Gateway is not responding',
|
|
277
|
+
fix: 'Check logs: docker logs openclaw',
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const modeDisplay = status.mode === 'docker' ? 'Running in Docker' : 'Using local installation';
|
|
282
|
+
results.push({ name: 'OpenClaw Gateway', severity: 'ok', message: `${modeDisplay} (ws://localhost:18789)` });
|
|
283
|
+
// Check for auto-detected AI config from environment
|
|
284
|
+
const aiConfig = (0, ai_config_js_1.detectAIConfigFromEnv)();
|
|
285
|
+
// Model info
|
|
286
|
+
if (status.modelInfo.configured && status.modelInfo.model) {
|
|
287
|
+
results.push({
|
|
288
|
+
name: 'OpenClaw Model',
|
|
289
|
+
severity: 'ok',
|
|
290
|
+
message: `${status.modelInfo.provider || 'custom'}: ${status.modelInfo.model}`,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
else if (aiConfig.provider !== 'none') {
|
|
294
|
+
results.push({
|
|
295
|
+
name: 'OpenClaw Model',
|
|
296
|
+
severity: 'ok',
|
|
297
|
+
message: `${aiConfig.provider}${aiConfig.model ? '/' + aiConfig.model : ''} (auto-detected)`,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
results.push({
|
|
302
|
+
name: 'OpenClaw Model',
|
|
303
|
+
severity: 'info',
|
|
304
|
+
message: 'Using bundled qwen model (limited)',
|
|
305
|
+
fix: 'Set ANTHROPIC_API_KEY or OPENAI_API_KEY for better AI',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// Quick AI test - optional, can be slow
|
|
309
|
+
// Use a simple gateway health check instead of full AI query
|
|
310
|
+
const httpUrl = openclaw_integration_js_1.CONFIG.gatewayUrl.replace('ws://', 'http://').replace('wss://', 'https://');
|
|
311
|
+
try {
|
|
312
|
+
const start = Date.now();
|
|
313
|
+
const response = await fetch(`${httpUrl}/health`, {
|
|
314
|
+
signal: AbortSignal.timeout(5000),
|
|
315
|
+
});
|
|
316
|
+
const latency = Date.now() - start;
|
|
317
|
+
if (response.ok) {
|
|
318
|
+
results.push({ name: 'OpenClaw AI Test', severity: 'ok', message: `Gateway healthy (${latency}ms)` });
|
|
319
|
+
}
|
|
391
320
|
else {
|
|
392
321
|
results.push({
|
|
393
|
-
name: 'OpenClaw
|
|
322
|
+
name: 'OpenClaw AI Test',
|
|
394
323
|
severity: 'warning',
|
|
395
|
-
message:
|
|
396
|
-
detail: 'Container is running but gateway health check failed',
|
|
324
|
+
message: `Gateway returned ${response.status}`,
|
|
397
325
|
fix: 'Check logs: docker logs openclaw',
|
|
398
326
|
});
|
|
399
327
|
}
|
|
400
328
|
}
|
|
401
|
-
|
|
402
|
-
// Check if Docker OpenClaw container exists but is not running
|
|
403
|
-
const runtime = await (0, docker_prereq_js_1.detectRuntime)();
|
|
404
|
-
if (runtime) {
|
|
405
|
-
try {
|
|
406
|
-
const psResult = await execCommand([runtime, 'ps', '-a', '--filter', 'name=openclaw', '--format', '{{.Status}}'], true);
|
|
407
|
-
const containerStatus = psResult.stdout.trim();
|
|
408
|
-
if (containerStatus && !containerStatus.toLowerCase().includes('up')) {
|
|
409
|
-
results.push({
|
|
410
|
-
name: 'OpenClaw Gateway',
|
|
411
|
-
severity: 'warning',
|
|
412
|
-
message: 'OpenClaw container exists but is not running',
|
|
413
|
-
detail: `Container status: ${containerStatus}`,
|
|
414
|
-
fix: 'Restart the platform with: /start',
|
|
415
|
-
fixCommand: '/start',
|
|
416
|
-
});
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
catch {
|
|
421
|
-
// Continue to default message
|
|
422
|
-
}
|
|
423
|
-
}
|
|
329
|
+
catch {
|
|
424
330
|
results.push({
|
|
425
|
-
name: 'OpenClaw
|
|
331
|
+
name: 'OpenClaw AI Test',
|
|
426
332
|
severity: 'warning',
|
|
427
|
-
message: '
|
|
428
|
-
|
|
429
|
-
fix: 'Start the platform with: /start to launch OpenClaw in Docker',
|
|
430
|
-
fixCommand: '/start',
|
|
333
|
+
message: 'Gateway health check failed',
|
|
334
|
+
fix: 'Check logs: docker logs openclaw',
|
|
431
335
|
});
|
|
432
336
|
}
|
|
433
337
|
}
|
|
434
338
|
catch (error) {
|
|
435
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
436
339
|
results.push({
|
|
437
340
|
name: 'OpenClaw Gateway',
|
|
438
341
|
severity: 'warning',
|
|
439
|
-
message: 'Could not check
|
|
440
|
-
detail: message,
|
|
441
|
-
fix: 'Ensure
|
|
342
|
+
message: 'Could not check status',
|
|
343
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
344
|
+
fix: 'Ensure container is running: docker ps | grep openclaw',
|
|
442
345
|
});
|
|
443
346
|
}
|
|
444
347
|
}
|
|
445
|
-
/**
|
|
446
|
-
* Check configuration issues
|
|
447
|
-
*/
|
|
448
348
|
function checkConfiguration(ctx, results) {
|
|
449
|
-
// Check API token secret
|
|
450
349
|
if (ctx.settings.api.tokenSecret === 'change-me-in-production') {
|
|
451
350
|
results.push({
|
|
452
351
|
name: 'API Token Secret',
|
|
453
352
|
severity: 'warning',
|
|
454
353
|
message: 'Using default token secret (not secure for production)',
|
|
455
|
-
fix: 'Set
|
|
354
|
+
fix: 'Set: API_TOKEN_SECRET=<your-secret>',
|
|
456
355
|
});
|
|
457
356
|
}
|
|
458
|
-
// Check MySQL credentials
|
|
459
357
|
if (!ctx.settings.mysql.adminPassword) {
|
|
460
358
|
results.push({
|
|
461
359
|
name: 'MySQL Credentials',
|
|
462
360
|
severity: 'warning',
|
|
463
361
|
message: 'MySQL admin password not configured',
|
|
464
|
-
|
|
465
|
-
fix: 'Set environment variable: MYSQL_ADMIN_PASSWORD=<password>',
|
|
362
|
+
fix: 'Set: MYSQL_ADMIN_PASSWORD=<password>',
|
|
466
363
|
});
|
|
467
364
|
}
|
|
468
|
-
// Check auto-failover
|
|
469
365
|
if (!ctx.settings.failover.autoFailoverEnabled) {
|
|
470
366
|
results.push({
|
|
471
367
|
name: 'Auto Failover',
|
|
@@ -474,333 +370,88 @@ function checkConfiguration(ctx, results) {
|
|
|
474
370
|
fix: 'Enable with: AUTO_FAILOVER_ENABLED=true',
|
|
475
371
|
});
|
|
476
372
|
}
|
|
477
|
-
// Check metadata database
|
|
478
373
|
if (!ctx.settings.metadataDb.host) {
|
|
479
|
-
results.push({
|
|
480
|
-
name: 'Metadata Database',
|
|
481
|
-
severity: 'ok',
|
|
482
|
-
message: 'Using auto-provisioned metadata-mysql container',
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Check MySQL instances health
|
|
488
|
-
*/
|
|
489
|
-
async function checkMySQLInstances(ctx, results) {
|
|
490
|
-
try {
|
|
491
|
-
const clusters = await ctx.orchestrator.getClusters();
|
|
492
|
-
for (const clusterName of clusters) {
|
|
493
|
-
const topology = await ctx.orchestrator.getTopology(clusterName);
|
|
494
|
-
if (!topology)
|
|
495
|
-
continue;
|
|
496
|
-
// Check primary
|
|
497
|
-
if (topology.primary) {
|
|
498
|
-
if (topology.primary.state !== 'online') {
|
|
499
|
-
results.push({
|
|
500
|
-
name: `Primary [${topology.name || clusterName}]`,
|
|
501
|
-
severity: 'error',
|
|
502
|
-
message: `Primary ${topology.primary.host}:${topology.primary.port} is ${topology.primary.state}`,
|
|
503
|
-
fix: 'Check MySQL instance status and connectivity',
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
results.push({
|
|
508
|
-
name: `Primary [${topology.name || clusterName}]`,
|
|
509
|
-
severity: 'ok',
|
|
510
|
-
message: `${topology.primary.host}:${topology.primary.port} is online`,
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
results.push({
|
|
516
|
-
name: `Primary [${topology.name || clusterName}]`,
|
|
517
|
-
severity: 'error',
|
|
518
|
-
message: 'No primary found for cluster',
|
|
519
|
-
fix: 'Check replication setup or promote a replica: /failover switchover',
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
// Check replicas
|
|
523
|
-
for (const replica of topology.replicas) {
|
|
524
|
-
if (replica.state !== 'online') {
|
|
525
|
-
results.push({
|
|
526
|
-
name: `Replica [${topology.name || clusterName}]`,
|
|
527
|
-
severity: 'warning',
|
|
528
|
-
message: `Replica ${replica.host}:${replica.port} is ${replica.state}`,
|
|
529
|
-
fix: 'Check replica MySQL status and replication connection',
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
else if (replica.replicationLag !== undefined && replica.replicationLag > 60) {
|
|
533
|
-
results.push({
|
|
534
|
-
name: `Replica Lag [${topology.name || clusterName}]`,
|
|
535
|
-
severity: 'warning',
|
|
536
|
-
message: `Replica ${replica.host}:${replica.port} has high lag (${replica.replicationLag}s)`,
|
|
537
|
-
fix: 'Check replica performance and network connectivity',
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
catch {
|
|
544
|
-
// Already handled in Orchestrator check
|
|
374
|
+
results.push({ name: 'Metadata Database', severity: 'ok', message: 'Using auto-provisioned container' });
|
|
545
375
|
}
|
|
546
376
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
async function checkReplicationTopology(ctx, results) {
|
|
551
|
-
try {
|
|
552
|
-
const analysis = await ctx.orchestrator.getReplicationAnalysis();
|
|
553
|
-
for (const issue of analysis) {
|
|
554
|
-
const analysisType = issue.Analysis;
|
|
555
|
-
const description = issue.Description;
|
|
556
|
-
const affectedHost = issue.Key?.Hostname;
|
|
557
|
-
if (analysisType && !analysisType.includes('NoProblem')) {
|
|
558
|
-
results.push({
|
|
559
|
-
name: 'Replication Analysis',
|
|
560
|
-
severity: 'warning',
|
|
561
|
-
message: `${analysisType}: ${affectedHost || 'unknown'}`,
|
|
562
|
-
detail: description,
|
|
563
|
-
fix: 'Review Orchestrator UI for details: http://localhost:3000',
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
catch {
|
|
569
|
-
// Orchestrator may not be available
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Check ProxySQL sync with Orchestrator topology
|
|
574
|
-
*/
|
|
575
|
-
async function checkProxySQLSync(ctx, results) {
|
|
576
|
-
try {
|
|
577
|
-
// Get topology from Orchestrator
|
|
578
|
-
const clusters = await ctx.orchestrator.getClusters();
|
|
579
|
-
if (clusters.length === 0) {
|
|
580
|
-
return; // No clusters to sync
|
|
581
|
-
}
|
|
582
|
-
// Get ProxySQL server stats and hostgroups
|
|
583
|
-
let proxysqlServers = [];
|
|
584
|
-
let hostgroups = [];
|
|
585
|
-
try {
|
|
586
|
-
await ctx.proxysql.connect();
|
|
587
|
-
proxysqlServers = await ctx.proxysql.getServers();
|
|
588
|
-
hostgroups = await ctx.proxysql.getReplicationHostgroups();
|
|
589
|
-
await ctx.proxysql.close();
|
|
590
|
-
}
|
|
591
|
-
catch {
|
|
592
|
-
// ProxySQL not available - already reported in checkProxySQL
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
// Build expected instances from Orchestrator
|
|
596
|
-
const expectedInstances = new Map();
|
|
597
|
-
for (const clusterName of clusters) {
|
|
598
|
-
const topology = await ctx.orchestrator.getTopology(clusterName);
|
|
599
|
-
if (!topology)
|
|
600
|
-
continue;
|
|
601
|
-
if (topology.primary) {
|
|
602
|
-
const key = `${topology.primary.host}:${topology.primary.port}`;
|
|
603
|
-
expectedInstances.set(key, {
|
|
604
|
-
host: topology.primary.host,
|
|
605
|
-
port: topology.primary.port,
|
|
606
|
-
role: 'primary',
|
|
607
|
-
cluster: topology.name || clusterName,
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
for (const replica of topology.replicas) {
|
|
611
|
-
const key = `${replica.host}:${replica.port}`;
|
|
612
|
-
expectedInstances.set(key, {
|
|
613
|
-
host: replica.host,
|
|
614
|
-
port: replica.port,
|
|
615
|
-
role: 'replica',
|
|
616
|
-
cluster: topology.name || clusterName,
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
// Build actual ProxySQL instances
|
|
621
|
-
const proxysqlInstances = new Map();
|
|
622
|
-
for (const server of proxysqlServers) {
|
|
623
|
-
const key = `${server.hostname}:${server.port}`;
|
|
624
|
-
proxysqlInstances.set(key, {
|
|
625
|
-
host: server.hostname,
|
|
626
|
-
port: server.port,
|
|
627
|
-
hostgroup: server.hostgroupId,
|
|
628
|
-
status: server.status,
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
// Determine default hostgroups (coerce to numbers)
|
|
632
|
-
const defaultWriterHG = hostgroups.length > 0 ? Number(hostgroups[0].writerHostgroup) : 10;
|
|
633
|
-
const defaultReaderHG = hostgroups.length > 0 ? Number(hostgroups[0].readerHostgroup) : 20;
|
|
634
|
-
// Check for missing instances in ProxySQL
|
|
635
|
-
const missingInProxySQL = [];
|
|
636
|
-
const wrongHostgroup = [];
|
|
637
|
-
for (const [key, instance] of expectedInstances) {
|
|
638
|
-
const proxysqlInstance = proxysqlInstances.get(key);
|
|
639
|
-
if (!proxysqlInstance) {
|
|
640
|
-
missingInProxySQL.push(`${key} (${instance.role})`);
|
|
641
|
-
}
|
|
642
|
-
else {
|
|
643
|
-
// Check if hostgroup is correct (coerce both to numbers for comparison)
|
|
644
|
-
const actualHG = Number(proxysqlInstance.hostgroup);
|
|
645
|
-
const expectedHG = instance.role === 'primary' ? defaultWriterHG : defaultReaderHG;
|
|
646
|
-
// Skip if both are NaN or if they match
|
|
647
|
-
if (!isNaN(actualHG) && !isNaN(expectedHG) && actualHG !== expectedHG) {
|
|
648
|
-
wrongHostgroup.push(`${key} is in hg:${actualHG}, expected hg:${expectedHG}`);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
// Check for orphan instances in ProxySQL (not in Orchestrator)
|
|
653
|
-
const orphanInstances = [];
|
|
654
|
-
for (const [key, instance] of proxysqlInstances) {
|
|
655
|
-
if (!expectedInstances.has(key)) {
|
|
656
|
-
orphanInstances.push(`${key} (hg:${instance.hostgroup})`);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
// Report findings
|
|
660
|
-
if (missingInProxySQL.length > 0) {
|
|
661
|
-
results.push({
|
|
662
|
-
name: 'ProxySQL Sync',
|
|
663
|
-
severity: 'warning',
|
|
664
|
-
message: `${missingInProxySQL.length} instance(s) from Orchestrator missing in ProxySQL`,
|
|
665
|
-
detail: missingInProxySQL.slice(0, 5).join(', ') + (missingInProxySQL.length > 5 ? '...' : ''),
|
|
666
|
-
fix: 'Sync clusters to ProxySQL with: /clusters sync',
|
|
667
|
-
fixCommand: '/clusters sync',
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
if (wrongHostgroup.length > 0) {
|
|
671
|
-
results.push({
|
|
672
|
-
name: 'ProxySQL Hostgroups',
|
|
673
|
-
severity: 'warning',
|
|
674
|
-
message: `${wrongHostgroup.length} instance(s) in wrong hostgroup`,
|
|
675
|
-
detail: wrongHostgroup.slice(0, 3).join('; '),
|
|
676
|
-
fix: 'Re-sync clusters to ProxySQL with: /clusters sync',
|
|
677
|
-
fixCommand: '/clusters sync',
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
if (orphanInstances.length > 0) {
|
|
681
|
-
results.push({
|
|
682
|
-
name: 'ProxySQL Orphans',
|
|
683
|
-
severity: 'warning',
|
|
684
|
-
message: `${orphanInstances.length} instance(s) in ProxySQL not in Orchestrator`,
|
|
685
|
-
detail: orphanInstances.slice(0, 3).join(', '),
|
|
686
|
-
fix: 'Remove orphan servers from ProxySQL or register them in Orchestrator',
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
// All synced properly
|
|
690
|
-
if (missingInProxySQL.length === 0 && wrongHostgroup.length === 0 && orphanInstances.length === 0 && expectedInstances.size > 0) {
|
|
691
|
-
results.push({
|
|
692
|
-
name: 'ProxySQL Sync',
|
|
693
|
-
severity: 'ok',
|
|
694
|
-
message: `All ${expectedInstances.size} instances properly synced`,
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
catch (error) {
|
|
699
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
700
|
-
results.push({
|
|
701
|
-
name: 'ProxySQL Sync',
|
|
702
|
-
severity: 'warning',
|
|
703
|
-
message: 'Could not verify ProxySQL sync',
|
|
704
|
-
detail: message,
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* Display diagnostic results
|
|
710
|
-
*/
|
|
377
|
+
// ============================================================================
|
|
378
|
+
// Display Utilities
|
|
379
|
+
// ============================================================================
|
|
711
380
|
function displayResults(results) {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
for (const result of errors) {
|
|
721
|
-
displayResult(result);
|
|
722
|
-
}
|
|
381
|
+
const groups = {
|
|
382
|
+
error: [],
|
|
383
|
+
warning: [],
|
|
384
|
+
info: [],
|
|
385
|
+
ok: [],
|
|
386
|
+
};
|
|
387
|
+
for (const r of results) {
|
|
388
|
+
groups[r.severity].push(r);
|
|
723
389
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
for (const result of warnings) {
|
|
728
|
-
displayResult(result);
|
|
729
|
-
}
|
|
390
|
+
if (groups.error.length > 0) {
|
|
391
|
+
console.log(components_js_1.theme.error.bold('✗ Errors:'));
|
|
392
|
+
groups.error.forEach(displayResult);
|
|
730
393
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
for (const result of info) {
|
|
735
|
-
displayResult(result);
|
|
736
|
-
}
|
|
394
|
+
if (groups.warning.length > 0) {
|
|
395
|
+
console.log(components_js_1.theme.warning.bold('◆ Warnings:'));
|
|
396
|
+
groups.warning.forEach(displayResult);
|
|
737
397
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
398
|
+
if (groups.info.length > 0) {
|
|
399
|
+
console.log(components_js_1.theme.info.bold('○ Information:'));
|
|
400
|
+
groups.info.forEach(displayResult);
|
|
401
|
+
}
|
|
402
|
+
if (groups.ok.length > 0) {
|
|
403
|
+
console.log(components_js_1.theme.success.bold('✓ Healthy:'));
|
|
404
|
+
groups.ok.forEach(r => console.log(components_js_1.theme.muted(` ${r.name}: `) + components_js_1.theme.success(r.message)));
|
|
744
405
|
}
|
|
745
406
|
}
|
|
746
|
-
/**
|
|
747
|
-
* Display a single diagnostic result
|
|
748
|
-
*/
|
|
749
407
|
function displayResult(result) {
|
|
750
|
-
const
|
|
751
|
-
error: components_js_1.theme.error,
|
|
752
|
-
warning: components_js_1.theme.warning,
|
|
753
|
-
ok: components_js_1.theme.success,
|
|
754
|
-
info: components_js_1.theme.info,
|
|
755
|
-
};
|
|
756
|
-
const severityIcons = {
|
|
408
|
+
const icons = {
|
|
757
409
|
error: components_js_1.theme.error(components_js_1.indicators.cross),
|
|
758
410
|
warning: components_js_1.theme.warning(components_js_1.indicators.warning),
|
|
759
|
-
ok: components_js_1.theme.success(components_js_1.indicators.check),
|
|
760
411
|
info: components_js_1.theme.info(components_js_1.indicators.info),
|
|
412
|
+
ok: components_js_1.theme.success(components_js_1.indicators.check),
|
|
761
413
|
};
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
414
|
+
const colorFn = {
|
|
415
|
+
error: components_js_1.theme.error,
|
|
416
|
+
warning: components_js_1.theme.warning,
|
|
417
|
+
info: components_js_1.theme.info,
|
|
418
|
+
ok: components_js_1.theme.success,
|
|
419
|
+
}[result.severity];
|
|
420
|
+
console.log(` ${icons[result.severity]} ${colorFn.bold(result.name)}: ${result.message}`);
|
|
421
|
+
if (result.detail)
|
|
766
422
|
console.log(components_js_1.theme.muted(` ${result.detail}`));
|
|
767
|
-
|
|
768
|
-
if (result.fix) {
|
|
423
|
+
if (result.fix)
|
|
769
424
|
console.log(components_js_1.theme.primary(` Fix: ${result.fix}`));
|
|
770
|
-
|
|
771
|
-
if (result.fixCommand) {
|
|
425
|
+
if (result.fixCommand)
|
|
772
426
|
console.log(components_js_1.theme.muted(` Command: ${result.fixCommand}`));
|
|
427
|
+
}
|
|
428
|
+
function printSummary(results) {
|
|
429
|
+
const errors = results.filter(r => r.severity === 'error').length;
|
|
430
|
+
const warnings = results.filter(r => r.severity === 'warning').length;
|
|
431
|
+
if (errors === 0 && warnings === 0) {
|
|
432
|
+
console.log(components_js_1.theme.success(`${components_js_1.indicators.check} All systems healthy!`));
|
|
773
433
|
}
|
|
434
|
+
else {
|
|
435
|
+
console.log(components_js_1.theme.warning(`Found ${errors} error(s) and ${warnings} warning(s)`));
|
|
436
|
+
}
|
|
437
|
+
// Add hint for MySQL cluster details
|
|
774
438
|
console.log();
|
|
439
|
+
console.log(components_js_1.theme.muted(' For MySQL cluster details, use: /topology or /clusters'));
|
|
775
440
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
441
|
+
// ============================================================================
|
|
442
|
+
// Process Execution
|
|
443
|
+
// ============================================================================
|
|
779
444
|
function execCommand(cmd, silent = false) {
|
|
780
445
|
return new Promise((resolve) => {
|
|
781
|
-
const proc = (0, child_process_1.spawn)(cmd[0], cmd.slice(1), {
|
|
782
|
-
stdio: silent ? 'pipe' : 'inherit',
|
|
783
|
-
});
|
|
446
|
+
const proc = (0, child_process_1.spawn)(cmd[0], cmd.slice(1), { stdio: silent ? 'pipe' : 'inherit' });
|
|
784
447
|
let stdout = '';
|
|
785
448
|
let stderr = '';
|
|
786
449
|
if (silent) {
|
|
787
450
|
proc.stdout?.on('data', (data) => { stdout += data; });
|
|
788
451
|
proc.stderr?.on('data', (data) => { stderr += data; });
|
|
789
452
|
}
|
|
790
|
-
proc.on('close', (code) => {
|
|
791
|
-
|
|
792
|
-
success: code === 0,
|
|
793
|
-
stdout,
|
|
794
|
-
stderr,
|
|
795
|
-
});
|
|
796
|
-
});
|
|
797
|
-
proc.on('error', () => {
|
|
798
|
-
resolve({
|
|
799
|
-
success: false,
|
|
800
|
-
stdout: '',
|
|
801
|
-
stderr: 'Failed to execute command',
|
|
802
|
-
});
|
|
803
|
-
});
|
|
453
|
+
proc.on('close', (code) => resolve({ success: code === 0, stdout, stderr }));
|
|
454
|
+
proc.on('error', () => resolve({ success: false, stdout: '', stderr: 'Failed to execute' }));
|
|
804
455
|
});
|
|
805
456
|
}
|
|
806
457
|
exports.default = exports.doctorCommand;
|