diff --git a/app/api/supervisor/logs/route.ts b/app/api/supervisor/logs/route.ts
new file mode 100644
index 0000000..90decab
--- /dev/null
+++ b/app/api/supervisor/logs/route.ts
@@ -0,0 +1,39 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { createSupervisorClient } from '@/lib/supervisor/client';
+
+export const dynamic = 'force-dynamic';
+
+// GET main supervisord log
+export async function GET(request: NextRequest) {
+ try {
+ const searchParams = request.nextUrl.searchParams;
+ const offset = parseInt(searchParams.get('offset') || '-4096', 10);
+ const length = parseInt(searchParams.get('length') || '4096', 10);
+
+ const client = createSupervisorClient();
+ const logs = await client.readLog(offset, length);
+
+ return NextResponse.json({ logs, offset, length });
+ } catch (error: any) {
+ console.error('Supervisor main log error:', error);
+ return NextResponse.json(
+ { error: error.message || 'Failed to fetch main log' },
+ { status: 500 }
+ );
+ }
+}
+
+// DELETE - Clear main supervisord log
+export async function DELETE() {
+ try {
+ const client = createSupervisorClient();
+ const result = await client.clearLog();
+ return NextResponse.json({ success: result, message: 'Main log cleared' });
+ } catch (error: any) {
+ console.error('Supervisor clear main log error:', error);
+ return NextResponse.json(
+ { error: error.message || 'Failed to clear main log' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/app/api/supervisor/processes/[name]/logs/route.ts b/app/api/supervisor/processes/[name]/logs/route.ts
new file mode 100644
index 0000000..5b9e518
--- /dev/null
+++ b/app/api/supervisor/processes/[name]/logs/route.ts
@@ -0,0 +1,22 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { createSupervisorClient } from '@/lib/supervisor/client';
+
+interface RouteParams {
+ params: Promise<{ name: string }>;
+}
+
+// DELETE - Clear process logs (both stdout and stderr)
+export async function DELETE(request: NextRequest, { params }: RouteParams) {
+ try {
+ const { name } = await params;
+ const client = createSupervisorClient();
+ const result = await client.clearProcessLogs(name);
+ return NextResponse.json({ success: result, message: `Logs cleared for ${name}` });
+ } catch (error: any) {
+ console.error('Supervisor clear process logs error:', error);
+ return NextResponse.json(
+ { error: error.message || 'Failed to clear process logs' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/app/api/supervisor/processes/logs/clear-all/route.ts b/app/api/supervisor/processes/logs/clear-all/route.ts
new file mode 100644
index 0000000..42ad5ed
--- /dev/null
+++ b/app/api/supervisor/processes/logs/clear-all/route.ts
@@ -0,0 +1,21 @@
+import { NextResponse } from 'next/server';
+import { createSupervisorClient } from '@/lib/supervisor/client';
+
+// POST - Clear all process logs
+export async function POST() {
+ try {
+ const client = createSupervisorClient();
+ const results = await client.clearAllProcessLogs();
+ return NextResponse.json({
+ success: true,
+ message: 'All process logs cleared',
+ results
+ });
+ } catch (error: any) {
+ console.error('Supervisor clear all logs error:', error);
+ return NextResponse.json(
+ { error: error.message || 'Failed to clear all logs' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/app/logs/page.tsx b/app/logs/page.tsx
index 8b5d218..a6afdd5 100644
--- a/app/logs/page.tsx
+++ b/app/logs/page.tsx
@@ -1,25 +1,177 @@
'use client';
+import { useState } from 'react';
+import { useProcesses, useProcessLogs, useMainLog, useClearProcessLogs, useClearMainLog } from '@/lib/hooks/useSupervisor';
+import { LogViewer } from '@/components/logs/LogViewer';
+import { LogControls } from '@/components/logs/LogControls';
+import { LogSearch } from '@/components/logs/LogSearch';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
import { AlertCircle } from 'lucide-react';
-import { Card, CardContent } from '@/components/ui/card';
export default function LogsPage() {
- return (
-
-
+ const [selectedProcess, setSelectedProcess] = useState
('main');
+ const [logType, setLogType] = useState<'stdout' | 'stderr'>('stdout');
+ const [searchTerm, setSearchTerm] = useState('');
+ const [isPlaying, setIsPlaying] = useState(true);
+ const [autoScroll, setAutoScroll] = useState(true);
+
+ const { data: processes, isError: processesError } = useProcesses({
+ refetchInterval: isPlaying ? 3000 : undefined,
+ });
+
+ // Fetch logs based on selection
+ const { data: processLogs, isLoading: processLogsLoading, refetch: refetchProcessLogs } = useProcessLogs(
+ selectedProcess as string,
+ logType,
+ {
+ enabled: selectedProcess !== 'main' && isPlaying,
+ refetchInterval: isPlaying ? 2000 : undefined,
+ }
+ );
+
+ const { data: mainLogData, isLoading: mainLogLoading, refetch: refetchMainLog } = useMainLog({
+ enabled: selectedProcess === 'main' && isPlaying,
+ refetchInterval: isPlaying ? 2000 : undefined,
+ });
+
+ const clearProcessLogsMutation = useClearProcessLogs();
+ const clearMainLogMutation = useClearMainLog();
+
+ const currentLogs = selectedProcess === 'main'
+ ? mainLogData?.logs || ''
+ : processLogs?.bytes || '';
+
+ const isLoading = selectedProcess === 'main' ? mainLogLoading : processLogsLoading;
+
+ const handleRefresh = () => {
+ if (selectedProcess === 'main') {
+ refetchMainLog();
+ } else {
+ refetchProcessLogs();
+ }
+ };
+
+ const handleClear = () => {
+ if (!confirm('Are you sure you want to clear these logs?')) return;
+
+ if (selectedProcess === 'main') {
+ clearMainLogMutation.mutate();
+ } else {
+ clearProcessLogsMutation.mutate(selectedProcess);
+ }
+ };
+
+ const handleDownload = () => {
+ const blob = new Blob([currentLogs], { type: 'text/plain' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${selectedProcess === 'main' ? 'supervisord' : selectedProcess}-${logType}-${Date.now()}.log`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ };
+
+ if (processesError) {
+ return (
+
Process Logs
-
Real-time log viewing
+
+
+
+ Failed to load processes
+
+ Could not connect to Supervisor. Please check your configuration.
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
Process Logs
+
Real-time log viewing and management
-
-
-
- Logs View Coming Soon
-
- This feature will display real-time logs from your processes with filtering and search capabilities.
-
+ {/* Controls */}
+
+
+ Log Controls
+
+
+ {/* Process Selector */}
+
+
+
+
+
+
+ {/* Log Type Selector (only for processes, not main) */}
+ {selectedProcess !== 'main' && (
+
+
+
+
+
+
+
+ )}
+
+
+ {/* Search and Controls */}
+
+
+ setIsPlaying(!isPlaying)}
+ autoScroll={autoScroll}
+ onToggleAutoScroll={() => setAutoScroll(!autoScroll)}
+ onRefresh={handleRefresh}
+ onClear={handleClear}
+ onDownload={handleDownload}
+ isClearing={clearProcessLogsMutation.isPending || clearMainLogMutation.isPending}
+ />
+
+
+ {/* Log Viewer */}
+
+
+
);
}
diff --git a/lib/hooks/useSupervisor.ts b/lib/hooks/useSupervisor.ts
index 0825821..d955963 100644
--- a/lib/hooks/useSupervisor.ts
+++ b/lib/hooks/useSupervisor.ts
@@ -188,3 +188,99 @@ export function useRestartProcess() {
},
});
}
+
+// Log Management
+
+async function fetchMainLog(offset: number = -4096, length: number = 4096): Promise<{ logs: string }> {
+ const response = await fetch(`/api/supervisor/logs?offset=${offset}&length=${length}`);
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || 'Failed to fetch main log');
+ }
+ return response.json();
+}
+
+async function clearMainLog(): Promise<{ success: boolean; message: string }> {
+ const response = await fetch('/api/supervisor/logs', { method: 'DELETE' });
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || 'Failed to clear main log');
+ }
+ return response.json();
+}
+
+async function clearProcessLogs(name: string): Promise<{ success: boolean; message: string }> {
+ const response = await fetch(`/api/supervisor/processes/${encodeURIComponent(name)}/logs`, {
+ method: 'DELETE',
+ });
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || 'Failed to clear process logs');
+ }
+ return response.json();
+}
+
+async function clearAllLogs(): Promise<{ success: boolean; message: string }> {
+ const response = await fetch('/api/supervisor/processes/logs/clear-all', {
+ method: 'POST',
+ });
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || 'Failed to clear all logs');
+ }
+ return response.json();
+}
+
+export function useMainLog(options?: { offset?: number; length?: number; enabled?: boolean; refetchInterval?: number }) {
+ return useQuery({
+ queryKey: [...supervisorKeys.all, 'mainLog', options?.offset, options?.length],
+ queryFn: () => fetchMainLog(options?.offset, options?.length),
+ enabled: options?.enabled ?? true,
+ refetchInterval: options?.refetchInterval ?? 2000,
+ });
+}
+
+export function useClearMainLog() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: clearMainLog,
+ onSuccess: (data) => {
+ toast.success(data.message);
+ queryClient.invalidateQueries({ queryKey: [...supervisorKeys.all, 'mainLog'] });
+ },
+ onError: (error: Error) => {
+ toast.error(`Failed to clear main log: ${error.message}`);
+ },
+ });
+}
+
+export function useClearProcessLogs() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (name: string) => clearProcessLogs(name),
+ onSuccess: (data, name) => {
+ toast.success(data.message);
+ queryClient.invalidateQueries({ queryKey: supervisorKeys.process(name) });
+ },
+ onError: (error: Error) => {
+ toast.error(`Failed to clear process logs: ${error.message}`);
+ },
+ });
+}
+
+export function useClearAllLogs() {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: clearAllLogs,
+ onSuccess: (data) => {
+ toast.success(data.message);
+ queryClient.invalidateQueries({ queryKey: supervisorKeys.all });
+ },
+ onError: (error: Error) => {
+ toast.error(`Failed to clear all logs: ${error.message}`);
+ },
+ });
+}