jan/web-app/src/routes/logs.tsx
Louis 919b6671a1
enahancement: mcp server activation response and error handling (#5220)
* fix: mcp server error handling

* fix: custom installation path of MCP package managers

* chore: clean up

* chore: clean up

* chore: append mcp server errors to app logs

* fix: logs reading

* chore: typo
2025-06-09 19:43:16 +07:00

101 lines
2.9 KiB
TypeScript

import { createFileRoute } from '@tanstack/react-router'
import { route } from '@/constants/routes'
import { useEffect, useState, useRef } from 'react'
import { readLogs } from '@/services/app'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const Route = createFileRoute(route.appLogs as any)({
component: LogsViewer,
})
// Define log entry type
function LogsViewer() {
const [logs, setLogs] = useState<LogEntry[]>([])
const logsContainerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
let lastLogsLength = 0
function updateLogs() {
readLogs().then((logData) => {
let needScroll = false
const filteredLogs = logData.filter(Boolean) as LogEntry[]
if (filteredLogs.length > lastLogsLength) needScroll = true
lastLogsLength = filteredLogs.length
setLogs(filteredLogs)
// Scroll to bottom after initial logs are loaded
if (needScroll) setTimeout(() => scrollToBottom(), 100)
})
}
updateLogs()
// repeat action each 3s
const intervalId = setInterval(() => updateLogs(), 3000)
return () => {
clearInterval(intervalId)
}
}, [])
// Function to scroll to the bottom of the logs container
const scrollToBottom = () => {
if (logsContainerRef.current) {
const { scrollHeight, clientHeight } = logsContainerRef.current
logsContainerRef.current.scrollTop = scrollHeight - clientHeight
}
}
// Function to get appropriate color for log level
const getLogLevelColor = (level: string) => {
switch (level) {
case 'error':
return 'text-red-500'
case 'warn':
return 'text-yellow-500'
case 'info':
return 'text-blue-500'
case 'debug':
return 'text-gray-500'
default:
return 'text-gray-500'
}
}
// Format timestamp to be more readable
const formatTimestamp = (timestamp: string | number) => {
const date = new Date(timestamp)
return date.toLocaleTimeString()
}
return (
<div className="flex flex-col h-full bg-main-view">
<div className="flex-1 overflow-auto" ref={logsContainerRef}>
<div className="font-mono p-2">
{logs.length === 0 ? (
<div className="text-center text-main-view-fg/50 py-8">
No logs available
</div>
) : (
logs.map((log, index) => (
<div key={index} className="mb-1 flex">
<span className="text-muted-foreground mr-2">
[{formatTimestamp(log.timestamp)}]
</span>
<span
className={`mr-2 font-semibold ${getLogLevelColor(log.level)}`}
>
{log.level.toUpperCase()}
</span>
<span>{log.message}</span>
</div>
))
)}
</div>
</div>
</div>
)
}