@oneuptime/common 10.0.54 → 10.0.56

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 (132) hide show
  1. package/Models/DatabaseModels/DockerHost.ts +662 -0
  2. package/Models/DatabaseModels/GlobalConfig.ts +112 -0
  3. package/Models/DatabaseModels/Index.ts +2 -0
  4. package/Server/API/TelemetryAPI.ts +352 -16
  5. package/Server/Infrastructure/ClickhouseConfig.ts +9 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.ts +76 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.ts +133 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.ts +51 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  10. package/Server/Services/DockerHostService.ts +173 -0
  11. package/Server/Services/ExceptionAggregationService.ts +335 -0
  12. package/Server/Services/Index.ts +2 -0
  13. package/Server/Services/LogAggregationService.ts +17 -0
  14. package/Server/Services/MonitorProbeService.ts +42 -21
  15. package/Server/Services/MonitorService.ts +21 -21
  16. package/Server/Services/TraceAggregationService.ts +514 -0
  17. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +73 -1
  18. package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
  19. package/Tests/__mocks__/mermaid.js +18 -0
  20. package/Tests/__mocks__/react-markdown.js +17 -0
  21. package/Tests/__mocks__/react-syntax-highlighter.js +19 -0
  22. package/Tests/__mocks__/remark-gfm.js +8 -0
  23. package/Types/Icon/IconProp.ts +1 -0
  24. package/Types/Monitor/DockerAlertTemplates.ts +507 -0
  25. package/Types/Monitor/DockerMetricCatalog.ts +226 -0
  26. package/Types/Monitor/MonitorStep.ts +33 -0
  27. package/Types/Monitor/MonitorStepDockerMonitor.ts +38 -0
  28. package/Types/Monitor/MonitorType.ts +15 -1
  29. package/Types/Permission.ts +38 -0
  30. package/UI/Components/Icon/Icon.tsx +87 -0
  31. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +7 -132
  32. package/UI/Components/ModelDetail/CardModelDetail.tsx +11 -1
  33. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +285 -0
  34. package/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.tsx +85 -0
  35. package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +156 -0
  36. package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +160 -0
  37. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +85 -0
  38. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +102 -0
  39. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +280 -0
  40. package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +125 -0
  41. package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +114 -0
  42. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +378 -0
  43. package/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.tsx +78 -0
  44. package/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.tsx +64 -0
  45. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +193 -0
  46. package/UI/Components/TelemetryViewer/types.ts +67 -0
  47. package/build/dist/Models/DatabaseModels/DockerHost.js +686 -0
  48. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -0
  49. package/build/dist/Models/DatabaseModels/GlobalConfig.js +117 -0
  50. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  51. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  52. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  53. package/build/dist/Server/API/TelemetryAPI.js +237 -16
  54. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  55. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +9 -0
  56. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  57. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js +35 -0
  58. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000002-MigrationName.js.map +1 -0
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js +52 -0
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775766676723-MigrationName.js.map +1 -0
  61. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js +26 -0
  62. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1775900000000-AddGlobalSmtpOAuth.js.map +1 -0
  63. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  64. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  65. package/build/dist/Server/Services/DockerHostService.js +162 -0
  66. package/build/dist/Server/Services/DockerHostService.js.map +1 -0
  67. package/build/dist/Server/Services/ExceptionAggregationService.js +224 -0
  68. package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -0
  69. package/build/dist/Server/Services/Index.js +2 -0
  70. package/build/dist/Server/Services/Index.js.map +1 -1
  71. package/build/dist/Server/Services/LogAggregationService.js +11 -0
  72. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  73. package/build/dist/Server/Services/MonitorProbeService.js +28 -14
  74. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  75. package/build/dist/Server/Services/MonitorService.js +19 -17
  76. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  77. package/build/dist/Server/Services/TraceAggregationService.js +364 -0
  78. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -0
  79. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +46 -1
  80. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  81. package/build/dist/Tests/Server/Services/LogAggregationService.test.js +2 -2
  82. package/build/dist/Tests/Server/Services/LogAggregationService.test.js.map +1 -1
  83. package/build/dist/Types/Icon/IconProp.js +1 -0
  84. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  85. package/build/dist/Types/Monitor/DockerAlertTemplates.js +410 -0
  86. package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -0
  87. package/build/dist/Types/Monitor/DockerMetricCatalog.js +192 -0
  88. package/build/dist/Types/Monitor/DockerMetricCatalog.js.map +1 -0
  89. package/build/dist/Types/Monitor/MonitorStep.js +23 -0
  90. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  91. package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js +21 -0
  92. package/build/dist/Types/Monitor/MonitorStepDockerMonitor.js.map +1 -0
  93. package/build/dist/Types/Monitor/MonitorType.js +14 -1
  94. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  95. package/build/dist/Types/Permission.js +36 -0
  96. package/build/dist/Types/Permission.js.map +1 -1
  97. package/build/dist/UI/Components/Icon/Icon.js +13 -0
  98. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  99. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +7 -75
  100. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  101. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +8 -1
  102. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  103. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +71 -0
  104. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -0
  105. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js +39 -0
  106. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js.map +1 -0
  107. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +61 -0
  108. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -0
  109. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +66 -0
  110. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -0
  111. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +41 -0
  112. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -0
  113. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +35 -0
  114. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -0
  115. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +132 -0
  116. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -0
  117. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +65 -0
  118. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -0
  119. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +52 -0
  120. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -0
  121. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +224 -0
  122. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -0
  123. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js +35 -0
  124. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchHelp.js.map +1 -0
  125. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js +27 -0
  126. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchSuggestions.js.map +1 -0
  127. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +97 -0
  128. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -0
  129. package/build/dist/UI/Components/TelemetryViewer/types.js +6 -0
  130. package/build/dist/UI/Components/TelemetryViewer/types.js.map +1 -0
  131. package/jest.config.json +6 -1
  132. package/package.json +1 -1
