realtime.c (sarg-2.3.11) | : | realtime.c (sarg-2.4.0) | ||
---|---|---|---|---|
/* | /* | |||
* SARG Squid Analysis Report Generator http://sarg.sourceforge.net | * SARG Squid Analysis Report Generator http://sarg.sourceforge.net | |||
* 1998, 2013 | * 1998, 2015 | |||
* | * | |||
* SARG donations: | * SARG donations: | |||
* please look at http://sarg.sourceforge.net/donations.php | * please look at http://sarg.sourceforge.net/donations.php | |||
* Support: | * Support: | |||
* http://sourceforge.net/projects/sarg/forums/forum/363374 | * http://sourceforge.net/projects/sarg/forums/forum/363374 | |||
* --------------------------------------------------------------------- | * --------------------------------------------------------------------- | |||
* | * | |||
* This program is free software; you can redistribute it and/or modify | * This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | * the Free Software Foundation; either version 2 of the License, or | |||
skipping to change at line 29 | skipping to change at line 29 | |||
* GNU General Public License for more details. | * GNU General Public License for more details. | |||
* | * | |||
* You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | |||
* along with this program; if not, write to the Free Software | * along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. | |||
* | * | |||
*/ | */ | |||
#include "include/conf.h" | #include "include/conf.h" | |||
#include "include/defs.h" | #include "include/defs.h" | |||
#include "include/filelist.h" | ||||
#include "include/readlog.h" | ||||
static int getdata(char*, FILE*); | //! Maximum length of the scheme plus host name from the url. | |||
static void datashow(const char *); | #define MAX_URL_HOST_LEN 260 | |||
static void getlog(void); | ||||
static void header(void); | ||||
void realtime(void) | /*! | |||
{ | \brief Data read from an input log file. | |||
getlog(); | */ | |||
} | struct RealtimeReadLogStruct | |||
static void getlog(void) | ||||
{ | { | |||
FILE *tmp, *fp; | //! The time corresponding to the entry. | |||
char template1[255]="/var/tmp/sargtpl1.XXXXXX"; | struct tm EntryTime; | |||
char template2[255]="/var/tmp/sargtpl2.XXXXXX"; | //! The IP address connecting to internet. | |||
char cmd[512]; | char Ip[48]; | |||
char *buf; | //! The user's name. | |||
int fd1,fd2; | char User[MAX_USER_LEN]; | |||
int cstatus; | /*! | |||
longline line; | The URL of the visited site. | |||
init_usertab(UserTabFile); | The pointer may be NULL if the URL doesn't exists in the log file. | |||
*/ | ||||
char Url[MAX_URL_HOST_LEN]; | ||||
//! HTTP method or NULL if the information is not stored in the log. | ||||
char HttpMethod[32]; | ||||
}; | ||||
#ifdef HAVE_MKSTEMP | extern FileListObject AccessLog; | |||
fd2 = mkstemp(template2); | ||||
if (fd2 == -1) { | ||||
debuga(_("Cannot create a temporary file name to produce the repo | ||||
rt: %s\n"),strerror(errno)); | ||||
exit(EXIT_FAILURE); | ||||
} | ||||
fd1 = mkstemp(template1); | ||||
#else | ||||
buf = mktemp(template2); | ||||
if (buf[0]=='\0') { | ||||
debuga(_("Cannot create a temporary file name to produce the repo | ||||
rt: %s\n"),strerror(errno)); | ||||
exit(EXIT_FAILURE); | ||||
} | ||||
fd2 = -1; | ||||
fd1 = open(mktemp(template1),O_RDWR); | ||||
#endif | ||||
if((fd1 == -1 ) || ((tmp = fdopen (fd1, "w+" )) == NULL) ) { /* failu | ||||
re, bail out */ | ||||
debugapos("realtime",_("mkstemp error: %s\n"),strerror(errno)); | ||||
exit(EXIT_FAILURE); | ||||
} | ||||
if ((line=longline_create())==NULL) { | static bool GetLatestModified(char *file_name,int file_name_size) | |||
debuga(_("Not enough memory to read the log file\n")); | { | |||
exit(EXIT_FAILURE); | FileListIterator FIter; | |||
const char *file; | ||||
bool found=false; | ||||
struct stat st; | ||||
time_t latest; | ||||
FIter=FileListIter_Open(AccessLog); | ||||
while ((file=FileListIter_Next(FIter))!=NULL) | ||||
{ | ||||
if (stat(file,&st)==-1) { | ||||
debuga(__FILE__,__LINE__,_("Cannot stat \"%s\": %s\n"),fi | ||||
le,strerror(errno)); | ||||
} | ||||
if (!found) | ||||
{ | ||||
found=true; | ||||
latest=st.st_mtime; | ||||
safe_strcpy(file_name,file,file_name_size); | ||||
} | ||||
else if (st.st_mtime>latest) | ||||
{ | ||||
latest=st.st_mtime; | ||||
safe_strcpy(file_name,file,file_name_size); | ||||
} | ||||
} | } | |||
FileListIter_Close(FIter); | ||||
return(found); | ||||
} | ||||
sprintf(cmd,"tail -%d \"%s\"",realtime_access_log_lines,AccessLog[0]); | /*! | |||
fp = popen(cmd, "r"); | * \brief Store a log entry. | |||
if (!fp) { | * | |||
debuga(_("Failed to get the %d trailing lines of \"%s\": %s\n"),r | * \param Dest A pointer to the list entry where to store the entry. | |||
ealtime_access_log_lines,AccessLog[0],strerror(errno)); | * \param Entry The entry to store. | |||
debuga(_("Failed \"tail\" command: %s\n"),cmd); | */ | |||
exit(EXIT_FAILURE); | static void StoreLogEntry(struct RealtimeReadLogStruct *Dest,struct ReadLogStruc | |||
t *Entry) | ||||
{ | ||||
memcpy(&Dest->EntryTime,&Entry->EntryTime,sizeof(Dest->EntryTime)); | ||||
safe_strcpy(Dest->Ip,Entry->Ip,sizeof(Dest->Ip)); | ||||
if (Entry->Url) | ||||
{ | ||||
int i; | ||||
const char *url=Entry->Url; | ||||
// skip the scheme | ||||
for (i=0 ; i<8 && url[i] && (isalnum(url[i]) || url[i]=='+' || ur | ||||
l[i]=='-' || url[i]=='.') ; i++); | ||||
if (url[i]==':' && url[i+1]=='/' && url[i+2]=='/') | ||||
{ | ||||
url+=i+3; | ||||
for (i=0 ; url[i] && url[i]!='/' ; i++); | ||||
} | ||||
if (i>=sizeof(Dest->Url)) i=sizeof(Dest->Url)-1; | ||||
strncpy(Dest->Url,url,i); | ||||
Dest->Url[i]='\0'; | ||||
} | } | |||
while((buf=longline_read(fp,line)) != NULL ) | safe_strcpy(Dest->User,Entry->User,sizeof(Dest->User)); | |||
if (getdata(buf,tmp)<0) { | safe_strcpy(Dest->HttpMethod,Entry->HttpMethod,sizeof(Dest->HttpMethod)); | |||
/* TRANSLATORS: The %s is the command returning the inval | } | |||
id data. */ | ||||
debuga(_("Invalid data returned by %s\n"),cmd); | ||||
exit(EXIT_FAILURE); | ||||
} | ||||
pclose(fp); | ||||
fclose(tmp); | ||||
longline_destroy(&line); | ||||
if (fd2!=-1) close(fd2);//not safe at all but good enough for now. | static void header(void) | |||
if (snprintf(cmd,sizeof(cmd),"sort -t \"\t\" -r -n -k 1,1 -o \"%s\" \"%s\ | { | |||
"",template2,template1)>=sizeof(cmd)) { | puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""); | |||
debuga(_("Command too long: ")); | puts(" \"http://www.w3.org/TR/html4/loose.dtd\">\n"); | |||
debuga_more("sort -t \"\t\" -r -n -k 1,1 -o \"%s\" \"%s\"",templa | puts("<html>\n"); | |||
te2,template1); | puts("<head>\n"); | |||
exit(EXIT_FAILURE); | if (realtime_refresh) | |||
} | printf(" <meta http-equiv=refresh content=\"%d\" url=\"sarg-php/ | |||
cstatus=system(cmd); | sarg-realtime.php\"; charset=\"%s\">\n",realtime_refresh,CharSet); | |||
if (!WIFEXITED(cstatus) || WEXITSTATUS(cstatus)) { | else | |||
debuga(_("sort command return status %d\n"),WEXITSTATUS(cstatus)) | printf(" <meta http-equiv=\"Content-Type\" content=\"text/html; | |||
; | charset=%s\">\n",CharSet); | |||
debuga(_("sort command: %s\n"),cmd); | css(stdout); | |||
exit(EXIT_FAILURE); | puts("</head>\n"); | |||
} | printf("<body style=\"font-family:%s;font-size:%s;background-color:%s;bac | |||
if (!KeepTempLog && unlink(template1)) { | kground-image:url(%s)\">\n",FontFace,TitleFontSize,BgColor,BgImage); | |||
debuga(_("Cannot delete \"%s\": %s\n"),template1,strerror(errno)) | puts("<div align=\"center\"><table cellpadding=\"1\" cellspacing=\"1\">\n | |||
; | "); | |||
exit(EXIT_FAILURE); | printf("<tr><th class=\"title_l\" colspan=\"10\">SARG %s</th></tr>\n",_(" | |||
} | Realtime")); | |||
datashow(template2); | printf("<tr><th class=\"text\" colspan=\"10\">%s: %d s</th></tr>\n",_("Au | |||
to refresh"),realtime_refresh); | ||||
printf("<tr><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th>< | ||||
th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_l\ | ||||
">%s</th></tr>\n",_("DATE/TIME"),_("IP/NAME"),_("USERID"),_("TYPE"),_("ACCESSED | ||||
SITE")); | ||||
} | } | |||
static int getdata(char *rec, FILE *ftmp) | static void datashow(struct RealtimeReadLogStruct *List,int Index,int Size) | |||
{ | { | |||
int dat; | char tbuf[128]; | |||
char typ[128]; | ||||
char warea[MAXLEN]; | ||||
char user[MAX_USER_LEN]; | char user[MAX_USER_LEN]; | |||
char ip[45]; | char name[MAX_USER_LEN]; | |||
char *url; | int i; | |||
struct getwordstruct gwarea; | struct RealtimeReadLogStruct *entry; | |||
getword_start(&gwarea,rec); | ||||
if (getword_atoi(&dat,&gwarea,'.')<0) { | ||||
debuga(_("The time stamp at column 1 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if (getword_skip(10,&gwarea,' ')<0) { | ||||
debuga(_("The time stamp decimal part at column 1 is too long\n") | ||||
); | ||||
return(-1); | ||||
} | ||||
if (getword(warea,sizeof(warea),&gwarea,' ')<0) { | ||||
debuga(_("The connection duration at column 2 is too long\n")); | ||||
return(-1); | ||||
} | ||||
while(strcmp(warea,"") == 0 && gwarea.current[0] != '\0') | ||||
if (getword(warea,sizeof(warea),&gwarea,' ')<0) { | ||||
return(-1); | ||||
} | ||||
if (getword(ip,sizeof(ip),&gwarea,' ')<0) { | ||||
debuga(_("The IP address at column 3 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if (getword_skip(MAXLEN,&gwarea,' ')<0) { | ||||
debuga(_("The status at column 4 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if (getword_skip(MAXLEN,&gwarea,' ')<0) { | ||||
debuga(_("The size at column 5 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if (getword(typ,sizeof(typ),&gwarea,' ')<0) { | ||||
debuga(_("The action at column 6 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if(strncmp(typ,"CONNECT",7) == 0) { | ||||
if (getword_ptr(rec,&url,&gwarea,' ')<0) { | ||||
debuga(_("The URL at column 7 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if (getword(user,sizeof(user),&gwarea,' ')<0) { | ||||
debuga(_("The user ID at column 8 is too long\n")); | ||||
return(-1); | ||||
} | ||||
}else { | ||||
if (getword_skip(MAXLEN,&gwarea,'/')<0) { | ||||
debuga(_("The URL at column 7 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if (getword_skip(MAXLEN,&gwarea,'/')<0) { | ||||
debuga(_("The URL at column 7 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if (getword_ptr(rec,&url,&gwarea,'/')<0) { | ||||
debuga(_("The URL at column 7 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if (getword_skip(MAXLEN,&gwarea,' ')<0) { | ||||
debuga(_("The data at column 8 is too long\n")); | ||||
return(-1); | ||||
} | ||||
if (getword(user,sizeof(user),&gwarea,' ')<0) { | ||||
debuga(_("The user at column 9 is too long\n")); | ||||
return(-1); | ||||
} | ||||
} | ||||
if(strncmp(user,"-",1) == 0 && RealtimeUnauthRec==REALTIME_UNAUTH_REC_IGN | header(); | |||
ORE) | for (i=0 ; i<realtime_access_log_lines ; i++) | |||
return(0); | { | |||
entry=List+Index; | ||||
Index--; | ||||
if (Index<0) Index=Size-1; | ||||
if (UserIp) | ||||
strcpy(user,entry->Ip); | ||||
else | ||||
strcpy(user,entry->User); | ||||
if (Ip2Name) | ||||
ip2name(user,sizeof(user)); | ||||
user_find(name, sizeof(name), user); | ||||
if (df=='u') | ||||
strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M", &entry->En | ||||
tryTime); | ||||
else if (df=='e') | ||||
strftime(tbuf, sizeof(tbuf), "%d-%m-%Y %H:%M", &entry->En | ||||
tryTime); | ||||
fprintf(ftmp,"%d\t%s\t%s\t%s\t%s\n",dat,ip,user,url,typ); | printf("<tr><td class=\"data\">%s</td><td class=\"data3\">%s</td> | |||
return(0); | <td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data2\"><a hre | |||
f=\"http://%s\">%s</td></tr>\n", | ||||
tbuf,entry->Ip,name,entry->HttpMethod,entry->Url,entry | ||||
->Url); | ||||
} | ||||
puts("</table>\n</div>\n</body>\n</html>\n"); | ||||
fflush(NULL); | ||||
} | } | |||
static void datashow(const char *tmp) | void realtime(void) | |||
{ | { | |||
FILE *fin; | FileObject *fp; | |||
time_t tt; | char file_name[2048]; | |||
struct tm *t; | ||||
char tbuf[128]; | ||||
int dat; | ||||
char *buf; | char *buf; | |||
char *url; | ||||
char *ourl=NULL; | ||||
char ouser[MAX_USER_LEN]=""; | ||||
char typ[128]; | ||||
char user[MAX_USER_LEN]; | ||||
char u2[MAX_USER_LEN]; | ||||
char ip[45]; | ||||
int url_len; | ||||
int ourl_size=0; | ||||
struct getwordstruct gwarea; | ||||
longline line; | longline line; | |||
struct ReadLogStruct log_entry; | ||||
enum ReadLogReturnCodeEnum log_entry_status; | ||||
struct LogLineStruct log_line; | ||||
struct RealtimeReadLogStruct *StoredLogEntries; | ||||
int StoreIndex=0; | ||||
int StoreSize=0; | ||||
int NextIndex=1; | ||||
if((fin=fopen(tmp,"r"))==NULL) { | init_usertab(UserTabFile); | |||
debugapos("realtime",_("Cannot open file \"%s\": %s\n"),tmp,strer | LogLine_Init(&log_line); | |||
ror(errno)); | ||||
/* | ||||
* Store one more entry to prepare the memory structure in place and reje | ||||
ct it if | ||||
* it is about the same user and url as the last stored one. | ||||
*/ | ||||
StoredLogEntries=calloc(realtime_access_log_lines+1,sizeof(struct Realtim | ||||
eReadLogStruct)); | ||||
if (!StoredLogEntries) | ||||
{ | ||||
debuga(__FILE__,__LINE__,_("Not enough memory to store %d records | ||||
"),realtime_access_log_lines); | ||||
exit(EXIT_FAILURE); | ||||
} | ||||
/* | ||||
* Clear the url and user strings so that strcmp on the user and url are | ||||
not | ||||
* satisfied and the first entry can be stored. | ||||
*/ | ||||
memset(StoredLogEntries,0,sizeof(struct RealtimeReadLogStruct)); | ||||
if (!GetLatestModified(file_name,sizeof(file_name))) | ||||
{ | ||||
debuga(__FILE__,__LINE__,_("No log file to read the last %d lines | ||||
from\n"),realtime_access_log_lines); | ||||
exit(EXIT_FAILURE); | ||||
} | ||||
fp = FileObject_Open(file_name); | ||||
if (!fp) { | ||||
debuga(__FILE__,__LINE__,_("Cannot open file \"%s\": %s\n"),file_ | ||||
name,FileObject_GetLastOpenError()); | ||||
exit(EXIT_FAILURE); | exit(EXIT_FAILURE); | |||
} | } | |||
header(); | ||||
if ((line=longline_create())==NULL) { | if ((line=longline_create())==NULL) { | |||
debuga(_("Not enough memory to read the log file\n")); | debuga(__FILE__,__LINE__,_("Not enough memory to read file \"%s\" \n"),file_name); | |||
exit(EXIT_FAILURE); | exit(EXIT_FAILURE); | |||
} | } | |||
while((buf=longline_read(fin,line))!=NULL) { | while((buf=longline_read(fp,line)) != NULL ) | |||
fixendofline(buf); | { | |||
getword_start(&gwarea,buf); | log_entry_status=LogLine_Parse(&log_line,&log_entry,buf); | |||
if (getword_atoi(&dat,&gwarea,'\t')<0) { | if (log_entry_status==RLRC_Unknown) | |||
debuga(_("Invalid time column in file %s\n"),tmp); | { | |||
exit(EXIT_FAILURE); | continue; | |||
} | ||||
if (getword(ip,sizeof(ip),&gwarea,'\t')<0) { | ||||
debuga(_("Invalid IP address in file \"%s\"\n"),tmp); | ||||
exit(EXIT_FAILURE); | ||||
} | ||||
if (getword(user,sizeof(user),&gwarea,'\t')<0) { | ||||
debuga(_("Invalid user in file \"%s\"\n"),tmp); | ||||
exit(EXIT_FAILURE); | ||||
} | ||||
if (strlen(user) < 1) continue; | ||||
if (getword_ptr(buf,&url,&gwarea,'\t')<0) { | ||||
debuga(_("Invalid url in file \"%s\"\n"),tmp); | ||||
exit(EXIT_FAILURE); | ||||
} | } | |||
if (getword(typ,sizeof(typ),&gwarea,'\t')<0) { | if (log_entry_status==RLRC_Ignore) | |||
debuga(_("Invalid access type in file \"%s\"\n"),tmp); | { | |||
exit(EXIT_FAILURE); | continue; | |||
} | } | |||
if(strstr(RealtimeTypes,typ) == 0) | if (log_entry.HttpMethod && strstr(RealtimeTypes,log_entry.HttpMe thod)==0) | |||
continue; | continue; | |||
if (RealtimeUnauthRec==REALTIME_UNAUTH_REC_IGNORE && log_entry.Us | ||||
if(strcmp(ouser,user) == 0 && ourl && strcmp(ourl,url) == 0) | er[0]=='-' && log_entry.User[1]=='\0') | |||
continue; | ||||
StoreLogEntry(StoredLogEntries+NextIndex,&log_entry); | ||||
if (strcmp(StoredLogEntries[StoreIndex].User,StoredLogEntries[Nex | ||||
tIndex].User)==0 && strcmp(StoredLogEntries[StoreIndex].Url,StoredLogEntries[Nex | ||||
tIndex].Url)==0) | ||||
continue; | continue; | |||
if(UserIp) | StoreIndex=NextIndex; | |||
strcpy(user,ip); | NextIndex++; | |||
strcpy(u2,user); | if (NextIndex>StoreSize) StoreSize=NextIndex; | |||
if(Ip2Name) | if (NextIndex>realtime_access_log_lines) NextIndex=0; | |||
ip2name(u2,sizeof(u2)); | ||||
user_find(name, sizeof(name), u2); | ||||
tt=(time_t)dat; | ||||
t=localtime(&tt); | ||||
if(DateFormat[0]=='u') | ||||
strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M", t); | ||||
else if(DateFormat[0]=='e') | ||||
strftime(tbuf, sizeof(tbuf), "%d-%m-%Y %H:%M", t); | ||||
printf("<tr><td class=\"data\">%s</td><td class=\"data3\">%s</td> | ||||
<td class=\"data3\">%s</td><td class=\"data3\">%s</td><td class=\"data2\"><a hre | ||||
f=\"http://%s\">%s</td></tr>\n",tbuf,ip,name,typ,url,url); | ||||
strcpy(ouser,user); | ||||
url_len=strlen(url); | ||||
if (!ourl || url_len>=ourl_size) { | ||||
ourl_size=url_len+1; | ||||
ourl=realloc(ourl,ourl_size); | ||||
if (!ourl) { | ||||
debuga(_("Not enough memory to store the url\n")) | ||||
; | ||||
exit(EXIT_FAILURE); | ||||
} | ||||
} | ||||
strcpy(ourl,url); | ||||
} | } | |||
longline_destroy(&line); | if (FileObject_Close(fp)) { | |||
if (ourl) free(ourl); | debuga(__FILE__,__LINE__,_("Read error in \"%s\": %s\n"),file_nam | |||
e,FileObject_GetLastCloseError()); | ||||
puts("</table>\n</div>\n</body>\n</html>\n"); | ||||
fclose(fin); | ||||
if (!KeepTempLog && unlink(tmp)) { | ||||
debuga(_("Cannot delete \"%s\": %s\n"),tmp,strerror(errno)); | ||||
exit(EXIT_FAILURE); | exit(EXIT_FAILURE); | |||
} | } | |||
fflush(NULL); | longline_destroy(&line); | |||
} | ||||
static void header(void) | datashow(StoredLogEntries,StoreIndex,StoreSize); | |||
{ | free(StoredLogEntries); | |||
puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""); | ||||
puts(" \"http://www.w3.org/TR/html4/loose.dtd\">\n"); | ||||
puts("<html>\n"); | ||||
puts("<head>\n"); | ||||
if(realtime_refresh) | ||||
printf(" <meta http-equiv=refresh content=\"%d\" url=\"sarg-php/ | ||||
sarg-realtime.php\"; charset=\"%s\">\n",realtime_refresh,CharSet); | ||||
else | ||||
printf(" <meta http-equiv=\"Content-Type\" content=\"text/html; | ||||
charset=%s\">\n",CharSet); | ||||
css(stdout); | ||||
puts("</head>\n"); | ||||
printf("<body style=\"font-family:%s;font-size:%s;background-color:%s;bac | ||||
kground-image:url(%s)\">\n",FontFace,TitleFontSize,BgColor,BgImage); | ||||
puts("<div align=\"center\"><table cellpadding=\"1\" cellspacing=\"1\">\n | ||||
"); | ||||
printf("<tr><th class=\"title_l\" colspan=\"10\">SARG %s</th></tr>\n",_(" | ||||
Realtime")); | ||||
printf("<tr><th class=\"text\" colspan=\"10\">%s: %d s</th></tr>\n",_("Au | ||||
to refresh"),realtime_refresh); | ||||
printf("<tr><th class=\"header_c\">%s</th><th class=\"header_c\">%s</th>< | ||||
th class=\"header_c\">%s</th><th class=\"header_c\">%s</th><th class=\"header_l\ | ||||
">%s</th></tr>\n",_("DATE/TIME"),_("IP/NAME"),_("USERID"),_("TYPE"),_("ACCESSED | ||||
SITE")); | ||||
} | } | |||
End of changes. 31 change blocks. | ||||
276 lines changed or deleted | 216 lines changed or added |