Skip to content

Commit 4704466

Browse files
committed
format common ansi foreground colors
1 parent ee8e0ad commit 4704466

2 files changed

Lines changed: 56 additions & 5 deletions

File tree

pkg/dashboard/frontend/src/components/logs/LogsExplorer.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Button } from '../ui/button'
55
import { useLogs } from '@/lib/hooks/use-logs'
66
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'
77
import { format } from 'date-fns/format'
8+
import { ansiToReact } from './ansi'
89

910
const Logs: React.FC = () => {
1011
const { data: logs, purgeLogs } = useLogs('/api/logs')
@@ -23,9 +24,9 @@ const Logs: React.FC = () => {
2324
Purge Logs
2425
</Button>
2526
</div>
26-
<div className="flex gap-3 border-b pb-2 text-lg font-semibold">
27-
<span className="w-[200px]">Time</span>
28-
<span className="w-[150px]">Service</span>
27+
<div className="grid grid-cols-[200px_150px_1fr] gap-x-3 border-b pb-2 text-lg font-semibold">
28+
<span>Time</span>
29+
<span>Service</span>
2930
<span>Message</span>
3031
</div>
3132
<div className="mt-1 flex flex-col font-mono text-sm">
@@ -35,7 +36,7 @@ const Logs: React.FC = () => {
3536
<div
3637
key={i}
3738
className={cn(
38-
'mt-0.5 flex cursor-pointer items-start gap-x-2 whitespace-pre-wrap rounded-sm px-1 py-[2px] hover:bg-gray-100 dark:hover:bg-gray-700',
39+
'mt-0.5 grid cursor-pointer grid-cols-[200px_150px_1fr] items-start gap-x-2 whitespace-pre-wrap rounded-sm px-1 py-[2px] hover:bg-gray-100 dark:hover:bg-gray-700',
3940
{
4041
'bg-red-100 hover:bg-red-200 dark:bg-red-800/70 dark:hover:bg-red-800/90':
4142
msg.toLowerCase().includes('error'),
@@ -55,7 +56,9 @@ const Logs: React.FC = () => {
5556
<p>{serviceName}</p>
5657
</TooltipContent>
5758
</Tooltip>
58-
<span className="border-l pl-2">{formattedLine}</span>
59+
<span className="border-l pl-2">
60+
{ansiToReact(formattedLine)}
61+
</span>
5962
</div>
6063
)
6164
})}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
export const ansiToReact = (msg: string): React.ReactNode[] => {
2+
// Map ANSI codes to Tailwind CSS classes, this only supports the most common foreground codes
3+
// https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
4+
const ansiClassMap: Record<string, string> = {
5+
// Foreground colors
6+
'30': 'text-black',
7+
'31': 'text-red-600',
8+
'32': 'text-green-600',
9+
'33': 'text-yellow-600',
10+
'34': 'text-blue-600',
11+
'35': 'text-purple-600',
12+
'36': 'text-cyan-600',
13+
'37': 'text-gray-600',
14+
// Reset
15+
'39': 'text-inherit',
16+
}
17+
18+
const parts: React.ReactNode[] = []
19+
const regex = /\u001b\[([0-9]+)m/g
20+
let lastIndex = 0
21+
let currentClass = ''
22+
23+
msg.replace(regex, (match, code, offset) => {
24+
if (offset > lastIndex) {
25+
parts.push(
26+
<span key={lastIndex} className={currentClass}>
27+
{msg.slice(lastIndex, offset)}
28+
</span>,
29+
)
30+
}
31+
32+
currentClass = ansiClassMap[code] || ''
33+
lastIndex = offset + match.length
34+
35+
return '' // Required for `.replace` but unused
36+
})
37+
38+
// Add any remaining text
39+
if (lastIndex < msg.length) {
40+
parts.push(
41+
<span key={lastIndex} className={currentClass}>
42+
{msg.slice(lastIndex)}
43+
</span>,
44+
)
45+
}
46+
47+
return parts
48+
}

0 commit comments

Comments
 (0)