@@ -15,6 +15,12 @@ import type { editor } from 'monaco-editor'
1515import { useTranslation } from 'react-i18next'
1616
1717import { TERMINAL_THEMES , TerminalTheme } from '@/types/themes'
18+ import {
19+ AnsiState ,
20+ generateAnsiCss ,
21+ getAnsiClassNames ,
22+ parseAnsi ,
23+ } from '@/lib/ansi-parser'
1824import { useLogsWebSocket } from '@/lib/api'
1925import { toSimpleContainer } from '@/lib/k8s'
2026import { Button } from '@/components/ui/button'
@@ -109,6 +115,8 @@ export function LogViewer({
109115 } )
110116 const editorRef = useRef < editor . IStandaloneCodeEditor | null > ( null )
111117 const [ logCount , setLogCount ] = useState ( 0 ) // Track log count for re-rendering
118+ const ansiStateRef = useRef < AnsiState > ( { } )
119+ const decorationIdsRef = useRef < string [ ] > ( [ ] )
112120
113121 const [ selectPodName , setSelectPodName ] = useState < string | undefined > (
114122 podName || pods ?. [ 0 ] ?. metadata ?. name || undefined
@@ -183,17 +191,26 @@ export function LogViewer({
183191 const appendLog = useCallback (
184192 ( log : string ) => {
185193 setLogCount ( ( count ) => count + 1 )
194+
195+ const { segments, finalState } = parseAnsi ( log , ansiStateRef . current )
196+ ansiStateRef . current = finalState
197+
198+ const plainText = segments . map ( ( s ) => s . text ) . join ( '' )
199+
186200 if ( filterTerm ) {
187- if ( ! log . toLocaleLowerCase ( ) . includes ( filterTerm ) ) {
201+ if ( ! plainText . toLocaleLowerCase ( ) . includes ( filterTerm . toLowerCase ( ) ) ) {
188202 return
189203 }
190204 }
205+
191206 if ( editorRef . current ) {
192207 const model = editorRef . current . getModel ( )
193208 if ( model ) {
194209 const lineCount = model . getLineCount ( )
195210 const lineMaxColumn = model . getLineMaxColumn ( lineCount )
196211 const prefix = model . getValueLength ( ) === 0 ? '' : '\n'
212+
213+ const textToInsert = `${ prefix } ${ plainText } `
197214 model . applyEdits ( [
198215 {
199216 range : {
@@ -202,10 +219,54 @@ export function LogViewer({
202219 startLineNumber : lineCount ,
203220 endLineNumber : lineCount ,
204221 } ,
205- text : ` ${ prefix } ${ log } ` ,
222+ text : textToInsert ,
206223 forceMoveMarkers : true ,
207224 } ,
208225 ] )
226+
227+ const newDecorations : editor . IModelDeltaDecoration [ ] = [ ]
228+
229+ // Starting position for decorations
230+ let currentLine = lineCount
231+ let currentColumn = lineMaxColumn
232+
233+ if ( prefix === '\n' ) {
234+ currentLine ++
235+ currentColumn = 1
236+ }
237+
238+ segments . forEach ( ( segment ) => {
239+ const lines = segment . text . split ( '\n' )
240+ const endLine = currentLine + lines . length - 1
241+ const endColumn =
242+ lines . length === 1
243+ ? currentColumn + lines [ 0 ] . length
244+ : lines [ lines . length - 1 ] . length + 1
245+
246+ const className = getAnsiClassNames ( segment . styles )
247+ if ( className ) {
248+ newDecorations . push ( {
249+ range : {
250+ startLineNumber : currentLine ,
251+ startColumn : currentColumn ,
252+ endLineNumber : endLine ,
253+ endColumn : endColumn ,
254+ } ,
255+ options : {
256+ inlineClassName : className ,
257+ } ,
258+ } )
259+ }
260+
261+ currentLine = endLine
262+ currentColumn = endColumn
263+ } )
264+
265+ if ( newDecorations . length > 0 ) {
266+ const newIds = model . deltaDecorations ( [ ] , newDecorations )
267+ decorationIdsRef . current . push ( ...newIds )
268+ }
269+
209270 const visibleRange = editorRef . current . getVisibleRanges ( ) [ 0 ]
210271 if ( visibleRange ?. endLineNumber + 2 >= lineCount ) {
211272 editorRef . current . revealLine ( model . getLineCount ( ) )
@@ -221,6 +282,8 @@ export function LogViewer({
221282
222283 const cleanLog = useCallback ( ( ) => {
223284 setLogCount ( 0 )
285+ ansiStateRef . current = { }
286+ decorationIdsRef . current = [ ]
224287 if ( editorRef . current ) {
225288 const model = editorRef . current . getModel ( )
226289 if ( model ) {
@@ -396,6 +459,7 @@ export function LogViewer({
396459 < Card
397460 className = { `h-full flex flex-col py-4 gap-0 ${ isFullscreen ? 'fixed inset-0 z-50 m-0 rounded-none' : '' } ${ wordWrap ? 'whitespace-pre-wrap' : 'whitespace-pre' } ` }
398461 >
462+ < style > { generateAnsiCss ( ) } </ style >
399463 < CardHeader >
400464 < div className = "flex items-center justify-between" >
401465 < div className = "flex items-center gap-2" >
0 commit comments