@oneuptime/common 8.0.5480 → 8.0.5488

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 (28) hide show
  1. package/UI/Components/LogsViewer/LogsViewer.tsx +331 -367
  2. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +343 -0
  3. package/UI/Components/LogsViewer/components/LogsFilterCard.tsx +74 -0
  4. package/UI/Components/LogsViewer/components/LogsPagination.tsx +109 -0
  5. package/UI/Components/LogsViewer/components/LogsTable.tsx +270 -0
  6. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +51 -0
  7. package/UI/Components/LogsViewer/components/SeverityBadge.tsx +28 -0
  8. package/UI/Components/LogsViewer/components/severityTheme.ts +69 -0
  9. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +211 -201
  10. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  11. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +151 -0
  12. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -0
  13. package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js +40 -0
  14. package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js.map +1 -0
  15. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +49 -0
  16. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -0
  17. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +130 -0
  18. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -0
  19. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +20 -0
  20. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -0
  21. package/build/dist/UI/Components/LogsViewer/components/SeverityBadge.js +13 -0
  22. package/build/dist/UI/Components/LogsViewer/components/SeverityBadge.js.map +1 -0
  23. package/build/dist/UI/Components/LogsViewer/components/severityTheme.js +54 -0
  24. package/build/dist/UI/Components/LogsViewer/components/severityTheme.js.map +1 -0
  25. package/package.json +1 -1
  26. package/UI/Components/LogsViewer/LogItem.tsx +0 -503
  27. package/build/dist/UI/Components/LogsViewer/LogItem.js +0 -221
  28. package/build/dist/UI/Components/LogsViewer/LogItem.js.map +0 -1
