diff --git a/app/src/components/LogViewer.tsx b/app/src/components/LogViewer.tsx index f32312e..75161ab 100644 --- a/app/src/components/LogViewer.tsx +++ b/app/src/components/LogViewer.tsx @@ -1,4 +1,5 @@ import { useEffect, useImperativeHandle, useRef, useState, forwardRef } from "react" +import { Maximize2, Minimize2, Trash2 } from "lucide-react" import { apiClient } from "@/services/api/client" interface LogViewerProps { @@ -17,53 +18,83 @@ export const LogViewer = forwardRef(function Lo ) { const [logs, setLogs] = useState([]) const [connected, setConnected] = useState(false) + const [maximized, setMaximized] = useState(false) const bottomRef = useRef(null) useEffect(() => { if (!follow) { - // Static fetch apiClient .get<{ lines: string[] }>(`/logs/${name}?n=${lines}`) .then((data) => setLogs(data.lines)) return } - // SSE follow const url = apiClient.streamUrl(`/logs/${name}?n=${lines}&follow=true`) const es = new EventSource(url) - es.onopen = () => setConnected(true) - es.onmessage = (e) => { setLogs((prev) => { const next = [...prev, e.data] - // Keep last 500 lines in memory return next.length > 500 ? next.slice(-500) : next }) } - es.onerror = () => setConnected(false) - return () => es.close() }, [name, lines, follow]) - useImperativeHandle(ref, () => ({ clear: () => setLogs([]) }), []) + const clear = () => setLogs([]) + useImperativeHandle(ref, () => ({ clear }), []) + + // Esc exits fullscreen. + useEffect(() => { + if (!maximized) return + const onKey = (e: KeyboardEvent) => e.key === "Escape" && setMaximized(false) + window.addEventListener("keydown", onKey) + return () => window.removeEventListener("keydown", onKey) + }, [maximized]) useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }) }, [logs]) + const btn = + "p-1 rounded text-[var(--muted)] hover:text-[var(--foreground)] hover:bg-white/10 transition-colors" + return ( -
+
- logs: {name} - {follow && ( - - {connected ? "streaming" : "disconnected"} - - )} + + logs: {name} + {follow && ( + + {connected ? "streaming" : "disconnected"} + + )} + + + + +
-
+      
         {logs.length === 0 ? (
           No logs yet
         ) : (
diff --git a/app/src/pages/ScheduledDetail.tsx b/app/src/pages/ScheduledDetail.tsx
index 67b7bfe..c9e3368 100644
--- a/app/src/pages/ScheduledDetail.tsx
+++ b/app/src/pages/ScheduledDetail.tsx
@@ -1,8 +1,7 @@
-import { useRef } from "react"
 import { useParams } from "react-router-dom"
-import { Clock, Trash2 } from "lucide-react"
+import { Clock } from "lucide-react"
 import { useJob, useStatus, useEventStream } from "@/services/api/hooks"
-import { LogViewer, type LogViewerHandle } from "@/components/LogViewer"
+import { LogViewer } from "@/components/LogViewer"
 import { DetailHeader } from "@/components/detail/DetailHeader"
 import { ServiceControls } from "@/components/detail/ServiceControls"
 import { SystemdPanel } from "@/components/detail/SystemdPanel"
@@ -10,7 +9,6 @@ import { ConfigPanel } from "@/components/detail/ConfigPanel"
 
 export function ScheduledDetailPage() {
   useEventStream()
-  const logRef = useRef(null)
   const { name } = useParams<{ name: string }>()
   const { data: deployment, isLoading, error, refetch } = useJob(name ?? "")
   const { data: statusResp } = useStatus()
@@ -74,16 +72,8 @@ export function ScheduledDetailPage() {
 
       {deployment.managed && (
         
-
-

Logs

- -
- +

Logs

+
)}
diff --git a/app/src/pages/ServiceDetail.tsx b/app/src/pages/ServiceDetail.tsx index 8a3a94f..9d93e51 100644 --- a/app/src/pages/ServiceDetail.tsx +++ b/app/src/pages/ServiceDetail.tsx @@ -1,10 +1,9 @@ -import { useRef } from "react" import { useParams, Link } from "react-router-dom" -import { Server, ExternalLink, Terminal, Trash2 } from "lucide-react" +import { Server, ExternalLink, Terminal } from "lucide-react" import { useService, useStatus, useEventStream, useCaddyfile } from "@/services/api/hooks" import { runnerLabel } from "@/lib/labels" import { HealthBadge } from "@/components/HealthBadge" -import { LogViewer, type LogViewerHandle } from "@/components/LogViewer" +import { LogViewer } from "@/components/LogViewer" import { DetailHeader } from "@/components/detail/DetailHeader" import { ServiceControls } from "@/components/detail/ServiceControls" import { SystemdPanel } from "@/components/detail/SystemdPanel" @@ -12,7 +11,6 @@ import { ConfigPanel } from "@/components/detail/ConfigPanel" export function ServiceDetailPage() { useEventStream() - const logRef = useRef(null) const { name } = useParams<{ name: string }>() const { data: deployment, isLoading, error, refetch } = useService(name ?? "") const { data: statusResp } = useStatus() @@ -141,16 +139,8 @@ export function ServiceDetailPage() { {deployment.managed && (
-
-

Logs

- -
- +

Logs

+
)}