Fix SSE connection handling and optimize Dockerfile

- Fixed SSE connection not being properly closed when pod logs dialog is closed
- Added proper cleanup for EventSource connections in K8sResourceList.tsx
- Added debugging logs to track SSE connection lifecycle
- Optimized Dockerfile to avoid copying frontend files during Go build stage
- Fixed backend handler to properly use context from request for log streaming

🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
loveuer
2025-11-17 10:33:45 +08:00
parent 7a666303be
commit 704d0fe0bf
5 changed files with 221 additions and 48 deletions

View File

@@ -76,6 +76,7 @@ export default function K8sResourceList() {
const [logsDialogOpen, setLogsDialogOpen] = useState(false)
const [logs, setLogs] = useState<string[]>([])
const [selectedPod, setSelectedPod] = useState<{ name: string; namespace: string } | null>(null)
const eventSourceRef = useRef<EventSource | null>(null)
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
const [deleteTarget, setDeleteTarget] = useState<{ name: string; namespace: string } | null>(null)
const [deleting, setDeleting] = useState(false)
@@ -98,6 +99,17 @@ export default function K8sResourceList() {
}
}, [selectedKind, namespace, nameFilter])
// Clean up SSE connection on component unmount
useEffect(() => {
return () => {
if (eventSourceRef.current) {
console.log('Cleaning up SSE connection on component unmount')
eventSourceRef.current.close()
eventSourceRef.current = null
}
}
}, [])
const fetchKubeconfig = async () => {
try {
const res = await fetch('/api/v1/k8s/config')
@@ -171,24 +183,62 @@ export default function K8sResourceList() {
}
const handleViewLogs = (podName: string, podNamespace: string) => {
console.log('handleViewLogs called with:', { podName, podNamespace })
setSelectedPod({ name: podName, namespace: podNamespace })
setLogs([])
setLogsDialogOpen(true)
// Close any existing connection
if (eventSourceRef.current) {
console.log('Closing existing EventSource connection')
eventSourceRef.current.close()
eventSourceRef.current = null
}
const eventSource = new EventSource(
`/api/v1/k8s/pod/logs?name=${encodeURIComponent(podName)}&namespace=${encodeURIComponent(podNamespace)}&tail=1000&follow=true`
)
// Save reference to the EventSource
eventSourceRef.current = eventSource
eventSource.onmessage = (event) => {
setLogs((prev) => [...prev, event.data])
setTimeout(() => logsEndRef.current?.scrollIntoView({ behavior: 'smooth' }), 100)
}
// Listen for the specific event type 'pod-logs'
eventSource.addEventListener('pod-logs', (event: MessageEvent) => {
try {
const message = JSON.parse(event.data)
if (message.type === 'log') {
setLogs((prev) => [...prev, message.data])
setTimeout(() => logsEndRef.current?.scrollIntoView({ behavior: 'smooth' }), 100)
} else if (message.type === 'EOF') {
// Handle end of stream if needed
} else if (message.type === 'error') {
setLogs((prev) => [...prev, `Error: ${message.data}`])
}
} catch (e) {
// If parsing fails, treat as plain text (fallback)
setLogs((prev) => [...prev, event.data])
setTimeout(() => logsEndRef.current?.scrollIntoView({ behavior: 'smooth' }), 100)
}
})
eventSource.onerror = () => {
eventSource.close()
console.log('EventSource error occurred')
if (eventSourceRef.current) {
eventSourceRef.current.close()
eventSourceRef.current = null
}
}
}
return () => eventSource.close()
const handleCloseLogsDialog = () => {
console.log('handleCloseLogsDialog called')
// Close the EventSource connection if it exists
if (eventSourceRef.current) {
console.log('Closing EventSource connection')
eventSourceRef.current.close()
eventSourceRef.current = null
}
setLogsDialogOpen(false)
}
const handleDeleteResource = async () => {
@@ -843,7 +893,7 @@ export default function K8sResourceList() {
<Dialog
open={logsDialogOpen}
onClose={() => setLogsDialogOpen(false)}
onClose={handleCloseLogsDialog}
maxWidth="lg"
fullWidth
>
@@ -852,7 +902,7 @@ export default function K8sResourceList() {
<Typography variant="h6">
Pod : {selectedPod?.name} ({selectedPod?.namespace})
</Typography>
<IconButton onClick={() => setLogsDialogOpen(false)}>
<IconButton onClick={handleCloseLogsDialog}>
<CloseIcon />
</IconButton>
</Box>