From d6aa5ecd114e5695ba317e9ce920e58da878383d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Mon, 2 Feb 2026 02:55:31 +0000 Subject: [PATCH] Improve deployment logs with colors, formatting, and expand view - Add syntax highlighting: errors (red), warnings (yellow), success (green) - Add line numbers - Add expand button for fullscreen modal view - Fix horizontal overflow with overflow-x-hidden and break-words - Dark terminal-style background for better readability Co-Authored-By: Claude Opus 4.5 --- src/components/DeploymentLogs.tsx | 155 ++++++++++++++++++++++++++---- src/components/Icons.tsx | 1 + 2 files changed, 135 insertions(+), 21 deletions(-) diff --git a/src/components/DeploymentLogs.tsx b/src/components/DeploymentLogs.tsx index 9669156..17e317e 100644 --- a/src/components/DeploymentLogs.tsx +++ b/src/components/DeploymentLogs.tsx @@ -10,10 +10,34 @@ interface DeploymentLogsProps { initialLogs?: string; } +// Color log lines based on content +function getLogLineStyle(log: DeploymentLog): string { + const output = log.output.toLowerCase(); + + if (log.type === 'stderr' || output.includes('error') || output.includes('failed')) { + return 'text-red-500 dark:text-red-400'; + } + if (output.includes('warning') || output.includes('warn')) { + return 'text-yellow-600 dark:text-yellow-400'; + } + if (output.includes('success') || output.includes('finished') || output.includes('done') || output.includes('✓')) { + return 'text-green-600 dark:text-green-400'; + } + if (output.startsWith('---') || output.startsWith('===') || output.startsWith('###')) { + return 'text-cyan-600 dark:text-cyan-400 font-semibold'; + } + // Commands often start with $ or > + if (output.startsWith('$') || output.startsWith('>') || output.startsWith('#')) { + return 'text-purple-600 dark:text-purple-400'; + } + return 'text-slate-600 dark:text-stone-400'; +} + export function DeploymentLogs({ deploymentUuid, status, initialLogs }: DeploymentLogsProps) { const [logs, setLogs] = useState(() => parseDeploymentLogs(initialLogs)); const [isPolling, setIsPolling] = useState(status === 'in_progress' || status === 'queued'); const [copied, setCopied] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); const logsEndRef = useRef(null); const containerRef = useRef(null); const [autoScroll, setAutoScroll] = useState(true); @@ -62,7 +86,6 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme const handleScroll = () => { if (!containerRef.current) return; const { scrollTop, scrollHeight, clientHeight } = containerRef.current; - // If user scrolled up more than 100px from bottom, disable auto-scroll setAutoScroll(scrollHeight - scrollTop - clientHeight < 100); }; @@ -77,14 +100,101 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme } }; + // Expanded modal view + if (isExpanded) { + return ( +
{ + e.stopPropagation(); + setIsExpanded(false); + }} + > +
e.stopPropagation()} + > + {/* Header */} +
+ Build Logs - {deploymentUuid.substring(0, 12)} +
+ {isPolling && ( + + + + + Polling + + )} + + +
+
+ + {/* Logs */} +
+ {logs.length === 0 ? ( +
+ {isPolling ? 'Waiting for logs...' : 'No logs available'} +
+ ) : ( + logs.map((log, index) => ( +
+ + {index + 1} + + {log.timestamp && ( + + {log.timestamp} + + )} + {log.output} +
+ )) + )} +
+
+ + {/* Footer */} +
+ {logs.length} lines +
+
+
+ ); + } + + // Inline view return ( -
+
{/* Header */} -
- Build Logs +
+ Build Logs
{isPolling && ( - + @@ -96,11 +206,21 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme e.stopPropagation(); copyToClipboard(); }} - className="flex items-center gap-1 px-2 py-1 text-xs text-slate-500 dark:text-stone-400 hover:text-slate-700 dark:hover:text-stone-200 hover:bg-slate-200 dark:hover:bg-stone-800 rounded transition-colors" + className="flex items-center gap-1 px-2 py-1 text-xs text-slate-400 hover:text-slate-200 hover:bg-slate-800 rounded transition-colors" > {copied ? 'Copied' : 'Copy'} +
@@ -109,26 +229,19 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme ref={containerRef} onScroll={handleScroll} onClick={(e) => e.stopPropagation()} - className="max-h-80 overflow-y-auto overflow-x-auto font-mono text-xs p-4 space-y-0.5" + className="h-64 overflow-y-auto overflow-x-hidden font-mono text-xs p-4 space-y-0.5" > {logs.length === 0 ? ( -
+
{isPolling ? 'Waiting for logs...' : 'No logs available'}
) : ( logs.map((log, index) => ( -
- {log.timestamp && ( - - {log.timestamp} - - )} - {log.output} +
+ + {index + 1} + + {log.output}
)) )} @@ -145,7 +258,7 @@ export function DeploymentLogs({ deploymentUuid, status, initialLogs }: Deployme containerRef.current.scrollTop = containerRef.current.scrollHeight; } }} - className="absolute bottom-4 right-4 px-3 py-1.5 bg-slate-200 dark:bg-stone-800 text-slate-700 dark:text-stone-300 text-xs rounded-full shadow-lg hover:bg-slate-300 dark:hover:bg-stone-700 transition-colors" + className="absolute bottom-4 right-4 px-3 py-1.5 bg-slate-700 text-slate-200 text-xs rounded-full shadow-lg hover:bg-slate-600 transition-colors" > Scroll to bottom diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index ab05dc5..8643123 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -106,6 +106,7 @@ export const icons: Record> = { 'external-link': createIcon(''), 'refresh-cw': createIcon(''), 'x': createIcon(''), + 'maximize-2': createIcon(''), 'settings': createIcon(''), 'loader': createIcon(''),