@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.
- package/UI/Components/LogsViewer/LogsViewer.tsx +331 -367
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +343 -0
- package/UI/Components/LogsViewer/components/LogsFilterCard.tsx +74 -0
- package/UI/Components/LogsViewer/components/LogsPagination.tsx +109 -0
- package/UI/Components/LogsViewer/components/LogsTable.tsx +270 -0
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +51 -0
- package/UI/Components/LogsViewer/components/SeverityBadge.tsx +28 -0
- package/UI/Components/LogsViewer/components/severityTheme.ts +69 -0
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +211 -201
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +151 -0
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js +40 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +49 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +130 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +20 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/SeverityBadge.js +13 -0
- package/build/dist/UI/Components/LogsViewer/components/SeverityBadge.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/components/severityTheme.js +54 -0
- package/build/dist/UI/Components/LogsViewer/components/severityTheme.js.map +1 -0
- package/package.json +1 -1
- package/UI/Components/LogsViewer/LogItem.tsx +0 -503
- package/build/dist/UI/Components/LogsViewer/LogItem.js +0 -221
- 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;
|