LogsFormatters.tsx252 lines · main
1/*
2 * Response Code
3 *
4 * for http response codes
5 */
6
7import dayjs from 'dayjs'
8import { AlertCircle, Info } from 'lucide-react'
9import React from 'react'
10import { cn } from 'ui'
11
12import { isUnixMicro, unixMicroToIsoTimestamp } from './Logs.utils'
13import CopyButton from '@/components/ui/CopyButton'
14
15export const RowLayout: React.FC<React.PropsWithChildren> = ({ children }) => (
16 <div className="flex h-full w-full items-center gap-4">{children}</div>
17)
18// renders a timestamp (either unix microsecond or iso timestamp)
19export const SelectionDetailedTimestampRow = ({ value }: { value: string | number }) => (
20 <SelectionDetailedRow
21 label="Timestamp"
22 value={isUnixMicro(value) ? unixMicroToIsoTimestamp(value) : String(value)}
23 />
24)
25export const SelectionDetailedRow = ({
26 label,
27 value,
28 valueRender,
29}: {
30 label: string
31 value: string
32 valueRender?: React.ReactNode
33}) => {
34 return (
35 <div className="group flex items-center gap-2 flex-wrap">
36 <span className="text-foreground-lighter text-sm col-span-3 whitespace-pre-wrap">
37 {label}
38 </span>
39 <span
40 title={value}
41 className="truncate font-mono text-foreground text-sm whitespace-pre-wrap break-all"
42 >
43 {valueRender ?? value}
44 </span>
45 <CopyButton
46 iconOnly
47 text={value}
48 className="group-hover:opacity-100 opacity-0 p-0 h-6 w-6"
49 type="text"
50 title="Copy to clipboard"
51 />
52 </div>
53 )
54}
55
56// used for column renderers
57export const TextFormatter: React.FC<{ className?: string; value: string }> = ({
58 value,
59 className,
60}) => (
61 <span className={cn('font-mono text-xs truncate select-text cursor-text', className)}>
62 {value}
63 </span>
64)
65
66export const ResponseCodeFormatter = ({ value }: { value: string }) => {
67 if (!value) {
68 return (
69 <div>
70 <label className="text-xs text-border-stronger">No data</label>
71 </div>
72 )
73 }
74
75 const ResponseCodeItem = ({
76 children,
77 className,
78 }: {
79 children: React.ReactNode
80 className?: string
81 }) => (
82 <div className="flex h-full items-center">
83 <div
84 className={cn(
85 `relative flex h-6 items-center rounded-md justify-center px-2 py-1 text-center`,
86 className
87 )}
88 >
89 <label className="block font-mono text-sm">{children}</label>
90 </div>
91 </div>
92 )
93
94 const split = value.toString().split('')[0]
95
96 switch (split) {
97 // 2XX || 1XX responses
98 case '1':
99 return <ResponseCodeItem>{value}</ResponseCodeItem>
100 case '2':
101 return <ResponseCodeItem className="bg-surface-100 text-brand">{value}</ResponseCodeItem>
102 // 5XX responses
103 case '5':
104 return <ResponseCodeItem className="bg-red-300 text-red-1100">{value}</ResponseCodeItem>
105
106 // 4XX || 3XX responses
107 case '4':
108 case '3':
109 return <ResponseCodeItem className="bg-warning/10 text-warning">{value}</ResponseCodeItem>
110
111 // All other responses
112 default:
113 return (
114 <ResponseCodeItem className="bg-surface-100 text-foreground-lighter">
115 {value}
116 </ResponseCodeItem>
117 )
118 }
119}
120
121/*
122 * Response Code
123 *
124 * for http response codes
125 */
126
127export const SeverityFormatter = ({
128 value,
129 uppercase = true,
130}: {
131 value: string
132 uppercase?: boolean
133}) => {
134 if (!value) {
135 return (
136 <div>
137 <label className="text-xs text-border-stronger">No data</label>
138 </div>
139 )
140 }
141
142 const uppercasedValue = value.toUpperCase()
143 const text = uppercase ? uppercasedValue : value
144 const Layout: React.FC<React.PropsWithChildren<{ className?: string }>> = ({
145 className,
146 children,
147 }) => <div className={`w-24 flex items-center h-full ${className}`}>{children}</div>
148
149 switch (uppercasedValue) {
150 case 'UNCAUGHTEXCEPTION':
151 case 'PANIC':
152 case 'FATAL':
153 case 'ERROR':
154 return (
155 <Layout className="gap-1">
156 <div className=" p-0.5 rounded-sm text-red-900!">
157 <AlertCircle size={14} strokeWidth={2} />
158 </div>
159 <span className="text-red-900! block! titlecase">{text}</span>
160 </Layout>
161 )
162 break
163
164 case 'INFO':
165
166 case 'DEBUG':
167 return (
168 <Layout className="gap-1">
169 <div className=" p-0.5 rounded-sm text-blue-900!">
170 <AlertCircle size={14} strokeWidth={2} />
171 </div>
172 <span className="text-blue-900! block! titlecase">{text}</span>
173 </Layout>
174 )
175 break
176
177 case 'LOG':
178 return (
179 <Layout className="gap-1">
180 <div className=" p-0.5 rounded-sm text-blue-900!">
181 <Info size={14} strokeWidth={2} />
182 </div>
183 <span className="text-blue-900! block! titlecase">{text}</span>
184 </Layout>
185 )
186 break
187
188 case 'WARNING':
189 return (
190 <Layout className="gap-1">
191 <div className=" p-0.5 rounded-sm text-amber-900!">
192 <AlertCircle size={14} strokeWidth={2} />
193 </div>
194 <span className="text-amber-900! block! titlecase">{text}</span>
195 </Layout>
196 )
197 break
198
199 // All other responses
200 default:
201 return (
202 <Layout>
203 <div className="relative rounded-sm px-2 py-1 text-center h-6 flex justify-center items-center bg-surface-100">
204 <label className="block font-mono text-sm text-foreground-lighter">{text}</label>
205 </div>
206 </Layout>
207 )
208 break
209 }
210}
211
212export const timestampLocalFormatter = (value: string | number) => {
213 const timestamp = isUnixMicro(value) ? unixMicroToIsoTimestamp(value) : value
214 return dayjs(timestamp).format('DD MMM HH:mm:ss')
215}
216
217/*
218 * JSON Syntax Highlighter
219 *
220 * for http response codes
221 */
222
223export function jsonSyntaxHighlight(input: Object) {
224 let json: string = JSON.stringify(input, null, 2)
225 json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
226
227 const newJson = json.replace(
228 /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
229
230 function (match) {
231 var cls = 'number text-tomato-900'
232 if (/^"/.test(match)) {
233 if (/:$/.test(match)) {
234 cls = 'key text-foreground'
235 } else {
236 cls = 'string text-brand-600'
237 }
238 } else if (/true|false/.test(match)) {
239 cls = 'boolean text-blue-900'
240 } else if (/null/.test(match)) {
241 cls = 'null text-amber-1100'
242 }
243 return '<span class="' + cls + '">' + match + '</span>'
244 }
245 )
246
247 const jsonWithLineWraps = newJson.split(`\n`).map((x) => {
248 return `<span class="line text-xs">${x}</span>`
249 })
250
251 return jsonWithLineWraps.join('\n')
252}