]>
git.ipfire.org Git - people/teissler/ipfire-2.x.git/blob - html/cgi-bin/imspector.cgi
3 # IMSpector real-time log viewer
4 # (c) SmoothWall Ltd 2008
6 # Released under the GPL v2.
8 use POSIX
qw(strftime) ;
10 # Common configuration parameters.
12 my $logbase = "/var/log/imspector/" ;
13 my $oururl = '/cgi-bin/imspector.cgi' ;
17 my $protocol_colour = '#06264d' ;
18 my $local_colour = '#1d398b' ;
19 my $remote_colour = '#2149c1' ;
20 my $conversation_colour = '#335ebe' ;
22 my $local_user_colour = 'blue' ;
23 my $remote_user_colour = 'green' ;
25 # No need to change anything from this point
27 # Page declaration, The following code should parse the CGI headers, and render the page
28 # accordingly... How you do this depends what environment you're in.
32 print "Content-type: text/html \n " ;
35 if ( $ENV { 'QUERY_STRING' })
37 my @vars = split ( '\&' , $ENV { 'QUERY_STRING' });
40 my ( $var , $val ) = split ( /\=/ );
41 $cgiparams { $var } = $val ;
45 # Act in Tail mode (as in just generate the raw logs and pass back to the other CGI
47 if ( defined $cgiparams { 'mode' } and $cgiparams { 'mode' } eq "render" ){
48 & parser
( $cgiparams { 'section' }, $cgiparams { 'offset' }, $cgiparams { 'conversation' }, $cgiparams { 'skimhtml' } );
52 # Start rendering the Page using Express' rendering functions
54 my $script = & scriptheader
();
56 # Print Some header information
59 <! DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" >
62 < title
> IMSpector real
- time log viewer
</ title
>
70 # and now finish off the HTML page.
79 # -----------------------------------------------------------------------------
80 # ---------------------- IMSPector Log Viewer Code ----------------------------
81 # -----------------------------------------------------------------------------
86 # Return the bulk of the page, which should reside in the pages <head> field
90 my ( $sec , $min , $hour , $mday , $mon , $year , $wday , $yday ) = localtime ( time () );
91 $year += 1900 ; $mon ++;
92 my $conversation = sprintf ( "%.4d-%.2d-%.2d" , $year , $mon , $mday );
95 < script language
= "Javascript" >
102 var conversationdate
= " $conversation " ;
104 function xmlhttpPost
()
108 if ( window
. XMLHttpRequest
) {
110 self
. xmlHttpReq
= new XMLHttpRequest
();
111 } else if ( window
. ActiveXObject
) {
113 self
. xmlHttpReq
= new ActiveXObject
( "Microsoft.XMLHTTP" );
116 var url
= " $url " + "?mode=render§ion=" + section
+ "&skimhtml=" + skimhtml
+ "&offset=" + offset
+ "&conversation=" + conversationdate
;
117 self
. xmlHttpReq
. open ( 'POST' , url
, true
);
118 self
. xmlHttpReq
. setRequestHeader
( 'Content-Type' , 'application/x-www-form-urlencoded' );
120 self
. xmlHttpReq
. onreadystatechange
= function
() {
121 if ( self
. xmlHttpReq
&& self
. xmlHttpReq
. readyState
== 4 ) {
122 updatepage
( self
. xmlHttpReq
. responseText
);
126 document
. getElementById
( 'status' ). style
. display
= "inline" ;
128 self
. xmlHttpReq
. send ( url
);
132 function updatepage
( str
){
133 /* update the list of conversations ( if we need to ) */
135 var parts
= str
. split ( "--END-- \\ n" );
137 var lines
= parts
[ 0 ]. split ( " \\ n" );
139 for ( var line
= 0 ; line
< lines
. length ; line
++ ){
140 var a
= lines
[ line
]. split ( "|" );
142 if ( ! a
[ 1 ] || ! a
[ 2 ] || ! a
[ 3 ] ){
146 /* convert the modification stamp into something sensible */
147 a
[ 5 ] = parseInt
( a
[ 5 ] * 24 * 60 * 60 );
149 /* create titling information if needed */
150 if ( ! document
. getElementById
( a
[ 1 ] ) ){
151 document
. getElementById
( 'conversations' ). innerHTML
+= "<div id='" + a
[ 1 ] + "_t' style='width: 100%; background-color: #d9d9f3; color: $protocol_colour ;'>" + a
[ 1 ] + "</div><div id='" + a
[ 1 ] + "' style='width: 100%; background-color: #e5e5f3;'></div>" ;
154 if ( ! document
. getElementById
( a
[ 1 ] + "_" + a
[ 2 ] ) ){
155 document
. getElementById
( a
[ 1 ] ). innerHTML
+= "<div id='" + a
[ 1 ] + "_" + a
[ 2 ] + "_t' style='width: 100%; color: $local_colour ; padding-left: 5px;'>" + a
[ 2 ] + "</div><div id='" + a
[ 1 ] + "_" + a
[ 2 ] + "' style='width: 100%; background-color: #efeffa; border-bottom: solid 1px #d9d9f3;'></div>" ;
158 if ( ! document
. getElementById
( a
[ 1 ] + "_" + a
[ 2 ] + "_" + a
[ 3 ] ) ){
159 document
. getElementById
( a
[ 1 ] + "_" + a
[ 2 ] ). innerHTML
+= "<div id='" + a
[ 1 ] + "_" + a
[ 2 ] + "_" + a
[ 3 ] + "_t' style='width: 100%; color: $remote_colour ; padding-left: 10px; cursor: pointer;' onClick=" + '"' + "setsection('" + a
[ 1 ] + "|" + a
[ 2 ] + "|" + a
[ 3 ] + "|" + a
[ 4 ] + "');" + '"' + "' + >» " + a
[ 3 ] + "</div><div id='" + a
[ 1 ] + "_" + a
[ 2 ] + "_" + a
[ 3 ] + "' style='width: 1%; display: none;'></div>" ;
162 if ( document
. getElementById
( a
[ 1 ] + "_" + a
[ 2 ] + "_" + a
[ 3 ] ) && a
[ 5 ] <= 60 ){
163 /* modified within the last minute! */
164 document
. getElementById
( a
[ 1 ] + "_" + a
[ 2 ] + "_" + a
[ 3 ] + "_t" ). style
. fontWeight
= "bold" ;
166 document
. getElementById
( a
[ 1 ] + "_" + a
[ 2 ] + "_" + a
[ 3 ] + "_t" ). style
. fontWeight
= "normal" ;
173 /* rework the list of active conversation dates ... */
175 var lines
= parts
[ 1 ]. split ( " \\ n" );
177 var the_select
= document
. getElementById
( 'conversationdates' );
178 the_select
. options
. length = 0 ;
180 for ( var line
= 0 ; line
< lines
. length ; line
++ ){
181 if ( lines
[ line
] != "" ){
182 the_select
. options
. length ++;
183 the_select
. options
[ line
]. text
= lines
[ line
];
184 the_select
. options
[ line
]. value
= lines
[ line
];
185 if ( lines
[ line
] == conversationdate
){
186 the_select
. selectedIndex
= line
;
194 /* determine the title of this conversation */
196 var details
= parts
[ 2 ]. split ( "," );
197 var title
= details
[ 0 ] + " conversation between <span style='color: $local_user_colour ;'>" + details
[ 1 ] + "</span> and <span style='color: $remote_user_colour ;'>" + details
[ 2 ] + "</span>" ;
202 document
. getElementById
( 'status' ). style
. display
= "none" ;
204 var bottom
= parseInt
( document
. getElementById
( 'content' ). scrollTop
);
205 var bottom2
= parseInt
( document
. getElementById
( 'content' ). style
. height
);
206 var absheight
= parseInt
( bottom
+ bottom2
);
208 if ( absheight
== document
. getElementById
( 'content' ). scrollHeight
){
212 fragment
+= parts
[ 4 ];
213 document
. getElementById
( 'content' ). innerHTML
= "<table style='width: 100%'>" + fragment
+ "</table>" ;
215 document
. getElementById
( 'content' ). scrollTop
= 0 ;
216 document
. getElementById
( 'content' ). scrollTop
= document
. getElementById
( 'content' ). scrollHeight
;
219 document
. getElementById
( 'content_title' ). innerHTML
= title
;
227 /* set the file offset */
231 document
. getElementById
( 'scrlck' ). style
. color
= 'green' ;
233 document
. getElementById
( 'scrlck' ). style
. color
= '#202020' ;
236 if ( skimhtml
== 1 ){
237 document
. getElementById
( 'skimhtml' ). style
. color
= 'green' ;
239 document
. getElementById
( 'skimhtml' ). style
. color
= '#202020' ;
244 the_timeout
= setTimeout
( "xmlhttpPost();" , 5000 );
247 function setsection
( value
)
253 clearTimeout
( the_timeout
);
255 document
. getElementById
( 'content' ). scrollTop
= 0 ;
256 document
. getElementById
( 'content' ). scrollTop
= document
. getElementById
( 'content' ). scrollHeight
;
259 function togglescrlck
()
263 document
. getElementById
( 'scrlck' ). style
. color
= '#202020' ;
266 document
. getElementById
( 'scrlck' ). style
. color
= 'green' ;
270 function toggleskimhtml
()
272 if ( skimhtml
== 1 ){
274 document
. getElementById
( 'skimhtml' ). style
. color
= '#202020' ;
277 document
. getElementById
( 'skimhtml' ). style
. color
= 'green' ;
279 clearTimeout
( the_timeout
);
285 var the_select
= document
. getElementById
( 'conversationdates' );
286 conversationdate
= the_select
. options
[ the_select
. selectedIndex
]. value
;
287 document
. getElementById
( 'conversations' ). innerHTML
= "" ;
291 clearTimeout
( the_timeout
);
303 # Return the HTML fragment which includes the page body.
308 < div style
= 'width: 100%; text-align: right;' >< span id
= 'status' style
= 'background-color: #fef1b5; display: none;' > Updating
< /span> </di v
>
318 color
: $remote_user_colour ;
323 color
: $local_user_colour ;
328 < table style
= 'width: 100%;' >
330 < td style
= 'width: 170px; text-align: left; vertical-align: top; overflow: auto; font-size: 8pt; border: solid 1px #c0c0c0;' >< div id
= 'conversations' style
= 'height: 400px; overflow: auto; font-size: 10px; overflow-x: hidden;' >< /div></ td
>
331 < td style
= 'border: solid 1px #c0c0c0;' >
332 < div id
= 'content_title' style
= 'height: 20px; overflow: auto; vertical-align: top; background-color: #E6E8FA; border-bottom: solid 1px #c0c0c0;' ></ div
>
333 < div id
= 'content' style
= 'height: 376px; overflow: auto; vertical-align: bottom; border-bottom: solid 1px #c0c0c0; overflow-x: hidden;' ></ div
>
334 < div id
= 'content_subtitle' style
= 'height: 24px; overflow: auto; vertical-align: top; background-color: #E6E8FA; width: 100%; padding: 2px;' >
335 < div style
= 'width: 60%; float: left;' id
= 'statuswindow' >
336 For conversations on
:& nbsp
;
337 < select id
= 'conversationdates' onChange
= 'setDate()' ;>
340 < div style
= 'width: 40%; text-align: right; float: right;' >
341 < span
class = 'powerbutton' id
= 'skimhtml' onClick
= 'toggleskimhtml();' >[ HTML
]</ span
>
342 < span
class = 'powerbutton' id
= 'scrlck' onClick
= 'togglescrlck();' >[ SCROLL LOCK
]</ span
>
348 < script
> xmlhttpPost
();</ script
>
353 # Parser function ...
355 # Retrieves the IMspector logs from their nestling place and displays them accordingly.
359 my ( $section , $offset , $conversationdate , $skimhtml ) = @_ ;
360 # render the user list ...
364 unless ( $offset =~ /^([\d]*)$/ ){
365 print STDERR
"Illegal offset ( $offset $1 ) resetting... \n " ;
369 # browse for the available protocols
370 unless ( opendir DIR
, $logbase ){
374 my %conversationaldates ;
375 my @protocols = grep {! /^\./ } readdir ( DIR
);
377 foreach my $protocol ( @protocols ){
378 unless ( opendir LUSER
, " $logbase $protocol " ){
382 my @localusers = grep {! /^\./ } readdir ( LUSER
);
383 foreach my $localuser ( @localusers ){
384 unless ( opendir RUSER
, " $logbase $protocol / $localuser /" ){
387 my @remoteusers = grep {! /^\./ } readdir ( RUSER
);
388 foreach my $remoteuser ( @remoteusers ){
389 unless ( opendir CONVERSATIONS
, " $logbase $protocol / $localuser / $remoteuser /" ){
392 my @conversations = grep {! /^\./ } readdir ( CONVERSATIONS
);
393 foreach my $conversation ( @conversations ){
394 $conversationaldates { $conversation } = $localuser ;
397 closedir CONVERSATIONS
;
399 my ( $sec , $min , $hour , $mday , $mon , $year , $wday , $yday ) = localtime ( time () );
400 $year += 1900 ; $mon ++;
401 my $conversation = sprintf ( "%.4d-%.2d-%.2d" , $year , $mon , $mday );
403 $conversation = $conversationdate if ( defined $conversationdate and $conversationdate ne "" );
405 if ( - e
" $logbase $protocol / $localuser / $remoteuser / $conversation " ){
406 my $modi = - M
" $logbase $protocol / $localuser / $remoteuser / $conversation " ;
407 print "| $protocol | $localuser | $remoteuser | $conversation | $modi \n " ;
418 # display a list of conversational dates .. i.e. the dates which we have conversations on.
419 foreach my $key ( sort keys %conversationaldates ){
426 # now check the log file ...
428 if ( $section ne "none" ){
429 my ( $protocol , $localuser , $remoteuser , $conversation ) = split /\|/ , $section ;
431 print " $protocol , $localuser , $remoteuser , $conversation \n " ;
434 my $filename = " $logbase $protocol / $localuser / $remoteuser / $conversation " ;
436 unless ( open ( FD
, " $filename " ) ){
440 # perform some *reasonably* complicated file hopping and stuff of that ilk.
441 # it's not beyond reason that logs *could* be extremely large, so what we
442 # should do to speed up their processing is to jump to the end of the file,
443 # then backtrack a little (say a meg, which is a reasonably amount of logs)
444 # and parse from that point onwards. This, *post* filtering might of course
445 # not leave us with the desired resolution for the tail. If this is the case,
446 # we keep that array and jump back another meg and have another go, concatinating
447 # the logs as we go.... <wheh>
449 my $jumpback = 100000 ; # not quite a meg, but hey ho
451 my $gonebacklimit = 1000000000 ; # don't go back more than 100MB
453 # firstly jump to the end of the file.
456 my $log_position = tell ( FD
);
457 my $end = $log_position ;
458 my $end_position = $log_position ;
466 $end_position = $log_position ;
469 # we were given a hint as to where we should have been anyhow ..
470 # so we might as well use that to go back to.
471 $log_position = $offset ;
472 $goneback = $end_position - $log_position ;
474 $log_position -= $jumpback ;
475 $goneback += $jumpback ;
478 last if ( $goneback > $gonebacklimit );
480 if ( $log_position > 0 ){
481 seek ( FD
, $log_position , 0 );
488 while ( my $line = < FD
> and ( tell ( FD
) <= $end_position ) ){
490 push @content , $line ;
492 shift @content if $#content >= $TAILSIZE ;
493 } while ( $#content < $TAILSIZE and $log_position > 0 and $offset == 0 );
495 # trim the content down as we may have more entries than we should.
497 while ( $#content > $TAILSIZE ){ shift @content ; };
500 print " $end_position \n --END-- \n " ;
502 foreach my $line ( @content ){
503 my ( $address , $timestamp , $direction , $type , $filtered , $cat , $data );
505 ( $address , $timestamp , $direction , $type , $filtered , $cat , $data ) = ( $line =~ /([^,]*),(\d+),(\d+),(\d+),(\d+),([^,]*),(.*)/ );
507 # are we using the oldstyle or new style logs ?
508 if ( not defined $address and not defined $timestamp ){
509 ( $address , $timestamp , $type , $data ) = ( $line =~ /([^,]*),([^,]*),([^,]*),(.*)/ );
513 } elsif ( $type eq "2" ){
516 } elsif ( $type eq "3" ){
519 } elsif ( $type eq "4" ){
525 my ( $severity , $classification ) = '0' , 'None' ;
527 ( $severity , $classification ) = split ( / / , $cat , 2 ); }
535 if ( $severity < 0 && $severity >= - 5 ) {
536 $red = 0 ; $green = abs ( $severity ) * ( 255 / 5 ); $blue = 0 ; }
537 elsif ( $severity > 0 && $severity <= 5 ) {
538 $red = $severity * ( 255 / 5 ); $green = 0 ; $blue = 0 ; }
540 $red = 0 ; $green = 0 ; $blue = 0 ; }
542 my $severitycolour = '' ;
544 $severitycolour = sprintf ( "background-color: # %02x %02x %02x ;" , $red , $green , $blue ); }
546 # some protocols (ICQ, I'm looking in your direction) have a habit of starting
547 # and ending each sentence with HTML (evil program)
549 if ( defined $skimhtml and $skimhtml eq "1" ){
550 $data =~ s/^<HTML><BODY[^>]*><FONT[^>]*>//ig ;
551 $data =~ s/<\/FONT><\/ BODY
>< \
/HTML>/ / ig
;
554 $data = & htmlescape
( $data );
555 $data =~ s/\r\\n/<br>\n/g ;
559 $bstyle = "style='background-color: #FFE4E1;'" if ( $filtered eq "1" );
562 # a message message (from remote user)
563 if ( $direction eq "0" ){
567 $user = "<<span class='remoteuser'> $u </span>>" ;
572 $user = "<<span class='localuser'> $u </span>>" ;
574 } elsif ( $type eq "2" ) {
575 if ( $direction eq "0" ){
579 $user = "<<span class='remoteuser'><b><i> $u </i></b></span>>" ;
584 $user = "<<span class='localuser'><b><i> $u </i></b></span>>" ;
588 my $t = strftime
" %H : %M : %S " , localtime ( $timestamp );
589 if ( $type eq "3" or $type eq "4" ) {
590 $data = "<b><i> $data </i></b>" ;
592 print "<tr $bstyle ><td style='width: 30px; vertical-align: top;'>[ $t ]</td><td style='width: 10px; $severitycolour ' title=' $cat '><td style=' width: 60px; vertical-align: top;'> $user </td><td style='vertical-align: top;'> $data </td></tr>" ;
601 $value =~ s/&/\&/g ;
602 $value =~ s/</\</g ;
603 $value =~ s/>/\>/g ;
604 $value =~ s/"/\"/g ;
605 $value =~ s/'/\'/g ;