feat: Add coordinator log viewer to cluster UI

- Created /api/cluster/logs endpoint to read coordinator.log
- Added real-time log display in cluster UI (updates every 3s)
- Shows last 100 lines of coordinator.log in terminal-style display
- Includes manual refresh button
- Improves debugging experience - no need to SSH for logs

User feedback: 'why dont we add the output of the log at the bottom of the page so i know whats going on'

This addresses poor visibility into coordinator errors and failures.
Next step: Fix SSH timeout issue blocking worker execution.
This commit is contained in:
mindesbunister
2025-12-01 11:49:23 +01:00
parent db33af9f17
commit 1f83a7d7c4
5 changed files with 727 additions and 36 deletions

View File

@@ -56,6 +56,8 @@ export default function ClusterPage() {
const [error, setError] = useState<string | null>(null)
const [controlLoading, setControlLoading] = useState(false)
const [controlMessage, setControlMessage] = useState<string | null>(null)
const [coordinatorLog, setCoordinatorLog] = useState<string>('')
const [logLoading, setLogLoading] = useState(false)
const fetchStatus = async () => {
try {
@@ -71,6 +73,21 @@ export default function ClusterPage() {
}
}
const fetchLog = async () => {
try {
setLogLoading(true)
const response = await fetch('/api/cluster/logs')
const data = await response.json()
if (data.success) {
setCoordinatorLog(data.log)
}
} catch (error) {
console.error('Failed to fetch coordinator log:', error)
} finally {
setLogLoading(false)
}
}
const handleControl = async (action: 'start' | 'stop') => {
setControlLoading(true)
setControlMessage(null)
@@ -94,8 +111,13 @@ export default function ClusterPage() {
useEffect(() => {
fetchStatus()
const interval = setInterval(fetchStatus, 30000) // Refresh every 30s
return () => clearInterval(interval)
fetchLog()
const statusInterval = setInterval(fetchStatus, 30000) // Refresh status every 30s
const logInterval = setInterval(fetchLog, 3000) // Refresh log every 3s
return () => {
clearInterval(statusInterval)
clearInterval(logInterval)
}
}, [])
if (loading) {
@@ -211,6 +233,28 @@ export default function ClusterPage() {
</div>
</div>
{/* Coordinator Log */}
<div className="bg-gray-900 rounded-lg p-6 border border-gray-800">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">Coordinator Log</h3>
<button
onClick={fetchLog}
disabled={logLoading}
className="px-3 py-1 bg-gray-800 hover:bg-gray-700 rounded text-sm disabled:opacity-50"
>
{logLoading ? '⏳ Loading...' : '🔄 Refresh'}
</button>
</div>
<div className="bg-black rounded-lg p-4 overflow-auto max-h-96">
<pre className="text-xs text-green-400 font-mono whitespace-pre-wrap">
{coordinatorLog || 'No log output available'}
</pre>
</div>
<div className="mt-2 text-xs text-gray-500">
Updates automatically every 3 seconds
</div>
</div>
{/* Worker Details */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{status.workers.map((worker) => (