@@ -5,75 +5,9 @@ import { aggregateLogData } from '../lib/log-aggregator';
55import { Dashboard } from './Dashboard' ;
66import { Eye , EyeOff , XCircle } from 'lucide-react' ;
77
8- // A more robust parser for common Nginx log formats.
9- const parseLogLine = ( line : string ) => {
10- try {
11- // This regex handles cases where the request field might be empty ("") or malformed.
12- // It also handles extra fields at the end (like the "-" at the end of some Nginx logs)
13- const match = line . match ( / ^ (?< ip > [ \d . ] + ) - (?< user > \S + ) \[ (?< timestamp > .+ ?) \] " (?< method > \S + ) (?< path > [ ^ " ] * ) H T T P \/ \d + \. \d + " (?< code > \d + ) (?< size > \d + ) " (?< referrer > [ ^ " ] * ) " " (?< agent > [ ^ " ] * ) " " (?< xForwardedFor > [ ^ " ] * ) " $ / ) ;
14-
15- if ( ! match || ! match . groups ) {
16- console . warn ( "Could not parse log line:" , line ) ;
17- return null ;
18- }
19-
20- const { ip, user, timestamp, method, path, code, size, referrer, agent, xForwardedFor } = match . groups ;
21-
22- // Define valid HTTP methods
23- const validHttpMethods = [ 'GET' , 'POST' , 'PUT' , 'DELETE' , 'PATCH' , 'HEAD' , 'OPTIONS' , 'TRACE' , 'CONNECT' ] ;
24-
25- // Initialize method and url
26- let finalMethod = 'N/A' ;
27- let url = 'N/A' ;
28-
29- // Only parse method and URL if request is not empty
30- if ( method && path ) {
31- // Check if the method is a valid HTTP method
32- if ( validHttpMethods . includes ( method ) ) {
33- finalMethod = method ;
34- url = path || 'N/A' ;
35- } else {
36- // Check for known attack patterns or clearly malformed requests
37- const isAttackPattern = method . includes ( '%' ) ||
38- method . includes ( ';' ) ||
39- method . includes ( 'wget' ) ||
40- method . includes ( 'curl' ) ||
41- method . length > 100 ;
42-
43- // Check for clearly non-HTTP method patterns
44- const isNonHttpPattern = method . includes ( '_DUPLEX_' ) ||
45- method . startsWith ( 'SSTP_' ) ||
46- method . includes ( 'Mozi' ) ||
47- method . includes ( '->' ) ;
48-
49- if ( isAttackPattern || isNonHttpPattern ) {
50- finalMethod = 'MALFORMED' ;
51- } else if ( method . length <= 25 ) {
52- // Short, non-attack patterns are classified as OTHER
53- finalMethod = 'OTHER' ;
54- } else {
55- // Long patterns are classified as MALFORMED
56- finalMethod = 'MALFORMED' ;
57- }
58-
59- url = `${ method } ${ path } ` ; // Keep the full request for analysis
60- }
61- }
62-
63- return {
64- ipAddress : ip ,
65- timestamp,
66- method : finalMethod ,
67- path : url ,
68- status : code ,
69- bodyBytesSent : size ,
70- referer : referrer ,
71- userAgent : agent ,
72- } ;
73- } catch ( error ) {
74- console . error ( 'Error parsing log line:' , error ) ;
75- return null ;
76- }
8+ // Create a worker for single line parsing
9+ const createSingleLineParserWorker = ( ) => {
10+ return new Worker ( new URL ( '../workers/singleLineParser.js' , import . meta. url ) ) ;
7711} ;
7812
7913export interface LogData {
@@ -128,6 +62,7 @@ export function LiveDashboard({ wsUrl: initialWsUrl }: { wsUrl: string }) {
12862 const pongTimeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
12963 const lastMessageTime = useRef < number > ( Date . now ( ) ) ;
13064 const reconnectDelay = useRef ( BASE_RECONNECT_DELAY ) ;
65+ const logParserWorkerRef = useRef < Worker | null > ( null ) ;
13166
13267 // Update wsUrl and newWsUrl when initialWsUrl changes
13368 useEffect ( ( ) => {
@@ -139,17 +74,44 @@ export function LiveDashboard({ wsUrl: initialWsUrl }: { wsUrl: string }) {
13974 return Math . min ( BASE_RECONNECT_DELAY * Math . pow ( 2 , attempt ) , MAX_RECONNECT_DELAY ) ;
14075 } ;
14176
77+
78+
14279 const handleNewLogLine = useCallback ( ( line : string ) => {
143- const parsedLog = parseLogLine ( line ) ;
144- if ( parsedLog ) {
145- setParsedLines ( prevLines => {
146- // Keep all logs, just prepend the new one
147- const updatedLines = [ parsedLog , ...prevLines ] ;
148- const aggregatedData = aggregateLogData ( updatedLines ) ;
149- setLogData ( aggregatedData ) ;
150- return updatedLines ;
151- } ) ;
152- }
80+ // console.log('Received log line:', line);
81+
82+ // Create a new worker for each line to avoid queueing issues
83+ const worker = createSingleLineParserWorker ( ) ;
84+
85+ worker . onmessage = ( event ) => {
86+ if ( event . data . error ) {
87+ console . error ( 'Error parsing log line:' , event . data . error ) ;
88+ } else if ( event . data . parsedLine ) {
89+ const parsedLine = event . data . parsedLine ;
90+ // console.log('Parsed line:', parsedLine);
91+
92+ if ( parsedLine . attackType ) {
93+ // console.log('ATTACK DETECTED:', parsedLine.attackType, 'in line:', line);
94+ }
95+
96+ setParsedLines ( prevLines => {
97+ const updatedLines = [ parsedLine , ...prevLines ] ;
98+ setLogData ( aggregateLogData ( updatedLines ) ) ;
99+ return updatedLines ;
100+ } ) ;
101+ }
102+ worker . terminate ( ) ;
103+ } ;
104+
105+ worker . onerror = ( error ) => {
106+ console . error ( 'Worker error:' , error ) ;
107+ worker . terminate ( ) ;
108+ } ;
109+
110+ // Send the log line to the worker for parsing
111+ worker . postMessage ( {
112+ line : line ,
113+ format : 'nginx' // or make this configurable
114+ } ) ;
153115 } , [ ] ) ;
154116
155117 const setupPingPong = useCallback ( ( ) => {
@@ -214,7 +176,7 @@ export function LiveDashboard({ wsUrl: initialWsUrl }: { wsUrl: string }) {
214176 wsRef . current = ws ;
215177
216178 ws . onopen = ( ) => {
217- console . log ( 'WebSocket connected' ) ;
179+ // console.log('WebSocket connected to:', url );
218180 // Check if component is still mounted before updating state
219181 if ( ! wsRef . current ) return ;
220182 setIsConnected ( true ) ;
@@ -234,6 +196,7 @@ export function LiveDashboard({ wsUrl: initialWsUrl }: { wsUrl: string }) {
234196
235197 if ( typeof event . data === 'string' ) {
236198 lastMessageTime . current = Date . now ( ) ;
199+ // console.log('WebSocket message received:', event.data);
237200
238201 try {
239202 const data = JSON . parse ( event . data ) ;
@@ -242,17 +205,19 @@ export function LiveDashboard({ wsUrl: initialWsUrl }: { wsUrl: string }) {
242205 clearTimeout ( pongTimeoutRef . current ) ;
243206 pongTimeoutRef . current = null ;
244207 }
208+ // console.log('Pong received');
245209 return ;
246210 }
247211 } catch ( e ) {
248212 // Not a JSON message, treat as log line
213+ // console.log('Treating message as log line');
249214 handleNewLogLine ( event . data ) ;
250215 }
251216 }
252217 } ;
253218
254219 ws . onclose = ( event ) => {
255- console . log ( 'WebSocket closed:' , event . code , event . reason ) ;
220+ // console.log('WebSocket closed:', event.code, event.reason);
256221 // Don't update state if component is unmounting
257222 if ( ! wsRef . current ) return ;
258223
@@ -271,7 +236,7 @@ export function LiveDashboard({ wsUrl: initialWsUrl }: { wsUrl: string }) {
271236
272237 // Special handling for our forced reconnection
273238 if ( event . code === 4001 ) { // Our custom code for visibility change
274- console . log ( 'Reconnecting after visibility change...' ) ;
239+ // console.log('Reconnecting after visibility change...');
275240 connectWebSocket ( ) ;
276241 return ;
277242 }
@@ -285,7 +250,7 @@ export function LiveDashboard({ wsUrl: initialWsUrl }: { wsUrl: string }) {
285250 reconnectDelay . current = calculateReconnectDelay ( nextAttempt ) ;
286251
287252 reconnectTimeoutRef . current = setTimeout ( ( ) => {
288- console . log ( `Attempting to reconnect (${ nextAttempt + 1 } /${ MAX_RECONNECT_ATTEMPTS } )...` ) ;
253+ // console.log(`Attempting to reconnect (${nextAttempt + 1}/${MAX_RECONNECT_ATTEMPTS})...`);
289254 connectWebSocket ( ) ;
290255 } , reconnectDelay . current ) ;
291256
@@ -573,7 +538,13 @@ export function LiveDashboard({ wsUrl: initialWsUrl }: { wsUrl: string }) {
573538 </ div >
574539 ) }
575540
576- { logData && parsedLines . length > 0 ? (
541+ { isConnected && ! logData && parsedLines . length === 0 ? (
542+ < div className = "flex items-center justify-center h-64 bg-gray-50 dark:bg-gray-800/50 rounded-xl" >
543+ < div className = "text-center" >
544+ < p className = "text-gray-500 dark:text-gray-400" > Connected to WebSocket. Waiting for log data...</ p >
545+ </ div >
546+ </ div >
547+ ) : logData && parsedLines . length > 0 ? (
577548 < Dashboard stats = { logData } parsedLines = { parsedLines } />
578549 ) : (
579550 < div className = "flex items-center justify-center h-64 bg-gray-50 dark:bg-gray-800/50 rounded-xl" >
0 commit comments