diff --git a/code/backend/cmd/server/main.go b/code/backend/cmd/server/main.go index b8ada40..cb89722 100644 --- a/code/backend/cmd/server/main.go +++ b/code/backend/cmd/server/main.go @@ -82,6 +82,7 @@ func main() { app.POST("/user", apiCfg.HandlerUser) app.POST("/log", auth.MiddlewareAuth(apiCfg.LogHandler)) app.GET("/logs", auth.MiddlewareAuth(apiCfg.GetUserLogsHandler)) + app.GET("/logs/:id", auth.MiddlewareAuth(apiCfg.GetUserLogHandler)) app.GET("/parsers", apiCfg.ListParsersHandler) app.GET("/parsers/:id", apiCfg.GetParserHandler) app.PATCH("/parsers/:id", apiCfg.UpdateParserHandler) diff --git a/code/backend/internal/api/get_user_logs.go b/code/backend/internal/api/get_user_logs.go index 028115a..6d0e1ab 100644 --- a/code/backend/internal/api/get_user_logs.go +++ b/code/backend/internal/api/get_user_logs.go @@ -6,6 +6,7 @@ import ( "github.com/EleventhHour-Projects/GoLogGo/code/backend/internal/auth" "github.com/nottechdm/notnet/pkg/notnet" + "go.mongodb.org/mongo-driver/v2/bson" ) // GetUserLogsHandler returns paginated requests + stats for the authenticated user. @@ -82,3 +83,35 @@ func (api *Config) GetUserLogsHandler(req *notnet.Request, res *notnet.Response, "totalPages": (total + limit - 1) / limit, }) } + +// GetUserLogHandler returns the original request and its parsed log entries. +// GET /logs/:id +func (api *Config) GetUserLogHandler(req *notnet.Request, res *notnet.Response, claims *auth.CustomClaims) error { + requestID, err := bson.ObjectIDFromHex(req.Param("id")) + if err != nil { + return res.JSON(http.StatusBadRequest, map[string]string{"error": "invalid log id"}) + } + + ctx := req.HTTPRequest.Context() + storedReq, err := api.Reqs.GetUserRequestByID(ctx, claims.UserID, requestID) + if err != nil { + return res.JSON(http.StatusNotFound, map[string]string{"error": "log not found"}) + } + + logs, err := api.Reqs.GetLogsByReqID(ctx, requestID) + if err != nil { + return res.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to fetch log details"}) + } + + return res.JSON(http.StatusOK, map[string]interface{}{ + "request": map[string]interface{}{ + "id": storedReq.ID.Hex(), + "payload": string(storedReq.Payload), + "status": storedReq.Status, + "attempts": storedReq.Attempts, + "createdAt": storedReq.CreatedAt, + "updatedAt": storedReq.UpdatedAt, + }, + "logs": logs, + }) +} diff --git a/code/backend/internal/database/get_user_logs.go b/code/backend/internal/database/get_user_logs.go index d87a253..c07328d 100644 --- a/code/backend/internal/database/get_user_logs.go +++ b/code/backend/internal/database/get_user_logs.go @@ -59,6 +59,19 @@ func (m *MongoDB) GetUserRequests( return reqs, total, cursor.Err() } +// GetUserRequestByID returns one request only when it belongs to the user. +func (m *MongoDB) GetUserRequestByID(ctx context.Context, userID, reqID bson.ObjectID) (*Req, error) { + var req Req + err := m.Collections[CollectionRequests].FindOne(ctx, bson.M{ + "_id": reqID, + "userId": userID, + }).Decode(&req) + if err != nil { + return nil, err + } + return &req, nil +} + // GetLogsByReqID returns all parsed log entries for a given request ID. func (m *MongoDB) GetLogsByReqID(ctx context.Context, reqID bson.ObjectID) ([]Log, error) { col := m.Collections[CollectionLogs] diff --git a/code/backend/internal/fingerprint/fingerprint_test.go b/code/backend/internal/fingerprint/fingerprint_test.go index 1641260..526331c 100644 --- a/code/backend/internal/fingerprint/fingerprint_test.go +++ b/code/backend/internal/fingerprint/fingerprint_test.go @@ -88,6 +88,20 @@ func TestKVEquivalence(t *testing.T) { } } +func TestKVFormattingEquivalence(t *testing.T) { + log1 := `user=alice action=login source=10.0.0.1` + log2 := `user="bob", action="login", source="192.168.1.2"` + + features1 := ExtractFingerprintFeatures(log1) + features2 := ExtractFingerprintFeatures(log2) + hash1 := GenerateFingerprintHash(features1) + hash2 := GenerateFingerprintHash(features2) + + if hash1 != hash2 { + t.Fatalf("expected equivalent KV formatting to produce the same hash, got %s and %s (%s vs %s)", hash1, hash2, CanonicalRepresentation(features1), CanonicalRepresentation(features2)) + } +} + // TestSyslogRFC5424Equivalence verifies RFC 5424 syslog logs. func TestSyslogRFC5424Equivalence(t *testing.T) { log1 := `<165>1 2026-08-30T13:02:11.003Z myhost.example.com myapp 1234 ID47 [exampleSDID@32473 iut="3" eventSource="Application"] User 10.0.0.1 authenticated` diff --git a/code/backend/internal/fingerprint/hash.go b/code/backend/internal/fingerprint/hash.go index bafa199..a38f2ad 100644 --- a/code/backend/internal/fingerprint/hash.go +++ b/code/backend/internal/fingerprint/hash.go @@ -47,10 +47,6 @@ func CanonicalRepresentation(feat FingerprintFeatures) string { case FormatKeyValue: if feat.KV != nil { - sb.WriteString("|sep=") - sb.WriteString(feat.KV.Separator) - sb.WriteString("|delim=") - sb.WriteString(feat.KV.Delimiter) sb.WriteString("|fields=") sb.WriteString(canonicalFieldSignatures(feat.KV.Fields)) } else { diff --git a/code/backend/internal/parser/engine.go b/code/backend/internal/parser/engine.go index 8672efc..5e13b82 100644 --- a/code/backend/internal/parser/engine.go +++ b/code/backend/internal/parser/engine.go @@ -28,6 +28,9 @@ func ParseLog(rawLog string, p *Parser) (*NormalizedLog, error) { if match == nil { return nil, fmt.Errorf("log does not match parser pattern") } + if match[0] != rawLog { + return nil, fmt.Errorf("log does not fully match parser pattern") + } fields := extractFields(re, match) diff --git a/code/backend/internal/parser/engine_test.go b/code/backend/internal/parser/engine_test.go index e89a0e0..ec34da1 100644 --- a/code/backend/internal/parser/engine_test.go +++ b/code/backend/internal/parser/engine_test.go @@ -53,6 +53,22 @@ func TestParseLog_BasicExample(t *testing.T) { } } +func TestParseLog_RejectsTrailingInput(t *testing.T) { + p := &Parser{Pattern: `^(?P\w+) (?P.*)$`, Mapping: map[string]string{ + "severity": "severity", + "message": "message", + }} + + if _, err := ParseLog("INFO message trailing", p); err != nil { + t.Fatalf("expected a valid full-line match, got %v", err) + } + + anchoredParser := &Parser{Pattern: `^INFO$`, Mapping: map[string]string{}} + if _, err := ParseLog("INFO unexpected", anchoredParser); err == nil { + t.Fatal("expected trailing input to be rejected") + } +} + func TestParseLog_ExtractedFieldTransformationKey(t *testing.T) { // Raw log with lowercase severity and raw_time field rawLog := "raw_time=2026-09-05 18:30:21 raw_sev=warn msg=something happened" diff --git a/code/frontend/gologgo-client/app/api/logs/[id]/route.js b/code/frontend/gologgo-client/app/api/logs/[id]/route.js new file mode 100644 index 0000000..69b9d64 --- /dev/null +++ b/code/frontend/gologgo-client/app/api/logs/[id]/route.js @@ -0,0 +1,22 @@ +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8080' + +export async function GET(request, { params }) { + try { + const { id } = await params + const token = (await cookies()).get('tokenString')?.value + if (!token) return NextResponse.json({ error: 'Unauthorized: No token found' }, { status: 401 }) + + const backendRes = await fetch(`${BACKEND_URL}/logs/${id}`, { + headers: { Authorization: `Bearer ${token}` }, + cache: 'no-store', + }) + const body = await backendRes.json().catch(() => ({ error: 'Invalid backend response' })) + return NextResponse.json(body, { status: backendRes.status }) + } catch (error) { + console.error('Error in log detail API:', error) + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }) + } +} diff --git a/code/frontend/gologgo-client/app/globals.css b/code/frontend/gologgo-client/app/globals.css index 9061318..a15fbab 100644 --- a/code/frontend/gologgo-client/app/globals.css +++ b/code/frontend/gologgo-client/app/globals.css @@ -5,6 +5,8 @@ @custom-variant dark (&:is(.dark *)); @theme inline { + --font-sans: "Avenir Next", "Segoe UI", sans-serif; + --font-mono: "SF Mono", "Cascadia Code", Menlo, monospace; --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); @@ -159,5 +161,7 @@ } body { @apply bg-background text-foreground; + font-family: "Avenir Next", "Segoe UI", sans-serif; + letter-spacing: 0; } } diff --git a/code/frontend/gologgo-client/app/unknown-logs/page.jsx b/code/frontend/gologgo-client/app/unknown-logs/page.jsx deleted file mode 100644 index 7868d31..0000000 --- a/code/frontend/gologgo-client/app/unknown-logs/page.jsx +++ /dev/null @@ -1,5 +0,0 @@ -import { UnknownLogs } from '@/components/unknown-logs' - -export default function UnknownLogsPage() { - return -} diff --git a/code/frontend/gologgo-client/components/log-dashboard.jsx b/code/frontend/gologgo-client/components/log-dashboard.jsx index 3d0ab34..3c1e9b1 100644 --- a/code/frontend/gologgo-client/components/log-dashboard.jsx +++ b/code/frontend/gologgo-client/components/log-dashboard.jsx @@ -1,21 +1,13 @@ 'use client' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Sidebar } from '@/components/sidebar' -import Link from 'next/link' import { useRouter } from 'next/navigation' import { BarChart3, - ChevronDown, CircleHelp, Database, - FileJson, - LayoutDashboard, - ListFilter, Menu, - Search, - TerminalSquare, - X, } from 'lucide-react' import { ThemeToggle } from '@/components/theme-toggle' import { toast } from 'sonner' @@ -25,33 +17,55 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' -const parserData = [ - { name: 'Nginx', count: 8421 }, - { name: 'Apache', count: 5921 }, - { name: 'Auth', count: 5241 }, - { name: 'PostgreSQL', count: 3241 }, - { name: 'Docker', count: 1698 }, - { name: 'Custom', count: 780 }, -] - -const logs = [ - { id: 'log-2241', time: '22:41:03', level: 'ERROR', service: 'auth-service', message: 'User authentication failed', status: 'Parsed' }, - { id: 'log-2240', time: '22:40:15', level: 'INFO', service: 'payment-service', message: 'Payment completed', status: 'Parsed' }, - { id: 'log-2239', time: '22:39:42', level: 'WARN', service: 'nginx', message: 'Invalid request', status: 'Unknown' }, - { id: 'log-2238', time: '22:38:51', level: 'INFO', service: 'user-service', message: 'User registered', status: 'Parsed' }, - { id: 'log-2237', time: '22:37:22', level: 'ERROR', service: 'database', message: 'Connection timeout', status: 'Parsed' }, -] - - - export function LogDashboard() { const router = useRouter() - const [range, setRange] = useState('Last 24 hours') - const [menuOpen, setMenuOpen] = useState(false) const [mobileOpen, setMobileOpen] = useState(false) - const max = parserData[0].count const [rawLogs, setRawLogs] = useState('') const [isProcessing, setIsProcessing] = useState(false) + const [stats, setStats] = useState(null) + const [parserData, setParserData] = useState([]) + const [recentLogs, setRecentLogs] = useState([]) + + useEffect(() => { + const loadDashboard = async () => { + try { + const [logsResponse, parsersResponse] = await Promise.all([ + fetch('/api/logs?limit=5&status=all', { cache: 'no-store' }), + fetch('/api/parsers', { cache: 'no-store' }), + ]) + + if (logsResponse.ok) { + const logsData = await logsResponse.json() + setStats(logsData.stats) + setRecentLogs((logsData.requests ?? []).map((request) => { + const normalized = request.logs?.[0]?.normalizedLog ?? {} + return { + id: request.id, + time: request.createdAt, + level: normalized.severity || '—', + service: normalized.service || '—', + message: normalized.message || request.payload || '—', + status: request.status, + } + })) + } + + if (parsersResponse.ok) { + const parsersData = await parsersResponse.json() + const parsers = parsersData?.parsers ?? parsersData ?? [] + setParserData(parsers + .map((parser) => ({ name: parser.name || parser.format || 'Unnamed', count: Number(parser.logsProcessed || 0) })) + .filter((parser) => parser.count > 0) + .sort((a, b) => b.count - a.count) + .slice(0, 6)) + } + } catch (error) { + console.error('Failed to load dashboard data:', error) + } + } + + loadDashboard() + }, []) const handleClearLogs = () => setRawLogs('') const handleProcessLogs = async () => { @@ -96,11 +110,11 @@ export function LogDashboard() {

Overview

Dashboard

Overview of your GoLogGo workspace

-
}
+
- {[['Total Logs', '24,521', '+12.4%', Database], ['Parsed Logs', '23,891', '+8.2%', BarChart3], ['Unknown Logs', '630', '-3.1%', CircleHelp], ['Error Logs', '42', '+2.4%', CircleHelp]].map(([label, value, trend, Icon]) =>

{label}

{value}

{trend}
)} + {[['Total Requests', stats?.total ?? '—', Database], ['Completed', stats?.completed ?? '—', BarChart3], ['Waiting Parser', stats?.waitingParser ?? '—', CircleHelp], ['Failed', stats?.failed ?? '—', CircleHelp]].map(([label, value, Icon]) =>

{label}

{value}

)}
@@ -129,9 +143,9 @@ export function LogDashboard() { -
Log Parsing Overview

Successfully processed logs by parser

{parserData.map((item) =>
{item.name}
{item.count.toLocaleString()}
)}
+
Log Parsing Overview

Successfully processed logs by parser

{parserData.length > 0 ?
{parserData.map((item) =>
{item.name}
{item.count.toLocaleString()}
)}
:

No parser activity yet.

} -
Recent Logs

Latest events received by GoLogGo

TimeLevelServiceMessageStatus{logs.map((log) => router.push(`/logs/${log.id}`)} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') router.push(`/logs/${log.id}`) }}>{log.time}{log.level}{log.service}{log.message}{log.status})}
+
Recent Logs

Latest events received by GoLogGo

{recentLogs.length > 0 ? TimeLevelServiceMessageStatus{recentLogs.map((log) => router.push(`/logs/${log.id}`)} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') router.push(`/logs/${log.id}`) }}>{log.time ? new Date(log.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '—'}{log.level}{log.service}{log.message}{log.status})}
:

No recent logs yet.

}
diff --git a/code/frontend/gologgo-client/components/log-details.jsx b/code/frontend/gologgo-client/components/log-details.jsx index 1e61480..a4154e7 100644 --- a/code/frontend/gologgo-client/components/log-details.jsx +++ b/code/frontend/gologgo-client/components/log-details.jsx @@ -1,49 +1,106 @@ 'use client' -import { useState } from 'react' +import { useEffect, useState } from 'react' +import { useParams, useRouter } from 'next/navigation' +import { ArrowLeft, Check, Copy, Menu } from 'lucide-react' import { Sidebar } from '@/components/sidebar' -import { useRouter } from 'next/navigation' -import { ArrowLeft, Check, CircleHelp, Copy, FileJson, LayoutDashboard, ListFilter, Menu, TerminalSquare, X } from 'lucide-react' import { ThemeToggle } from '@/components/theme-toggle' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +function HighlightedJSON({ code }) { + const tokenPattern = /("(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*")|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|(true|false|null)|([{}[\],:])/g + const parts = [] + let lastIndex = 0 + let match + while ((match = tokenPattern.exec(code)) !== null) { + if (match.index > lastIndex) parts.push({code.slice(lastIndex, match.index)}) + if (match[1]) { + const isKey = /"\s*$/.test(match[1]) && code.slice(tokenPattern.lastIndex).match(/^\s*:/) + parts.push({match[1]}) + } else if (match[2]) { + parts.push({match[2]}) + } else if (match[3]) { + parts.push({match[3]}) + } else { + parts.push({match[4]}) + } -const summary = [['Timestamp', '31 Aug 2026, 22:41:03'], ['Level', 'ERROR'], ['Service', 'auth-service'], ['Format', 'Authentication'], ['Parser', 'AuthParser'], ['Status', 'Parsed'], ['Log ID', 'log_8f32a91']] -const rawLog = '2026-08-31 22:41:03 ERROR auth-service:\nUser 9287 authentication failed from 10.20.30.4' -const normalized = '{\n "timestamp": "2026-08-31T22:41:03",\n "level": "ERROR",\n "service": "auth-service",\n "event": "authentication_failed",\n "user": "9287",\n "source_ip": "10.20.30.4"\n}' + lastIndex = tokenPattern.lastIndex + } -const HighlightedRaw = () => ( - <> - 2026-08-31 22:41:03 ERROR auth-service:
- User 9287 authentication failed from 10.20.30.4 - -) - -const HighlightedJSON = () => ( - <> - {'{'}
- {' '}"timestamp": "2026-08-31T22:41:03",
- {' '}"level": "ERROR",
- {' '}"service": "auth-service",
- {' '}"event": "authentication_failed",
- {' '}"user": "9287",
- {' '}"source_ip": "10.20.30.4"
- {'}'} - -) + if (lastIndex < code.length) parts.push({code.slice(lastIndex)}) + return <>{parts} +} -function CodePanel({ title, code, isRaw = false, isNormalized = false, onCopy }) { - return {title}
{isNormalized ?  : isRaw ?  : code}
+function CodePanel({ title, code, onCopy, isJSON = false }) { + return ( + + + {title} + + + +
{isJSON ?  : code}
+
+
+ ) } export function LogDetails() { const router = useRouter() + const { id } = useParams() const [mobileOpen, setMobileOpen] = useState(false) const [copied, setCopied] = useState('') - const copy = async (name, value) => { await navigator.clipboard?.writeText(value); setCopied(name); setTimeout(() => setCopied(''), 1200) } - return
{mobileOpen &&
}

Log Details

Parsed

Log Information

{summary.map(([label, value]) =>

{label}

{value}

)}
copy('raw', rawLog)} /> copy('normalized', normalized)} />
Detected Fields{[['event', 'authentication_failed'], ['user', '9287'], ['source_ip', '10.20.30.4'], ['level', 'ERROR'], ['service', 'auth-service']].map(([key, value]) =>
{key}{value}
)}
Parser Information{[['Parser', 'AuthParser'], ['Format', 'Authentication Log'], ['Status', 'Active'], ['Logs Processed', '5,241'], ['Last Used', 'Just now']].map(([label, value]) =>
{label}{value}
)}
{copied &&
Copied {copied} log
}
+ const [data, setData] = useState(null) + const [error, setError] = useState('') + + useEffect(() => { + if (!id) return + fetch(`/api/logs/${id}`, { cache: 'no-store' }) + .then(async (response) => { + const body = await response.json().catch(() => ({})) + if (!response.ok) throw new Error(body.error || 'Failed to load log details') + return body + }) + .then(setData) + .catch((loadError) => setError(loadError.message)) + }, [id]) + + const request = data?.request + const log = data?.logs?.[0] + const normalized = log?.normalizedLog ? JSON.stringify(log.normalizedLog, null, 2) : '' + const copy = async (name, value) => { + await navigator.clipboard?.writeText(value) + setCopied(name) + setTimeout(() => setCopied(''), 1200) + } + + return ( +
+
+ {mobileOpen &&
} +
+
+
+

Log Details

+
{request && {request.status}}
+
+ {error &&

{error}

} + {!error && !data &&

Loading log details...

} + {request && <> +

Log Information

Submitted

{new Date(request.createdAt).toLocaleString()}

Status

{request.status}

Attempts

{request.attempts}

Log ID

{request.id}

+
copy('raw', request.payload)} />{log && copy('normalized', normalized)} />}
+ {log?.hash &&

Fingerprint: {log.hash}

} + } + {copied &&
Copied {copied} log
} +
+
+
+ ) } diff --git a/code/frontend/gologgo-client/components/logs-explorer.jsx b/code/frontend/gologgo-client/components/logs-explorer.jsx index 12057c3..00bddd7 100644 --- a/code/frontend/gologgo-client/components/logs-explorer.jsx +++ b/code/frontend/gologgo-client/components/logs-explorer.jsx @@ -4,9 +4,14 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { Sidebar } from '@/components/sidebar' import { useRouter } from 'next/navigation' import { + Activity, + AlertTriangle, + CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, + Clock3, + Database, Menu, RefreshCw, Search, @@ -30,7 +35,7 @@ const STATUS_META = { function StatusPill({ status }) { const meta = STATUS_META[status] ?? { label: status, color: 'text-muted-foreground', dot: 'bg-muted-foreground', pulse: false } return ( - + {meta.label} @@ -58,11 +63,15 @@ function FilterSelect({ label, value, onChange, options }) { // ─── Stat card ──────────────────────────────────────────────────────────────── -function StatCard({ label, value, colorClass = 'text-foreground' }) { +function StatCard({ label, value, colorClass = 'text-foreground', Icon = Activity }) { return ( -
-

{label}

-

{value ?? '—'}

+
+
+

{label}

+ +
+

{value ?? '—'}

+
) } @@ -200,6 +209,20 @@ export function LogsExplorer() { } catch { return iso } } + const formatDate = (iso) => { + if (!iso) return '—' + try { + return new Date(iso).toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }) + } catch { return iso } + } + + const formatClock = (iso) => { + if (!iso) return '—' + try { + return new Date(iso).toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) + } catch { return '' } + } + const trimPayload = (payload, len = 80) => { if (!payload) return '—' const first = payload.split('\n')[0].trim() @@ -274,8 +297,8 @@ export function LogsExplorer() {
-

Workspace

-

Logs

+

Workspace / Activity

+

Logs

Browse and investigate your submitted log events

@@ -296,23 +319,23 @@ export function LogsExplorer() { {/* ── Stats ── */}
- - - - - - + + + + + +
{/* ── Filters ── */} -
+
setSearch(e.target.value)} placeholder="Search by payload, ID, or log fields…" - className="h-9 border-input bg-background pl-9 text-sm" + className="h-10 border-input/80 bg-background/70 pl-9 text-sm shadow-inner placeholder:text-muted-foreground/60" />
@@ -338,10 +361,10 @@ export function LogsExplorer() {
{/* ── Table ── */} -
-
+
+
-

Your Log Requests

+

Your Log Requests

{loading ? 'Loading…' : `${totalCount.toLocaleString()} request${totalCount !== 1 ? 's' : ''} found`}

@@ -371,14 +394,14 @@ export function LogsExplorer() {
) : (
- +
- - Submitted At - Payload Preview - Status - Attempts - Parsed Logs + + Submitted At + Payload Preview + Status + Attempts + Parsed Logs @@ -387,23 +410,24 @@ export function LogsExplorer() { router.push(`/logs/${req.id}`)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') router.push(`/logs/${req.id}`) }} > - - {formatTime(req.createdAt)} + + {formatDate(req.createdAt)} + {formatClock(req.createdAt)} - - {trimPayload(req.payload)} + + {trimPayload(req.payload, 140)} - + {req.attempts} - + {req.logs != null ? ( {req.logs.length} log{req.logs.length !== 1 ? 's' : ''} @@ -412,7 +436,7 @@ export function LogsExplorer() { — )} - › + › ))} diff --git a/code/frontend/gologgo-client/components/sidebar.jsx b/code/frontend/gologgo-client/components/sidebar.jsx index 50f76e0..350dec8 100644 --- a/code/frontend/gologgo-client/components/sidebar.jsx +++ b/code/frontend/gologgo-client/components/sidebar.jsx @@ -2,13 +2,12 @@ import { useEffect, useState } from 'react' import { useRouter, usePathname } from 'next/navigation' -import { CircleHelp, FileJson, LayoutDashboard, ListFilter, X } from 'lucide-react' +import { FileJson, LayoutDashboard, ListFilter, X } from 'lucide-react' import { Button } from '@/components/ui/button' const navItems = [ { label: 'Dashboard', icon: LayoutDashboard, href: '/' }, { label: 'Logs', icon: ListFilter, href: '/logs' }, - { label: 'Unknown Logs', icon: CircleHelp, href: '/unknown-logs' }, { label: 'Parsers', icon: FileJson, href: '/parsers' }, ] diff --git a/code/frontend/gologgo-client/components/unknown-logs.jsx b/code/frontend/gologgo-client/components/unknown-logs.jsx deleted file mode 100644 index 64c8e91..0000000 --- a/code/frontend/gologgo-client/components/unknown-logs.jsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client' - -import { useMemo, useState } from 'react' -import { Sidebar } from '@/components/sidebar' -import { useRouter } from 'next/navigation' -import { ChevronDown, ChevronLeft, ChevronRight, CircleHelp, FileJson, LayoutDashboard, ListFilter, Menu, RefreshCw, Search, TerminalSquare, X } from 'lucide-react' -import { ThemeToggle } from '@/components/theme-toggle' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' - - -const baseRows = [ - { id: 'unknown-2241', time: '22:39:42', raw: 'AUTH_FAIL :: usr#9287 :: src=10.20.30.4', source: 'auth-service', status: 'Analyzing', format: '—', confidence: '—' }, - { id: 'unknown-2240', time: '22:35:21', raw: 'XYZ code=E42 node=server-4', source: 'xyz-service', status: 'Format Detected', format: 'Custom Event', confidence: '94%' }, - { id: 'unknown-2239', time: '22:31:08', raw: '[CUSTOM] req#821 status=blocked', source: 'gateway', status: 'Format Detected', format: 'Gateway Security', confidence: '89%' }, - { id: 'unknown-2238', time: '22:27:51', raw: 'evt@unknown type=payment_failure user=481', source: 'payment-service', status: 'Analyzing', format: '—', confidence: '—' }, - { id: 'unknown-2237', time: '22:24:17', raw: 'node=server-7 :: action=restricted :: code=R41', source: 'security-service', status: 'Format Detected', format: 'Security Event', confidence: '91%' }, -] -const rows = Array(12).fill(baseRows).flat().map((row, i) => ({ ...row, id: `${row.id}-${i}` })) - - - -function SelectFilter({ label, value, options, onChange }) { - return -} - -export function UnknownLogs() { - const router = useRouter() - const [search, setSearch] = useState('') - const [status, setStatus] = useState('All Status') - const [range, setRange] = useState('Last 24 hours') - const [mobileOpen, setMobileOpen] = useState(false) - const [refreshing, setRefreshing] = useState(false) - const [currentPage, setCurrentPage] = useState(1) - const [pageSize, setPageSize] = useState(10) - - const filtered = useMemo(() => { - setCurrentPage(1) - return rows.filter((row) => `${row.raw} ${row.source} ${row.status} ${row.format}`.toLowerCase().includes(search.toLowerCase()) && (status === 'All Status' || row.status === status)) - }, [search, status]) - - const totalPages = Math.ceil(filtered.length / pageSize) - const paginated = filtered.slice((currentPage - 1) * pageSize, currentPage * pageSize) - - const clear = () => { setSearch(''); setStatus('All Status'); setRange('Last 24 hours') } - return
{mobileOpen &&
}

Workspace

Unknown Logs

Logs that could not be matched to an existing parser

Live

Unknown Logs

630

Analyzing

18

Formats Detected

42

setSearch(event.target.value)} placeholder="Search unknown logs..." className="h-9 border-input bg-background pl-9 text-sm" />
TimeRaw LogSourceStatusDetected FormatConfidenceAction{paginated.map((row) => router.push(`/unknown-logs/${row.id}`)} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') router.push(`/unknown-logs/${row.id}`) }}>{row.time}{row.raw}{row.source}{row.status}{row.format}{row.confidence}{row.status === 'Format Detected' ? : Analyzing...})}
-}