LogsFormatters.tsx 6.5 KB

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