250 lines
7.5 KiB
Python
250 lines
7.5 KiB
Python
from datetime import datetime, timedelta
|
|
import sys
|
|
from PyQt6.QtWidgets import (
|
|
QApplication,
|
|
QWidget,
|
|
QVBoxLayout,
|
|
QDateEdit,
|
|
QGridLayout,
|
|
QHBoxLayout,
|
|
QPushButton,
|
|
QLabel,
|
|
QListWidget,
|
|
QListWidgetItem,
|
|
QFrame,
|
|
QFileDialog,
|
|
QMessageBox
|
|
)
|
|
|
|
|
|
from parser import ApacheLogEntry
|
|
|
|
DATA_ROLE = 1001
|
|
LINES_PER_PAGE = 100
|
|
|
|
|
|
LABELS = [
|
|
"Host",
|
|
"Date",
|
|
"Time",
|
|
"Timezone",
|
|
"Status code",
|
|
"Method",
|
|
"Path",
|
|
"Size",
|
|
]
|
|
|
|
class LogViewer(QWidget):
|
|
def __init__(self):
|
|
self.value_labels = {}
|
|
super().__init__()
|
|
self.init()
|
|
self.current_page = 0
|
|
self.total_pages = 0
|
|
self.file_lines = []
|
|
|
|
def init(self):
|
|
self.setWindowTitle("Log Viewer")
|
|
self.setGeometry(100, 100, 900, 500)
|
|
|
|
mainLayout = QVBoxLayout()
|
|
|
|
topLayout = QHBoxLayout()
|
|
self.pathLabel = QLabel("Select working file...")
|
|
self.openButton = QPushButton("Open")
|
|
self.openButton.clicked.connect(self.open_file_dialog)
|
|
|
|
topLayout.addWidget(self.pathLabel)
|
|
topLayout.addWidget(self.openButton)
|
|
|
|
|
|
filterLayout = QHBoxLayout()
|
|
self.fromDateEdit = QDateEdit()
|
|
self.fromDateEdit.setDate(datetime.now() - timedelta(days=7))
|
|
self.toDateEdit = QDateEdit()
|
|
self.toDateEdit.setDate(datetime.now())
|
|
self.filterButton = QPushButton("Filter")
|
|
self.filterButton.clicked.connect(self.apply_date_filter)
|
|
|
|
filterLayout.addWidget(QLabel("From"))
|
|
filterLayout.addWidget(self.fromDateEdit)
|
|
filterLayout.addWidget(QLabel("To"))
|
|
filterLayout.addWidget(self.toDateEdit)
|
|
filterLayout.addWidget(self.filterButton)
|
|
|
|
|
|
middleLayout = QHBoxLayout()
|
|
self.logListWidget = QListWidget()
|
|
self.logListWidget.itemClicked.connect(self.select_log)
|
|
middleLayout.addWidget(self.logListWidget, 2)
|
|
|
|
self.detailsFrame = QFrame()
|
|
detailsLayout = QVBoxLayout(self.detailsFrame)
|
|
self.create_details_widget(detailsLayout)
|
|
middleLayout.addWidget(self.detailsFrame, 2)
|
|
|
|
bottomLayout = QHBoxLayout()
|
|
self.prevButton = QPushButton("Previous")
|
|
self.nextButton = QPushButton("Next")
|
|
self.prevButton.clicked.connect(self.show_previous_page)
|
|
self.nextButton.clicked.connect(self.show_next_page)
|
|
self.pageLabel = QLabel("")
|
|
bottomLayout.addWidget(self.prevButton)
|
|
bottomLayout.addStretch()
|
|
bottomLayout.addWidget(self.pageLabel)
|
|
bottomLayout.addStretch()
|
|
bottomLayout.addWidget(self.nextButton)
|
|
|
|
mainLayout.addLayout(topLayout)
|
|
mainLayout.addLayout(filterLayout)
|
|
mainLayout.addLayout(middleLayout)
|
|
mainLayout.addLayout(bottomLayout)
|
|
|
|
self.setLayout(mainLayout)
|
|
|
|
def reset_ui(self):
|
|
self.pathLabel.setText("Select working file...")
|
|
self.prevButton.setEnabled(False)
|
|
self.nextButton.setEnabled(True)
|
|
self.pageLabel.setText("")
|
|
|
|
self.logListWidget.clear()
|
|
|
|
for label in LABELS:
|
|
self.value_labels[label].setText("")
|
|
|
|
|
|
|
|
def apply_date_filter(self):
|
|
from_date = self.fromDateEdit.date().toPyDate()
|
|
to_date = self.toDateEdit.date().toPyDate()
|
|
|
|
if from_date > to_date:
|
|
self.show_error("Date 'From' must not be after date 'To'")
|
|
|
|
self.filtered_lines = []
|
|
for line in self.file_lines:
|
|
log = ApacheLogEntry.from_log(line)
|
|
if not log:
|
|
self.show_parse_error(line)
|
|
continue
|
|
date = datetime.strptime(log.timestamp.strftime("%Y-%m-%d"), "%Y-%m-%d").date()
|
|
|
|
if from_date <= date:
|
|
if date <= to_date:
|
|
self.filtered_lines.append(line)
|
|
else:
|
|
break
|
|
|
|
self.total_pages = (len(self.filtered_lines) - 1) // LINES_PER_PAGE + 1
|
|
self.current_page = 0
|
|
self.update_list()
|
|
|
|
def select_log(self, item):
|
|
log = item.data(DATA_ROLE)
|
|
self.value_labels["Host"].setText(log.host)
|
|
self.value_labels["Date"].setText(log.timestamp.strftime("%Y-%m-%d"))
|
|
self.value_labels["Time"].setText(log.timestamp.strftime("%H:%M:%S"))
|
|
self.value_labels["Timezone"].setText(log.timestamp.strftime("%z"))
|
|
self.value_labels["Status code"].setText(str(log.status_code))
|
|
self.value_labels["Method"].setText(log.method)
|
|
self.value_labels["Path"].setText(log.path)
|
|
self.value_labels["Size"].setText(str(log.bytes_sent or "-"))
|
|
|
|
def create_details_widget(self, layout):
|
|
gridLayout = QGridLayout()
|
|
|
|
|
|
for index, label in enumerate(LABELS):
|
|
labelWidget = QLabel(f"{label}:")
|
|
valueLabel = QLabel()
|
|
gridLayout.addWidget(labelWidget, index, 0)
|
|
gridLayout.addWidget(valueLabel, index, 1)
|
|
self.value_labels[label] = valueLabel
|
|
|
|
layout.addLayout(gridLayout)
|
|
|
|
def open_file_dialog(self):
|
|
fileName, _ = QFileDialog.getOpenFileName(
|
|
self,
|
|
"Open Log File",
|
|
"",
|
|
"All Files (*)",
|
|
)
|
|
if fileName:
|
|
self.pathLabel.setText(fileName)
|
|
self.load_log_file(fileName)
|
|
|
|
def load_log_file(self, file_path):
|
|
self.file_lines = []
|
|
self.reset_ui()
|
|
try:
|
|
with open(file_path, "r") as file:
|
|
self.file_lines = file.readlines()
|
|
self.filtered_lines = self.file_lines
|
|
except:
|
|
self.show_error(f"could not open file {file_path}")
|
|
return
|
|
|
|
self.total_pages = (len(self.file_lines) - 1) // LINES_PER_PAGE + 1
|
|
if self.total_pages == 1:
|
|
self.nextButton.setEnabled(False)
|
|
|
|
self.current_page = 0
|
|
self.update_list()
|
|
|
|
def update_list(self):
|
|
self.logListWidget.clear()
|
|
start_index = self.current_page * LINES_PER_PAGE
|
|
end_index = start_index + LINES_PER_PAGE
|
|
|
|
self.pageLabel.setText(f"{self.current_page + 1} / {max(1, self.total_pages)}")
|
|
|
|
for line in self.filtered_lines[start_index:end_index]:
|
|
parsed_data = ApacheLogEntry.from_log(line.strip())
|
|
if not parsed_data:
|
|
self.show_parse_error(line)
|
|
continue
|
|
|
|
item = QListWidgetItem(line.strip())
|
|
item.setData(DATA_ROLE, parsed_data)
|
|
self.logListWidget.addItem(item)
|
|
|
|
def show_next_page(self):
|
|
if self.current_page < self.total_pages - 1:
|
|
self.current_page += 1
|
|
self.update_list()
|
|
self.prevButton.setEnabled(True)
|
|
if self.current_page == self.total_pages - 1:
|
|
self.nextButton.setEnabled(False)
|
|
|
|
|
|
def show_previous_page(self):
|
|
if self.current_page > 0:
|
|
self.current_page -= 1
|
|
self.update_list()
|
|
self.nextButton.setEnabled(True)
|
|
if self.current_page == 0:
|
|
self.prevButton.setEnabled(False)
|
|
|
|
def show_error(self, text):
|
|
message_box = QMessageBox()
|
|
message_box.setIcon(QMessageBox.Icon.Critical)
|
|
message_box.setText("Error")
|
|
message_box.setWindowTitle("Error")
|
|
message_box.setInformativeText(text)
|
|
message_box.exec()
|
|
|
|
def show_parse_error(self, line):
|
|
self.show_error(f"Failed to parse data from line: {line}, ignoring.")
|
|
|
|
def main():
|
|
app = QApplication(sys.argv)
|
|
ex = LogViewer()
|
|
ex.show()
|
|
sys.exit(app.exec())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|