'use client'; import { useState, useEffect, Suspense } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useSearchParams } from 'next/navigation'; import api from '@/lib/api'; import type { Application, LogEntry, LogSearchResult, LogStatsResult } from '@/types'; import { Loader2, RefreshCw, AlertCircle, FileText, ChevronLeft, ChevronRight, } from 'lucide-react'; const LOG_LEVELS = [ { value: '', label: 'All levels' }, { value: 'error', label: 'Error' }, { value: 'warn', label: 'Warning' }, { value: 'info', label: 'Info' }, { value: 'debug', label: 'Debug' }, ]; const WORKLOADS = [ { value: '', label: 'All sources' }, { value: 'app', label: 'Application' }, { value: 'redis', label: 'Redis' }, { value: 'rabbitmq', label: 'RabbitMQ' }, { value: 'database', label: 'Database' }, ]; const TIME_RANGES = [ { value: '1h', label: 'Last hour' }, { value: '6h', label: 'Last 6 hours' }, { value: '24h', label: 'Last 24 hours' }, { value: '7d', label: 'Last 7 days' }, ]; function levelBadgeClass(level: string) { switch (level?.toLowerCase()) { case 'error': return 'bg-red-100 text-red-800'; case 'warn': case 'warning': return 'bg-amber-100 text-amber-800'; case 'info': return 'bg-blue-100 text-blue-800'; default: return 'bg-gray-100 text-gray-700'; } } function LogsPageContent() { const searchParams = useSearchParams(); const initialAppId = searchParams.get('appId') || ''; const [appId, setAppId] = useState(initialAppId); const [workload, setWorkload] = useState(''); const [level, setLevel] = useState(''); const [search, setSearch] = useState(''); const [timeRange, setTimeRange] = useState('24h'); const [page, setPage] = useState(1); const [autoRefresh, setAutoRefresh] = useState(false); useEffect(() => { if (initialAppId) setAppId(initialAppId); }, [initialAppId]); const { data: loggingStatus } = useQuery({ queryKey: ['logs-status'], queryFn: () => api.get('/logs/status').then((r) => r.data as { available: boolean }), }); const { data: applications = [] } = useQuery({ queryKey: ['applications'], queryFn: () => api.get('/applications').then((r) => r.data), }); const buildTimeRange = () => { const now = new Date(); const from = new Date(); switch (timeRange) { case '1h': from.setHours(now.getHours() - 1); break; case '6h': from.setHours(now.getHours() - 6); break; case '7d': from.setDate(now.getDate() - 7); break; default: from.setDate(now.getDate() - 1); } return { from: from.toISOString(), to: now.toISOString() }; }; const { from, to } = buildTimeRange(); const { data: logsResult, isLoading, isFetching, refetch, error, } = useQuery({ queryKey: ['logs', appId, workload, level, search, timeRange, page], queryFn: () => { const params = new URLSearchParams(); if (appId) params.set('appId', appId); if (workload) params.set('workload', workload); if (level) params.set('level', level); if (search) params.set('search', search); params.set('from', from); params.set('to', to); params.set('page', String(page)); params.set('limit', '100'); return api.get(`/logs?${params.toString()}`).then((r) => r.data); }, enabled: loggingStatus?.available !== false, refetchInterval: autoRefresh ? 5000 : false, }); const { data: stats } = useQuery({ queryKey: ['log-stats', appId, workload, timeRange], queryFn: () => { const params = new URLSearchParams(); if (appId) params.set('appId', appId); if (workload) params.set('workload', workload); params.set('period', timeRange); return api.get(`/logs/stats?${params.toString()}`).then((r) => r.data); }, enabled: loggingStatus?.available !== false, }); const totalPages = logsResult ? Math.max(1, Math.ceil(logsResult.total / logsResult.limit)) : 1; if (loggingStatus && !loggingStatus.available) { return (

Logging not available

Central Elasticsearch is not deployed on the cluster. Enable the logging addon when deploying an app, and ask an administrator to deploy the logging stack.

); } return (

Logs

Application, Redis, RabbitMQ, and database logs in one place

{ setSearch(e.target.value); setPage(1); }} placeholder="Search message..." className="input w-full text-sm" />
{stats && (

Total ({stats.period})

{stats.total}

Errors

{stats.errors}

Warnings

{stats.warnings}

Sources

{Object.entries(stats.byWorkload || {}) .map(([k, v]) => `${k}: ${v}`) .join(' · ') || '—'}

)} {error && (
Failed to load logs. Ensure logging is enabled on your application and Elasticsearch is running.
)}

Log entries

{logsResult && ( {logsResult.total} total · page {page}/{totalPages} )}
{isLoading ? (
Loading logs...
) : !logsResult?.hits?.length ? (
No logs found for the selected filters. {!appId &&

Deploy an app with logging enabled to start collecting logs.

}
) : (
{logsResult.hits.map((entry: LogEntry) => ( ))}
Time Level App Source Message
{new Date(entry.timestamp).toLocaleString()} {entry.level?.toUpperCase()} {entry.applicationName || '—'} {entry.workload || 'app'} {entry.message}
)} {logsResult && logsResult.total > logsResult.limit && (
)}
); } export default function LogsPage() { return ( Loading...}> ); }