Add log collection feature to SettingsPanel and TRPC router

- Implemented log collection functionality in the SettingsPanel, allowing users to start/stop recording events and view the count of collected logs.
- Enhanced the TRPC router with procedures for managing log collection state, downloading collected logs, and clearing logs.
- Updated MonitorPage to handle log collection state and integrate new log management features, improving event tracking and user experience.
This commit is contained in:
luccast
2026-01-26 08:31:38 -05:00
parent 54a0a23359
commit 35aecf42d0
3 changed files with 162 additions and 1 deletions
+61 -1
View File
@@ -1,13 +1,18 @@
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Settings, X, History, Wifi, WifiOff, RefreshCw, Terminal } from 'lucide-react'
import { Settings, X, History, Wifi, WifiOff, RefreshCw, Terminal, Download, Trash2, Database } from 'lucide-react'
interface SettingsPanelProps {
connected: boolean
historicalMode: boolean
debugMode: boolean
logCollection: boolean
logCount: number
onHistoricalModeChange: (enabled: boolean) => void
onDebugModeChange: (enabled: boolean) => void
onLogCollectionChange: (enabled: boolean) => void
onDownloadLogs: () => void
onClearLogs: () => void
onConnect: () => void
onDisconnect: () => void
onRefresh: () => void
@@ -17,8 +22,13 @@ export function SettingsPanel({
connected,
historicalMode,
debugMode,
logCollection,
logCount,
onHistoricalModeChange,
onDebugModeChange,
onLogCollectionChange,
onDownloadLogs,
onClearLogs,
onConnect,
onDisconnect,
onRefresh,
@@ -161,6 +171,56 @@ export function SettingsPanel({
</button>
</div>
{/* Log collection */}
<div className="panel-retro p-4">
<div className="flex items-center gap-3 mb-2">
<Database size={18} className="text-shell-500" />
<span className="font-display text-sm font-medium text-gray-200 uppercase tracking-wide">
Log Collection
</span>
</div>
<p className="font-console text-[10px] text-shell-500 mb-3">
<span className="text-crab-600">&gt;</span> collect raw events for export
</p>
{logCount > 0 && (
<div className="font-console text-[10px] text-neon-mint mb-3">
<span className="text-crab-600">&gt;</span> {logCount} events collected
</div>
)}
<button
onClick={() => onLogCollectionChange(!logCollection)}
className={`w-full px-4 py-2 font-display text-xs uppercase tracking-wide rounded-lg border-2 transition-all mb-2 ${
logCollection
? 'bg-neon-mint/20 border-neon-mint/50 text-neon-mint'
: 'bg-shell-800 border-shell-700 text-gray-400 hover:border-shell-600'
}`}
>
{logCollection ? 'Recording...' : 'Start Recording'}
</button>
<div className="flex gap-2">
<button
onClick={onDownloadLogs}
disabled={logCount === 0}
className="flex-1 flex items-center justify-center gap-2 px-3 py-2 font-display text-xs uppercase tracking-wide bg-shell-800 hover:bg-shell-700 border-2 border-shell-700 rounded-lg transition-all disabled:opacity-40 disabled:cursor-not-allowed"
>
<Download size={12} />
Save
</button>
<button
onClick={onClearLogs}
disabled={logCount === 0}
className="flex-1 flex items-center justify-center gap-2 px-3 py-2 font-display text-xs uppercase tracking-wide bg-shell-800 hover:bg-crab-900/50 border-2 border-shell-700 hover:border-crab-700 rounded-lg transition-all disabled:opacity-40 disabled:cursor-not-allowed"
>
<Trash2 size={12} />
Clear
</button>
</div>
</div>
{/* Info panel */}
<div className="panel-retro p-4 bg-shell-950/50">
<h3 className="font-display text-xs text-gray-400 uppercase tracking-wide mb-3">
+44
View File
@@ -13,6 +13,10 @@ import {
// Server-side debug mode state
let debugMode = false
// Server-side log collection
let collectLogs = false
const collectedEvents: Array<{ timestamp: number; event: unknown }> = []
const t = initTRPC.create({
transformer: superjson,
})
@@ -66,6 +70,38 @@ const clawdbotRouter = router({
return { debugMode }
}),
// Log collection
setLogCollection: publicProcedure
.input(z.object({ enabled: z.boolean() }))
.mutation(({ input }) => {
collectLogs = input.enabled
if (input.enabled) {
console.log(`[clawdbot] log collection started`)
} else {
console.log(`[clawdbot] log collection stopped, ${collectedEvents.length} events collected`)
}
return { collectLogs, eventCount: collectedEvents.length }
}),
getLogCollection: publicProcedure.query(() => {
return { collectLogs, eventCount: collectedEvents.length }
}),
downloadLogs: publicProcedure.query(() => {
return {
events: collectedEvents,
count: collectedEvents.length,
collectedAt: new Date().toISOString(),
}
}),
clearLogs: publicProcedure.mutation(() => {
const count = collectedEvents.length
collectedEvents.length = 0
console.log(`[clawdbot] cleared ${count} collected events`)
return { cleared: count }
}),
sessions: publicProcedure
.input(
z
@@ -103,6 +139,14 @@ const clawdbotRouter = router({
const client = getClawdbotClient()
const unsubscribe = client.onEvent((event) => {
// Collect raw event when log collection is enabled
if (collectLogs) {
collectedEvents.push({
timestamp: Date.now(),
event,
})
}
// Log raw event when debug mode is enabled
if (debugMode) {
console.log('\n[DEBUG] Raw event:', JSON.stringify(event, null, 2))
+57
View File
@@ -63,6 +63,8 @@ function MonitorPage() {
const [error, setError] = useState<string | null>(null)
const [historicalMode, setHistoricalMode] = useState(false)
const [debugMode, setDebugMode] = useState(false)
const [logCollection, setLogCollection] = useState(false)
const [logCount, setLogCount] = useState(0)
const [selectedSession, setSelectedSession] = useState<string | null>(null)
// Sidebar collapse state - default to collapsed on mobile
@@ -168,6 +170,56 @@ function MonitorPage() {
}
}
const handleLogCollectionChange = async (enabled: boolean) => {
setLogCollection(enabled)
try {
const result = await trpc.clawdbot.setLogCollection.mutate({ enabled })
setLogCount(result.eventCount)
} catch (e) {
console.error('Failed to set log collection:', e)
}
}
const handleDownloadLogs = async () => {
try {
const result = await trpc.clawdbot.downloadLogs.query()
const blob = new Blob([JSON.stringify(result, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `clawdbot-events-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
} catch (e) {
console.error('Failed to download logs:', e)
}
}
const handleClearLogs = async () => {
try {
await trpc.clawdbot.clearLogs.mutate()
setLogCount(0)
} catch (e) {
console.error('Failed to clear logs:', e)
}
}
// Poll log count while collecting
useEffect(() => {
if (!logCollection) return
const interval = setInterval(async () => {
try {
const result = await trpc.clawdbot.getLogCollection.query()
setLogCount(result.eventCount)
} catch {
// ignore
}
}, 2000)
return () => clearInterval(interval)
}, [logCollection])
const handleToggleSidebar = useCallback(() => {
setSidebarCollapsed((prev) => !prev)
}, [])
@@ -275,8 +327,13 @@ function MonitorPage() {
connected={connected}
historicalMode={historicalMode}
debugMode={debugMode}
logCollection={logCollection}
logCount={logCount}
onHistoricalModeChange={handleHistoricalModeChange}
onDebugModeChange={handleDebugModeChange}
onLogCollectionChange={handleLogCollectionChange}
onDownloadLogs={handleDownloadLogs}
onClearLogs={handleClearLogs}
onConnect={handleConnect}
onDisconnect={handleDisconnect}
onRefresh={handleRefresh}