@oneuptime/common 10.0.34 → 10.0.35

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 (66) hide show
  1. package/Models/DatabaseModels/Index.ts +3 -0
  2. package/Models/DatabaseModels/KubernetesCluster.ts +640 -0
  3. package/Server/API/IPWhitelistAPI.ts +31 -0
  4. package/Server/API/Index.ts +2 -0
  5. package/Server/EnvironmentConfig.ts +2 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1773761409952-MigrationName.ts +137 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1774000000000-MigrationName.ts +80 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  9. package/Server/Services/KubernetesClusterService.ts +109 -0
  10. package/Server/Services/UserService.ts +6 -0
  11. package/Server/Services/UserSessionService.ts +23 -0
  12. package/Server/Utils/Express.ts +0 -6
  13. package/Server/Utils/StartServer.ts +7 -3
  14. package/Server/Utils/VM/VMRunner.ts +3 -0
  15. package/Types/Icon/IconProp.ts +1 -0
  16. package/Types/Permission.ts +42 -0
  17. package/UI/Components/Icon/Icon.tsx +51 -0
  18. package/UI/Components/LogsViewer/LogsViewer.tsx +2 -0
  19. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +25 -0
  20. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +85 -53
  21. package/UI/Components/Navbar/NavBarMenu.tsx +2 -2
  22. package/Utils/Traces/CriticalPath.ts +348 -0
  23. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  24. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  25. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +661 -0
  26. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -0
  27. package/build/dist/Server/API/IPWhitelistAPI.js +24 -0
  28. package/build/dist/Server/API/IPWhitelistAPI.js.map +1 -0
  29. package/build/dist/Server/API/Index.js +2 -0
  30. package/build/dist/Server/API/Index.js.map +1 -1
  31. package/build/dist/Server/EnvironmentConfig.js +1 -0
  32. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  33. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773761409952-MigrationName.js +52 -0
  34. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773761409952-MigrationName.js.map +1 -0
  35. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000000-MigrationName.js +35 -0
  36. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000000-MigrationName.js.map +1 -0
  37. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  38. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  39. package/build/dist/Server/Services/KubernetesClusterService.js +117 -0
  40. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -0
  41. package/build/dist/Server/Services/UserService.js +5 -0
  42. package/build/dist/Server/Services/UserService.js.map +1 -1
  43. package/build/dist/Server/Services/UserSessionService.js +20 -0
  44. package/build/dist/Server/Services/UserSessionService.js.map +1 -1
  45. package/build/dist/Server/Utils/Express.js +1 -42
  46. package/build/dist/Server/Utils/Express.js.map +1 -1
  47. package/build/dist/Server/Utils/StartServer.js +4 -3
  48. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  49. package/build/dist/Server/Utils/VM/VMRunner.js +3 -0
  50. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  51. package/build/dist/Types/Icon/IconProp.js +1 -0
  52. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  53. package/build/dist/Types/Permission.js +36 -0
  54. package/build/dist/Types/Permission.js.map +1 -1
  55. package/build/dist/UI/Components/Icon/Icon.js +27 -0
  56. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  57. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +1 -1
  58. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  59. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +4 -0
  60. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  61. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +50 -43
  62. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  63. package/build/dist/UI/Components/Navbar/NavBarMenu.js +2 -2
  64. package/build/dist/Utils/Traces/CriticalPath.js +240 -0
  65. package/build/dist/Utils/Traces/CriticalPath.js.map +1 -0
  66. package/package.json +1 -1