@@ -1,503 +0,0 @@
1
- /**
2
- * LogItem UI
3
- * - Collapsed row: compact line with timestamp, service, severity badge, message preview, quick copy.
4
- * - Expanded panel: left severity-colored border, header with service, severity, timestamp; sections for Message, JSON body, Trace/Span IDs, and Attributes, each with copy buttons.
5
- * - Accessibility: entire item is keyboard-activatable (Enter/Space) and exposes aria-expanded.
6
- * - Styling hooks: severityBadgeClasses and leftBorderColor derive from props.log.severityText for consistent theming.
7
- * - Non-breaking: props and behavior remain backward compatible.
8
- */
9
- import CopyTextButton from "../CopyTextButton/CopyTextButton";
10
- import OneUptimeDate from "../../../Types/Date";
11
- import Dictionary from "../../../Types/Dictionary";
12
- import JSONFunctions from "../../../Types/JSONFunctions";
13
- import Log from "../../../Models/AnalyticsModels/Log";
14
- import LogSeverity from "../../../Types/Log/LogSeverity";
15
- import TelemetryService from "../../../Models/DatabaseModels/TelemetryService";
16
- import React, { FunctionComponent, ReactElement, useEffect } from "react";
17
- import { Logger } from "../../Utils/Logger";
18
- import Icon from "../Icon/Icon";
19
- import IconProp from "../../../Types/Icon/IconProp";
20
- import Link from "../Link/Link";
21
- import Route from "../../../Types/API/Route";
22
- import URL from "../../../Types/API/URL";
23
-
24
- export interface ComponentProps {
25
- log: Log;
26
- serviceMap: Dictionary<TelemetryService>;
27
- getTraceRoute?: (traceId: string, log: Log) => Route | URL | undefined;
28
- getSpanRoute?: (spanId: string, log: Log) => Route | URL | undefined;
29
- }
30
-
31
- const LogItem: FunctionComponent<ComponentProps> = (
32
- props: ComponentProps,
33
- ): ReactElement => {
34
- const serviceId: string = props.log.serviceId?.toString() || "";
35
- const serviceName: string = props.serviceMap[serviceId]?.name || serviceId;
36
- const serviceColor: string =
37
- props.serviceMap[serviceId]?.serviceColor?.toString() || "text-slate-200";
38
-
39
- const [isCollapsed, setIsCollapsed] = React.useState<boolean>(true);
40
-
41
- useEffect(() => {
42
- setIsCollapsed(true);
43
- }, []);
44
-
45
- let bodyColor: string = "text-slate-200";
46
- let leftBorderColor: string = "border-l-slate-700";
47
- let severityDotClass: string = "bg-slate-400";
48
-
49
- type GetCopyButtonFunction = (textToBeCopied: string) => ReactElement;
50
-
51
- const getCopyButton: GetCopyButtonFunction = (textToBeCopied: string) => {
52
- return (
53
- <CopyTextButton
54
- textToBeCopied={textToBeCopied}
55
- size="xs"
56
- variant="ghost"
57
- iconOnly={true}
58
- title="Copy"
59
- className="flex-none"
60
- />
61
- );
62
- };
63
-
64
- type RenderTraceIdFunction = () => ReactElement;
65
-
66
- const renderTraceId: RenderTraceIdFunction = (): ReactElement => {
67
- const traceId: string = props.log.traceId?.toString() || "";
68
-
69
- const traceRoute: Route | URL | undefined =
70
- traceId && props.getTraceRoute
71
- ? props.getTraceRoute(traceId, props.log)
72
- : undefined;
73
-
74
- const baseContainerClassName: string = `${bodyColor} font-mono text-sm bg-slate-950 px-2.5 py-1.5 rounded border border-slate-800 flex-1 transition-colors`;
75
-
76
- if (traceRoute) {
77
- const linkContainerClassName: string = `${baseContainerClassName} flex items-center gap-1 min-w-0 hover:border-blue-500/60 hover:text-blue-200`;
78
-
79
- return (
80
- <div
81
- className="flex-1"
82
- onClick={(event: React.MouseEvent<HTMLDivElement>) => {
83
- event.stopPropagation();
84
- }}
85
- onAuxClick={(event: React.MouseEvent<HTMLDivElement>) => {
86
- event.stopPropagation();
87
- }}
88
- >
89
- <Link
90
- to={traceRoute}
91
- className="group flex-1 min-w-0 block hover:no-underline"
92
- >
93
- <div
94
- className={linkContainerClassName}
95
- title={`View trace ${traceId}`}
96
- >
97
- <span className="truncate underline underline-offset-2 decoration-slate-500 group-hover:decoration-blue-300">
98
- {traceId}
99
- </span>
100
- <Icon
101
- icon={IconProp.ExternalLink}
102
- className="w-3.5 h-3.5 flex-none text-slate-500 group-hover:text-blue-300"
103
- />
104
- </div>
105
- </Link>
106
- </div>
107
- );
108
- }
109
-
110
- return (
111
- <div className={`${baseContainerClassName} truncate`} title={traceId}>
112
- {traceId}
113
- </div>
114
- );
115
- };
116
-
117
- type RenderSpanIdFunction = () => ReactElement;
118
-
119
- const renderSpanId: RenderSpanIdFunction = (): ReactElement => {
120
- const spanId: string = props.log.spanId?.toString() || "";
121
-
122
- if (!spanId) {
123
- return (
124
- <div className="text-slate-500 italic text-sm" title="No span id">
125
- No span
126
- </div>
127
- );
128
- }
129
-
130
- const resolveSpanRoute: () => Route | URL | undefined = () => {
131
- if (props.getSpanRoute) {
132
- return props.getSpanRoute(spanId, props.log);
133
- }
134
-
135
- if (props.getTraceRoute && props.log.traceId) {
136
- const baseRoute: Route | URL | undefined = props.getTraceRoute(
137
- props.log.traceId.toString(),
138
- props.log,
139
- );
140
-
141
- if (!baseRoute) {
142
- return undefined;
143
- }
144
-
145
- if (baseRoute instanceof Route) {
146
- const clonedRoute: Route = new Route(baseRoute.toString());
147
- clonedRoute.addQueryParams({ spanId });
148
- return clonedRoute;
149
- }
150
-
151
- if (baseRoute instanceof URL) {
152
- const clonedURL: URL = URL.fromURL(baseRoute);
153
- clonedURL.addQueryParam("spanId", spanId);
154
- return clonedURL;
155
- }
156
- }
157
-
158
- return undefined;
159
- };
160
-
161
- const spanRoute: Route | URL | undefined = resolveSpanRoute();
162
-
163
- const baseContainerClassName: string = `${bodyColor} font-mono text-sm bg-slate-950 px-2.5 py-1.5 rounded border border-slate-800 flex-1 transition-colors`;
164
-
165
- if (spanRoute) {
166
- const linkContainerClassName: string = `${baseContainerClassName} flex items-center gap-1 min-w-0 hover:border-blue-500/60 hover:text-blue-200`;
167
-
168
- return (
169
- <div
170
- className="flex-1"
171
- onClick={(event: React.MouseEvent<HTMLDivElement>) => {
172
- event.stopPropagation();
173
- }}
174
- onAuxClick={(event: React.MouseEvent<HTMLDivElement>) => {
175
- event.stopPropagation();
176
- }}
177
- >
178
- <Link
179
- to={spanRoute}
180
- className="group flex-1 min-w-0 block hover:no-underline"
181
- >
182
- <div
183
- className={linkContainerClassName}
184
- title={`View span ${spanId}`}
185
- >
186
- <span className={`truncate underline underline-offset-2`}>
187
- {spanId}
188
- </span>
189
- <Icon
190
- icon={IconProp.ExternalLink}
191
- className={`w-3.5 h-3.5 flex-none`}
192
- />
193
- </div>
194
- </Link>
195
- </div>
196
- );
197
- }
198
-
199
- return (
200
- <div className={`${baseContainerClassName} truncate`} title={spanId}>
201
- {spanId}
202
- </div>
203
- );
204
- };
205
-
206
- if (props.log.severityText === LogSeverity.Warning) {
207
- bodyColor = "text-amber-400";
208
- leftBorderColor = "border-l-amber-500/60";
209
- severityDotClass = "bg-amber-500";
210
- } else if (props.log.severityText === LogSeverity.Error) {
211
- bodyColor = "text-rose-400";
212
- leftBorderColor = "border-l-rose-500/60";
213
- severityDotClass = "bg-rose-500";
214
- } else if (
215
- props.log.severityText === LogSeverity.Trace ||
216
- props.log.severityText === LogSeverity.Debug
217
- ) {
218
- bodyColor = "text-slate-400";
219
- leftBorderColor =
220
- props.log.severityText === LogSeverity.Debug
221
- ? "border-l-purple-500/60"
222
- : "border-l-slate-500/60";
223
- severityDotClass =
224
- props.log.severityText === LogSeverity.Debug
225
- ? "bg-purple-500"
226
- : "bg-slate-500";
227
- } else if (props.log.severityText === LogSeverity.Information) {
228
- leftBorderColor = "border-l-blue-500/60";
229
- severityDotClass = "bg-blue-500";
230
- } else if (props.log.severityText === LogSeverity.Fatal) {
231
- leftBorderColor = "border-l-rose-700/70";
232
- severityDotClass = "bg-rose-700";
233
- }
234
-
235
- let logBody: string = props.log.body?.toString() || "";
236
- let logBodyMinified: string = "";
237
-
238
- let isBodyInJSON: boolean = false;
239
-
240
- try {
241
- const parsed: any = JSON.parse(logBody);
242
- logBody = JSON.stringify(parsed, null, 2);
243
- logBodyMinified = JSON.stringify(parsed);
244
- isBodyInJSON = true;
245
- } catch (e) {
246
- Logger.error(e as Error);
247
- isBodyInJSON = false;
248
- }
249
-
250
- const toggleCollapsed: () => void = (): void => {
251
- setIsCollapsed((v: boolean) => {
252
- return !v;
253
- });
254
- };
255
-
256
- const handleKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void = (
257
- e: React.KeyboardEvent<HTMLDivElement>,
258
- ): void => {
259
- if (e.key === "Enter" || e.key === " ") {
260
- e.preventDefault();
261
- toggleCollapsed();
262
- }
263
- };
264
-
265
- if (isCollapsed) {
266
- return (
267
- <div
268
- className={`group relative text-slate-200 flex items-center gap-2 cursor-pointer hover:bg-slate-800/40 px-2 py-0.5 border border-transparent border-l ${leftBorderColor} rounded-sm transition-colors duration-100 font-mono`}
269
- onClick={toggleCollapsed}
270
- role="button"
271
- aria-expanded={!isCollapsed}
272
- tabIndex={0}
273
- onKeyDown={handleKeyDown}
274
- >
275
- {/* Timestamp and Service Name */}
276
- {props.log.time && (
277
- <div className="flex items-center space-x-2 flex-none w-52">
278
- <div className="text-[10px] text-slate-400 font-mono tabular-nums">
279
- {OneUptimeDate.getDateAsUserFriendlyFormattedString(
280
- props.log.time,
281
- )
282
- .split(" ")
283
- .slice(1)
284
- .join(" ")}
285
- </div>
286
- <div
287
- className="text-[11px] font-medium truncate"
288
- style={{ color: serviceColor }}
289
- title={serviceName}
290
- >
291
- {serviceName}
292
- </div>
293
- </div>
294
- )}
295
-
296
- {/* Severity Badge */}
297
- <div className="flex-none mr-1">
298
- <span className="inline-flex items-center gap-2">
299
- <span
300
- className={`inline-block w-1.5 h-1.5 rounded-full ${severityDotClass}`}
301
- aria-hidden="true"
302
- />
303
- <span className="text-[9px] uppercase tracking-wide text-slate-400">
304
- {(props.log.severityText || "").toString() || "UNKNOWN"}
305
- </span>
306
- </span>
307
- </div>
308
-
309
- {/* Log Message */}
310
- <div
311
- className={`${bodyColor} font-mono text-[13px] md:text-sm leading-5 tracking-tight subpixel-antialiased flex-1 min-w-0`}
312
- >
313
- {isBodyInJSON ? (
314
- <div className="truncate font-mono" title={logBodyMinified}>
315
- {logBodyMinified}
316
- </div>
317
- ) : (
318
- <div
319
- className="truncate font-mono"
320
- title={props.log.body?.toString()}
321
- >
322
- {props.log.body?.toString()}
323
- </div>
324
- )}
325
- </div>
326
-
327
- {/* Quick copy button (message) */}
328
- <div className="opacity-0 group-hover:opacity-100 transition-opacity ml-1">
329
- {getCopyButton(props.log.body?.toString() || "")}
330
- </div>
331
-
332
- {/* Expand Indicator */}
333
- <div className="flex-none ml-1 text-slate-500 group-hover:text-slate-300 transition-transform duration-200">
334
- <Icon icon={IconProp.ChevronDown} className="w-3 h-3" />
335
- </div>
336
- </div>
337
- );
338
- }
339
-
340
- return (
341
- <div
342
- className={`group relative text-slate-200 bg-slate-950/70 border ${leftBorderColor} border-l border-slate-900 rounded-sm p-2 hover:border-slate-700 transition-colors`}
343
- >
344
- {/* Header with Service Name and Close Indicator */}
345
- <div
346
- className="flex items-center justify-between mb-1 pb-1 border-b border-slate-800/80"
347
- onClick={() => {
348
- toggleCollapsed();
349
- }}
350
- >
351
- {serviceName && (
352
- <div
353
- className="text-[13px] font-semibold"
354
- style={{ color: serviceColor }}
355
- >
356
- {serviceName}
357
- </div>
358
- )}
359
- <div className="flex items-center gap-2">
360
- <span className="inline-flex items-center gap-2">
361
- <span
362
- className={`inline-block w-1.5 h-1.5 rounded-full ${severityDotClass}`}
363
- aria-hidden="true"
364
- />
365
- <span className="text-[9px] uppercase tracking-wide text-slate-400">
366
- {(props.log.severityText || "").toString() || "UNKNOWN"}
367
- </span>
368
- </span>
369
- {props.log.time && (
370
- <div className="text-[11px] text-slate-400 font-mono tabular-nums">
371
- {OneUptimeDate.getDateAsUserFriendlyFormattedString(
372
- props.log.time,
373
- )}
374
- </div>
375
- )}
376
- <button
377
- type="button"
378
- title="Collapse"
379
- aria-label="Collapse"
380
- onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
381
- e.stopPropagation();
382
- toggleCollapsed();
383
- }}
384
- className="flex-none text-slate-500 hover:text-slate-300 transition-colors"
385
- >
386
- <Icon icon={IconProp.ChevronUp} className="w-3.5 h-3.5" />
387
- </button>
388
- </div>
389
- </div>
390
-
391
- {/* Meta row (compact) */}
392
- <div className="grid grid-cols-1 md:grid-cols-2 gap-1.5 mb-1.5 text-[11px] text-slate-400">
393
- {props.log.time && (
394
- <div className="flex items-center gap-2">
395
- <span className="uppercase tracking-wide">Timestamp</span>
396
- <span className="text-slate-300 font-mono tabular-nums">
397
- {OneUptimeDate.getDateAsUserFriendlyFormattedString(
398
- props.log.time,
399
- )}
400
- </span>
401
- </div>
402
- )}
403
- {props.log.severityText && (
404
- <div className="flex items-center gap-2">
405
- <span className="uppercase tracking-wide">Severity</span>
406
- <span className="inline-flex items-center gap-2 text-slate-300">
407
- <span
408
- className={`inline-block w-1.5 h-1.5 rounded-full ${severityDotClass}`}
409
- />
410
- <span className="text-[10px] uppercase">
411
- {(props.log.severityText || "").toString()}
412
- </span>
413
- </span>
414
- </div>
415
- )}
416
- </div>
417
-
418
- {/* Message */}
419
- <div className="mb-1.5">
420
- <div className="flex items-center justify-between text-[10px] font-semibold text-slate-400 uppercase tracking-wide mb-1">
421
- Message
422
- <div className="flex items-center gap-2 normal-case">
423
- {getCopyButton(
424
- isBodyInJSON ? logBody : props.log.body?.toString() || "",
425
- )}
426
- </div>
427
- </div>
428
- <div className="bg-slate-950 rounded p-1.5 border border-slate-800">
429
- {!isBodyInJSON && (
430
- <div className={`${bodyColor} font-mono text-sm leading-snug`}>
431
- {props.log.body?.toString()}
432
- </div>
433
- )}
434
- {isBodyInJSON && (
435
- <pre
436
- className={`${bodyColor} font-mono text-sm leading-snug whitespace-pre overflow-auto max-h-40 w-full block`}
437
- >
438
- {logBody}
439
- </pre>
440
- )}
441
- </div>
442
- </div>
443
-
444
- {/* Trace and Span Information */}
445
- {(props.log.traceId || props.log.spanId) && (
446
- <div className="grid grid-cols-1 md:grid-cols-2 gap-1.5 mb-1.5">
447
- {props.log.traceId && (
448
- <div className="flex flex-col space-y-2">
449
- <div className="text-[10px] font-semibold text-slate-400 uppercase tracking-wide">
450
- Trace ID
451
- </div>
452
- <div className="flex items-center space-x-2">
453
- {renderTraceId()}
454
- {getCopyButton(props.log.traceId?.toString() || "")}
455
- </div>
456
- </div>
457
- )}
458
-
459
- {props.log.spanId && (
460
- <div className="flex flex-col space-y-2">
461
- <div className="text-[10px] font-semibold text-slate-400 uppercase tracking-wide">
462
- Span ID
463
- </div>
464
- <div className="flex items-center space-x-2">
465
- {renderSpanId()}
466
- {getCopyButton(props.log.spanId?.toString() || "")}
467
- </div>
468
- </div>
469
- )}
470
- </div>
471
- )}
472
-
473
- {/* Attributes */}
474
- {props.log.attributes && (
475
- <div>
476
- <div className="flex items-center justify-between text-[10px] font-semibold text-slate-400 uppercase tracking-wide mb-1">
477
- Attributes
478
- {getCopyButton(
479
- JSON.stringify(
480
- JSONFunctions.unflattenObject(props.log.attributes || {}),
481
- null,
482
- 2,
483
- ) || "",
484
- )}
485
- </div>
486
- <div className="bg-slate-950 rounded p-1.5 border border-slate-800">
487
- <pre
488
- className={`${bodyColor} font-mono text-sm leading-snug whitespace-pre overflow-auto max-h-40 w-full block`}
489
- >
490
- {JSON.stringify(
491
- JSONFunctions.unflattenObject(props.log.attributes || {}),
492
- null,
493
- 2,
494
- )}
495
- </pre>
496
- </div>
497
- </div>
498
- )}
499
- </div>
500
- );
501
- };
502
-
503
- export default LogItem;