@zerry_jin/k8s-doctor-mcp 1.0.0
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/LICENSE +21 -0
- package/README.ko.md +330 -0
- package/README.md +330 -0
- package/dist/analyzers/log-analyzer.d.ts +17 -0
- package/dist/analyzers/log-analyzer.js +402 -0
- package/dist/diagnostics/cluster-health.d.ts +13 -0
- package/dist/diagnostics/cluster-health.js +176 -0
- package/dist/diagnostics/pod-diagnostics.d.ts +23 -0
- package/dist/diagnostics/pod-diagnostics.js +654 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +678 -0
- package/dist/types.d.ts +337 -0
- package/dist/types.js +6 -0
- package/dist/utils/cache.d.ts +59 -0
- package/dist/utils/cache.js +99 -0
- package/dist/utils/formatters.d.ts +48 -0
- package/dist/utils/formatters.js +129 -0
- package/dist/utils/k8s-client.d.ts +37 -0
- package/dist/utils/k8s-client.js +74 -0
- package/dist/utils/retry.d.ts +25 -0
- package/dist/utils/retry.js +70 -0
- package/package.json +65 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* K8s Doctor type definitions
|
|
3
|
+
*
|
|
4
|
+
* @author zerry
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Diagnostic severity
|
|
8
|
+
* Represents urgency of Kubernetes issues
|
|
9
|
+
*/
|
|
10
|
+
export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
11
|
+
/**
|
|
12
|
+
* Pod status
|
|
13
|
+
* Possible pod states in K8s
|
|
14
|
+
*/
|
|
15
|
+
export type PodPhase = 'Pending' | 'Running' | 'Succeeded' | 'Failed' | 'Unknown';
|
|
16
|
+
/**
|
|
17
|
+
* Diagnostic issue
|
|
18
|
+
*
|
|
19
|
+
* Contains each detected problem and solution.
|
|
20
|
+
* Not just showing status but explaining "why this is a problem"
|
|
21
|
+
*/
|
|
22
|
+
export interface DiagnosticIssue {
|
|
23
|
+
/** Issue type (e.g., "CrashLoopBackOff", "ImagePullBackOff") */
|
|
24
|
+
type: string;
|
|
25
|
+
/** Severity */
|
|
26
|
+
severity: Severity;
|
|
27
|
+
/** Human-readable description */
|
|
28
|
+
message: string;
|
|
29
|
+
/** Root cause analysis */
|
|
30
|
+
rootCause: string;
|
|
31
|
+
/** Suggested solution (includes specific kubectl commands) */
|
|
32
|
+
solution: string;
|
|
33
|
+
/** Related resource (pod name, namespace, etc.) */
|
|
34
|
+
resource?: {
|
|
35
|
+
kind: string;
|
|
36
|
+
name: string;
|
|
37
|
+
namespace: string;
|
|
38
|
+
};
|
|
39
|
+
/** Related log lines */
|
|
40
|
+
relevantLogs?: string[];
|
|
41
|
+
/** Related events */
|
|
42
|
+
relatedEvents?: K8sEvent[];
|
|
43
|
+
/** Discovery timestamp */
|
|
44
|
+
timestamp: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pod diagnostics result
|
|
48
|
+
*
|
|
49
|
+
* Comprehensive diagnostics combining pod status, resource usage, events, etc.
|
|
50
|
+
*/
|
|
51
|
+
export interface PodDiagnostics {
|
|
52
|
+
/** Pod basic information */
|
|
53
|
+
podInfo: {
|
|
54
|
+
name: string;
|
|
55
|
+
namespace: string;
|
|
56
|
+
phase: PodPhase;
|
|
57
|
+
startTime?: string;
|
|
58
|
+
nodeName?: string;
|
|
59
|
+
hostIP?: string;
|
|
60
|
+
podIP?: string;
|
|
61
|
+
};
|
|
62
|
+
/** Container status */
|
|
63
|
+
containers: ContainerStatus[];
|
|
64
|
+
/** Detected issues */
|
|
65
|
+
issues: DiagnosticIssue[];
|
|
66
|
+
/** Resource usage */
|
|
67
|
+
resources: ResourceUsage;
|
|
68
|
+
/** Recent events (problem-related) */
|
|
69
|
+
events: K8sEvent[];
|
|
70
|
+
/** Diagnosis summary */
|
|
71
|
+
summary: string;
|
|
72
|
+
/** Health score (0-100) */
|
|
73
|
+
healthScore: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Container status
|
|
77
|
+
*/
|
|
78
|
+
export interface ContainerStatus {
|
|
79
|
+
/** Container name */
|
|
80
|
+
name: string;
|
|
81
|
+
/** Ready status */
|
|
82
|
+
ready: boolean;
|
|
83
|
+
/** Restart count */
|
|
84
|
+
restartCount: number;
|
|
85
|
+
/** Current state */
|
|
86
|
+
state: {
|
|
87
|
+
running?: {
|
|
88
|
+
startedAt: string;
|
|
89
|
+
};
|
|
90
|
+
waiting?: {
|
|
91
|
+
reason: string;
|
|
92
|
+
message?: string;
|
|
93
|
+
};
|
|
94
|
+
terminated?: {
|
|
95
|
+
reason: string;
|
|
96
|
+
exitCode: number;
|
|
97
|
+
message?: string;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
/** Last state (if restarted) */
|
|
101
|
+
lastState?: {
|
|
102
|
+
terminated?: {
|
|
103
|
+
reason: string;
|
|
104
|
+
exitCode: number;
|
|
105
|
+
finishedAt: string;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
/** Image */
|
|
109
|
+
image: string;
|
|
110
|
+
/** Image pull status */
|
|
111
|
+
imageID?: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resource usage
|
|
115
|
+
*
|
|
116
|
+
* CPU and memory usage with threshold comparison results
|
|
117
|
+
*/
|
|
118
|
+
export interface ResourceUsage {
|
|
119
|
+
/** CPU usage */
|
|
120
|
+
cpu: {
|
|
121
|
+
/** Current usage (millicores) */
|
|
122
|
+
current?: number;
|
|
123
|
+
/** Requested amount (requests) */
|
|
124
|
+
requested?: number;
|
|
125
|
+
/** Limit amount (limits) */
|
|
126
|
+
limit?: number;
|
|
127
|
+
/** Usage percentage (%) */
|
|
128
|
+
usagePercent?: number;
|
|
129
|
+
/** Threshold exceeded */
|
|
130
|
+
isThrottled: boolean;
|
|
131
|
+
};
|
|
132
|
+
/** Memory usage */
|
|
133
|
+
memory: {
|
|
134
|
+
/** Current usage (bytes) */
|
|
135
|
+
current?: number;
|
|
136
|
+
/** Requested amount (requests) */
|
|
137
|
+
requested?: number;
|
|
138
|
+
/** Limit amount (limits) */
|
|
139
|
+
limit?: number;
|
|
140
|
+
/** Usage percentage (%) */
|
|
141
|
+
usagePercent?: number;
|
|
142
|
+
/** OOM risk */
|
|
143
|
+
isOOMRisk: boolean;
|
|
144
|
+
};
|
|
145
|
+
/** Disk usage */
|
|
146
|
+
disk?: {
|
|
147
|
+
current?: number;
|
|
148
|
+
limit?: number;
|
|
149
|
+
usagePercent?: number;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* K8s event
|
|
154
|
+
*
|
|
155
|
+
* Events shown in kubectl describe
|
|
156
|
+
*/
|
|
157
|
+
export interface K8sEvent {
|
|
158
|
+
/** Event type (Normal/Warning) */
|
|
159
|
+
type: string;
|
|
160
|
+
/** Reason (e.g., "Failed", "BackOff") */
|
|
161
|
+
reason: string;
|
|
162
|
+
/** Message */
|
|
163
|
+
message: string;
|
|
164
|
+
/** Occurrence count */
|
|
165
|
+
count: number;
|
|
166
|
+
/** First occurrence timestamp */
|
|
167
|
+
firstTimestamp: string;
|
|
168
|
+
/** Last occurrence timestamp */
|
|
169
|
+
lastTimestamp: string;
|
|
170
|
+
/** Source component */
|
|
171
|
+
source?: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Log analysis result
|
|
175
|
+
*
|
|
176
|
+
* Not just log output, but analyzed error patterns
|
|
177
|
+
*/
|
|
178
|
+
export interface LogAnalysis {
|
|
179
|
+
/** Total line count */
|
|
180
|
+
totalLines: number;
|
|
181
|
+
/** Error lines */
|
|
182
|
+
errorLines: LogEntry[];
|
|
183
|
+
/** Warning lines */
|
|
184
|
+
warningLines: LogEntry[];
|
|
185
|
+
/** Detected error patterns */
|
|
186
|
+
patterns: ErrorPattern[];
|
|
187
|
+
/** Repeated errors (same error multiple times) */
|
|
188
|
+
repeatedErrors: RepeatedError[];
|
|
189
|
+
/** Log analysis summary */
|
|
190
|
+
summary: string;
|
|
191
|
+
/** Recommendations */
|
|
192
|
+
recommendations: string[];
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Log entry
|
|
196
|
+
*/
|
|
197
|
+
export interface LogEntry {
|
|
198
|
+
/** Line number */
|
|
199
|
+
lineNumber: number;
|
|
200
|
+
/** Log content */
|
|
201
|
+
content: string;
|
|
202
|
+
/** Timestamp (if parseable) */
|
|
203
|
+
timestamp?: string;
|
|
204
|
+
/** Log level */
|
|
205
|
+
level?: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Error pattern
|
|
209
|
+
*
|
|
210
|
+
* Patterns of commonly occurring errors with solutions
|
|
211
|
+
*/
|
|
212
|
+
export interface ErrorPattern {
|
|
213
|
+
/** Pattern name */
|
|
214
|
+
name: string;
|
|
215
|
+
/** Matched log lines */
|
|
216
|
+
matchedLines: number[];
|
|
217
|
+
/** Pattern description */
|
|
218
|
+
description: string;
|
|
219
|
+
/** Possible causes */
|
|
220
|
+
possibleCauses: string[];
|
|
221
|
+
/** Solutions */
|
|
222
|
+
solutions: string[];
|
|
223
|
+
/** Severity */
|
|
224
|
+
severity: Severity;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Repeated error
|
|
228
|
+
*/
|
|
229
|
+
export interface RepeatedError {
|
|
230
|
+
/** Error message (normalized) */
|
|
231
|
+
message: string;
|
|
232
|
+
/** Occurrence count */
|
|
233
|
+
count: number;
|
|
234
|
+
/** First occurrence line */
|
|
235
|
+
firstLine: number;
|
|
236
|
+
/** Last occurrence line */
|
|
237
|
+
lastLine: number;
|
|
238
|
+
/** Is pattern */
|
|
239
|
+
isPattern: boolean;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Network diagnostics result
|
|
243
|
+
*/
|
|
244
|
+
export interface NetworkDiagnostics {
|
|
245
|
+
/** Service connectivity */
|
|
246
|
+
serviceConnectivity: ServiceConnectivity[];
|
|
247
|
+
/** DNS issues */
|
|
248
|
+
dnsIssues: DiagnosticIssue[];
|
|
249
|
+
/** Network policy issues */
|
|
250
|
+
networkPolicyIssues: DiagnosticIssue[];
|
|
251
|
+
/** Ingress issues */
|
|
252
|
+
ingressIssues: DiagnosticIssue[];
|
|
253
|
+
/** Diagnosis summary */
|
|
254
|
+
summary: string;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Service connectivity
|
|
258
|
+
*/
|
|
259
|
+
export interface ServiceConnectivity {
|
|
260
|
+
/** Service name */
|
|
261
|
+
serviceName: string;
|
|
262
|
+
/** Namespace */
|
|
263
|
+
namespace: string;
|
|
264
|
+
/** Reachable status */
|
|
265
|
+
isReachable: boolean;
|
|
266
|
+
/** Endpoint count */
|
|
267
|
+
endpointCount: number;
|
|
268
|
+
/** Issues */
|
|
269
|
+
issues: string[];
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Storage diagnostics result
|
|
273
|
+
*/
|
|
274
|
+
export interface StorageDiagnostics {
|
|
275
|
+
/** PVC status */
|
|
276
|
+
pvcStatus: PVCStatus[];
|
|
277
|
+
/** Volume mount issues */
|
|
278
|
+
mountIssues: DiagnosticIssue[];
|
|
279
|
+
/** Capacity issues */
|
|
280
|
+
capacityIssues: DiagnosticIssue[];
|
|
281
|
+
/** Diagnosis summary */
|
|
282
|
+
summary: string;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* PVC status
|
|
286
|
+
*/
|
|
287
|
+
export interface PVCStatus {
|
|
288
|
+
/** PVC name */
|
|
289
|
+
name: string;
|
|
290
|
+
/** Namespace */
|
|
291
|
+
namespace: string;
|
|
292
|
+
/** Phase (Bound/Pending/Lost) */
|
|
293
|
+
phase: string;
|
|
294
|
+
/** Requested capacity */
|
|
295
|
+
requestedCapacity: string;
|
|
296
|
+
/** Actual capacity */
|
|
297
|
+
actualCapacity?: string;
|
|
298
|
+
/** Storage class */
|
|
299
|
+
storageClass?: string;
|
|
300
|
+
/** Issues */
|
|
301
|
+
issues: string[];
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Cluster health diagnosis
|
|
305
|
+
*/
|
|
306
|
+
export interface ClusterHealth {
|
|
307
|
+
/** Overall health score (0-100) */
|
|
308
|
+
overallScore: number;
|
|
309
|
+
/** Node health */
|
|
310
|
+
nodeHealth: {
|
|
311
|
+
total: number;
|
|
312
|
+
ready: number;
|
|
313
|
+
notReady: number;
|
|
314
|
+
issues: DiagnosticIssue[];
|
|
315
|
+
};
|
|
316
|
+
/** Pod health */
|
|
317
|
+
podHealth: {
|
|
318
|
+
total: number;
|
|
319
|
+
running: number;
|
|
320
|
+
pending: number;
|
|
321
|
+
failed: number;
|
|
322
|
+
crashLooping: number;
|
|
323
|
+
issues: DiagnosticIssue[];
|
|
324
|
+
};
|
|
325
|
+
/** Resource utilization */
|
|
326
|
+
resourceUtilization: {
|
|
327
|
+
cpu: number;
|
|
328
|
+
memory: number;
|
|
329
|
+
storage: number;
|
|
330
|
+
};
|
|
331
|
+
/** Critical issues */
|
|
332
|
+
criticalIssues: DiagnosticIssue[];
|
|
333
|
+
/** Recommendations */
|
|
334
|
+
recommendations: string[];
|
|
335
|
+
/** Diagnosis summary */
|
|
336
|
+
summary: string;
|
|
337
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple in-memory cache with TTL
|
|
3
|
+
*
|
|
4
|
+
* @author zerry
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Simple memory cache with TTL support
|
|
8
|
+
*/
|
|
9
|
+
export declare class MemoryCache<T> {
|
|
10
|
+
private cache;
|
|
11
|
+
private defaultTTL;
|
|
12
|
+
/**
|
|
13
|
+
* @param defaultTTL Default time-to-live in milliseconds (default: 30 seconds)
|
|
14
|
+
*/
|
|
15
|
+
constructor(defaultTTL?: number);
|
|
16
|
+
/**
|
|
17
|
+
* Get value from cache
|
|
18
|
+
*
|
|
19
|
+
* @param key Cache key
|
|
20
|
+
* @returns Cached value or undefined if not found or expired
|
|
21
|
+
*/
|
|
22
|
+
get(key: string): T | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Set value in cache
|
|
25
|
+
*
|
|
26
|
+
* @param key Cache key
|
|
27
|
+
* @param value Value to cache
|
|
28
|
+
* @param ttl Time-to-live in milliseconds (optional, uses default if not provided)
|
|
29
|
+
*/
|
|
30
|
+
set(key: string, value: T, ttl?: number): void;
|
|
31
|
+
/**
|
|
32
|
+
* Delete value from cache
|
|
33
|
+
*
|
|
34
|
+
* @param key Cache key
|
|
35
|
+
*/
|
|
36
|
+
delete(key: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Clear all cache entries
|
|
39
|
+
*/
|
|
40
|
+
clear(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Get cache size
|
|
43
|
+
*/
|
|
44
|
+
size(): number;
|
|
45
|
+
/**
|
|
46
|
+
* Clean up expired entries
|
|
47
|
+
*/
|
|
48
|
+
cleanup(): void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get or compute cached value
|
|
52
|
+
*
|
|
53
|
+
* @param cache Cache instance
|
|
54
|
+
* @param key Cache key
|
|
55
|
+
* @param computeFn Function to compute value if not cached
|
|
56
|
+
* @param ttl Optional TTL override
|
|
57
|
+
* @returns Cached or computed value
|
|
58
|
+
*/
|
|
59
|
+
export declare function getOrCompute<T>(cache: MemoryCache<T>, key: string, computeFn: () => Promise<T>, ttl?: number): Promise<T>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple in-memory cache with TTL
|
|
3
|
+
*
|
|
4
|
+
* @author zerry
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Simple memory cache with TTL support
|
|
8
|
+
*/
|
|
9
|
+
export class MemoryCache {
|
|
10
|
+
cache = new Map();
|
|
11
|
+
defaultTTL;
|
|
12
|
+
/**
|
|
13
|
+
* @param defaultTTL Default time-to-live in milliseconds (default: 30 seconds)
|
|
14
|
+
*/
|
|
15
|
+
constructor(defaultTTL = 30000) {
|
|
16
|
+
this.defaultTTL = defaultTTL;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get value from cache
|
|
20
|
+
*
|
|
21
|
+
* @param key Cache key
|
|
22
|
+
* @returns Cached value or undefined if not found or expired
|
|
23
|
+
*/
|
|
24
|
+
get(key) {
|
|
25
|
+
const entry = this.cache.get(key);
|
|
26
|
+
if (!entry) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
// Check if expired
|
|
30
|
+
if (Date.now() > entry.expiresAt) {
|
|
31
|
+
this.cache.delete(key);
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return entry.value;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Set value in cache
|
|
38
|
+
*
|
|
39
|
+
* @param key Cache key
|
|
40
|
+
* @param value Value to cache
|
|
41
|
+
* @param ttl Time-to-live in milliseconds (optional, uses default if not provided)
|
|
42
|
+
*/
|
|
43
|
+
set(key, value, ttl) {
|
|
44
|
+
const expiresAt = Date.now() + (ttl ?? this.defaultTTL);
|
|
45
|
+
this.cache.set(key, { value, expiresAt });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Delete value from cache
|
|
49
|
+
*
|
|
50
|
+
* @param key Cache key
|
|
51
|
+
*/
|
|
52
|
+
delete(key) {
|
|
53
|
+
this.cache.delete(key);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Clear all cache entries
|
|
57
|
+
*/
|
|
58
|
+
clear() {
|
|
59
|
+
this.cache.clear();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get cache size
|
|
63
|
+
*/
|
|
64
|
+
size() {
|
|
65
|
+
return this.cache.size;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Clean up expired entries
|
|
69
|
+
*/
|
|
70
|
+
cleanup() {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
73
|
+
if (now > entry.expiresAt) {
|
|
74
|
+
this.cache.delete(key);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get or compute cached value
|
|
81
|
+
*
|
|
82
|
+
* @param cache Cache instance
|
|
83
|
+
* @param key Cache key
|
|
84
|
+
* @param computeFn Function to compute value if not cached
|
|
85
|
+
* @param ttl Optional TTL override
|
|
86
|
+
* @returns Cached or computed value
|
|
87
|
+
*/
|
|
88
|
+
export async function getOrCompute(cache, key, computeFn, ttl) {
|
|
89
|
+
// Try to get from cache first
|
|
90
|
+
const cached = cache.get(key);
|
|
91
|
+
if (cached !== undefined) {
|
|
92
|
+
return cached;
|
|
93
|
+
}
|
|
94
|
+
// Compute value
|
|
95
|
+
const value = await computeFn();
|
|
96
|
+
// Cache it
|
|
97
|
+
cache.set(key, value, ttl);
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result formatting utilities
|
|
3
|
+
*
|
|
4
|
+
* @author zerry
|
|
5
|
+
*/
|
|
6
|
+
import type { DiagnosticIssue, Severity } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Convert bytes to human-readable format
|
|
9
|
+
*
|
|
10
|
+
* 1024 -> "1 KiB"
|
|
11
|
+
* 1048576 -> "1 MiB"
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatBytes(bytes: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Convert millicores to human-readable format
|
|
16
|
+
*
|
|
17
|
+
* 1000 -> "1 core"
|
|
18
|
+
* 500 -> "0.5 cores"
|
|
19
|
+
*/
|
|
20
|
+
export declare function formatCPU(millicores: number): string;
|
|
21
|
+
/**
|
|
22
|
+
* Return emoji based on severity
|
|
23
|
+
*/
|
|
24
|
+
export declare function getSeverityEmoji(severity: Severity): string;
|
|
25
|
+
/**
|
|
26
|
+
* Convert health score to emoji
|
|
27
|
+
*/
|
|
28
|
+
export declare function getHealthEmoji(score: number): string;
|
|
29
|
+
/**
|
|
30
|
+
* Convert timestamp to relative time
|
|
31
|
+
*
|
|
32
|
+
* "2 hours ago", "5 minutes ago" λ±
|
|
33
|
+
*/
|
|
34
|
+
export declare function timeAgo(timestamp: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Format issue list as markdown
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatIssues(issues: DiagnosticIssue[]): string;
|
|
39
|
+
/**
|
|
40
|
+
* Output diagnosis results as clean table
|
|
41
|
+
*/
|
|
42
|
+
export declare function createTable(headers: string[], rows: string[][]): string;
|
|
43
|
+
/**
|
|
44
|
+
* Display percent as progress bar
|
|
45
|
+
*
|
|
46
|
+
* 85% -> "ββββββββββ 85%"
|
|
47
|
+
*/
|
|
48
|
+
export declare function progressBar(percent: number, width?: number): string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result formatting utilities
|
|
3
|
+
*
|
|
4
|
+
* @author zerry
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Convert bytes to human-readable format
|
|
8
|
+
*
|
|
9
|
+
* 1024 -> "1 KiB"
|
|
10
|
+
* 1048576 -> "1 MiB"
|
|
11
|
+
*/
|
|
12
|
+
export function formatBytes(bytes) {
|
|
13
|
+
if (bytes === 0)
|
|
14
|
+
return '0 B';
|
|
15
|
+
const k = 1024;
|
|
16
|
+
const sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
|
|
17
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
18
|
+
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Convert millicores to human-readable format
|
|
22
|
+
*
|
|
23
|
+
* 1000 -> "1 core"
|
|
24
|
+
* 500 -> "0.5 cores"
|
|
25
|
+
*/
|
|
26
|
+
export function formatCPU(millicores) {
|
|
27
|
+
const cores = millicores / 1000;
|
|
28
|
+
return `${cores.toFixed(2)} ${cores === 1 ? 'core' : 'cores'}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Return emoji based on severity
|
|
32
|
+
*/
|
|
33
|
+
export function getSeverityEmoji(severity) {
|
|
34
|
+
const emojis = {
|
|
35
|
+
critical: 'π΄',
|
|
36
|
+
high: 'π ',
|
|
37
|
+
medium: 'π‘',
|
|
38
|
+
low: 'π΅',
|
|
39
|
+
info: 'βͺ',
|
|
40
|
+
};
|
|
41
|
+
return emojis[severity] || 'βͺ';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert health score to emoji
|
|
45
|
+
*/
|
|
46
|
+
export function getHealthEmoji(score) {
|
|
47
|
+
if (score >= 90)
|
|
48
|
+
return 'π';
|
|
49
|
+
if (score >= 70)
|
|
50
|
+
return 'π';
|
|
51
|
+
if (score >= 50)
|
|
52
|
+
return 'π§‘';
|
|
53
|
+
return 'β€οΈ';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Convert timestamp to relative time
|
|
57
|
+
*
|
|
58
|
+
* "2 hours ago", "5 minutes ago" λ±
|
|
59
|
+
*/
|
|
60
|
+
export function timeAgo(timestamp) {
|
|
61
|
+
const now = new Date();
|
|
62
|
+
const past = new Date(timestamp);
|
|
63
|
+
const diffMs = now.getTime() - past.getTime();
|
|
64
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
65
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
66
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
67
|
+
const diffDay = Math.floor(diffHour / 24);
|
|
68
|
+
if (diffDay > 0)
|
|
69
|
+
return `${diffDay}days ago`;
|
|
70
|
+
if (diffHour > 0)
|
|
71
|
+
return `${diffHour}hours ago`;
|
|
72
|
+
if (diffMin > 0)
|
|
73
|
+
return `${diffMin}minutes ago`;
|
|
74
|
+
return `${diffSec}seconds ago`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format issue list as markdown
|
|
78
|
+
*/
|
|
79
|
+
export function formatIssues(issues) {
|
|
80
|
+
if (issues.length === 0) {
|
|
81
|
+
return 'β
No issues found!';
|
|
82
|
+
}
|
|
83
|
+
let result = `## π Detected Issues (${issues.length})\n\n`;
|
|
84
|
+
// Severityλ³λ‘ μ λ ¬
|
|
85
|
+
const sorted = [...issues].sort((a, b) => {
|
|
86
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
87
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
88
|
+
});
|
|
89
|
+
for (const issue of sorted) {
|
|
90
|
+
result += `### ${getSeverityEmoji(issue.severity)} ${issue.type}\n\n`;
|
|
91
|
+
result += `**Severity**: ${issue.severity.toUpperCase()}\n\n`;
|
|
92
|
+
result += `**Problem**: ${issue.message}\n\n`;
|
|
93
|
+
result += `**Root Cause**: ${issue.rootCause}\n\n`;
|
|
94
|
+
result += `**Solution**:\n${issue.solution}\n\n`;
|
|
95
|
+
if (issue.relevantLogs && issue.relevantLogs.length > 0) {
|
|
96
|
+
result += `**Related Logs**:\n\`\`\`\n${issue.relevantLogs.join('\n')}\n\`\`\`\n\n`;
|
|
97
|
+
}
|
|
98
|
+
if (issue.resource) {
|
|
99
|
+
result += `**Resource**: ${issue.resource.kind}/${issue.resource.name} (ns: ${issue.resource.namespace})\n\n`;
|
|
100
|
+
}
|
|
101
|
+
result += '---\n\n';
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Output diagnosis results as clean table
|
|
107
|
+
*/
|
|
108
|
+
export function createTable(headers, rows) {
|
|
109
|
+
const colWidths = headers.map((h, i) => {
|
|
110
|
+
const maxRowWidth = Math.max(...rows.map(r => (r[i] || '').length));
|
|
111
|
+
return Math.max(h.length, maxRowWidth);
|
|
112
|
+
});
|
|
113
|
+
let table = '| ' + headers.map((h, i) => h.padEnd(colWidths[i])).join(' | ') + ' |\n';
|
|
114
|
+
table += '| ' + colWidths.map(w => '-'.repeat(w)).join(' | ') + ' |\n';
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
table += '| ' + row.map((cell, i) => (cell || '').padEnd(colWidths[i])).join(' | ') + ' |\n';
|
|
117
|
+
}
|
|
118
|
+
return table;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Display percent as progress bar
|
|
122
|
+
*
|
|
123
|
+
* 85% -> "ββββββββββ 85%"
|
|
124
|
+
*/
|
|
125
|
+
export function progressBar(percent, width = 10) {
|
|
126
|
+
const filled = Math.round((percent / 100) * width);
|
|
127
|
+
const empty = width - filled;
|
|
128
|
+
return 'β'.repeat(filled) + 'β'.repeat(empty) + ` ${percent.toFixed(1)}%`;
|
|
129
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes client initialization
|
|
3
|
+
*
|
|
4
|
+
* @author zerry
|
|
5
|
+
*/
|
|
6
|
+
import * as k8s from '@kubernetes/client-node';
|
|
7
|
+
/**
|
|
8
|
+
* Load K8s configuration
|
|
9
|
+
*
|
|
10
|
+
* Automatically finds and loads kubeconfig.
|
|
11
|
+
* Works in-cluster as well
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadK8sConfig(): k8s.KubeConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Create K8s API clients
|
|
16
|
+
*
|
|
17
|
+
* Creates frequently used API clients at once
|
|
18
|
+
*/
|
|
19
|
+
export declare function createK8sClients(kc: k8s.KubeConfig): {
|
|
20
|
+
core: k8s.CoreV1Api;
|
|
21
|
+
apps: k8s.AppsV1Api;
|
|
22
|
+
batch: k8s.BatchV1Api;
|
|
23
|
+
networking: k8s.NetworkingV1Api;
|
|
24
|
+
storage: k8s.StorageV1Api;
|
|
25
|
+
log: k8s.Log;
|
|
26
|
+
metrics: k8s.Metrics;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Validate namespace
|
|
30
|
+
*
|
|
31
|
+
* Check if namespace actually exists
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateNamespace(coreApi: k8s.CoreV1Api, namespace: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if pod exists
|
|
36
|
+
*/
|
|
37
|
+
export declare function podExists(coreApi: k8s.CoreV1Api, namespace: string, podName: string): Promise<boolean>;
|