@@ -208,9 +208,9 @@ const CodeBlock: FunctionComponent<{
208
208
  (language ? language.charAt(0).toUpperCase() + language.slice(1) : "");
209
209
 
210
210
  return (
211
- <div className="relative rounded-lg mt-4 mb-4 overflow-hidden border border-gray-700">
211
+ <div className="relative rounded-lg mt-3 mb-3 overflow-hidden border border-gray-200 shadow-sm">
212
212
  {/* Header bar */}
213
- <div className="flex items-center justify-between px-3 py-1.5 bg-gray-800/60 border-b border-gray-700/60">
213
+ <div className="flex items-center justify-between px-3 py-1.5 bg-gray-800 border-b border-gray-700">
214
214
  <span className="text-[11px] font-medium uppercase tracking-wider text-gray-400 select-none">
215
215
  {displayLang}
216
216
  </span>
@@ -261,7 +261,7 @@ const CodeBlock: FunctionComponent<{
261
261
  children={content}
262
262
  language={language}
263
263
  style={vscDarkPlus}
264
- className="!rounded-none !mt-0 !mb-0 !bg-gray-900 !pt-6 !pb-4 !px-4 text-sm !border-0"
264
+ className="!rounded-none !mt-0 !mb-0 !bg-gray-900 !pt-3 !pb-3 !px-4 text-sm !border-0"
265
265
  codeTagProps={{ className: "font-mono" }}
266
266
  />
267
267
  </div>
@@ -279,7 +279,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
279
279
  h1: ({ ...props }: any) => {
280
280
  return (
281
281
  <h1
282
- className="text-4xl mt-8 mb-6 border-b-2 border-blue-500 pb-2 text-gray-900 font-bold"
282
+ className="text-lg mt-6 mb-3 text-gray-900 font-bold"
283
283
  {...props}
284
284
  />
285
285
  );
@@ -287,7 +287,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
287
287
  h2: ({ ...props }: any) => {
288
288
  return (
289
289
  <h2
290
- className="text-3xl mt-6 mb-4 border-b border-gray-300 pb-1 text-gray-900 font-semibold"
290
+ className="text-base mt-6 mb-2 text-gray-900 font-semibold"
291
291
  {...props}
292
292
  />
293
293
  );
@@ -295,7 +295,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
295
295
  h3: ({ ...props }: any) => {
296
296
  return (
297
297
  <h3
298
- className="text-2xl mt-6 mb-3 text-gray-900 font-semibold"
298
+ className="text-base mt-4 mb-2 text-gray-900 font-semibold"
299
299
  {...props}
300
300
  />
301
301
  );
@@ -303,7 +303,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
303
303
  h4: ({ ...props }: any) => {
304
304
  return (
305
305
  <h4
306
- className="text-xl mt-5 mb-3 text-gray-900 font-medium"
306
+ className="text-sm mt-3 mb-2 text-gray-900 font-semibold"
307
307
  {...props}
308
308
  />
309
309
  );
@@ -311,7 +311,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
311
311
  h5: ({ ...props }: any) => {
312
312
  return (
313
313
  <h5
314
- className="text-lg mt-4 mb-2 text-gray-900 font-medium"
314
+ className="text-sm mt-3 mb-1 text-gray-900 font-medium"
315
315
  {...props}
316
316
  />
317
317
  );
@@ -319,7 +319,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
319
319
  h6: ({ ...props }: any) => {
320
320
  return (
321
321
  <h6
322
- className="text-base mt-3 mb-2 text-gray-900 font-medium"
322
+ className="text-sm mt-2 mb-1 text-gray-700 font-medium"
323
323
  {...props}
324
324
  />
325
325
  );
@@ -327,7 +327,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
327
327
  p: ({ ...props }: any) => {
328
328
  return (
329
329
  <p
330
- className="text-base mt-3 mb-4 text-gray-700 leading-relaxed"
330
+ className="text-sm mt-2 mb-3 text-gray-700 leading-relaxed"
331
331
  {...props}
332
332
  />
333
333
  );
@@ -352,24 +352,24 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
352
352
  return <>{children}</>;
353
353
  }
354
354
 
355
- // Avoid double borders when SyntaxHighlighter is already styling the block.
356
- const isSyntaxHighlighter: boolean =
355
+ /*
356
+ * If the child is a custom component (CodeBlock, MermaidDiagram, etc.)
357
+ * rather than a plain HTML element like <code>, skip pre styling.
358
+ * Checking typeof type !== "string" is minification-safe unlike checking type.name.
359
+ */
360
+ const isCustomComponent: boolean =
357
361
  React.isValidElement(children) &&
358
- // name can be 'SyntaxHighlighter' or wrapped/minified; fall back to presence of 'children' prop with 'react-syntax-highlighter' data attribute.
359
- (((children as any).type &&
360
- ((children as any).type.name === "SyntaxHighlighter" ||
361
- (children as any).type.displayName ===
362
- "SyntaxHighlighter")) ||
363
- (children as any).props?.className?.includes(
364
- "syntax-highlighter",
365
- ));
362
+ typeof (children as any).type !== "string";
366
363
 
367
- const baseClass: string = isSyntaxHighlighter
368
- ? "mt-4 mb-4 rounded-lg overflow-hidden"
369
- : "bg-gray-900 text-gray-100 mt-4 mb-4 p-2 rounded-lg text-sm overflow-x-auto border border-gray-700";
364
+ if (isCustomComponent) {
365
+ return <>{children}</>;
366
+ }
370
367
 
371
368
  return (
372
- <pre className={baseClass} {...rest}>
369
+ <pre
370
+ className="bg-gray-900 text-gray-100 mt-3 mb-3 p-3 rounded-md text-sm overflow-x-auto border border-gray-700"
371
+ {...rest}
372
+ >
373
373
  {children}
374
374
  </pre>
375
375
  );
@@ -377,7 +377,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
377
377
  strong: ({ ...props }: any) => {
378
378
  return (
379
379
  <strong
380
- className="text-base font-semibold text-gray-900"
380
+ className="text-sm font-semibold text-gray-900"
381
381
  {...props}
382
382
  />
383
383
  );
@@ -385,56 +385,76 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
385
385
  li: ({ ...props }: any) => {
386
386
  return (
387
387
  <li
388
- className="text-base mt-2 mb-1 text-gray-700 leading-relaxed"
388
+ className="text-sm mt-1 mb-1 text-gray-700 leading-relaxed"
389
389
  {...props}
390
390
  />
391
391
  );
392
392
  },
393
393
  ul: ({ ...props }: any) => {
394
- return <ul className="list-disc pl-8 mt-2 mb-4" {...props} />;
394
+ return <ul className="list-disc pl-6 mt-1 mb-3" {...props} />;
395
395
  },
396
396
  ol: ({ ...props }: any) => {
397
- return <ol className="list-decimal pl-8 mt-2 mb-4" {...props} />;
397
+ return <ol className="list-decimal pl-6 mt-1 mb-3" {...props} />;
398
398
  },
399
- blockquote: ({ ...props }: any) => {
399
+ blockquote: ({ children, ...props }: any) => {
400
400
  return (
401
401
  <blockquote
402
- className="border-l-4 border-blue-500 pl-4 italic text-gray-600 bg-gray-50 py-2 my-4"
402
+ className="rounded-lg border border-amber-200 bg-amber-50/50 my-4 not-italic overflow-hidden"
403
403
  {...props}
404
- />
404
+ >
405
+ <div className="flex items-start gap-3 px-4 py-3">
406
+ <svg
407
+ className="h-5 w-5 flex-shrink-0 text-amber-500 mt-0.5"
408
+ fill="none"
409
+ stroke="currentColor"
410
+ strokeWidth={2}
411
+ viewBox="0 0 24 24"
412
+ >
413
+ <path
414
+ strokeLinecap="round"
415
+ strokeLinejoin="round"
416
+ d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"
417
+ />
418
+ </svg>
419
+ <div className="text-sm text-gray-700 leading-relaxed [&>p]:mt-0 [&>p]:mb-0 [&>p>strong:first-child]:text-amber-700 [&>p>strong:first-child]:mr-1">
420
+ {children}
421
+ </div>
422
+ </div>
423
+ </blockquote>
405
424
  );
406
425
  },
407
426
  table: ({ ...props }: any) => {
408
427
  return (
409
- <table
410
- className="min-w-full table-auto border-collapse border border-gray-300 mt-4 mb-4"
411
- {...props}
412
- />
428
+ <div className="overflow-hidden rounded-lg border border-gray-200 mt-4 mb-4 shadow-sm">
429
+ <table
430
+ className="min-w-full table-auto border-collapse text-sm"
431
+ {...props}
432
+ />
433
+ </div>
413
434
  );
414
435
  },
415
436
  thead: ({ ...props }: any) => {
416
- return <thead className="bg-gray-100" {...props} />;
437
+ return <thead className="bg-gray-50" {...props} />;
417
438
  },
418
439
  tbody: ({ ...props }: any) => {
419
- return <tbody {...props} />;
440
+ return <tbody className="divide-y divide-gray-100" {...props} />;
420
441
  },
421
442
  tr: ({ ...props }: any) => {
422
- return <tr className="border-b border-gray-200" {...props} />;
443
+ return (
444
+ <tr className="hover:bg-gray-50 transition-colors" {...props} />
445
+ );
423
446
  },
424
447
  th: ({ ...props }: any) => {
425
448
  return (
426
449
  <th
427
- className="px-4 py-2 text-left text-sm font-semibold text-gray-900 border border-gray-300"
450
+ className="px-4 py-2.5 text-left text-xs font-semibold uppercase tracking-wider text-gray-500 border-b border-gray-200"
428
451
  {...props}
429
452
  />
430
453
  );
431
454
  },
432
455
  td: ({ ...props }: any) => {
433
456
  return (
434
- <td
435
- className="px-4 py-2 text-sm text-gray-700 border border-gray-300"
436
- {...props}
437
- />
457
+ <td className="px-4 py-2.5 text-sm text-gray-700" {...props} />
438
458
  );
439
459
  },
440
460
  hr: ({ ...props }: any) => {
@@ -457,19 +477,31 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
457
477
  return <MermaidDiagram chart={content} />;
458
478
  }
459
479
 
460
- const codeClassName: string =
461
- content.includes("\n") ||
462
- (match &&
480
+ const isMultiline: boolean = content.includes("\n");
481
+ const hasLanguage: boolean = Boolean(
482
+ match &&
463
483
  match?.filter((item: string) => {
464
484
  return item.includes("language-");
465
- }).length > 0)
466
- ? ""
467
- : "text-sm px-2 py-1 bg-gray-200 rounded text-gray-900 font-mono";
485
+ }).length > 0,
486
+ );
468
487
 
469
- return match ? (
470
- <CodeBlock language={match[1]!} content={content} rest={rest} />
471
- ) : (
472
- <code className={codeClassName} {...rest}>
488
+ // Multiline code blocks (with or without language) get the full CodeBlock treatment
489
+ if (hasLanguage || isMultiline) {
490
+ return (
491
+ <CodeBlock
492
+ language={match ? match[1]! : "text"}
493
+ content={content}
494
+ rest={rest}
495
+ />
496
+ );
497
+ }
498
+
499
+ // Inline code
500
+ return (
501
+ <code
502
+ className="text-xs px-1.5 py-0.5 bg-gray-100 border border-gray-200 rounded text-gray-800 font-mono"
503
+ {...rest}
504
+ >
473
505
  {children}
474
506
  </code>
475
507
  );
@@ -39,7 +39,7 @@ const NavBarMenu: FunctionComponent<ComponentProps> = (
39
39
  );
40
40
 
41
41
  return (
42
- <div className="absolute left-0 z-10 mt-8 w-screen max-w-5xl transform px-2 sm:px-0">
42
+ <div className="absolute left-0 z-50 mt-8 w-screen max-w-5xl transform px-2 sm:px-0">
43
43
  <div className="overflow-hidden rounded-2xl shadow-xl ring-1 ring-black ring-opacity-5 bg-white">
44
44
  {/* Sections */}
45
45
  <div className="p-6">
@@ -127,7 +127,7 @@ const NavBarMenu: FunctionComponent<ComponentProps> = (
127
127
 
128
128
  return (
129
129
  <div
130
- className={`absolute left-1/2 z-10 mt-8 w-screen max-w-md -translate-x-1/2 transform px-2 sm:px-0 ${maxWidthClass}`}
130
+ className={`absolute left-1/2 z-50 mt-8 w-screen max-w-md -translate-x-1/2 transform px-2 sm:px-0 ${maxWidthClass}`}
131
131
  >
132
132
  <div className="overflow-hidden rounded-2xl shadow-xl ring-1 ring-black ring-opacity-5 bg-white">
133
133
  {/* Menu Items */}
@@ -0,0 +1,348 @@
1
+ /*
2
+ * Critical Path Analysis for distributed traces
3
+ * Computes self-time, critical path, and bottleneck identification
4
+ */
5
+
6
+ export interface SpanData {
7
+ spanId: string;
8
+ parentSpanId: string | undefined;
9
+ startTimeUnixNano: number;
10
+ endTimeUnixNano: number;
11
+ durationUnixNano: number;
12
+ serviceId: string | undefined;
13
+ name: string | undefined;
14
+ }
15
+
16
+ export interface SpanSelfTime {
17
+ spanId: string;
18
+ selfTimeUnixNano: number;
19
+ childTimeUnixNano: number;
20
+ totalTimeUnixNano: number;
21
+ selfTimePercent: number;
22
+ }
23
+
24
+ export interface CriticalPathResult {
25
+ criticalPathSpanIds: string[];
26
+ totalTraceDurationUnixNano: number;
27
+ criticalPathDurationUnixNano: number;
28
+ }
29
+
30
+ export interface ServiceBreakdown {
31
+ serviceId: string;
32
+ totalDurationUnixNano: number;
33
+ selfTimeUnixNano: number;
34
+ spanCount: number;
35
+ percentOfTrace: number;
36
+ }
37
+
38
+ export default class CriticalPathUtil {
39
+ /**
40
+ * Compute self-time for each span.
41
+ * Self-time = span duration minus the time covered by direct children,
42
+ * accounting for overlapping children.
43
+ */
44
+ public static computeSelfTimes(spans: SpanData[]): Map<string, SpanSelfTime> {
45
+ const result: Map<string, SpanSelfTime> = new Map();
46
+
47
+ // Build parent -> children index
48
+ const childrenMap: Map<string, SpanData[]> = new Map();
49
+ for (const span of spans) {
50
+ if (span.parentSpanId) {
51
+ const children: SpanData[] = childrenMap.get(span.parentSpanId) || [];
52
+ children.push(span);
53
+ childrenMap.set(span.parentSpanId, children);
54
+ }
55
+ }
56
+
57
+ for (const span of spans) {
58
+ const children: SpanData[] = childrenMap.get(span.spanId) || [];
59
+ const childTimeUnixNano: number =
60
+ CriticalPathUtil.computeMergedChildDuration(
61
+ children,
62
+ span.startTimeUnixNano,
63
+ span.endTimeUnixNano,
64
+ );
65
+
66
+ const selfTimeUnixNano: number = Math.max(
67
+ 0,
68
+ span.durationUnixNano - childTimeUnixNano,
69
+ );
70
+
71
+ result.set(span.spanId, {
72
+ spanId: span.spanId,
73
+ selfTimeUnixNano,
74
+ childTimeUnixNano,
75
+ totalTimeUnixNano: span.durationUnixNano,
76
+ selfTimePercent:
77
+ span.durationUnixNano > 0
78
+ ? (selfTimeUnixNano / span.durationUnixNano) * 100
79
+ : 0,
80
+ });
81
+ }
82
+
83
+ return result;
84
+ }
85
+
86
+ /**
87
+ * Compute the merged duration of child spans within the parent's time window.
88
+ * Handles overlapping children by merging intervals.
89
+ */
90
+ private static computeMergedChildDuration(
91
+ children: SpanData[],
92
+ parentStart: number,
93
+ parentEnd: number,
94
+ ): number {
95
+ if (children.length === 0) {
96
+ return 0;
97
+ }
98
+
99
+ // Clamp children to parent boundaries and create intervals
100
+ const intervals: Array<{ start: number; end: number }> = children
101
+ .map((child: SpanData) => {
102
+ return {
103
+ start: Math.max(child.startTimeUnixNano, parentStart),
104
+ end: Math.min(child.endTimeUnixNano, parentEnd),
105
+ };
106
+ })
107
+ .filter((interval: { start: number; end: number }) => {
108
+ return interval.end > interval.start;
109
+ });
110
+
111
+ if (intervals.length === 0) {
112
+ return 0;
113
+ }
114
+
115
+ // Sort by start time
116
+ intervals.sort(
117
+ (
118
+ a: { start: number; end: number },
119
+ b: { start: number; end: number },
120
+ ) => {
121
+ return a.start - b.start;
122
+ },
123
+ );
124
+
125
+ // Merge overlapping intervals
126
+ let mergedDuration: number = 0;
127
+ let currentStart: number = intervals[0]!.start;
128
+ let currentEnd: number = intervals[0]!.end;
129
+
130
+ for (let i: number = 1; i < intervals.length; i++) {
131
+ const interval: { start: number; end: number } = intervals[i]!;
132
+ if (interval.start <= currentEnd) {
133
+ // Overlapping - extend
134
+ currentEnd = Math.max(currentEnd, interval.end);
135
+ } else {
136
+ // Non-overlapping - add previous and start new
137
+ mergedDuration += currentEnd - currentStart;
138
+ currentStart = interval.start;
139
+ currentEnd = interval.end;
140
+ }
141
+ }
142
+
143
+ mergedDuration += currentEnd - currentStart;
144
+
145
+ return mergedDuration;
146
+ }
147
+
148
+ /**
149
+ * Compute the critical path through the trace.
150
+ * The critical path is the longest sequential chain of spans,
151
+ * accounting for parallelism (parallel children don't add to the critical path together).
152
+ */
153
+ public static computeCriticalPath(spans: SpanData[]): CriticalPathResult {
154
+ if (spans.length === 0) {
155
+ return {
156
+ criticalPathSpanIds: [],
157
+ totalTraceDurationUnixNano: 0,
158
+ criticalPathDurationUnixNano: 0,
159
+ };
160
+ }
161
+
162
+ // Find total trace duration
163
+ let traceStart: number = spans[0]!.startTimeUnixNano;
164
+ let traceEnd: number = spans[0]!.endTimeUnixNano;
165
+ for (const span of spans) {
166
+ if (span.startTimeUnixNano < traceStart) {
167
+ traceStart = span.startTimeUnixNano;
168
+ }
169
+ if (span.endTimeUnixNano > traceEnd) {
170
+ traceEnd = span.endTimeUnixNano;
171
+ }
172
+ }
173
+
174
+ // Build parent -> children index
175
+ const childrenMap: Map<string, SpanData[]> = new Map();
176
+ const spanMap: Map<string, SpanData> = new Map();
177
+ const allSpanIds: Set<string> = new Set();
178
+
179
+ for (const span of spans) {
180
+ spanMap.set(span.spanId, span);
181
+ allSpanIds.add(span.spanId);
182
+ }
183
+
184
+ for (const span of spans) {
185
+ if (span.parentSpanId && allSpanIds.has(span.parentSpanId)) {
186
+ const children: SpanData[] = childrenMap.get(span.parentSpanId) || [];
187
+ children.push(span);
188
+ childrenMap.set(span.parentSpanId, children);
189
+ }
190
+ }
191
+
192
+ // Find root spans
193
+ const rootSpans: SpanData[] = spans.filter((span: SpanData) => {
194
+ return !span.parentSpanId || !allSpanIds.has(span.parentSpanId);
195
+ });
196
+
197
+ if (rootSpans.length === 0) {
198
+ return {
199
+ criticalPathSpanIds: [],
200
+ totalTraceDurationUnixNano: traceEnd - traceStart,
201
+ criticalPathDurationUnixNano: 0,
202
+ };
203
+ }
204
+
205
+ // For each span, compute the critical path weight (longest path through this span and descendants)
206
+ const criticalPathCache: Map<string, { weight: number; path: string[] }> =
207
+ new Map();
208
+
209
+ const computeWeight: (spanId: string) => {
210
+ weight: number;
211
+ path: string[];
212
+ } = (spanId: string): { weight: number; path: string[] } => {
213
+ const cached: { weight: number; path: string[] } | undefined =
214
+ criticalPathCache.get(spanId);
215
+ if (cached) {
216
+ return cached;
217
+ }
218
+
219
+ const span: SpanData | undefined = spanMap.get(spanId);
220
+ if (!span) {
221
+ return { weight: 0, path: [] };
222
+ }
223
+
224
+ const children: SpanData[] = childrenMap.get(spanId) || [];
225
+
226
+ if (children.length === 0) {
227
+ const result: { weight: number; path: string[] } = {
228
+ weight: span.durationUnixNano,
229
+ path: [spanId],
230
+ };
231
+ criticalPathCache.set(spanId, result);
232
+ return result;
233
+ }
234
+
235
+ // Find the child with the longest critical path
236
+ let maxChildWeight: number = 0;
237
+ let maxChildPath: string[] = [];
238
+
239
+ for (const child of children) {
240
+ const childResult: { weight: number; path: string[] } = computeWeight(
241
+ child.spanId,
242
+ );
243
+ if (childResult.weight > maxChildWeight) {
244
+ maxChildWeight = childResult.weight;
245
+ maxChildPath = childResult.path;
246
+ }
247
+ }
248
+
249
+ // Self time contribution
250
+ const selfTimes: Map<string, SpanSelfTime> =
251
+ CriticalPathUtil.computeSelfTimes([span, ...children]);
252
+ const selfTime: SpanSelfTime | undefined = selfTimes.get(spanId);
253
+ const selfTimeValue: number = selfTime ? selfTime.selfTimeUnixNano : 0;
254
+
255
+ const result: { weight: number; path: string[] } = {
256
+ weight: selfTimeValue + maxChildWeight,
257
+ path: [spanId, ...maxChildPath],
258
+ };
259
+ criticalPathCache.set(spanId, result);
260
+ return result;
261
+ };
262
+
263
+ // Find the root with the longest critical path
264
+ let maxWeight: number = 0;
265
+ let criticalPath: string[] = [];
266
+
267
+ for (const rootSpan of rootSpans) {
268
+ const result: { weight: number; path: string[] } = computeWeight(
269
+ rootSpan.spanId,
270
+ );
271
+ if (result.weight > maxWeight) {
272
+ maxWeight = result.weight;
273
+ criticalPath = result.path;
274
+ }
275
+ }
276
+
277
+ return {
278
+ criticalPathSpanIds: criticalPath,
279
+ totalTraceDurationUnixNano: traceEnd - traceStart,
280
+ criticalPathDurationUnixNano: maxWeight,
281
+ };
282
+ }
283
+
284
+ /**
285
+ * Compute latency breakdown by service.
286
+ */
287
+ public static computeServiceBreakdown(spans: SpanData[]): ServiceBreakdown[] {
288
+ const selfTimes: Map<string, SpanSelfTime> =
289
+ CriticalPathUtil.computeSelfTimes(spans);
290
+
291
+ // Find total trace duration
292
+ let traceStart: number = Number.MAX_SAFE_INTEGER;
293
+ let traceEnd: number = 0;
294
+ for (const span of spans) {
295
+ if (span.startTimeUnixNano < traceStart) {
296
+ traceStart = span.startTimeUnixNano;
297
+ }
298
+ if (span.endTimeUnixNano > traceEnd) {
299
+ traceEnd = span.endTimeUnixNano;
300
+ }
301
+ }
302
+ const totalDuration: number = traceEnd - traceStart;
303
+
304
+ // Aggregate by service
305
+ const serviceMap: Map<
306
+ string,
307
+ { totalDuration: number; selfTime: number; spanCount: number }
308
+ > = new Map();
309
+
310
+ for (const span of spans) {
311
+ const serviceId: string = span.serviceId || "unknown";
312
+ const entry: {
313
+ totalDuration: number;
314
+ selfTime: number;
315
+ spanCount: number;
316
+ } = serviceMap.get(serviceId) || {
317
+ totalDuration: 0,
318
+ selfTime: 0,
319
+ spanCount: 0,
320
+ };
321
+
322
+ entry.totalDuration += span.durationUnixNano;
323
+ const selfTime: SpanSelfTime | undefined = selfTimes.get(span.spanId);
324
+ entry.selfTime += selfTime ? selfTime.selfTimeUnixNano : 0;
325
+ entry.spanCount += 1;
326
+ serviceMap.set(serviceId, entry);
327
+ }
328
+
329
+ const result: ServiceBreakdown[] = [];
330
+ for (const [serviceId, data] of serviceMap.entries()) {
331
+ result.push({
332
+ serviceId,
333
+ totalDurationUnixNano: data.totalDuration,
334
+ selfTimeUnixNano: data.selfTime,
335
+ spanCount: data.spanCount,
336
+ percentOfTrace:
337
+ totalDuration > 0 ? (data.selfTime / totalDuration) * 100 : 0,
338
+ });
339
+ }
340
+
341
+ // Sort by self-time descending (biggest contributors first)
342
+ result.sort((a: ServiceBreakdown, b: ServiceBreakdown) => {
343
+ return b.selfTimeUnixNano - a.selfTimeUnixNano;
344
+ });
345
+
346
+ return result;
347
+ }
348
+ }
@@ -1,5 +1,6 @@
1
1
  import AcmeCertificate from "./AcmeCertificate";
2
2
  import AcmeChallenge from "./AcmeChallenge";
3
+ import KubernetesCluster from "./KubernetesCluster";
3
4
  // API Keys
4
5
  import ApiKey from "./ApiKey";
5
6
  import ApiKeyPermission from "./ApiKeyPermission";
@@ -430,6 +431,7 @@ const AllModelTypes = [
430
431
  ProjectSCIM,
431
432
  ProjectSCIMLog,
432
433
  StatusPageSCIMLog,
434
+ KubernetesCluster,
433
435
  ];
434
436
  const modelTypeMap = {};
435
437
  export const getModelTypeByName = (tableName) => {