@oneuptime/common 8.0.5174 → 8.0.5179
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/Server/API/SlackAPI.ts +69 -0
- package/Server/Utils/Workspace/Slack/Slack.ts +2 -2
- package/UI/Components/CopyTextButton/CopyTextButton.tsx +66 -5
- package/UI/Components/LogsViewer/LogItem.tsx +250 -165
- package/UI/Components/LogsViewer/LogsViewer.tsx +137 -39
- package/build/dist/Server/API/SlackAPI.js +35 -0
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +2 -2
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +29 -2
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogItem.js +109 -75
- package/build/dist/UI/Components/LogsViewer/LogItem.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +65 -22
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,3 +1,11 @@
|
|
|
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
|
+
*/
|
|
1
9
|
import CopyTextButton from "../CopyTextButton/CopyTextButton";
|
|
2
10
|
import OneUptimeDate from "../../../Types/Date";
|
|
3
11
|
import Dictionary from "../../../Types/Dictionary";
|
|
@@ -7,6 +15,8 @@ import LogSeverity from "../../../Types/Log/LogSeverity";
|
|
|
7
15
|
import TelemetryService from "../../../Models/DatabaseModels/TelemetryService";
|
|
8
16
|
import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
|
9
17
|
import { Logger } from "../../Utils/Logger";
|
|
18
|
+
import Icon from "../Icon/Icon";
|
|
19
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
10
20
|
|
|
11
21
|
export interface ComponentProps {
|
|
12
22
|
log: Log;
|
|
@@ -28,6 +38,8 @@ const LogItem: FunctionComponent<ComponentProps> = (
|
|
|
28
38
|
}, []);
|
|
29
39
|
|
|
30
40
|
let bodyColor: string = "text-slate-200";
|
|
41
|
+
let leftBorderColor: string = "border-l-slate-700";
|
|
42
|
+
let severityDotClass: string = "bg-slate-400";
|
|
31
43
|
|
|
32
44
|
type GetCopyButtonFunction = (textToBeCopied: string) => ReactElement;
|
|
33
45
|
|
|
@@ -35,87 +47,141 @@ const LogItem: FunctionComponent<ComponentProps> = (
|
|
|
35
47
|
return (
|
|
36
48
|
<CopyTextButton
|
|
37
49
|
textToBeCopied={textToBeCopied}
|
|
38
|
-
|
|
50
|
+
size="xs"
|
|
51
|
+
variant="ghost"
|
|
52
|
+
iconOnly={true}
|
|
53
|
+
title="Copy"
|
|
54
|
+
className="flex-none"
|
|
39
55
|
/>
|
|
40
56
|
);
|
|
41
57
|
};
|
|
42
58
|
|
|
43
59
|
if (props.log.severityText === LogSeverity.Warning) {
|
|
44
60
|
bodyColor = "text-amber-400";
|
|
61
|
+
leftBorderColor = "border-l-amber-500/60";
|
|
62
|
+
severityDotClass = "bg-amber-500";
|
|
45
63
|
} else if (props.log.severityText === LogSeverity.Error) {
|
|
46
64
|
bodyColor = "text-rose-400";
|
|
65
|
+
leftBorderColor = "border-l-rose-500/60";
|
|
66
|
+
severityDotClass = "bg-rose-500";
|
|
47
67
|
} else if (
|
|
48
68
|
props.log.severityText === LogSeverity.Trace ||
|
|
49
69
|
props.log.severityText === LogSeverity.Debug
|
|
50
70
|
) {
|
|
51
71
|
bodyColor = "text-slate-400";
|
|
72
|
+
leftBorderColor =
|
|
73
|
+
props.log.severityText === LogSeverity.Debug
|
|
74
|
+
? "border-l-purple-500/60"
|
|
75
|
+
: "border-l-slate-500/60";
|
|
76
|
+
severityDotClass =
|
|
77
|
+
props.log.severityText === LogSeverity.Debug
|
|
78
|
+
? "bg-purple-500"
|
|
79
|
+
: "bg-slate-500";
|
|
80
|
+
} else if (props.log.severityText === LogSeverity.Information) {
|
|
81
|
+
leftBorderColor = "border-l-blue-500/60";
|
|
82
|
+
severityDotClass = "bg-blue-500";
|
|
83
|
+
} else if (props.log.severityText === LogSeverity.Fatal) {
|
|
84
|
+
leftBorderColor = "border-l-rose-700/70";
|
|
85
|
+
severityDotClass = "bg-rose-700";
|
|
52
86
|
}
|
|
53
87
|
|
|
54
88
|
let logBody: string = props.log.body?.toString() || "";
|
|
89
|
+
let logBodyMinified: string = "";
|
|
55
90
|
|
|
56
91
|
let isBodyInJSON: boolean = false;
|
|
57
92
|
|
|
58
93
|
try {
|
|
59
|
-
|
|
94
|
+
const parsed: any = JSON.parse(logBody);
|
|
95
|
+
logBody = JSON.stringify(parsed, null, 2);
|
|
96
|
+
logBodyMinified = JSON.stringify(parsed);
|
|
60
97
|
isBodyInJSON = true;
|
|
61
98
|
} catch (e) {
|
|
62
99
|
Logger.error(e as Error);
|
|
63
100
|
isBodyInJSON = false;
|
|
64
101
|
}
|
|
65
102
|
|
|
103
|
+
const toggleCollapsed: () => void = (): void => {
|
|
104
|
+
setIsCollapsed((v: boolean) => {
|
|
105
|
+
return !v;
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void = (
|
|
110
|
+
e: React.KeyboardEvent<HTMLDivElement>,
|
|
111
|
+
): void => {
|
|
112
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
toggleCollapsed();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
66
118
|
if (isCollapsed) {
|
|
67
119
|
return (
|
|
68
120
|
<div
|
|
69
|
-
className=
|
|
70
|
-
onClick={
|
|
71
|
-
|
|
72
|
-
}
|
|
121
|
+
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`}
|
|
122
|
+
onClick={toggleCollapsed}
|
|
123
|
+
role="button"
|
|
124
|
+
aria-expanded={!isCollapsed}
|
|
125
|
+
tabIndex={0}
|
|
126
|
+
onKeyDown={handleKeyDown}
|
|
73
127
|
>
|
|
128
|
+
{/* Timestamp and Service Name */}
|
|
74
129
|
{props.log.time && (
|
|
75
|
-
<div
|
|
76
|
-
className="text-slate-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<div className="text-amber-400 courier-prime flex-none">
|
|
92
|
-
[WARN]
|
|
93
|
-
</div>
|
|
94
|
-
)}
|
|
95
|
-
{props.log.severityText === LogSeverity.Trace && (
|
|
96
|
-
<div className="text-slate-400 courier-prime flex-none">
|
|
97
|
-
[TRACE]
|
|
98
|
-
</div>
|
|
99
|
-
)}
|
|
100
|
-
{props.log.severityText === LogSeverity.Debug && (
|
|
101
|
-
<div className="text-slate-400 courier-prime flex-none">
|
|
102
|
-
[DEBUG]
|
|
103
|
-
</div>
|
|
104
|
-
)}
|
|
105
|
-
{props.log.severityText === LogSeverity.Error && (
|
|
106
|
-
<div className="text-rose-400 courier-prime flex-none">
|
|
107
|
-
[ERROR]
|
|
108
|
-
</div>
|
|
109
|
-
)}
|
|
110
|
-
{props.log.severityText === LogSeverity.Fatal && (
|
|
111
|
-
<div className="text-rose-400 courier-prime flex-none">
|
|
112
|
-
[FATAL]
|
|
130
|
+
<div className="flex items-center space-x-2 flex-none w-52">
|
|
131
|
+
<div className="text-[10px] text-slate-400 font-mono tabular-nums">
|
|
132
|
+
{OneUptimeDate.getDateAsUserFriendlyFormattedString(
|
|
133
|
+
props.log.time,
|
|
134
|
+
)
|
|
135
|
+
.split(" ")
|
|
136
|
+
.slice(1)
|
|
137
|
+
.join(" ")}
|
|
138
|
+
</div>
|
|
139
|
+
<div
|
|
140
|
+
className="text-[11px] font-medium truncate"
|
|
141
|
+
style={{ color: serviceColor }}
|
|
142
|
+
title={serviceName}
|
|
143
|
+
>
|
|
144
|
+
{serviceName}
|
|
145
|
+
</div>
|
|
113
146
|
</div>
|
|
114
147
|
)}
|
|
115
148
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
149
|
+
{/* Severity Badge */}
|
|
150
|
+
<div className="flex-none mr-1">
|
|
151
|
+
<span className="inline-flex items-center gap-2">
|
|
152
|
+
<span
|
|
153
|
+
className={`inline-block w-1.5 h-1.5 rounded-full ${severityDotClass}`}
|
|
154
|
+
aria-hidden="true"
|
|
155
|
+
/>
|
|
156
|
+
<span className="text-[9px] uppercase tracking-wide text-slate-400">
|
|
157
|
+
{(props.log.severityText || "").toString() || "UNKNOWN"}
|
|
158
|
+
</span>
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{/* Log Message */}
|
|
163
|
+
<div
|
|
164
|
+
className={`${bodyColor} font-mono text-[13px] md:text-sm leading-5 tracking-tight subpixel-antialiased flex-1 min-w-0`}
|
|
165
|
+
>
|
|
166
|
+
{isBodyInJSON ? (
|
|
167
|
+
<div className="truncate" title={logBodyMinified}>
|
|
168
|
+
{logBodyMinified}
|
|
169
|
+
</div>
|
|
170
|
+
) : (
|
|
171
|
+
<div className="truncate" title={props.log.body?.toString()}>
|
|
172
|
+
{props.log.body?.toString()}
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{/* Quick copy button (message) */}
|
|
178
|
+
<div className="opacity-0 group-hover:opacity-100 transition-opacity ml-1">
|
|
179
|
+
{getCopyButton(props.log.body?.toString() || "")}
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
{/* Expand Indicator */}
|
|
183
|
+
<div className="flex-none ml-1 text-slate-500 group-hover:text-slate-300 transition-transform duration-200">
|
|
184
|
+
<Icon icon={IconProp.ChevronDown} className="w-3 h-3" />
|
|
119
185
|
</div>
|
|
120
186
|
</div>
|
|
121
187
|
);
|
|
@@ -123,145 +189,164 @@ const LogItem: FunctionComponent<ComponentProps> = (
|
|
|
123
189
|
|
|
124
190
|
return (
|
|
125
191
|
<div
|
|
126
|
-
className=
|
|
127
|
-
onClick={() => {
|
|
128
|
-
setIsCollapsed(true);
|
|
129
|
-
}}
|
|
192
|
+
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`}
|
|
130
193
|
>
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
</div>
|
|
140
|
-
)}
|
|
141
|
-
{props.log.time && (
|
|
142
|
-
<div className="flex">
|
|
143
|
-
<div className="font-medium text-slate-200 courier-prime mr-2">
|
|
144
|
-
DATE TIME:
|
|
145
|
-
</div>
|
|
146
|
-
<div className="text-slate-500 courier-prime">
|
|
147
|
-
{OneUptimeDate.getDateAsUserFriendlyFormattedString(props.log.time)}{" "}
|
|
148
|
-
{" "}
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
)}
|
|
152
|
-
{props.log.severityText === LogSeverity.Information && (
|
|
153
|
-
<div className="flex">
|
|
154
|
-
<div className="font-medium text-slate-200 courier-prime mr-2">
|
|
155
|
-
SEVERITY:
|
|
156
|
-
</div>
|
|
157
|
-
<div className="text-sky-400 courier-prime">[INFO] </div>
|
|
158
|
-
</div>
|
|
159
|
-
)}
|
|
160
|
-
{props.log.severityText === LogSeverity.Unspecified && (
|
|
161
|
-
<div className="flex">
|
|
162
|
-
<div className="font-medium text-slate-200 courier-prime mr-2">
|
|
163
|
-
SEVERITY:
|
|
164
|
-
</div>
|
|
165
|
-
<div className="text-sky-400 courier-prime">[UNKNOWN] </div>
|
|
166
|
-
</div>
|
|
167
|
-
)}
|
|
168
|
-
{props.log.severityText === LogSeverity.Warning && (
|
|
169
|
-
<div className="flex">
|
|
170
|
-
<div className="font-medium text-slate-200 courier-prime mr-2">
|
|
171
|
-
SEVERITY:
|
|
172
|
-
</div>
|
|
173
|
-
<div className="text-amber-400 courier-prime">[WARN] </div>
|
|
174
|
-
</div>
|
|
175
|
-
)}
|
|
176
|
-
{props.log.severityText === LogSeverity.Trace && (
|
|
177
|
-
<div className="flex">
|
|
178
|
-
<div className="font-medium text-slate-200 courier-prime mr-2">
|
|
179
|
-
SEVERITY:
|
|
180
|
-
</div>
|
|
181
|
-
<div className="text-slate-400 courier-prime">[TRACE] </div>
|
|
182
|
-
</div>
|
|
183
|
-
)}
|
|
184
|
-
{props.log.severityText === LogSeverity.Debug && (
|
|
185
|
-
<div className="flex">
|
|
186
|
-
<div className="font-medium text-slate-200 courier-prime mr-2">
|
|
187
|
-
SEVERITY:
|
|
194
|
+
{/* Header with Service Name and Close Indicator */}
|
|
195
|
+
<div className="flex items-center justify-between mb-1 pb-1 border-b border-slate-800/80">
|
|
196
|
+
{serviceName && (
|
|
197
|
+
<div
|
|
198
|
+
className="text-[13px] font-semibold"
|
|
199
|
+
style={{ color: serviceColor }}
|
|
200
|
+
>
|
|
201
|
+
{serviceName}
|
|
188
202
|
</div>
|
|
189
|
-
|
|
203
|
+
)}
|
|
204
|
+
<div className="flex items-center gap-2">
|
|
205
|
+
<span className="inline-flex items-center gap-2">
|
|
206
|
+
<span
|
|
207
|
+
className={`inline-block w-1.5 h-1.5 rounded-full ${severityDotClass}`}
|
|
208
|
+
aria-hidden="true"
|
|
209
|
+
/>
|
|
210
|
+
<span className="text-[9px] uppercase tracking-wide text-slate-400">
|
|
211
|
+
{(props.log.severityText || "").toString() || "UNKNOWN"}
|
|
212
|
+
</span>
|
|
213
|
+
</span>
|
|
214
|
+
{props.log.time && (
|
|
215
|
+
<div className="text-[11px] text-slate-400 font-mono tabular-nums">
|
|
216
|
+
{OneUptimeDate.getDateAsUserFriendlyFormattedString(
|
|
217
|
+
props.log.time,
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
title="Collapse"
|
|
224
|
+
aria-label="Collapse"
|
|
225
|
+
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
226
|
+
e.stopPropagation();
|
|
227
|
+
toggleCollapsed();
|
|
228
|
+
}}
|
|
229
|
+
className="flex-none text-slate-500 hover:text-slate-300 transition-colors"
|
|
230
|
+
>
|
|
231
|
+
<Icon icon={IconProp.ChevronUp} className="w-3.5 h-3.5" />
|
|
232
|
+
</button>
|
|
190
233
|
</div>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
{/* Meta row (compact) */}
|
|
237
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-1.5 mb-1.5 text-[11px] text-slate-400">
|
|
238
|
+
{props.log.time && (
|
|
239
|
+
<div className="flex items-center gap-2">
|
|
240
|
+
<span className="uppercase tracking-wide">Timestamp</span>
|
|
241
|
+
<span className="text-slate-300 font-mono tabular-nums">
|
|
242
|
+
{OneUptimeDate.getDateAsUserFriendlyFormattedString(
|
|
243
|
+
props.log.time,
|
|
244
|
+
)}
|
|
245
|
+
</span>
|
|
196
246
|
</div>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
247
|
+
)}
|
|
248
|
+
{props.log.severityText && (
|
|
249
|
+
<div className="flex items-center gap-2">
|
|
250
|
+
<span className="uppercase tracking-wide">Severity</span>
|
|
251
|
+
<span className="inline-flex items-center gap-2 text-slate-300">
|
|
252
|
+
<span
|
|
253
|
+
className={`inline-block w-1.5 h-1.5 rounded-full ${severityDotClass}`}
|
|
254
|
+
/>
|
|
255
|
+
<span className="text-[10px] uppercase">
|
|
256
|
+
{(props.log.severityText || "").toString()}
|
|
257
|
+
</span>
|
|
258
|
+
</span>
|
|
204
259
|
</div>
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)}
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
208
262
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
263
|
+
{/* Message */}
|
|
264
|
+
<div className="mb-1.5">
|
|
265
|
+
<div className="flex items-center justify-between text-[10px] font-semibold text-slate-400 uppercase tracking-wide mb-1">
|
|
266
|
+
Message
|
|
267
|
+
<div className="flex items-center gap-2 normal-case">
|
|
268
|
+
{getCopyButton(
|
|
269
|
+
isBodyInJSON ? logBody : props.log.body?.toString() || "",
|
|
270
|
+
)}
|
|
213
271
|
</div>
|
|
272
|
+
</div>
|
|
273
|
+
<div className="bg-slate-950 rounded p-1.5 border border-slate-800">
|
|
214
274
|
{!isBodyInJSON && (
|
|
215
|
-
<div className={`${bodyColor}
|
|
275
|
+
<div className={`${bodyColor} font-mono text-sm leading-snug`}>
|
|
216
276
|
{props.log.body?.toString()}
|
|
217
277
|
</div>
|
|
218
278
|
)}
|
|
279
|
+
{isBodyInJSON && (
|
|
280
|
+
<pre
|
|
281
|
+
className={`${bodyColor} font-mono text-sm leading-snug whitespace-pre overflow-auto max-h-40 w-full block`}
|
|
282
|
+
>
|
|
283
|
+
{logBody}
|
|
284
|
+
</pre>
|
|
285
|
+
)}
|
|
219
286
|
</div>
|
|
220
|
-
{isBodyInJSON && (
|
|
221
|
-
<pre className={`${bodyColor} courier-prime whitespace-pre-wrap`}>
|
|
222
|
-
{logBody}
|
|
223
|
-
</pre>
|
|
224
|
-
)}
|
|
225
287
|
</div>
|
|
226
288
|
|
|
227
|
-
{
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
289
|
+
{/* Trace and Span Information */}
|
|
290
|
+
{(props.log.traceId || props.log.spanId) && (
|
|
291
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-1.5 mb-1.5">
|
|
292
|
+
{props.log.traceId && (
|
|
293
|
+
<div className="flex flex-col space-y-2">
|
|
294
|
+
<div className="text-[10px] font-semibold text-slate-400 uppercase tracking-wide">
|
|
295
|
+
Trace ID
|
|
296
|
+
</div>
|
|
297
|
+
<div className="flex items-center space-x-2">
|
|
298
|
+
<div
|
|
299
|
+
className={`${bodyColor} font-mono text-sm bg-slate-950 px-2.5 py-1.5 rounded border border-slate-800 flex-1 truncate`}
|
|
300
|
+
>
|
|
301
|
+
{props.log.traceId?.toString()}
|
|
302
|
+
</div>
|
|
303
|
+
{getCopyButton(props.log.traceId?.toString() || "")}
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
)}
|
|
238
307
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
308
|
+
{props.log.spanId && (
|
|
309
|
+
<div className="flex flex-col space-y-2">
|
|
310
|
+
<div className="text-[10px] font-semibold text-slate-400 uppercase tracking-wide">
|
|
311
|
+
Span ID
|
|
312
|
+
</div>
|
|
313
|
+
<div className="flex items-center space-x-2">
|
|
314
|
+
<div
|
|
315
|
+
className={`${bodyColor} font-mono text-sm bg-slate-950 px-2.5 py-1.5 rounded border border-slate-800 flex-1 truncate`}
|
|
316
|
+
>
|
|
317
|
+
{props.log.spanId?.toString()}
|
|
318
|
+
</div>
|
|
319
|
+
{getCopyButton(props.log.spanId?.toString() || "")}
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
)}
|
|
248
323
|
</div>
|
|
249
324
|
)}
|
|
250
325
|
|
|
326
|
+
{/* Attributes */}
|
|
251
327
|
{props.log.attributes && (
|
|
252
328
|
<div>
|
|
253
|
-
<div className="flex">
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
null,
|
|
262
|
-
2,
|
|
329
|
+
<div className="flex items-center justify-between text-[10px] font-semibold text-slate-400 uppercase tracking-wide mb-1">
|
|
330
|
+
Attributes
|
|
331
|
+
{getCopyButton(
|
|
332
|
+
JSON.stringify(
|
|
333
|
+
JSONFunctions.unflattenObject(props.log.attributes || {}),
|
|
334
|
+
null,
|
|
335
|
+
2,
|
|
336
|
+
) || "",
|
|
263
337
|
)}
|
|
264
|
-
</
|
|
338
|
+
</div>
|
|
339
|
+
<div className="bg-slate-950 rounded p-1.5 border border-slate-800">
|
|
340
|
+
<pre
|
|
341
|
+
className={`${bodyColor} font-mono text-sm leading-snug whitespace-pre overflow-auto max-h-40 w-full block`}
|
|
342
|
+
>
|
|
343
|
+
{JSON.stringify(
|
|
344
|
+
JSONFunctions.unflattenObject(props.log.attributes || {}),
|
|
345
|
+
null,
|
|
346
|
+
2,
|
|
347
|
+
)}
|
|
348
|
+
</pre>
|
|
349
|
+
</div>
|
|
265
350
|
</div>
|
|
266
351
|
)}
|
|
267
352
|
</div>
|