@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
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import React, { Fragment, FunctionComponent, ReactElement } from "react";
|
|
2
|
+
import Log from "../../../../Models/AnalyticsModels/Log";
|
|
3
|
+
import TelemetryService from "../../../../Models/DatabaseModels/TelemetryService";
|
|
4
|
+
import Dictionary from "../../../../Types/Dictionary";
|
|
5
|
+
import OneUptimeDate from "../../../../Types/Date";
|
|
6
|
+
import CopyTextButton from "../../CopyTextButton/CopyTextButton";
|
|
7
|
+
import ComponentLoader from "../../ComponentLoader/ComponentLoader";
|
|
8
|
+
import SeverityBadge from "./SeverityBadge";
|
|
9
|
+
import { getSeverityTheme, SeverityTheme } from "./severityTheme";
|
|
10
|
+
import SortOrder from "../../../../Types/BaseDatabase/SortOrder";
|
|
11
|
+
import Icon from "../../Icon/Icon";
|
|
12
|
+
import IconProp from "../../../../Types/Icon/IconProp";
|
|
13
|
+
|
|
14
|
+
export interface LogsTableProps {
|
|
15
|
+
logs: Array<Log>;
|
|
16
|
+
serviceMap: Dictionary<TelemetryService>;
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
emptyMessage?: string | undefined;
|
|
19
|
+
onRowClick: (log: Log, rowId: string) => void;
|
|
20
|
+
selectedLogId?: string | null;
|
|
21
|
+
renderExpandedContent?: (log: Log) => ReactElement | null;
|
|
22
|
+
sortField?: LogsTableSortField | undefined;
|
|
23
|
+
sortOrder?: SortOrder | undefined;
|
|
24
|
+
onSortChange?: (field: LogsTableSortField) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const resolveLogIdentifier: (log: Log, index: number) => string = (
|
|
28
|
+
log: Log,
|
|
29
|
+
index: number,
|
|
30
|
+
): string => {
|
|
31
|
+
const possibleIds: Array<unknown> = [
|
|
32
|
+
(log as any).id,
|
|
33
|
+
(log as any)._id,
|
|
34
|
+
(log as any)._objectId,
|
|
35
|
+
log.traceId,
|
|
36
|
+
log.timeUnixNano,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (const candidate of possibleIds) {
|
|
40
|
+
if (!candidate) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
return candidate.toString();
|
|
46
|
+
} catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return `log-row-${index}`;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type LogsTableSortField = "time" | "severityText";
|
|
55
|
+
|
|
56
|
+
const LogsTable: FunctionComponent<LogsTableProps> = (
|
|
57
|
+
props: LogsTableProps,
|
|
58
|
+
): ReactElement => {
|
|
59
|
+
const showEmptyState: boolean = !props.isLoading && props.logs.length === 0;
|
|
60
|
+
const activeSortField: LogsTableSortField | undefined = props.sortField;
|
|
61
|
+
const activeSortOrder: SortOrder = props.sortOrder || SortOrder.Descending;
|
|
62
|
+
|
|
63
|
+
const resolveSortIcon: (field: LogsTableSortField) => IconProp = (
|
|
64
|
+
field: LogsTableSortField,
|
|
65
|
+
): IconProp => {
|
|
66
|
+
if (activeSortField !== field) {
|
|
67
|
+
return IconProp.ArrowUpDown;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return activeSortOrder === SortOrder.Descending
|
|
71
|
+
? IconProp.ChevronDown
|
|
72
|
+
: IconProp.ChevronUp;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const resolveSortIconClass: (field: LogsTableSortField) => string = (
|
|
76
|
+
field: LogsTableSortField,
|
|
77
|
+
): string => {
|
|
78
|
+
const base: string = "h-3.5 w-3.5 flex-none transition-colors";
|
|
79
|
+
if (activeSortField === field) {
|
|
80
|
+
return `${base} text-indigo-300`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return `${base} text-slate-600`;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="relative">
|
|
88
|
+
<div className="overflow-x-auto overflow-y-hidden border-b border-slate-900 bg-slate-950">
|
|
89
|
+
<table className="min-w-full divide-y divide-slate-900/80">
|
|
90
|
+
<thead className="bg-slate-950">
|
|
91
|
+
<tr className="text-left text-[11px] font-semibold uppercase tracking-wider text-slate-200">
|
|
92
|
+
<th scope="col" className="px-4 py-3">
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
className={`flex items-center gap-2 text-left font-semibold tracking-wider text-slate-300 transition-colors hover:text-slate-100 focus:outline-none ${
|
|
96
|
+
activeSortField === "time" ? "text-slate-100" : ""
|
|
97
|
+
}`}
|
|
98
|
+
onClick={() => {
|
|
99
|
+
props.onSortChange?.("time");
|
|
100
|
+
}}
|
|
101
|
+
aria-sort={
|
|
102
|
+
activeSortField === "time"
|
|
103
|
+
? activeSortOrder === SortOrder.Descending
|
|
104
|
+
? "descending"
|
|
105
|
+
: "ascending"
|
|
106
|
+
: "none"
|
|
107
|
+
}
|
|
108
|
+
>
|
|
109
|
+
<span>Time</span>
|
|
110
|
+
<Icon
|
|
111
|
+
icon={resolveSortIcon("time")}
|
|
112
|
+
className={resolveSortIconClass("time")}
|
|
113
|
+
aria-hidden="true"
|
|
114
|
+
/>
|
|
115
|
+
</button>
|
|
116
|
+
</th>
|
|
117
|
+
<th scope="col" className="px-4 py-3">
|
|
118
|
+
Service
|
|
119
|
+
</th>
|
|
120
|
+
<th scope="col" className="px-4 py-3">
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
className={`flex items-center gap-2 text-left font-semibold tracking-wider text-slate-300 transition-colors hover:text-slate-100 focus:outline-none ${
|
|
124
|
+
activeSortField === "severityText" ? "text-slate-100" : ""
|
|
125
|
+
}`}
|
|
126
|
+
onClick={() => {
|
|
127
|
+
props.onSortChange?.("severityText");
|
|
128
|
+
}}
|
|
129
|
+
aria-sort={
|
|
130
|
+
activeSortField === "severityText"
|
|
131
|
+
? activeSortOrder === SortOrder.Descending
|
|
132
|
+
? "descending"
|
|
133
|
+
: "ascending"
|
|
134
|
+
: "none"
|
|
135
|
+
}
|
|
136
|
+
>
|
|
137
|
+
<span>Severity</span>
|
|
138
|
+
<Icon
|
|
139
|
+
icon={resolveSortIcon("severityText")}
|
|
140
|
+
className={resolveSortIconClass("severityText")}
|
|
141
|
+
aria-hidden="true"
|
|
142
|
+
/>
|
|
143
|
+
</button>
|
|
144
|
+
</th>
|
|
145
|
+
<th scope="col" className="px-4 py-3">
|
|
146
|
+
Message
|
|
147
|
+
</th>
|
|
148
|
+
</tr>
|
|
149
|
+
</thead>
|
|
150
|
+
<tbody className="divide-y divide-slate-900/70 bg-slate-950">
|
|
151
|
+
{props.logs.map((log: Log, index: number) => {
|
|
152
|
+
const rowId: string = resolveLogIdentifier(log, index);
|
|
153
|
+
const serviceId: string = log.serviceId?.toString() || "";
|
|
154
|
+
const service: TelemetryService | undefined =
|
|
155
|
+
props.serviceMap[serviceId];
|
|
156
|
+
const serviceName: string =
|
|
157
|
+
service?.name || serviceId || "Unknown";
|
|
158
|
+
const serviceColor: string =
|
|
159
|
+
(service?.serviceColor && service?.serviceColor.toString()) ||
|
|
160
|
+
"#94a3b8";
|
|
161
|
+
|
|
162
|
+
const message: string = log.body?.toString() || "";
|
|
163
|
+
const traceId: string = log.traceId?.toString() || "";
|
|
164
|
+
const spanId: string = log.spanId?.toString() || "";
|
|
165
|
+
|
|
166
|
+
const isSelected: boolean = props.selectedLogId === rowId;
|
|
167
|
+
const severityTheme: SeverityTheme = getSeverityTheme(
|
|
168
|
+
log.severityText,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<Fragment key={rowId}>
|
|
173
|
+
<tr
|
|
174
|
+
onClick={() => {
|
|
175
|
+
props.onRowClick(log, rowId);
|
|
176
|
+
}}
|
|
177
|
+
className={`group cursor-pointer align-top transition-colors duration-150 hover:bg-slate-900 ${
|
|
178
|
+
isSelected
|
|
179
|
+
? "bg-slate-900 ring-1 ring-inset ring-indigo-500/40"
|
|
180
|
+
: ""
|
|
181
|
+
}`}
|
|
182
|
+
aria-selected={isSelected}
|
|
183
|
+
aria-expanded={isSelected}
|
|
184
|
+
>
|
|
185
|
+
<td className="whitespace-nowrap px-4 py-3 text-[13px] font-mono text-slate-200">
|
|
186
|
+
{log.time
|
|
187
|
+
? OneUptimeDate.getDateAsUserFriendlyFormattedString(
|
|
188
|
+
log.time,
|
|
189
|
+
)
|
|
190
|
+
: "—"}
|
|
191
|
+
</td>
|
|
192
|
+
<td className="px-4 py-3">
|
|
193
|
+
<div className="flex items-center gap-3 text-sm text-slate-300">
|
|
194
|
+
<span
|
|
195
|
+
className="h-2.5 w-2.5 flex-none rounded-full border border-slate-900/40 shadow-sm"
|
|
196
|
+
style={{ backgroundColor: serviceColor }}
|
|
197
|
+
aria-hidden="true"
|
|
198
|
+
/>
|
|
199
|
+
<span className="truncate" title={serviceName}>
|
|
200
|
+
{serviceName}
|
|
201
|
+
</span>
|
|
202
|
+
</div>
|
|
203
|
+
</td>
|
|
204
|
+
<td className="px-4 py-3">
|
|
205
|
+
<SeverityBadge severity={log.severityText} />
|
|
206
|
+
</td>
|
|
207
|
+
<td className="px-4 py-3">
|
|
208
|
+
<div className="flex items-start justify-between gap-3">
|
|
209
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
210
|
+
<p
|
|
211
|
+
className={`whitespace-pre-wrap break-words text-sm text-slate-200 transition-colors duration-150 group-hover:text-slate-50 ${severityTheme.textClass}`}
|
|
212
|
+
title={message}
|
|
213
|
+
>
|
|
214
|
+
{message || "—"}
|
|
215
|
+
</p>
|
|
216
|
+
{(traceId || spanId) && (
|
|
217
|
+
<div className="flex flex-wrap gap-3 text-[11px] tracking-wide text-slate-500">
|
|
218
|
+
{traceId && <span>Trace: {traceId}</span>}
|
|
219
|
+
{spanId && <span>Span: {spanId}</span>}
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
<CopyTextButton
|
|
224
|
+
textToBeCopied={message}
|
|
225
|
+
size="xs"
|
|
226
|
+
variant="ghost"
|
|
227
|
+
iconOnly={true}
|
|
228
|
+
title="Copy log message"
|
|
229
|
+
/>
|
|
230
|
+
</div>
|
|
231
|
+
</td>
|
|
232
|
+
</tr>
|
|
233
|
+
|
|
234
|
+
{isSelected && props.renderExpandedContent && (
|
|
235
|
+
<tr className="bg-slate-950/70">
|
|
236
|
+
<td colSpan={4} className="px-6 pb-6 pt-3">
|
|
237
|
+
{props.renderExpandedContent(log)}
|
|
238
|
+
</td>
|
|
239
|
+
</tr>
|
|
240
|
+
)}
|
|
241
|
+
</Fragment>
|
|
242
|
+
);
|
|
243
|
+
})}
|
|
244
|
+
</tbody>
|
|
245
|
+
</table>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
{props.isLoading && (
|
|
249
|
+
<div className="absolute inset-0 flex items-center justify-center bg-slate-950/70 backdrop-blur-md">
|
|
250
|
+
<ComponentLoader />
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{showEmptyState && (
|
|
255
|
+
<div className="flex h-full items-center justify-center px-6 py-12 text-center bg-slate-950">
|
|
256
|
+
<div className="w-full max-w-xl rounded-md border border-slate-900/70 bg-slate-950 p-6 text-left shadow-inner">
|
|
257
|
+
<p className="font-mono text-sm uppercase text-slate-400">
|
|
258
|
+
No logs found
|
|
259
|
+
</p>
|
|
260
|
+
<p className="mt-3 font-mono text-xs text-slate-500">
|
|
261
|
+
{props.emptyMessage || "Adjust filters or check again later."}
|
|
262
|
+
</p>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export default LogsTable;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement } from "react";
|
|
2
|
+
import Button, { ButtonSize, ButtonStyleType } from "../../Button/Button";
|
|
3
|
+
|
|
4
|
+
export interface LogsViewerToolbarProps {
|
|
5
|
+
resultCount: number;
|
|
6
|
+
showApplyButton?: boolean;
|
|
7
|
+
onApplyFilters?: () => void;
|
|
8
|
+
currentPage?: number;
|
|
9
|
+
totalPages?: number;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const LogsViewerToolbar: FunctionComponent<LogsViewerToolbarProps> = (
|
|
14
|
+
props: LogsViewerToolbarProps,
|
|
15
|
+
): ReactElement => {
|
|
16
|
+
const { currentPage, totalPages } = props;
|
|
17
|
+
const hasPaginationSummary: boolean = Boolean(
|
|
18
|
+
currentPage && totalPages && totalPages > 0,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
className={`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between ${props.className || ""}`}
|
|
24
|
+
>
|
|
25
|
+
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-400">
|
|
26
|
+
<span className="font-medium text-slate-300">
|
|
27
|
+
{props.resultCount.toLocaleString()} result
|
|
28
|
+
{props.resultCount === 1 ? "" : "s"}
|
|
29
|
+
</span>
|
|
30
|
+
{hasPaginationSummary && (
|
|
31
|
+
<span className="text-slate-500">
|
|
32
|
+
Page {currentPage} of {totalPages}
|
|
33
|
+
</span>
|
|
34
|
+
)}
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
38
|
+
{props.showApplyButton && props.onApplyFilters && (
|
|
39
|
+
<Button
|
|
40
|
+
title="Apply Filters"
|
|
41
|
+
buttonStyle={ButtonStyleType.NORMAL}
|
|
42
|
+
buttonSize={ButtonSize.Small}
|
|
43
|
+
onClick={props.onApplyFilters}
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default LogsViewerToolbar;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement } from "react";
|
|
2
|
+
import LogSeverity from "../../../../Types/Log/LogSeverity";
|
|
3
|
+
import { getSeverityTheme, SeverityTheme } from "./severityTheme";
|
|
4
|
+
|
|
5
|
+
export interface SeverityBadgeProps {
|
|
6
|
+
severity?: LogSeverity | string | null | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const SeverityBadge: FunctionComponent<SeverityBadgeProps> = (
|
|
10
|
+
props: SeverityBadgeProps,
|
|
11
|
+
): ReactElement => {
|
|
12
|
+
const label: string = props.severity
|
|
13
|
+
? props.severity.toString().toUpperCase()
|
|
14
|
+
: "UNKNOWN";
|
|
15
|
+
|
|
16
|
+
const theme: SeverityTheme = getSeverityTheme(props.severity);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<span
|
|
20
|
+
className={`inline-flex items-center gap-2 rounded-full px-2.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide ring-1 ring-inset ${theme.badgeClass}`}
|
|
21
|
+
>
|
|
22
|
+
<span className={`h-1.5 w-1.5 rounded-full ${theme.dotClass}`} />
|
|
23
|
+
<span>{label}</span>
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default SeverityBadge;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import LogSeverity from "../../../../Types/Log/LogSeverity";
|
|
2
|
+
|
|
3
|
+
export interface SeverityTheme {
|
|
4
|
+
badgeClass: string;
|
|
5
|
+
dotClass: string;
|
|
6
|
+
borderClass: string;
|
|
7
|
+
textClass: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const severityThemeMap: Record<string, SeverityTheme> = {
|
|
11
|
+
[LogSeverity.Fatal]: {
|
|
12
|
+
badgeClass: "bg-rose-950/80 text-rose-100 ring-rose-500/40",
|
|
13
|
+
dotClass: "bg-rose-500",
|
|
14
|
+
borderClass: "border-rose-500/50",
|
|
15
|
+
textClass: "text-rose-100",
|
|
16
|
+
},
|
|
17
|
+
[LogSeverity.Error]: {
|
|
18
|
+
badgeClass: "bg-rose-900/60 text-rose-100 ring-rose-500/30",
|
|
19
|
+
dotClass: "bg-rose-400",
|
|
20
|
+
borderClass: "border-rose-500/40",
|
|
21
|
+
textClass: "text-rose-100",
|
|
22
|
+
},
|
|
23
|
+
[LogSeverity.Warning]: {
|
|
24
|
+
badgeClass: "bg-amber-900/50 text-amber-100 ring-amber-500/30",
|
|
25
|
+
dotClass: "bg-amber-400",
|
|
26
|
+
borderClass: "border-amber-400/40",
|
|
27
|
+
textClass: "text-amber-100",
|
|
28
|
+
},
|
|
29
|
+
[LogSeverity.Information]: {
|
|
30
|
+
badgeClass: "bg-sky-900/50 text-sky-100 ring-sky-500/30",
|
|
31
|
+
dotClass: "bg-sky-400",
|
|
32
|
+
borderClass: "border-sky-400/40",
|
|
33
|
+
textClass: "text-sky-100",
|
|
34
|
+
},
|
|
35
|
+
[LogSeverity.Debug]: {
|
|
36
|
+
badgeClass: "bg-purple-900/50 text-purple-100 ring-purple-500/30",
|
|
37
|
+
dotClass: "bg-purple-400",
|
|
38
|
+
borderClass: "border-purple-500/30",
|
|
39
|
+
textClass: "text-purple-100",
|
|
40
|
+
},
|
|
41
|
+
[LogSeverity.Trace]: {
|
|
42
|
+
badgeClass: "bg-slate-900/60 text-slate-300 ring-slate-500/20",
|
|
43
|
+
dotClass: "bg-slate-400",
|
|
44
|
+
borderClass: "border-slate-600/40",
|
|
45
|
+
textClass: "text-slate-200",
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const defaultTheme: SeverityTheme = {
|
|
50
|
+
badgeClass: "bg-slate-800/60 text-slate-300 ring-slate-600/20",
|
|
51
|
+
dotClass: "bg-slate-500",
|
|
52
|
+
borderClass: "border-slate-700",
|
|
53
|
+
textClass: "text-slate-200",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const getSeverityTheme: (
|
|
57
|
+
severity?: LogSeverity | string | null,
|
|
58
|
+
) => SeverityTheme = (
|
|
59
|
+
severity?: LogSeverity | string | null,
|
|
60
|
+
): SeverityTheme => {
|
|
61
|
+
if (!severity) {
|
|
62
|
+
return defaultTheme;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const normalized: string = severity.toString();
|
|
66
|
+
return severityThemeMap[normalized] || defaultTheme;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default severityThemeMap;
|