@@ -0,0 +1,280 @@
1
+ import React, {
2
+ FunctionComponent,
3
+ ReactElement,
4
+ useMemo,
5
+ useCallback,
6
+ useRef,
7
+ useState,
8
+ } from "react";
9
+ import {
10
+ BarChart,
11
+ Bar,
12
+ XAxis,
13
+ YAxis,
14
+ Tooltip,
15
+ ResponsiveContainer,
16
+ ReferenceArea,
17
+ } from "recharts";
18
+ import { HistogramBucket, HistogramSeriesOption } from "../types";
19
+ import TelemetryHistogramTooltip from "./TelemetryHistogramTooltip";
20
+ import ComponentLoader from "../../ComponentLoader/ComponentLoader";
21
+ import OneUptimeDate from "../../../../Types/Date";
22
+
23
+ export interface TelemetryHistogramProps {
24
+ buckets: Array<HistogramBucket>;
25
+ isLoading: boolean;
26
+ /*
27
+ * All possible series stacked in the chart (in rendering order).
28
+ * Only series that have at least one bucket will be drawn + legended.
29
+ */
30
+ series: Array<HistogramSeriesOption>;
31
+ title?: string | undefined;
32
+ onTimeRangeSelect?: ((startTime: Date, endTime: Date) => void) | undefined;
33
+ }
34
+
35
+ interface PivotedRow {
36
+ time: string;
37
+ [series: string]: number | string;
38
+ }
39
+
40
+ function pivotBuckets(buckets: Array<HistogramBucket>): Array<PivotedRow> {
41
+ const map: Map<string, PivotedRow> = new Map();
42
+
43
+ for (const bucket of buckets) {
44
+ let row: PivotedRow | undefined = map.get(bucket.time);
45
+
46
+ if (!row) {
47
+ row = { time: bucket.time };
48
+ map.set(bucket.time, row);
49
+ }
50
+
51
+ row[bucket.series] = ((row[bucket.series] as number) || 0) + bucket.count;
52
+ }
53
+
54
+ return Array.from(map.values());
55
+ }
56
+
57
+ function formatTickTime(time: string): string {
58
+ const date: Date = OneUptimeDate.fromString(time);
59
+
60
+ if (isNaN(date.getTime())) {
61
+ return time;
62
+ }
63
+
64
+ return date.toLocaleTimeString([], {
65
+ hour: "2-digit",
66
+ minute: "2-digit",
67
+ hour12: false,
68
+ });
69
+ }
70
+
71
+ function formatYAxisTick(value: number): string {
72
+ if (value >= 1000000) {
73
+ return `${(value / 1000000).toFixed(1)}M`;
74
+ }
75
+
76
+ if (value >= 1000) {
77
+ return `${(value / 1000).toFixed(value >= 10000 ? 0 : 1)}K`;
78
+ }
79
+
80
+ return value.toString();
81
+ }
82
+
83
+ const TelemetryHistogram: FunctionComponent<TelemetryHistogramProps> = (
84
+ props: TelemetryHistogramProps,
85
+ ): ReactElement => {
86
+ const [selectionStart, setSelectionStart] = useState<string | null>(null);
87
+ const [selectionEnd, setSelectionEnd] = useState<string | null>(null);
88
+ const isSelecting: React.MutableRefObject<boolean> = useRef(false);
89
+
90
+ const pivotedData: Array<PivotedRow> = useMemo(() => {
91
+ return pivotBuckets(props.buckets);
92
+ }, [props.buckets]);
93
+
94
+ const seriesByKey: Record<string, HistogramSeriesOption> = useMemo(() => {
95
+ const map: Record<string, HistogramSeriesOption> = {};
96
+ for (const option of props.series) {
97
+ map[option.key] = option;
98
+ }
99
+ return map;
100
+ }, [props.series]);
101
+
102
+ const activeSeries: Array<HistogramSeriesOption> = useMemo(() => {
103
+ const present: Set<string> = new Set<string>();
104
+
105
+ for (const bucket of props.buckets) {
106
+ present.add(bucket.series);
107
+ }
108
+
109
+ return props.series.filter((option: HistogramSeriesOption): boolean => {
110
+ return present.has(option.key);
111
+ });
112
+ }, [props.buckets, props.series]);
113
+
114
+ const handleMouseDown: (e: any) => void = useCallback(
115
+ (e: any): void => {
116
+ if (!props.onTimeRangeSelect || !e?.activeLabel) {
117
+ return;
118
+ }
119
+
120
+ isSelecting.current = true;
121
+ setSelectionStart(e.activeLabel as string);
122
+ setSelectionEnd(null);
123
+ },
124
+ [props.onTimeRangeSelect],
125
+ );
126
+
127
+ const handleMouseMove: (e: any) => void = useCallback((e: any): void => {
128
+ if (!isSelecting.current || !e?.activeLabel) {
129
+ return;
130
+ }
131
+
132
+ setSelectionEnd(e.activeLabel as string);
133
+ }, []);
134
+
135
+ const handleMouseUp: () => void = useCallback((): void => {
136
+ if (
137
+ !isSelecting.current ||
138
+ !selectionStart ||
139
+ !selectionEnd ||
140
+ !props.onTimeRangeSelect
141
+ ) {
142
+ isSelecting.current = false;
143
+ setSelectionStart(null);
144
+ setSelectionEnd(null);
145
+ return;
146
+ }
147
+
148
+ isSelecting.current = false;
149
+
150
+ const start: Date = OneUptimeDate.fromString(selectionStart);
151
+ const end: Date = OneUptimeDate.fromString(selectionEnd);
152
+
153
+ if (isNaN(start.getTime()) || isNaN(end.getTime())) {
154
+ setSelectionStart(null);
155
+ setSelectionEnd(null);
156
+ return;
157
+ }
158
+
159
+ const earlierDate: Date = start < end ? start : end;
160
+ const laterDate: Date = start < end ? end : start;
161
+
162
+ props.onTimeRangeSelect(earlierDate, laterDate);
163
+
164
+ setSelectionStart(null);
165
+ setSelectionEnd(null);
166
+ }, [selectionStart, selectionEnd, props.onTimeRangeSelect]);
167
+
168
+ if (props.isLoading && pivotedData.length === 0) {
169
+ return (
170
+ <div className="flex h-32 items-center justify-center rounded-lg border border-gray-200 bg-white">
171
+ <ComponentLoader />
172
+ </div>
173
+ );
174
+ }
175
+
176
+ if (pivotedData.length === 0) {
177
+ return <></>;
178
+ }
179
+
180
+ return (
181
+ <div className="rounded-lg border border-gray-200 bg-white">
182
+ <div className="flex items-center justify-between border-b border-gray-100 px-4 py-2">
183
+ <div className="flex items-center gap-2">
184
+ <span className="text-xs font-medium text-gray-500">
185
+ {props.title || "Volume"}
186
+ </span>
187
+ {props.onTimeRangeSelect && (
188
+ <span className="text-[10px] text-gray-300">Drag to zoom</span>
189
+ )}
190
+ </div>
191
+ <div className="flex items-center gap-3">
192
+ {activeSeries.map((option: HistogramSeriesOption) => {
193
+ return (
194
+ <div key={option.key} className="flex items-center gap-1.5">
195
+ <span
196
+ className="inline-block h-2.5 w-2.5 rounded-sm"
197
+ style={{ backgroundColor: option.color }}
198
+ />
199
+ <span className="text-[11px] text-gray-500">
200
+ {option.label}
201
+ </span>
202
+ </div>
203
+ );
204
+ })}
205
+ </div>
206
+ </div>
207
+
208
+ <div
209
+ className="px-2 pb-1 pt-2"
210
+ style={{
211
+ height: 120,
212
+ cursor: props.onTimeRangeSelect ? "crosshair" : "default",
213
+ }}
214
+ >
215
+ <ResponsiveContainer width="100%" height="100%">
216
+ <BarChart
217
+ data={pivotedData}
218
+ margin={{ top: 4, right: 8, bottom: 0, left: -4 }}
219
+ onMouseDown={handleMouseDown}
220
+ onMouseMove={handleMouseMove}
221
+ onMouseUp={handleMouseUp}
222
+ barCategoryGap="15%"
223
+ barGap={0}
224
+ >
225
+ <XAxis
226
+ dataKey="time"
227
+ tickFormatter={formatTickTime}
228
+ tick={{ fontSize: 10, fill: "#9ca3af" }}
229
+ axisLine={{ stroke: "#e5e7eb" }}
230
+ tickLine={false}
231
+ minTickGap={40}
232
+ dy={4}
233
+ interval="preserveStartEnd"
234
+ />
235
+ <YAxis
236
+ tick={{ fontSize: 10, fill: "#9ca3af" }}
237
+ axisLine={false}
238
+ tickLine={false}
239
+ width={48}
240
+ allowDecimals={false}
241
+ tickFormatter={formatYAxisTick}
242
+ />
243
+ <Tooltip
244
+ content={<TelemetryHistogramTooltip seriesByKey={seriesByKey} />}
245
+ cursor={{ fill: "rgba(99,102,241,0.06)" }}
246
+ />
247
+ {activeSeries.map(
248
+ (option: HistogramSeriesOption, index: number) => {
249
+ const isLast: boolean = index === activeSeries.length - 1;
250
+ return (
251
+ <Bar
252
+ key={option.key}
253
+ dataKey={option.key}
254
+ stackId="series"
255
+ fill={option.color}
256
+ radius={isLast ? [1.5, 1.5, 0, 0] : [0, 0, 0, 0]}
257
+ isAnimationActive={false}
258
+ maxBarSize={24}
259
+ />
260
+ );
261
+ },
262
+ )}
263
+ {selectionStart && selectionEnd && (
264
+ <ReferenceArea
265
+ x1={selectionStart}
266
+ x2={selectionEnd}
267
+ fill="rgba(99,102,241,0.12)"
268
+ stroke="rgba(99,102,241,0.5)"
269
+ strokeWidth={1}
270
+ radius={2}
271
+ />
272
+ )}
273
+ </BarChart>
274
+ </ResponsiveContainer>
275
+ </div>
276
+ </div>
277
+ );
278
+ };
279
+
280
+ export default TelemetryHistogram;
@@ -0,0 +1,125 @@
1
+ import React, { FunctionComponent, ReactElement } from "react";
2
+ import OneUptimeDate from "../../../../Types/Date";
3
+ import { HistogramSeriesOption } from "../types";
4
+
5
+ export interface TooltipEntry {
6
+ series: string;
7
+ count: number;
8
+ }
9
+
10
+ export interface TelemetryHistogramTooltipProps {
11
+ active?: boolean;
12
+ label?: string;
13
+ payload?: Array<{
14
+ dataKey: string;
15
+ value: number;
16
+ payload: Record<string, number>;
17
+ }>;
18
+ seriesByKey: Record<string, HistogramSeriesOption>;
19
+ }
20
+
21
+ function formatTooltipTime(label: string | undefined): string {
22
+ if (!label) {
23
+ return "";
24
+ }
25
+
26
+ const date: Date = OneUptimeDate.fromString(label);
27
+
28
+ if (isNaN(date.getTime())) {
29
+ return label;
30
+ }
31
+
32
+ const now: Date = new Date();
33
+ const isToday: boolean = date.toDateString() === now.toDateString();
34
+
35
+ const time: string = date.toLocaleTimeString([], {
36
+ hour: "2-digit",
37
+ minute: "2-digit",
38
+ second: "2-digit",
39
+ hour12: false,
40
+ });
41
+
42
+ if (isToday) {
43
+ return time;
44
+ }
45
+
46
+ const dateStr: string = date.toLocaleDateString([], {
47
+ month: "short",
48
+ day: "numeric",
49
+ });
50
+
51
+ return `${dateStr}, ${time}`;
52
+ }
53
+
54
+ const TelemetryHistogramTooltip: FunctionComponent<
55
+ TelemetryHistogramTooltipProps
56
+ > = (props: TelemetryHistogramTooltipProps): ReactElement | null => {
57
+ if (!props.active || !props.payload || props.payload.length === 0) {
58
+ return null;
59
+ }
60
+
61
+ const entries: Array<TooltipEntry> = props.payload
62
+ .filter((entry: { value: number }): boolean => {
63
+ return entry.value > 0;
64
+ })
65
+ .map((entry: { dataKey: string; value: number }): TooltipEntry => {
66
+ return {
67
+ series: entry.dataKey,
68
+ count: entry.value,
69
+ };
70
+ });
71
+
72
+ if (entries.length === 0) {
73
+ return null;
74
+ }
75
+
76
+ const total: number = entries.reduce(
77
+ (sum: number, e: TooltipEntry): number => {
78
+ return sum + e.count;
79
+ },
80
+ 0,
81
+ );
82
+
83
+ return (
84
+ <div className="rounded-md border border-gray-200 bg-white px-3 py-2 shadow-md">
85
+ <p className="mb-1.5 border-b border-gray-100 pb-1.5 font-mono text-[11px] font-medium text-gray-500">
86
+ {formatTooltipTime(props.label)}
87
+ </p>
88
+ <div className="space-y-0.5">
89
+ {entries.map((entry: TooltipEntry) => {
90
+ const series: HistogramSeriesOption | undefined =
91
+ props.seriesByKey[entry.series];
92
+ const color: string = series?.color || "#cbd5e1";
93
+ const label: string = series?.label || entry.series;
94
+ return (
95
+ <div
96
+ key={entry.series}
97
+ className="flex items-center justify-between gap-6 py-0.5"
98
+ >
99
+ <div className="flex items-center gap-1.5">
100
+ <span
101
+ className="inline-block h-2.5 w-2.5 rounded-sm"
102
+ style={{ backgroundColor: color }}
103
+ />
104
+ <span className="text-xs text-gray-600">{label}</span>
105
+ </div>
106
+ <span className="font-mono text-xs font-semibold tabular-nums text-gray-800">
107
+ {entry.count.toLocaleString()}
108
+ </span>
109
+ </div>
110
+ );
111
+ })}
112
+ </div>
113
+ {entries.length > 1 && (
114
+ <div className="mt-1.5 flex items-center justify-between border-t border-gray-100 pt-1.5">
115
+ <span className="text-xs text-gray-500">Total</span>
116
+ <span className="font-mono text-xs font-semibold tabular-nums text-gray-800">
117
+ {total.toLocaleString()}
118
+ </span>
119
+ </div>
120
+ )}
121
+ </div>
122
+ );
123
+ };
124
+
125
+ export default TelemetryHistogramTooltip;
@@ -0,0 +1,114 @@
1
+ import React, { FunctionComponent, ReactElement } from "react";
2
+
3
+ export interface TelemetryPaginationProps {
4
+ currentPage: number;
5
+ totalItems: number;
6
+ pageSize: number;
7
+ pageSizeOptions: Array<number>;
8
+ onPageChange: (page: number) => void;
9
+ onPageSizeChange: (size: number) => void;
10
+ isDisabled?: boolean;
11
+ itemLabel?: string | undefined;
12
+ }
13
+
14
+ const TelemetryPagination: FunctionComponent<TelemetryPaginationProps> = (
15
+ props: TelemetryPaginationProps,
16
+ ): ReactElement => {
17
+ const totalPages: number = Math.max(
18
+ 1,
19
+ Math.ceil(
20
+ props.totalItems === 0
21
+ ? 1
22
+ : props.totalItems / Math.max(props.pageSize, 1),
23
+ ),
24
+ );
25
+
26
+ const safeCurrentPage: number = Math.min(props.currentPage, totalPages);
27
+
28
+ const firstItemIndex: number =
29
+ props.totalItems === 0 ? 0 : (safeCurrentPage - 1) * props.pageSize + 1;
30
+ const lastItemIndex: number =
31
+ props.totalItems === 0
32
+ ? 0
33
+ : Math.min(props.totalItems, safeCurrentPage * props.pageSize);
34
+
35
+ const disablePrev: boolean =
36
+ props.isDisabled || props.totalItems === 0 || safeCurrentPage <= 1;
37
+ const disableNext: boolean =
38
+ props.isDisabled || props.totalItems === 0 || safeCurrentPage >= totalPages;
39
+
40
+ return (
41
+ <div className="flex flex-col gap-3 border-t border-gray-200 bg-gray-50/50 px-4 py-2.5 text-xs text-gray-500 md:flex-row md:items-center md:justify-between">
42
+ <div>
43
+ {props.totalItems === 0 ? (
44
+ <span className="text-gray-500">
45
+ No {props.itemLabel || "results"} to display.
46
+ </span>
47
+ ) : (
48
+ <span className="text-gray-500">
49
+ Showing {firstItemIndex.toLocaleString()}-
50
+ {lastItemIndex.toLocaleString()} of{" "}
51
+ {props.totalItems.toLocaleString()}
52
+ </span>
53
+ )}
54
+ </div>
55
+
56
+ <div className="flex flex-wrap items-center gap-3">
57
+ <label className="flex items-center gap-2">
58
+ <span className="text-[10px] uppercase tracking-wide text-gray-400">
59
+ Rows
60
+ </span>
61
+ <select
62
+ className="rounded-md border border-gray-200 bg-white px-2 py-1 text-xs text-gray-700 focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-200"
63
+ value={props.pageSize}
64
+ onChange={(event: React.ChangeEvent<HTMLSelectElement>) => {
65
+ const size: number = Number(event.target.value) || props.pageSize;
66
+ props.onPageSizeChange(size);
67
+ }}
68
+ disabled={props.isDisabled}
69
+ >
70
+ {props.pageSizeOptions.map((option: number) => {
71
+ return (
72
+ <option key={option} value={option}>
73
+ {option}
74
+ </option>
75
+ );
76
+ })}
77
+ </select>
78
+ </label>
79
+
80
+ <div className="inline-flex items-center gap-1 rounded-lg border border-gray-200 bg-white p-0.5">
81
+ <button
82
+ type="button"
83
+ className="rounded-md px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-300 disabled:cursor-not-allowed disabled:opacity-40"
84
+ onClick={() => {
85
+ if (!disablePrev) {
86
+ props.onPageChange(Math.max(1, safeCurrentPage - 1));
87
+ }
88
+ }}
89
+ disabled={disablePrev}
90
+ >
91
+ Previous
92
+ </button>
93
+ <span className="px-3 text-[11px] text-gray-400">
94
+ Page {safeCurrentPage} / {totalPages}
95
+ </span>
96
+ <button
97
+ type="button"
98
+ className="rounded-md px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-300 disabled:cursor-not-allowed disabled:opacity-40"
99
+ onClick={() => {
100
+ if (!disableNext) {
101
+ props.onPageChange(Math.min(totalPages, safeCurrentPage + 1));
102
+ }
103
+ }}
104
+ disabled={disableNext}
105
+ >
106
+ Next
107
+ </button>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ );
112
+ };
113
+
114
+ export default TelemetryPagination;