initial commit

This commit is contained in:
overcuriousity
2025-09-03 13:20:23 +02:00
parent 13855a70ae
commit 759acc855d
57 changed files with 7306 additions and 2 deletions

View File

View File

@@ -0,0 +1,398 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QToolTip, QTreeWidget, QTreeWidgetItem, QHBoxLayout, QLabel, QScrollArea
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
import logging
from logline_leviathan.database.database_manager import EntitiesTable, DistinctEntitiesTable, EntityTypesTable, FileMetadata, session_scope
class CustomCheckBox(QCheckBox):
def __init__(self, *args, **kwargs):
super(CustomCheckBox, self).__init__(*args, **kwargs)
self.setMouseTracking(True) # Enable mouse tracking
self.setStyleSheet("QCheckBox { color: white; }")
def mouseMoveEvent(self, event):
QToolTip.showText(event.globalPos(), self.toolTip()) # Show tooltip at mouse position
super(CustomCheckBox, self).mouseMoveEvent(event)
class FileCheckboxItem(QWidget):
def __init__(self, text, parent=None):
super(FileCheckboxItem, self).__init__(parent)
layout = QHBoxLayout(self)
self.checkBox = QCheckBox()
self.checkBox.setChecked(True)
self.label = QLabel(text)
self.label.setStyleSheet("QLabel { color: white; }") # Set text color
layout.addWidget(self.checkBox)
layout.addWidget(self.label)
layout.addStretch(1) # Add stretch factor to push content to the left
self.setLayout(layout)
class CheckboxPanel(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
self.treeWidget = QTreeWidget()
self.treeWidget.setHeaderHidden(True)
self.treeWidget.setStyleSheet("""
QTreeWidget::branch {color: white; /* White color for branches */
}
""")
layout.addWidget(self.treeWidget)
def _addChildren(self, parentItem, parent_entity_type, db_session, used_ids, depth=0):
try:
# Log the depth of recursion
#logging.debug(f"Adding children at depth: {depth}, parent entity type: {parent_entity_type}")
child_entity_types = db_session.query(EntityTypesTable).filter(EntityTypesTable.parent_type == parent_entity_type).all()
for child_entity_type in child_entity_types:
count = db_session.query(EntitiesTable).filter(EntitiesTable.entity_types_id == child_entity_type.entity_type_id).count()
text = f"{child_entity_type.gui_name}"
childItem = QTreeWidgetItem(parentItem)
isCheckable = not child_entity_type.entity_type.startswith("category_")
childItem.setFlags(childItem.flags() | Qt.ItemIsUserCheckable) if isCheckable else None
childItem.setCheckState(0, Qt.Unchecked) if isCheckable else None
text += f" ({count} Erwähnungen)" if isCheckable else ""
childItem.setText(0, text)
childItem.setToolTip(0, child_entity_type.gui_tooltip)
childItem.entity_type_id = child_entity_type.entity_type_id
childItem.entity_type = child_entity_type.entity_type
if child_entity_type.entity_type_id in used_ids and not child_entity_type.parser_enabled == False:
color = QColor('green')
elif not child_entity_type.entity_type.startswith("category_") and not child_entity_type.parser_enabled == True:
color = QColor('red')
else:
color = QColor('white')
childItem.setForeground(0, color)
# Recursive call with increased depth
depth = depth + 1
self._addChildren(childItem, child_entity_type.entity_type, db_session, used_ids, depth)
except Exception as e:
logging.error(f"Error adding children: {e}")
def updateCheckboxes(self, db_session):
#logging.info("Updating checkboxes with database content")
with session_scope() as db_session:
try:
# Query database for entity types
entity_types = db_session.query(EntityTypesTable).all()
used_ids = {d.entity_types_id for d in db_session.query(DistinctEntitiesTable.entity_types_id).distinct()}
#logging.debug(f"Used IDs: {used_ids}")
# Clear existing items
self.treeWidget.clear()
rootItems = {}
# Construct hierarchical tree structure
for entity_type in entity_types:
if entity_type.parent_type != 'root': # Skip non-root items
continue
count = db_session.query(EntitiesTable).filter(EntitiesTable.entity_types_id == entity_type.entity_type_id).count()
text = f"{entity_type.gui_name}"
treeItem = QTreeWidgetItem()
treeItem.setToolTip(0, entity_type.gui_tooltip)
treeItem.entity_type_id = entity_type.entity_type_id
treeItem.entity_type = entity_type.entity_type
if not entity_type.entity_type.startswith("category_"):
treeItem.setFlags(treeItem.flags() | Qt.ItemIsUserCheckable)
treeItem.setCheckState(0, Qt.Unchecked)
text = f"{entity_type.gui_name} ({count} Erwähnungen)"
treeItem.setText(0, text)
# Add item to tree widget
self.treeWidget.addTopLevelItem(treeItem)
rootItems[entity_type.entity_type_id] = treeItem
# Call recursive function to add children
self._addChildren(treeItem, entity_type.entity_type, db_session, used_ids)
# Optionally expand all tree items
self.treeWidget.expandAll()
except Exception as e:
logging.error("Error updating checkboxes", exc_info=True)
def filterCheckboxes(self, filter_text):
def filterTreeItem(treeItem):
# Check if the current item or any of its properties match the filter text
try:
match = filter_text.lower() in treeItem.text(0).lower() or filter_text.lower() in treeItem.toolTip(0).lower()
except Exception as e:
logging.error(f"Error checking filter match for tree item: {e}")
match = False
# Recursively check child items and set 'childMatch' if any child matches
childMatch = False
for j in range(treeItem.childCount()):
if filterTreeItem(treeItem.child(j)):
childMatch = True
# Unhide the item and its parents if there's a match in the item or its children
if match or childMatch:
treeItem.setHidden(False)
parent = treeItem.parent()
while parent:
parent.setHidden(False)
parent = parent.parent()
return True
else:
treeItem.setHidden(True)
return False
# Filter all top-level items
for i in range(self.treeWidget.topLevelItemCount()):
filterTreeItem(self.treeWidget.topLevelItem(i))
def checkAllVisible(self):
with session_scope() as db_session:
used_ids = self.getUsedIds(db_session)
self._setCheckStateForVisibleItems(Qt.Checked, used_ids)
def uncheckAllVisible(self):
with session_scope() as db_session:
used_ids = self.getUsedIds(db_session)
self._setCheckStateForVisibleItems(Qt.Unchecked, used_ids)
def _setCheckStateForVisibleItems(self, state, used_ids):
def setCheckState(item):
try:
if (item.flags() & Qt.ItemIsUserCheckable) and not item.isHidden(): # and item.parent():
# Check if entity_type_id is in used_ids
if hasattr(item, 'entity_type_id') and item.entity_type_id in used_ids:
item.setCheckState(0, state)
#logging.debug(f"Set check state for item with entity_type_id: {item.entity_type_id}")
#else:
#logging.debug(f"Item with entity_type_id: {getattr(item, 'entity_type_id', 'N/A')} skipped")
for i in range(item.childCount()):
childItem = item.child(i)
setCheckState(childItem)
except Exception as e:
logging.error(f"Error in setCheckState: {e}")
try:
for i in range(self.treeWidget.topLevelItemCount()):
topItem = self.treeWidget.topLevelItem(i)
setCheckState(topItem)
except Exception as e:
logging.error(f"Error in _setCheckStateForVisibleItems: {e}")
def getUsedIds(self, db_session):
# Assuming db_session is your database session object
try:
used_ids = {d.entity_types_id for d in db_session.query(DistinctEntitiesTable.entity_types_id).distinct()}
return used_ids
except Exception as e:
logging.error(f"Error in getUsedIds: {e}")
return set()
def expandAllTreeItems(self):
for i in range(self.treeWidget.topLevelItemCount()):
self._expandCollapseRecursive(self.treeWidget.topLevelItem(i), True)
def collapseAllTreeItems(self):
for i in range(self.treeWidget.topLevelItemCount()):
self._expandCollapseRecursive(self.treeWidget.topLevelItem(i), False)
def _expandCollapseRecursive(self, treeItem, expand=True):
if treeItem is not None:
treeItem.setExpanded(expand)
for j in range(treeItem.childCount()):
self._expandCollapseRecursive(treeItem.child(j), expand)
class DatabasePanel(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
self.treeWidget = QTreeWidget()
self.treeWidget.setHeaderHidden(True) # Hide the header
self.treeWidget.setStyleSheet("""QTreeWidget::branch {color: white; /* White color for branches */}""")
layout.addWidget(self.treeWidget)
def _getTotalCountForChildren(self, entity_type, db_session):
# Recursive function to get total count
total_count = db_session.query(EntitiesTable).filter(EntitiesTable.entity_types_id == entity_type.entity_type_id).count()
child_entity_types = db_session.query(EntityTypesTable).filter(EntityTypesTable.parent_type == entity_type.entity_type).all()
for child_entity_type in child_entity_types:
total_count += self._getTotalCountForChildren(child_entity_type, db_session)
return total_count
def _addChildren(self, parentItem, parent_entity_type, db_session, used_ids, depth=0):
try:
# Log the depth of recursion
#logging.debug(f"Adding children at depth: {depth}, parent entity type: {parent_entity_type}")
child_entity_types = db_session.query(EntityTypesTable).filter(EntityTypesTable.parent_type == parent_entity_type).all()
for child_entity_type in child_entity_types:
if not child_entity_type.entity_type.startswith("category_"):
count = db_session.query(EntitiesTable).filter(EntitiesTable.entity_types_id == child_entity_type.entity_type_id).count()
text = f" {count} - {child_entity_type.gui_name} ({child_entity_type.entity_type})"
else:
# Use the new method to get the total count for this category
total_count = self._getTotalCountForChildren(child_entity_type, db_session)
text = f" {total_count} - {child_entity_type.gui_name} (Total)"
childItem = QTreeWidgetItem(parentItem)
childItem.setText(0, text)
childItem.setToolTip(0, child_entity_type.gui_tooltip)
childItem.entity_type_id = child_entity_type.entity_type_id
childItem.entity_type = child_entity_type.entity_type
if child_entity_type.entity_type_id in used_ids and child_entity_type.parser_enabled:
color = QColor('green')
elif not child_entity_type.entity_type.startswith("category_") and not child_entity_type.parser_enabled:
color = QColor('red')
else:
color = QColor('white')
childItem.setForeground(0, color)
# Recursive call with increased depth
depth = depth + 1
self._addChildren(childItem, child_entity_type.entity_type, db_session, used_ids, depth)
except Exception as e:
logging.error(f"Error in _addChildren: {e}")
def updateTree(self, db_session):
#logging.info("Updating checkboxes with database content")
with session_scope() as db_session:
try:
# Query database for entity types
entity_types = db_session.query(EntityTypesTable).all()
used_ids = {d.entity_types_id for d in db_session.query(DistinctEntitiesTable.entity_types_id).distinct()}
#logging.debug(f"Used IDs: {used_ids}")
# Clear existing items
self.treeWidget.clear()
rootItems = {}
# Construct hierarchical tree structure
for entity_type in entity_types:
if entity_type.parent_type != 'root': # Skip non-root items
continue
if not entity_type.entity_type.startswith("category_"):
count = db_session.query(EntitiesTable).filter(EntitiesTable.entity_types_id == entity_type.entity_type_id).count()
text = f"{count} - {entity_type.gui_name} {entity_type.entity_type}"
else:
# Use the new method to get the total count for this category
total_count = self._getTotalCountForChildren(entity_type, db_session)
text = f"{total_count} - {entity_type.gui_name} (Total)"
treeItem = QTreeWidgetItem()
treeItem.setText(0, text)
treeItem.setToolTip(0, entity_type.gui_tooltip)
treeItem.entity_type_id = entity_type.entity_type_id
treeItem.entity_type = entity_type.entity_type
if entity_type.entity_type_id in used_ids and entity_type.parser_enabled:
color = QColor('green')
elif not entity_type.entity_type.startswith("category_") and not entity_type.parser_enabled:
color = QColor('red')
else:
color = QColor('white')
treeItem.setForeground(0, color)
self.treeWidget.addTopLevelItem(treeItem)
# Call recursive function to add children
self._addChildren(treeItem, entity_type.entity_type, db_session, used_ids)
# Optionally expand all tree items
self.treeWidget.expandAll()
except Exception as e:
logging.error("Error updating database tree", exc_info=True)
def expandAllTreeItems(self):
for i in range(self.treeWidget.topLevelItemCount()):
self._expandCollapseRecursive(self.treeWidget.topLevelItem(i), True)
def collapseAllTreeItems(self):
for i in range(self.treeWidget.topLevelItemCount()):
self._expandCollapseRecursive(self.treeWidget.topLevelItem(i), False)
def _expandCollapseRecursive(self, treeItem, expand=True):
if treeItem is not None:
treeItem.setExpanded(expand)
for j in range(treeItem.childCount()):
self._expandCollapseRecursive(treeItem.child(j), expand)
class FileCheckboxPanel(QWidget):
def __init__(self):
super().__init__()
self.mainLayout = QVBoxLayout(self)
self.scrollArea = QScrollArea(self)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaContents = QWidget()
self.scrollLayout = QVBoxLayout(self.scrollAreaContents)
self.scrollArea.setWidget(self.scrollAreaContents)
self.mainLayout.addWidget(self.scrollArea)
self.items = [] # Keep track of the custom widgets
def updateCheckboxes(self, db_session):
try:
# Clear existing items
for item in self.items:
item.deleteLater()
self.items.clear()
with session_scope() as db_session:
try:
file_metadata = db_session.query(FileMetadata).all()
for file in file_metadata:
entity_count = db_session.query(EntitiesTable).filter(EntitiesTable.file_id == file.file_id).count()
item_text = f"{file.file_name} ({entity_count})"
custom_widget = FileCheckboxItem(item_text)
self.scrollLayout.addWidget(custom_widget)
self.items.append(custom_widget)
# Add a stretch to push everything up
self.scrollLayout.addStretch(1)
except Exception as e:
logging.error("Error updating file checkboxes", exc_info=True)
except Exception as e:
logging.error("Error updating file checkboxes", exc_info=True)
def filterCheckboxes(self, filter_text):
for item in self.items:
if filter_text.lower() in item.label.text().lower():
item.show()
else:
item.hide()
def checkAllVisible(self):
for item in self.items:
if not item.isHidden():
item.checkBox.setChecked(True)
def uncheckAllVisible(self):
for item in self.items:
if not item.isHidden():
item.checkBox.setChecked(False)
def getCheckedFiles(self):
checked_files = []
for custom_widget in self.items:
if custom_widget.checkBox.isChecked():
# Extract the file name from the item text
file_name = custom_widget.label.text().split(" (")[0]
checked_files.append(file_name)
return checked_files
def _setCheckStateForVisibleItems(self, state):
for custom_widget in self.items:
if not custom_widget.isHidden():
custom_widget.checkBox.setChecked(state)

View File

@@ -0,0 +1,77 @@
from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem, QDialog, QVBoxLayout, QHBoxLayout, QComboBox, QPushButton
from logline_leviathan.database.database_manager import EntitiesTable, DistinctEntitiesTable, EntityTypesTable, ContextTable, FileMetadata, session_scope
from sqlalchemy import func, label
from sqlalchemy.orm import aliased
class CustomizeResultsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.db_session = session_scope()
self.setWindowTitle("Report anpassen")
stylesheet = """
/* Style for the main window */
QWidget {
background-color: #282C34; /* Dark grey background */
color: white; /* White text */
}
/* Style for buttons */
QPushButton {
background-color: #4B5563; /* Dark grey background */
color: white; /* White text */
border-style: outset;
border-width: 2px;
border-radius: 1px; /* Rounded corners */
border-color: #4A4A4A;
padding: 6px;
min-width: 60px;
min-height: 20px;
}
QPushButton:hover {
background-color: #6E6E6E; /* Slightly lighter grey on hover */
}
QPushButton:pressed {
background-color: #484848; /* Even darker grey when pressed */
}
"""
self.layout = QVBoxLayout(self)
self.setStyleSheet(stylesheet)
self.comboBoxLayout = QHBoxLayout()
self.layout.addLayout(self.comboBoxLayout)
# Initially add one combo box
self.addComboBox()
# Button to add more combo boxes
self.addButton = QPushButton("Spalte hinzufügen", self)
self.addButton.clicked.connect(self.addComboBox)
self.layout.addWidget(self.addButton)
# OK and Cancel buttons
self.okButton = QPushButton("OK", self)
self.okButton.clicked.connect(self.accept)
self.cancelButton = QPushButton("Abbruch", self)
self.cancelButton.clicked.connect(self.reject)
self.buttonLayout = QHBoxLayout()
self.buttonLayout.addWidget(self.okButton)
self.buttonLayout.addWidget(self.cancelButton)
self.layout.addLayout(self.buttonLayout)
self.selectedColumns = []
def addComboBox(self):
comboBox = QComboBox(self)
comboBox.addItems(['Entitätentyp', 'Entität', 'Anzahl Erwähnungen', 'Dateiname', 'Zeilennummer', 'Zeitstempel', 'Kontext - gleiche Zeile', 'Kontext - mittelgroß', 'Kontext - umfangreich'])
self.comboBoxLayout.addWidget(comboBox)
def comboBoxes(self):
# Utility method to get all combo boxes
return [self.comboBoxLayout.itemAt(i).widget() for i in range(self.comboBoxLayout.count())]
def on_accept(self):
self.selectedColumns = [comboBox.currentText() for comboBox in self.comboBoxes()]
self.accept()

View File

@@ -0,0 +1,328 @@
import os
from PyQt5.QtWidgets import (QMessageBox, QWidget, QApplication,
QFileDialog, QLabel, QPushButton, QGridLayout, QGroupBox, QHBoxLayout, QVBoxLayout, QLineEdit)
from PyQt5.QtCore import Qt
from logline_leviathan.gui.checkbox_panel import *
from logline_leviathan.gui.ui_helper import UIHelper
from logline_leviathan.database.database_manager import session_scope
from logline_leviathan.exporter.wordlist_export import generate_wordlist
from logline_leviathan.gui.checkbox_panel import *
import shutil
import glob
class DBBrowserWindow(QWidget):
def __init__(self, app):
super().__init__()
self.app = app
self.initialize_dbbrowser_window(app)
def initialize_dbbdrowser_window(dbbrowser_window, app):
dbbrowser_window.setWindowTitle('Logline Leviathan - Database-Browser')
dbbrowser_window.mainLayout = QVBoxLayout(dbbrowser_window)
#dbbrowser_window.extendedLayout = QHBoxLayout(dbbrowser_window)
dbbrowser_window.db_session = None
stylesheet = """
/* Style for the main window */
QWidget {
background-color: #282C34; /* Dark grey background */
color: white; /* White text */
}
/* Style for buttons */
QPushButton {
background-color: #4B5563; /* Dark grey background */
color: white; /* White text */
border-style: outset;
border-width: 2px;
border-radius: 1px; /* Rounded corners */
border-color: #4A4A4A;
padding: 6px;
min-width: 50px;
min-height: 15px;
}
QPushButton:hover {
background-color: #6E6E6E; /* Slightly lighter grey on hover */
}
QPushButton:pressed {
background-color: #484848; /* Even darker grey when pressed */
}
"""
highlited_button_style = """
QPushButton {
background-color: #3C8CCE; /* Lighter blue background */
color: white; /* White text */
border-style: outset;
border-width: 2px;
border-radius: 1px; /* Rounded corners */
border-color: #4A4A4A;
padding: 6px;
min-width: 50px;
min-height: 15px;
}
QPushButton:hover {
background-color: #7EC0EE; /* Even lighter blue on hover */
}
QPushButton:pressed {
background-color: #4A86E8; /* Slightly darker blue when pressed */
}
"""
dbbrowser_window.setStyleSheet(stylesheet)
dbbrowser_window.statusLabel = QLabel(' Erwarte Selektion der Entitätentypen', dbbrowser_window)
dbbrowser_window.statusLabel.setWordWrap(True)
dbbrowser_window.statusLabel.setMinimumHeight(40)
dbbrowser_window.statusLabel.setStyleSheet("QLabel { background-color: #3C4043; color: white; }")
dbbrowser_window.mainLayout.addWidget(dbbrowser_window.statusLabel)
# Create a GroupBox for the CheckboxPanel
exportOptionsGroupBox = QGroupBox("SELEKTION", dbbrowser_window)
exportOptionsLayout = QVBoxLayout(exportOptionsGroupBox)
dbbrowser_window.checkboxPanel = CheckboxPanel()
# Create a horizontal layout
filterLayout = QHBoxLayout()
# Create the "Check All" button
checkAllButton = QPushButton("Alle markieren", dbbrowser_window)
checkAllButton.clicked.connect(lambda: dbbrowser_window.checkboxPanel.checkAllVisible())
# Create the "Uncheck All" button
uncheckAllButton = QPushButton("Keine markieren", dbbrowser_window)
uncheckAllButton.clicked.connect(lambda: dbbrowser_window.checkboxPanel.uncheckAllVisible())
expandAllButton = QPushButton("Expandieren", dbbrowser_window)
expandAllButton.clicked.connect(lambda: dbbrowser_window.checkboxPanel.expandAllTreeItems())
collapseAllButton = QPushButton("Komprimieren", dbbrowser_window)
collapseAllButton.clicked.connect(lambda: dbbrowser_window.checkboxPanel.collapseAllTreeItems())
# Add buttons to the filter layout, to the left of the filter label
filterLayout.addWidget(checkAllButton)
filterLayout.addWidget(uncheckAllButton)
filterLayout.addWidget(expandAllButton)
filterLayout.addWidget(collapseAllButton)
# Create the label for the filter
filterLabel = QLabel("Filtern:")
filterLayout.addWidget(filterLabel) # Add label to the horizontal layout
# Add Text Input for Filtering
filterLineEdit = QLineEdit(dbbrowser_window)
filterLineEdit.setPlaceholderText(" nach Typ, Tooltip oder Kurzbezeichnung filtern...")
filterLineEdit.setStyleSheet("""
QLineEdit {
background-color: #3C4043; /* Background color */
color: white; /* Text color */
min-height: 20px;
}
""")
filterLayout.addWidget(filterLineEdit) # Add line edit to the horizontal layout
exportOptionsLayout.addLayout(filterLayout) # Add the horizontal layout to the export options layout
# Add CheckboxPanel to the GroupBox's Layout
exportOptionsLayout.addWidget(dbbrowser_window.checkboxPanel)
# Connect the textChanged signal of QLineEdit to a new method
filterLineEdit.textChanged.connect(dbbrowser_window.checkboxPanel.filterCheckboxes)
dbbrowser_window.mainLayout.addWidget(exportOptionsGroupBox)
copyWordlistToParserDirButton = QPushButton('Soeben generierte Wordlist zur Analyse hinzufügen (kopiert erzeugte Datei)', dbbrowser_window)
copyWordlistToParserDirButton.clicked.connect(dbbrowser_window.copyWordlistToParserDir)
dbbrowser_window.mainLayout.addWidget(copyWordlistToParserDirButton)
# Exit Button Layout
bottomLayout = QGridLayout()
dbbrowser_window.openWordlistPathButton = QPushButton('Ziel-Dateipfad...', dbbrowser_window)
dbbrowser_window.openWordlistPathButton.clicked.connect(dbbrowser_window.openWordlistPath)
bottomLayout.addWidget(dbbrowser_window.openWordlistPathButton, 1, 1)
# Start Export Button
dbbrowser_window.startExportButton = QPushButton('Wordlist erstellen', dbbrowser_window)
dbbrowser_window.startExportButton.clicked.connect(dbbrowser_window.start_export_process)
dbbrowser_window.startExportButton.setStyleSheet(highlited_button_style)
bottomLayout.addWidget(dbbrowser_window.startExportButton, 1, 2)
# Output File Directory
dbbrowser_window.selectOutputFileButton = QPushButton('Zieldateipfad setzen...', dbbrowser_window)
dbbrowser_window.selectOutputFileButton.clicked.connect(dbbrowser_window.selectOutputFile)
bottomLayout.addWidget(dbbrowser_window.selectOutputFileButton, 2, 1)
# Exit Button
dbbrowser_window.exitButton = QPushButton('Schließen', dbbrowser_window)
dbbrowser_window.exitButton.clicked.connect(dbbrowser_window.close)
bottomLayout.addWidget(dbbrowser_window.exitButton, 2, 2)
dbbrowser_window.crossmatchesCheckbox = QCheckBox('Nur Kreuztreffer (Entitäten, die in mehreren Dateien vorkommen)', dbbrowser_window)
bottomLayout.addWidget(dbbrowser_window.crossmatchesCheckbox, 0, 1)
# Output File Path Label
dbbrowser_window.WordlistPathLabel = QLabel('', dbbrowser_window)
dbbrowser_window.updateWordlistPathLabel() # Call this method to set the initial text
bottomLayout.addWidget(dbbrowser_window.WordlistPathLabel, 0, 2)
dbbrowser_window.mainLayout.addLayout(bottomLayout)
dbbrowser_window.setLayout(dbbrowser_window.mainLayout)
def updateCheckboxes(self):
with session_scope() as session:
self.checkboxPanel.updateCheckboxes(session)
def getSelectedCheckboxes(self):
selected_checkboxes = []
def traverseTreeItems(treeItem):
if treeItem.checkState(0) == Qt.Checked:
selected_checkboxes.append(treeItem)
for i in range(treeItem.childCount()):
traverseTreeItems(treeItem.child(i))
for i in range(self.checkboxPanel.treeWidget.topLevelItemCount()):
traverseTreeItems(self.checkboxPanel.treeWidget.topLevelItem(i))
return selected_checkboxes
def updateWordlistPathLabel(self):
outputDirPath = os.path.dirname(self.WordlistPath)
display_text = f'{outputDirPath}/'
self.WordlistPathLabel.setText(display_text)
def openWordlistPath(self):
outputDirPath = os.path.dirname(self.WordlistPath)
wordlistPath = os.path.join(outputDirPath, 'wordlist')
self.ui_helper.openFile(wordlistPath)
def selectOutputFile(self):
options = QFileDialog.Options()
output_format = self.outputFormatList.currentItem().text().lower()
extension_map = {'html': '.html', 'xlsx': '.xlsx'}
default_extension = extension_map.get(output_format, '')
selected_file, _ = QFileDialog.getSaveFileName(
self,
"Selektieren des Ziel-Dateipfads",
self.WordlistPath,
f"{output_format.upper()} Files (*{default_extension});;All Files (*)",
options=options
)
if selected_file:
if not selected_file.endswith(default_extension):
selected_file += default_extension
self.WordlistPath = selected_file
self.outputDir = os.path.dirname(selected_file)
self.updateWordlistPathLabel()
def get_unique_filename(self, base_path):
directory, filename = os.path.split(base_path)
name, extension = os.path.splitext(filename)
counter = 1
new_path = base_path
while os.path.exists(new_path):
new_filename = f"{name}_{counter}{extension}"
new_path = os.path.join(directory, new_filename)
counter += 1
return new_path
def copyWordlistToParserDir(self):
try:
# Path to the parser directory
parser_dir = os.path.join(os.getcwd(), 'data', 'parser')
# Ensure the parser directory exists
os.makedirs(parser_dir, exist_ok=True)
# Find the newest .txt file in the WordlistPath directory
list_of_files = glob.glob(os.path.join(self.WordlistPath, '*.txt'))
if not list_of_files:
raise FileNotFoundError("No .txt files found in the WordlistPath directory.")
newest_file = max(list_of_files, key=os.path.getctime)
# Destination file path
destination_file = os.path.join(parser_dir, 'generated_wordlist.txt')
# Copy and overwrite the newest file to the destination
shutil.copy2(newest_file, destination_file)
self.statusLabel.setText(f" Wordlist erfolgreich kopiert nach {destination_file}")
except Exception as e:
self.message("Fehler beim kopieren", f"Fehler beim kopieren: {str(e)}")
def start_export_process(self):
# Base filename for the wordlist file
base_filename = "wordlist.txt"
# Construct the full path with the base filename
full_output_path = os.path.join(self.WordlistPath, base_filename)
# Generate a unique filename to avoid overwriting existing files
unique_output_path = self.get_unique_filename(full_output_path)
try:
with session_scope() as session:
selected_checkboxes = self.getSelectedCheckboxes()
if not selected_checkboxes:
self.message("Generieren nicht möglich", "Keine Typen selektiert. Auswahl vornehmen.")
return
only_crossmatches = self.crossmatchesCheckbox.isChecked()
generate_wordlist(unique_output_path, session, selected_checkboxes, only_crossmatches)
self.statusLabel.setText(f" Generierte Liste gespeichert unter {unique_output_path}")
except Exception as e:
self.statusLabel.setText(f" Fehler beim speichern: {str(e)}")
logging.error(f"Export Error: {str(e)}")
def message(self, title, text, extra_widget=None):
msgBox = QMessageBox()
msgBox.setStyleSheet("""
QMessageBox {
background-color: #282C34; /* Dark grey background */
}
QLabel {
color: white; /* White text */
}
QPushButton {
color: white; /* White text for buttons */
background-color: #4B5563; /* Dark grey background for buttons */
border-style: solid;
border-width: 2px;
border-radius: 5px;
border-color: #4A4A4A;
padding: 6px;
min-width: 80px;
min-height: 30px;
}
""")
msgBox.setIcon(QMessageBox.Warning)
msgBox.setWindowTitle(title)
msgBox.setText(text)
if extra_widget:
msgBox.setInformativeText('')
msgBox.layout().addWidget(extra_widget, 1, 1)
msgBox.exec_()

View File

@@ -0,0 +1,188 @@
import os
import logging
from PyQt5.QtWidgets import (QMessageBox, QWidget, QApplication,
QFileDialog)
from PyQt5.QtCore import Qt
from logline_leviathan.gui.initui_report_window import initialize_generate_report_window
from logline_leviathan.gui.checkbox_panel import CheckboxPanel, FileCheckboxPanel
from logline_leviathan.gui.ui_helper import UIHelper
from logline_leviathan.gui.customize_results import CustomizeResultsDialog
from logline_leviathan.database.database_manager import session_scope
from logline_leviathan.exporter.html_export import generate_html_file
from logline_leviathan.exporter.xlsx_export import generate_xlsx_file
from logline_leviathan.exporter.nice_export import generate_niceoutput_file
class GenerateReportWindow(QWidget):
def __init__(self, app):
super().__init__()
self.app = app
self.checkboxPanel = CheckboxPanel()
self.fileCheckboxPanel = FileCheckboxPanel()
self.ui_helper = UIHelper(self)
self.outputFilePath = os.path.join(os.getcwd(), 'output')
initialize_generate_report_window(self, app)
self.updateCheckboxes()
def updateCheckboxes(self):
with session_scope() as session:
self.checkboxPanel.updateCheckboxes(session)
self.fileCheckboxPanel.updateCheckboxes(session)
def getSelectedCheckboxes(self):
selected_checkboxes = []
def traverseTreeItems(treeItem):
if treeItem.checkState(0) == Qt.Checked:
selected_checkboxes.append(treeItem)
for i in range(treeItem.childCount()):
traverseTreeItems(treeItem.child(i))
for i in range(self.checkboxPanel.treeWidget.topLevelItemCount()):
traverseTreeItems(self.checkboxPanel.treeWidget.topLevelItem(i))
return selected_checkboxes
def updateOutputFilePathLabel(self):
self.outputFilePathLabel.setText(self.outputFilePath)
def openOutputFilepath(self):
if not os.path.isdir(self.outputFilePath):
self.outputFilePath = os.path.dirname(self.outputFilePath)
self.ui_helper.openFile(self.outputFilePath)
def openCustomizeResultsDialog(self):
dialog = CustomizeResultsDialog()
if dialog.exec_():
selected_columns = [dialog.comboBoxLayout.itemAt(i).widget().currentText() for i in range(dialog.comboBoxLayout.count())]
def start_export_process(self):
current_item = self.outputFormatList.currentItem()
if current_item is not None:
output_format = current_item.text().lower()
extension_map = {'html': '.html', 'interactive html': '.html', 'xlsx': '.xlsx'}
selected_extension = extension_map.get(output_format, '.html')
only_crossmatches = self.crossmatchesCheckbox.isChecked()
include_flagged = self.flaggedEntriesCheckbox.isChecked()
only_flagged = self.flaggedRadioButton.isChecked()
only_unflagged = self.notflaggedRadioButton.isChecked()
# Get custom filename from QLineEdit or use default
custom_filename = self.setOutputFileNameLineEdit.text().strip()
if not custom_filename:
custom_filename = "entities_export" # Default filename if not specified
initial_output_path = os.path.join(self.outputFilePath, f"{custom_filename}{selected_extension}")
unique_output_path = self.get_unique_filename(initial_output_path)
try:
with session_scope() as session:
selected_checkboxes = self.getSelectedCheckboxes() # Get selected checkboxes from the tree
selected_files = self.fileCheckboxPanel.getCheckedFiles()
if not selected_checkboxes:
self.message("Export nicht möglich", "Keine Entitäten ausgewählt. Auf dem Panel eine Selektion vornehmen.")
return
if not selected_files:
self.message("Export nicht möglich", "Keine Dateien ausgewählt. Auf dem Panel eine Selektion vornehmen.")
return
if self.timestampFilterCheckbox.isChecked():
start_date = self.startDateEdit.date().toPyDate()
end_date = self.endDateEdit.date().toPyDate()
else:
start_date = end_date = None
if output_format == 'html':
logging.debug(f"only_crossmatches: {only_crossmatches}")
generate_html_file(unique_output_path, session, selected_checkboxes, self.fileCheckboxPanel, self.exportContextList.currentItem().text(), only_crossmatches, start_date, end_date, include_flagged, only_flagged, only_unflagged)
elif output_format == 'interactive html':
logging.debug(f"only_crossmatches: {only_crossmatches}")
generate_niceoutput_file(unique_output_path, session, selected_checkboxes, self.fileCheckboxPanel, self.exportContextList.currentItem().text(), only_crossmatches, start_date, end_date, include_flagged, only_flagged, only_unflagged)
elif output_format == 'xlsx':
logging.debug(f"only_crossmatches: {only_crossmatches}")
generate_xlsx_file(unique_output_path, session, selected_checkboxes, self.fileCheckboxPanel, self.exportContextList.currentItem().text(), only_crossmatches, start_date, end_date, include_flagged, only_flagged, only_unflagged)
else:
raise ValueError(f"Unsupported format: {output_format}")
self.statusLabel.setText(f" Export gespeichert unter: {unique_output_path}")
# Check if 'Open After Export' is checked, and open the file if so
if self.openAfterExportCheckbox.isChecked():
if os.path.exists(unique_output_path):
self.ui_helper.openFile(unique_output_path)
except Exception as e:
self.statusLabel.setText(f" Export Error: {str(e)}")
logging.error(f"Export Error: {str(e)}")
else:
self.message("Export nicht möglich", "Ausgabeformat und Umfang des Kontexts spezifizieren.")
def selectOutputFile(self):
options = QFileDialog.Options()
# Set the dialog for directory selection
options |= QFileDialog.ShowDirsOnly
# Open a dialog to select a directory
selected_directory = QFileDialog.getExistingDirectory(
self,
"Select Output Directory",
self.outputFilePath, # Start at the current output file path
options=options
)
# If a directory was selected, update the output file path
if selected_directory:
self.outputFilePath = selected_directory
self.updateOutputFilePathLabel() # Update any labels or UI components as necessary
def get_unique_filename(self, base_path):
directory, filename = os.path.split(base_path)
name, extension = os.path.splitext(filename)
counter = 1
new_path = base_path
while os.path.exists(new_path):
new_filename = f"{name}_{counter}{extension}"
new_path = os.path.join(directory, new_filename)
counter += 1
return new_path
def message(self, title, text, extra_widget=None):
msgBox = QMessageBox()
msgBox.setStyleSheet("""
QMessageBox {
background-color: #282C34; /* Dark grey background */
}
QLabel {
color: white; /* White text */
}
QPushButton {
color: white; /* White text for buttons */
background-color: #4B5563; /* Dark grey background for buttons */
border-style: solid;
border-width: 2px;
border-radius: 5px;
border-color: #4A4A4A;
padding: 6px;
min-width: 80px;
min-height: 30px;
}
""")
msgBox.setIcon(QMessageBox.Warning)
msgBox.setWindowTitle(title)
msgBox.setText(text)
if extra_widget:
msgBox.setInformativeText('')
msgBox.layout().addWidget(extra_widget, 1, 1)
msgBox.exec_()

View File

@@ -0,0 +1,416 @@
import os
from PyQt5.QtWidgets import (QMessageBox, QWidget, QRadioButton,
QFileDialog, QLabel, QPushButton, QGridLayout, QGroupBox, QHBoxLayout, QVBoxLayout, QLineEdit, QDateTimeEdit)
from PyQt5.QtCore import Qt, QDate
from logline_leviathan.gui.checkbox_panel import *
from logline_leviathan.gui.ui_helper import UIHelper
from logline_leviathan.database.database_manager import session_scope
from logline_leviathan.database.database_operations import DatabaseOperations
from logline_leviathan.exporter.wordlist_export import generate_wordlist
from logline_leviathan.gui.checkbox_panel import *
import shutil
import glob
class GenerateWordlistWindow(QWidget):
def __init__(self, app):
super().__init__()
self.app = app
self.checkboxPanel = CheckboxPanel()
self.database_operations = DatabaseOperations(self, app)
self.ui_helper = UIHelper(self)
self.WordlistPath = os.path.join(os.getcwd(), 'data', 'wordlist')
os.makedirs(self.WordlistPath, exist_ok=True)
self.initialize_generate_wordlist_window(app)
self.updateCheckboxes()
def initialize_generate_wordlist_window(generate_wordlist_window, app):
generate_wordlist_window.setWindowTitle('Logline Leviathan - Wordlist-Generator')
generate_wordlist_window.mainLayout = QVBoxLayout(generate_wordlist_window)
#generate_wordlist_window.extendedLayout = QHBoxLayout(generate_wordlist_window)
generate_wordlist_window.db_session = None
stylesheet = """
/* Style for the main window */
QWidget {
background-color: #282C34; /* Dark grey background */
color: white; /* White text */
}
/* Style for buttons */
QPushButton {
background-color: #4B5563; /* Dark grey background */
color: white; /* White text */
border-style: outset;
border-width: 2px;
border-radius: 1px; /* Rounded corners */
border-color: #4A4A4A;
padding: 6px;
min-width: 50px;
min-height: 15px;
}
QPushButton:hover {
background-color: #6E6E6E; /* Slightly lighter grey on hover */
}
QPushButton:pressed {
background-color: #484848; /* Even darker grey when pressed */
}
"""
highlited_button_style = """
QPushButton {
background-color: #3C8CCE; /* Lighter blue background */
color: white; /* White text */
border-style: outset;
border-width: 2px;
border-radius: 1px; /* Rounded corners */
border-color: #4A4A4A;
padding: 6px;
min-width: 50px;
min-height: 15px;
}
QPushButton:hover {
background-color: #7EC0EE; /* Even lighter blue on hover */
}
QPushButton:pressed {
background-color: #4A86E8; /* Slightly darker blue when pressed */
}
"""
generate_wordlist_window.setStyleSheet(stylesheet)
generate_wordlist_window.statusLabel = QLabel(' Erwarte Selektion der Entitätentypen', generate_wordlist_window)
generate_wordlist_window.statusLabel.setWordWrap(True)
generate_wordlist_window.statusLabel.setMinimumHeight(40)
generate_wordlist_window.statusLabel.setStyleSheet("QLabel { background-color: #3C4043; color: white; }")
generate_wordlist_window.mainLayout.addWidget(generate_wordlist_window.statusLabel)
# Create a GroupBox for the CheckboxPanel
exportOptionsGroupBox = QGroupBox("SELEKTION", generate_wordlist_window)
exportOptionsLayout = QVBoxLayout(exportOptionsGroupBox)
generate_wordlist_window.checkboxPanel = CheckboxPanel()
# Create a horizontal layout
filterLayout = QHBoxLayout()
# Create the "Check All" button
checkAllButton = QPushButton("Alle markieren", generate_wordlist_window)
checkAllButton.clicked.connect(lambda: generate_wordlist_window.checkboxPanel.checkAllVisible())
# Create the "Uncheck All" button
uncheckAllButton = QPushButton("Keine markieren", generate_wordlist_window)
uncheckAllButton.clicked.connect(lambda: generate_wordlist_window.checkboxPanel.uncheckAllVisible())
expandAllButton = QPushButton("Expandieren", generate_wordlist_window)
expandAllButton.clicked.connect(lambda: generate_wordlist_window.checkboxPanel.expandAllTreeItems())
collapseAllButton = QPushButton("Komprimieren", generate_wordlist_window)
collapseAllButton.clicked.connect(lambda: generate_wordlist_window.checkboxPanel.collapseAllTreeItems())
# Add buttons to the filter layout, to the left of the filter label
filterLayout.addWidget(checkAllButton)
filterLayout.addWidget(uncheckAllButton)
filterLayout.addWidget(expandAllButton)
filterLayout.addWidget(collapseAllButton)
# Create the label for the filter
filterLabel = QLabel("Filtern:")
filterLayout.addWidget(filterLabel) # Add label to the horizontal layout
# Add Text Input for Filtering
filterLineEdit = QLineEdit(generate_wordlist_window)
filterLineEdit.setPlaceholderText(" nach Typ, Tooltip oder Kurzbezeichnung filtern...")
filterLineEdit.setStyleSheet("""
QLineEdit {
background-color: #3C4043; /* Background color */
color: white; /* Text color */
min-height: 20px;
}
""")
filterLayout.addWidget(filterLineEdit) # Add line edit to the horizontal layout
exportOptionsLayout.addLayout(filterLayout) # Add the horizontal layout to the export options layout
# Add CheckboxPanel to the GroupBox's Layout
exportOptionsLayout.addWidget(generate_wordlist_window.checkboxPanel)
# Connect the textChanged signal of QLineEdit to a new method
filterLineEdit.textChanged.connect(generate_wordlist_window.checkboxPanel.filterCheckboxes)
generate_wordlist_window.mainLayout.addWidget(exportOptionsGroupBox)
copyWordlistToParserDirButton = QPushButton('Soeben generierte Wordlist zur Analyse hinzufügen (kopiert erzeugte Datei)', generate_wordlist_window)
copyWordlistToParserDirButton.clicked.connect(generate_wordlist_window.copyWordlistToParserDir)
generate_wordlist_window.mainLayout.addWidget(copyWordlistToParserDirButton)
purgeWordlistEntriesButton = QPushButton('Alte Wordlist-Eintraege aus Datenbank entfernen (empfohlen, sofern neue Wordlist generiert)', generate_wordlist_window)
purgeWordlistEntriesButton.clicked.connect(generate_wordlist_window.purgeWordlistEntries)
generate_wordlist_window.mainLayout.addWidget(purgeWordlistEntriesButton)
twoWordlistButtonsLayout = QHBoxLayout()
openActiveWordlistButton = QPushButton('Bestehende Wordlist oeffnen', generate_wordlist_window)
openActiveWordlistButton.clicked.connect(generate_wordlist_window.openActiveWordlist)
twoWordlistButtonsLayout.addWidget(openActiveWordlistButton)
deleteActiveWordlistButton = QPushButton('Bestehende Wordlist entfernen', generate_wordlist_window)
deleteActiveWordlistButton.clicked.connect(generate_wordlist_window.deleteActiveWordlist)
twoWordlistButtonsLayout.addWidget(deleteActiveWordlistButton)
generate_wordlist_window.mainLayout.addLayout(twoWordlistButtonsLayout)
# Exit Button Layout
bottomLayout = QGridLayout()
generate_wordlist_window.openWordlistPathButton = QPushButton('Wordlist-Dateipfad...', generate_wordlist_window)
generate_wordlist_window.openWordlistPathButton.clicked.connect(generate_wordlist_window.openWordlistPath)
bottomLayout.addWidget(generate_wordlist_window.openWordlistPathButton, 3, 1)
# Start Export Button
generate_wordlist_window.startExportButton = QPushButton('Wordlist erstellen', generate_wordlist_window)
generate_wordlist_window.startExportButton.clicked.connect(generate_wordlist_window.start_export_process)
generate_wordlist_window.startExportButton.setStyleSheet(highlited_button_style)
bottomLayout.addWidget(generate_wordlist_window.startExportButton, 3, 2)
# Output File Directory
generate_wordlist_window.selectOutputFileButton = QPushButton('Wordlist-Ausgabepfad setzen...', generate_wordlist_window)
generate_wordlist_window.selectOutputFileButton.clicked.connect(generate_wordlist_window.selectOutputFile)
bottomLayout.addWidget(generate_wordlist_window.selectOutputFileButton, 4, 1)
# Exit Button
generate_wordlist_window.exitButton = QPushButton('Schließen', generate_wordlist_window)
generate_wordlist_window.exitButton.clicked.connect(generate_wordlist_window.close)
bottomLayout.addWidget(generate_wordlist_window.exitButton, 4, 2)
generate_wordlist_window.crossmatchesCheckbox = QCheckBox('Nur Kreuztreffer (Entitäten, die in mehreren Dateien vorkommen)', generate_wordlist_window)
bottomLayout.addWidget(generate_wordlist_window.crossmatchesCheckbox, 0, 1)
generate_wordlist_window.timestampFilterCheckbox = QCheckBox('Nach Zeitstempel filtern:', generate_wordlist_window)
generate_wordlist_window.startDateEdit = QDateTimeEdit(generate_wordlist_window)
generate_wordlist_window.startDateEdit.setCalendarPopup(True)
generate_wordlist_window.startDateEdit.setDate(QDate.currentDate())
generate_wordlist_window.endDateEdit = QDateTimeEdit(generate_wordlist_window)
generate_wordlist_window.endDateEdit.setCalendarPopup(True)
generate_wordlist_window.endDateEdit.setDate(QDate.currentDate())
generate_wordlist_window.timestampFilterQHBoxLayout = QHBoxLayout()
generate_wordlist_window.timestampFilterQHBoxLayout.addWidget(generate_wordlist_window.timestampFilterCheckbox)
generate_wordlist_window.timestampFilterQHBoxLayout.addWidget(generate_wordlist_window.startDateEdit)
generate_wordlist_window.timestampFilterQHBoxLayout.addWidget(generate_wordlist_window.endDateEdit)
bottomLayout.addLayout(generate_wordlist_window.timestampFilterQHBoxLayout, 1, 1)
generate_wordlist_window.flaggedEntriesLayout = QHBoxLayout()
generate_wordlist_window.flaggedEntriesCheckbox = QCheckBox('Markierte Einträge berücksichtigen', generate_wordlist_window)
generate_wordlist_window.flaggedEntriesLayout.addWidget(generate_wordlist_window.flaggedEntriesCheckbox)
generate_wordlist_window.flaggedRadioButtonLayout = QHBoxLayout()
generate_wordlist_window.flaggedRadioButton = QRadioButton('Nur markierte Einträge')
generate_wordlist_window.notflaggedRadioButton = QRadioButton('Nur nicht markierte Einträge')
generate_wordlist_window.flaggedRadioButtonLayout.addWidget(generate_wordlist_window.flaggedRadioButton)
generate_wordlist_window.flaggedRadioButtonLayout.addWidget(generate_wordlist_window.notflaggedRadioButton)
generate_wordlist_window.flaggedRadioButton.setChecked(True)
generate_wordlist_window.flaggedEntriesLayout.addLayout(generate_wordlist_window.flaggedRadioButtonLayout)
bottomLayout.addLayout(generate_wordlist_window.flaggedEntriesLayout, 2, 1)
# Output File Path Label
generate_wordlist_window.WordlistPathLabel = QLabel('', generate_wordlist_window)
generate_wordlist_window.updateWordlistPathLabel() # Call this method to set the initial text
bottomLayout.addWidget(generate_wordlist_window.WordlistPathLabel, 0, 2)
generate_wordlist_window.mainLayout.addLayout(bottomLayout)
generate_wordlist_window.setLayout(generate_wordlist_window.mainLayout)
def updateCheckboxes(self):
with session_scope() as session:
self.checkboxPanel.updateCheckboxes(session)
def getSelectedCheckboxes(self):
selected_checkboxes = []
def traverseTreeItems(treeItem):
if treeItem.checkState(0) == Qt.Checked:
selected_checkboxes.append(treeItem)
for i in range(treeItem.childCount()):
traverseTreeItems(treeItem.child(i))
for i in range(self.checkboxPanel.treeWidget.topLevelItemCount()):
traverseTreeItems(self.checkboxPanel.treeWidget.topLevelItem(i))
return selected_checkboxes
def updateWordlistPathLabel(self):
outputDirPath = os.path.dirname(self.WordlistPath)
display_text = f'{outputDirPath}/'
self.WordlistPathLabel.setText(display_text)
def openWordlistPath(self):
outputDirPath = os.path.dirname(self.WordlistPath)
wordlistPath = os.path.join(outputDirPath, 'wordlist')
self.ui_helper.openFile(wordlistPath)
def selectOutputFile(self):
options = QFileDialog.Options()
output_format = self.outputFormatList.currentItem().text().lower()
extension_map = {'html': '.html', 'xlsx': '.xlsx'}
default_extension = extension_map.get(output_format, '')
selected_file, _ = QFileDialog.getSaveFileName(
self,
"Selektieren des Ziel-Dateipfads",
self.WordlistPath,
f"{output_format.upper()} Files (*{default_extension});;All Files (*)",
options=options
)
if selected_file:
if not selected_file.endswith(default_extension):
selected_file += default_extension
self.WordlistPath = selected_file
self.outputDir = os.path.dirname(selected_file)
self.updateWordlistPathLabel()
def get_unique_filename(self, base_path):
directory, filename = os.path.split(base_path)
name, extension = os.path.splitext(filename)
counter = 1
new_path = base_path
while os.path.exists(new_path):
new_filename = f"{name}_{counter}{extension}"
new_path = os.path.join(directory, new_filename)
counter += 1
return new_path
def copyWordlistToParserDir(self):
try:
# Path to the parser directory
parser_dir = os.path.join(os.getcwd(), 'data', 'parser')
# Ensure the parser directory exists
os.makedirs(parser_dir, exist_ok=True)
# Find the newest .txt file in the WordlistPath directory
list_of_files = glob.glob(os.path.join(self.WordlistPath, '*.txt'))
if not list_of_files:
raise FileNotFoundError("No .txt files found in the WordlistPath directory.")
newest_file = max(list_of_files, key=os.path.getctime)
# Destination file path
destination_file = os.path.join(parser_dir, 'generated_wordlist.txt')
# Copy and overwrite the newest file to the destination
shutil.copy2(newest_file, destination_file)
self.statusLabel.setText(f" Wordlist erfolgreich kopiert nach {destination_file}")
except Exception as e:
self.message("Fehler beim kopieren", f"Fehler beim kopieren: {str(e)}")
def openActiveWordlist(self):
try:
parser_dir = os.path.join(os.getcwd(), 'data', 'parser')
wordlist_file = os.path.join(parser_dir, 'generated_wordlist.txt')
if os.path.exists(wordlist_file):
self.ui_helper.openFile(wordlist_file)
else:
raise FileNotFoundError("Wordlist file not found.")
except Exception as e:
self.message("Fehler beim Öffnen", f"Fehler beim Öffnen: {str(e)}")
def deleteActiveWordlist(self):
try:
parser_dir = os.path.join(os.getcwd(), 'data', 'parser')
wordlist_file = os.path.join(parser_dir, 'generated_wordlist.txt')
if os.path.exists(wordlist_file):
os.remove(wordlist_file)
self.statusLabel.setText(" Wordlist erfolgreich gelöscht.")
else:
raise FileNotFoundError("Wordlist file not found.")
except Exception as e:
self.message("Fehler beim Löschen", f"Fehler beim Löschen: {str(e)}")
def start_export_process(self):
# Base filename for the wordlist file
base_filename = "wordlist.txt"
# Construct the full path with the base filename
full_output_path = os.path.join(self.WordlistPath, base_filename)
# Generate a unique filename to avoid overwriting existing files
unique_output_path = self.get_unique_filename(full_output_path)
# Retrieve dates from QDateEdit widgets
start_date = self.startDateEdit.date().toPyDate() if self.timestampFilterCheckbox.isChecked() else None
end_date = self.endDateEdit.date().toPyDate() if self.timestampFilterCheckbox.isChecked() else None
include_flagged = self.flaggedEntriesCheckbox.isChecked()
only_flagged = self.flaggedRadioButton.isChecked()
only_unflagged = self.notflaggedRadioButton.isChecked()
try:
with session_scope() as session:
selected_checkboxes = self.getSelectedCheckboxes()
if not selected_checkboxes:
self.message("Generieren nicht möglich", "Keine Typen selektiert. Auswahl vornehmen.")
return
only_crossmatches = self.crossmatchesCheckbox.isChecked()
# Call the generate_wordlist function with timestamp parameters
generate_wordlist(unique_output_path, session, selected_checkboxes, only_crossmatches, start_date, end_date, include_flagged, only_flagged, only_unflagged)
self.statusLabel.setText(f" Generierte Liste gespeichert unter {unique_output_path}")
except Exception as e:
self.statusLabel.setText(f" Fehler beim speichern: {str(e)}")
logging.error(f"Export Error: {str(e)}")
def purgeWordlistEntries(self):
self.database_operations.purgeWordlistEntries()
self.updateCheckboxes()
def message(self, title, text, extra_widget=None):
msgBox = QMessageBox()
msgBox.setStyleSheet("""
QMessageBox {
background-color: #282C34; /* Dark grey background */
}
QLabel {
color: white; /* White text */
}
QPushButton {
color: white; /* White text for buttons */
background-color: #4B5563; /* Dark grey background for buttons */
border-style: solid;
border-width: 2px;
border-radius: 5px;
border-color: #4A4A4A;
padding: 6px;
min-width: 80px;
min-height: 30px;
}
""")
msgBox.setIcon(QMessageBox.Warning)
msgBox.setWindowTitle(title)
msgBox.setText(text)
if extra_widget:
msgBox.setInformativeText('')
msgBox.layout().addWidget(extra_widget, 1, 1)
msgBox.exec_()

View File

@@ -0,0 +1,306 @@
import os
from PyQt5.QtWidgets import (QGridLayout, QPushButton, QLabel, QHBoxLayout, QApplication,
QVBoxLayout, QProgressBar, QGroupBox)
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
import logline_leviathan.gui.versionvars as versionvars
from logline_leviathan.gui.query_window import QueryLineEdit
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
def initialize_main_window(main_window, app):
main_window.setWindowTitle('Logline Leviathan')
main_window.mainLayout = QVBoxLayout(main_window)
#main_window.extendedLayout = QHBoxLayout(main_window)
main_window.db_session = None
# Logo
pixmap = QPixmap(os.path.join('logline_leviathan', 'gui', 'logo.png'))
scaled_pixmap = pixmap.scaled(400, 400, Qt.KeepAspectRatio, Qt.SmoothTransformation)
logoLabel = QLabel(main_window)
logoLabel.setPixmap(scaled_pixmap)
# Version label
versionLabel = QLabel(versionvars.version_string, main_window) # Replace X.X.X with your actual version number
versionLabel.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
# Horizontal layout
hbox = QHBoxLayout()
hbox.addWidget(versionLabel) # Add version label to the left
hbox.addStretch() # Add stretchable space between the version label and logo
hbox.addWidget(logoLabel, alignment=Qt.AlignRight) # Add logo label to the right
# Add horizontal layout to the main layout
main_window.mainLayout.addLayout(hbox)
stylesheet = """
/* Style for the main window */
QWidget {
background-color: #282C34; /* Dark grey background */
color: white; /* White text */
}
/* Style for buttons */
QPushButton {
background-color: #4B5563; /* Dark grey background */
color: white; /* White text */
border-style: outset;
border-width: 2px;
border-radius: 1px; /* Rounded corners */
border-color: #4A4A4A;
padding: 6px;
min-width: 60px;
min-height: 15px;
}
QPushButton:hover {
background-color: #6E6E6E; /* Slightly lighter grey on hover */
}
QPushButton:pressed {
background-color: #484848; /* Even darker grey when pressed */
}
"""
highlited_button_style = """
QPushButton {
background-color: #3C8CCE; /* Lighter blue background */
color: white; /* White text */
border-style: outset;
border-width: 2px;
border-radius: 1px; /* Rounded corners */
border-color: #4A4A4A;
padding: 6px;
min-width: 50px;
min-height: 15px;
}
QPushButton:hover {
background-color: #7EC0EE; /* Even lighter blue on hover */
}
QPushButton:pressed {
background-color: #4A86E8; /* Slightly darker blue when pressed */
}
"""
main_window.setStyleSheet(stylesheet)
# Data Ingestion Settings Label
main_window.dataIngestionLabel = QLabel(' Willkommen beim LoglineLeviathan - Analyse/Export-Modul.\n Der Quick-Start-Button ermöglicht eine schnelle Selektion der zu analysierenden Daten.\n Nach Abschluss der Selektion über den Abbrechen-Button startet die Analyse sofort.')
main_window.dataIngestionLabel.setWordWrap(True)
main_window.dataIngestionLabel.setMinimumHeight(60)
main_window.dataIngestionLabel.setStyleSheet("QLabel { background-color: #3C4043; color: white; }")
# Quick Start Button
quickStartButton = QPushButton('Quick Start', main_window)
quickStartButton.setStyleSheet(highlited_button_style)
quickStartButton.setFixedSize(270, 55)
quickStartButton.clicked.connect(main_window.quickStartWorkflow)
# Horizontal layout for label and button
hBoxLayout = QHBoxLayout()
hBoxLayout.addWidget(quickStartButton)
hBoxLayout.addWidget(main_window.dataIngestionLabel)
# Add horizontal layout to the main layout
main_window.mainLayout.addLayout(hBoxLayout)
# Grid Layout for Top Buttons
topButtonGridLayout = QGridLayout()
# Create Buttons
main_window.openButton = QPushButton('Einzelne Dateien selektieren...', main_window)
main_window.openButton.clicked.connect(main_window.openFileNameDialog)
main_window.addDirButton = QPushButton('Pfad zur rekursiven Analyse selektieren...', main_window)
main_window.addDirButton.clicked.connect(main_window.openDirNameDialog)
main_window.openFileSettingsButton = QPushButton('Selektierte Dateien...', main_window)
main_window.openFileSettingsButton.clicked.connect(lambda: main_window.openFileSettingsWindow())
main_window.createDbButton = QPushButton('Lokale Datenbank neu erstellen', main_window)
main_window.createDbButton.clicked.connect(main_window.purgeDatabase)
main_window.importDbButton = QPushButton('Existierende Datenbank importieren...', main_window)
main_window.importDbButton.clicked.connect(main_window.importDatabase)
main_window.exportDBButton = QPushButton('Lokale Datenbank speichern/exportieren...', main_window)
main_window.exportDBButton.clicked.connect(main_window.exportDatabase)
main_window.openAnalysisSettingsButton = QPushButton('Analyse-Einstellungen...', main_window)
main_window.openAnalysisSettingsButton.clicked.connect(lambda: main_window.openAnalysisSettingsWindow())
main_window.processButton = QPushButton('Verarbeitung beginnen', main_window)
main_window.processButton.setStyleSheet(highlited_button_style)
main_window.processButton.clicked.connect(main_window.processFiles)
main_window.abortAnalysisButton = QPushButton('Verarbeitung abbrechen', main_window)
main_window.abortAnalysisButton.clicked.connect(main_window.abortAnalysis)
# Create GroupBoxes
fileSelectionGroup = QGroupBox("Datenselektion")
databaseGroup = QGroupBox("Datenbank - Management")
analysisGroup = QGroupBox("Analyse - Management")
# Create Layouts for each GroupBox
fileSelectionLayout = QVBoxLayout()
databaseLayout = QVBoxLayout()
analysisLayout = QVBoxLayout()
# Add Buttons to their respective Layout
fileSelectionLayout.addWidget(main_window.openButton)
fileSelectionLayout.addWidget(main_window.addDirButton)
fileSelectionLayout.addWidget(main_window.openFileSettingsButton)
databaseLayout.addWidget(main_window.createDbButton)
databaseLayout.addWidget(main_window.importDbButton)
databaseLayout.addWidget(main_window.exportDBButton)
analysisLayout.addWidget(main_window.openAnalysisSettingsButton)
analysisLayout.addWidget(main_window.processButton)
analysisLayout.addWidget(main_window.abortAnalysisButton)
# Set Layouts to GroupBoxes
fileSelectionGroup.setLayout(fileSelectionLayout)
databaseGroup.setLayout(databaseLayout)
analysisGroup.setLayout(analysisLayout)
# Add GroupBoxes to Grid
topButtonGridLayout.addWidget(fileSelectionGroup, 0, 0)
topButtonGridLayout.addWidget(databaseGroup, 0, 1)
topButtonGridLayout.addWidget(analysisGroup, 0, 2)
# Set uniform spacing
topButtonGridLayout.setHorizontalSpacing(20)
topButtonGridLayout.setVerticalSpacing(10)
# Add the Grid Layout to the Main Layout
main_window.mainLayout.addLayout(topButtonGridLayout)
# Progress Bar, Status Label, Entity Rate Label, File Count Label
main_window.progressBar = QProgressBar(main_window)
main_window.mainLayout.addWidget(main_window.progressBar)
main_window.statusLabel = QLabel(' Bereit // Analyse starten oder Export generieren', main_window)
main_window.statusLabel.setWordWrap(True)
main_window.statusLabel.setMinimumHeight(40)
main_window.statusLabel.setStyleSheet("QLabel { background-color: #3C4043; color: white; }")
main_window.mainLayout.addWidget(main_window.statusLabel)
main_window.entityRateLabel = QLabel(' Bereit // Analyse starten oder Export generieren', main_window)
main_window.mainLayout.addWidget(main_window.entityRateLabel)
main_window.fileCountLabel = QLabel(' Keine Dateien selektiert', main_window)
main_window.fileCountLabel.setMinimumHeight(40)
main_window.fileCountLabel.setStyleSheet("QLabel { background-color: #3C4043; color: white; }")
main_window.mainLayout.addWidget(main_window.fileCountLabel)
# Create the new QGroupBox for Database Query
databaseQueryGroupBox = QGroupBox("Datensatz durchsuchen", main_window)
databaseQueryLayout = QVBoxLayout(databaseQueryGroupBox)
databaseQueryLayout.setAlignment(Qt.AlignTop)
# Create QLineEdit for text input
databaseQueryLineEdit = QueryLineEdit(main_window)
databaseQueryLineEdit.setPlaceholderText(" Suchbegriff...")
databaseQueryLineEdit.setStyleSheet("""
QLineEdit {
background-color: #3C4043;
color: white;
min-height: 20px;
}
""")
databaseQueryLineEdit.returnPressed.connect(lambda: main_window.execute_query_wrapper(databaseQueryLineEdit.text()))
databaseQueryLabel = QLabel("\nIntelligentes Durchsuchen der Datenbank nach jeglichem Suchbegriff. Die Nutzung von Suchoperatoren +, - und '' ist möglich. Als Suchbegriffe können jegliche Entitäten, aber auch Dateinamen oder Sätze im Kontext, sowie Entitätentyp-Kurzbezeichnungen (s. rechts) verwendet werden.\n\n", main_window)
databaseQueryLabel.setWordWrap(True)
# Create QPushButton for executing the query
executeQueryButton = QPushButton("Ausführen", main_window)
executeQueryButton.clicked.connect(lambda: main_window.execute_query_wrapper(databaseQueryLineEdit.text()))
main_window.databaseStatusLabel = QLabel(" Datenbank noch nicht initialisiert", main_window)
# Add QLineEdit and QPushButton to the QVBoxLayout
databaseQueryLayout.addWidget(databaseQueryLineEdit)
databaseQueryLayout.addWidget(databaseQueryLabel)
databaseQueryLayout.addWidget(executeQueryButton)
databaseQueryLayout.addWidget(main_window.databaseStatusLabel)
# Set the QVBoxLayout as the layout for the QGroupBox
databaseQueryGroupBox.setLayout(databaseQueryLayout)
databaseContentsGroupBox = QGroupBox("Datensatz", main_window)
databaseContentsLayout = QHBoxLayout(databaseContentsGroupBox)
databaseContentSwitchLayout = QVBoxLayout()
expandAllButton = QPushButton("Expandieren", main_window)
expandAllButton.clicked.connect(lambda: main_window.databaseTree.expandAllTreeItems())
collapseAllButton = QPushButton("Komprimieren", main_window)
collapseAllButton.clicked.connect(lambda: main_window.databaseTree.collapseAllTreeItems())
databaseContentSwitchLayout.addWidget(expandAllButton)
databaseContentSwitchLayout.addWidget(collapseAllButton)
databaseContentSwitchLayout.setAlignment(Qt.AlignTop)
databaseContentsLayout.addWidget(main_window.databaseTree)
databaseContentsLayout.addLayout(databaseContentSwitchLayout)
generationOptionsGroupBox = QGroupBox("Generator - Selektion", main_window)
generationOptionsLayout = QVBoxLayout(generationOptionsGroupBox)
generationOptionsLayout.setAlignment(Qt.AlignTop)
# Corrected button creation
openGenerateReportWindowButton = QPushButton("Report-Datei generieren", main_window)
openGenerateReportWindowButton.clicked.connect(main_window.openGenerateReportWindow)
openGenerateReportWindowButtonDescriptor = QLabel("REPORT-DATEI GENERIEREN:\nGeneriert eine Report-Datei, die einfach extern geteilt oder inspiziert werden kann. Im Auswahlfenster sind detaillierte Einstellungen verfügbar.\n", main_window)
openGenerateReportWindowButtonDescriptor.setWordWrap(True)
openGenerateWordlistButton = QPushButton("Wortliste generieren", main_window)
openGenerateWordlistButton.clicked.connect(main_window.openGenerateWordlistWindow)
openGenerateWordlistButtonDescriptor = QLabel("WORTLISTE GENERIEREN:\nGeneriert eine Wortliste, die entweder für die Analyse (auch für weitere Datensätze) genutzt werden, oder extern weiterverwendet werden kann. Im Auswahlfenster sind detaillierte Einstellungen verfügbar.\n\n\n", main_window)
openGenerateWordlistButtonDescriptor.setWordWrap(True)
generationOptionsLayout.addWidget(openGenerateReportWindowButton)
generationOptionsLayout.addWidget(openGenerateReportWindowButtonDescriptor)
generationOptionsLayout.addWidget(openGenerateWordlistButton)
generationOptionsLayout.addWidget(openGenerateWordlistButtonDescriptor)
# Create a new QGridLayout for arranging QGroupBoxes
groupBoxLayout = QGridLayout()
databaseQueryGroupBox.setFixedWidth(300)
databaseContentsGroupBox.setFixedWidth(500)
generationOptionsGroupBox.setFixedWidth(300)
# Add databaseQueryGroupBox to the grid layout
groupBoxLayout.addWidget(databaseQueryGroupBox, 0, 0)
groupBoxLayout.addWidget(databaseContentsGroupBox, 0, 1)
groupBoxLayout.addWidget(generationOptionsGroupBox, 0, 2)
# Link to GitHub Repo
main_window.githubLink = QLabel(f'<a href="{versionvars.repo_link}">{versionvars.repo_link_text}</a>', main_window)
main_window.githubLink.setOpenExternalLinks(True)
main_window.openLogDirButton = QPushButton('Log-Verzeichnis', main_window)
main_window.openLogDirButton.clicked.connect(main_window.openLogDir)
# Exit Button
main_window.exitButton = QPushButton('Beenden', main_window)
main_window.exitButton.clicked.connect(main_window.close)
groupBoxLayout.addWidget(main_window.githubLink, 1, 1)
groupBoxLayout.addWidget(main_window.openLogDirButton, 1, 0)
groupBoxLayout.addWidget(main_window.exitButton, 1, 2)
# Add this grid layout to the main layout of the main window
main_window.mainLayout.addLayout(groupBoxLayout)
main_window.update()

View File

@@ -0,0 +1,339 @@
from PyQt5.QtWidgets import QRadioButton, QDateTimeEdit, QVBoxLayout, QCheckBox, QHBoxLayout, QGroupBox, QPushButton, QLineEdit, QGridLayout, QLabel, QListWidget, QGridLayout
from PyQt5.QtCore import QDate
import logline_leviathan.gui.versionvars as versionvars
from logline_leviathan.gui.checkbox_panel import *
def initialize_generate_report_window(generate_report_window, app):
generate_report_window.setWindowTitle('Logline Leviathan - Report - Generator')
generate_report_window.mainLayout = QVBoxLayout(generate_report_window)
#generate_report_window.extendedLayout = QHBoxLayout(generate_report_window)
generate_report_window.db_session = None
stylesheet = """
/* Style for the main window */
QWidget {
background-color: #282C34; /* Dark grey background */
color: white; /* White text */
}
/* Style for buttons */
QPushButton {
background-color: #4B5563; /* Dark grey background */
color: white; /* White text */
border-style: outset;
border-width: 2px;
border-radius: 1px; /* Rounded corners */
border-color: #4A4A4A;
padding: 6px;
min-width: 50px;
min-height: 15px;
}
QPushButton:hover {
background-color: #6E6E6E; /* Slightly lighter grey on hover */
}
QPushButton:pressed {
background-color: #484848; /* Even darker grey when pressed */
}
"""
highlited_button_style = """
QPushButton {
background-color: #3C8CCE; /* Lighter blue background */
color: white; /* White text */
border-style: outset;
border-width: 2px;
border-radius: 1px; /* Rounded corners */
border-color: #4A4A4A;
padding: 6px;
min-width: 50px;
min-height: 15px;
}
QPushButton:hover {
background-color: #7EC0EE; /* Even lighter blue on hover */
}
QPushButton:pressed {
background-color: #4A86E8; /* Slightly darker blue when pressed */
}
"""
# Update function for output format selection label with custom text and line breaks
def update_output_format_label(current):
if current is not None:
format_text = current.text()
format_descriptions = {
'HTML': " HTML\n Generiert eine einzelne HTML-Datei, die einfach\n geteilt und mit jedem Browser geöffnet werden kann.\n Geeignet für Übersichtsanalyse, sofern der Datensatz nicht zu\n umfangreich ist.",
'Interactive HTML': " Interaktive HTML-Datei.\n Generiert eine einzelne HTML-Datei, die mit einem Webbrowser,\n der JavaScript unterstützt, angezeigt wird.\n Geeignet für umfangreichere Datensätze.",
'XLSX': " XLSX\n Exportiert Daten in eine Excel-Datei.\n Schreibe sämtliche Entitätentypen in\n separate Sheets, unterstützt keine visuelle Hervorhebung.\n Geeignet für weitere Analyse über MS-Excel."
}
generate_report_window.outputFormatSelectionLabel.setText(format_descriptions.get(format_text, ""))
def update_export_context_label(current):
if current is not None:
context_text = current.text() # Get the text of the current item
context_descriptions = {
"Kontext - gleiche Zeile": " Kontext - gleiche Zeile\n Eine Zeile pro Fundstelle einer Entität.\n Der Kontext innerhalb der gleichen Zeile\n wird dargestellt.\n",
"Kontext - mittelgroß": " Kontext - mittelgroß\n Eine Zeile pro Fundstelle einer Entität.\n Der Kontext von +/- 8 Zeilen wird dargestellt.\n",
"Kontext - umfangreich": " Kontext - umfangreich\n Eine Zeile pro Fundstelle einer Entität.\n Der Kontext von +/- 15 Zeilen wird dargestellt.\n",
"Kompakte Zusammenfassung ohne Kontext": " Kompakte Zusammenfassung ohne Kontext\n Listet Entitäten untereinander auf.\n Die jeweiligen Fundstellen werden komprimiert\n dargestellt.\n Kontext wird nicht unterstützt."
}
generate_report_window.exportContextSelectionLabel.setText(context_descriptions.get(context_text, ""))
generate_report_window.setStyleSheet(stylesheet)
generate_report_window.statusLabel = QLabel(' Erwarte Selektion von Entitätentypen, die im Export dargestellt werden.', generate_report_window)
generate_report_window.statusLabel.setWordWrap(True)
generate_report_window.statusLabel.setMinimumHeight(40)
generate_report_window.statusLabel.setStyleSheet("QLabel { background-color: #3C4043; color: white; }")
generate_report_window.mainLayout.addWidget(generate_report_window.statusLabel)
# Create a GroupBox for the CheckboxPanel
exportOptionsGroupBox = QGroupBox("Typen - Selektion", generate_report_window)
exportOptionsLayout = QVBoxLayout(exportOptionsGroupBox)
generate_report_window.checkboxPanel = CheckboxPanel()
# Checkbox Panel Filter Layout
checkboxFilterLayout = QHBoxLayout()
# Create the "Check All" button
checkAllButton = QPushButton("Alle markieren", generate_report_window)
checkAllButton.clicked.connect(lambda: generate_report_window.checkboxPanel.checkAllVisible())
# Create the "Uncheck All" button
uncheckAllButton = QPushButton("Keine markieren", generate_report_window)
uncheckAllButton.clicked.connect(lambda: generate_report_window.checkboxPanel.uncheckAllVisible())
expandAllButton = QPushButton("Expandieren", generate_report_window)
expandAllButton.clicked.connect(lambda: generate_report_window.checkboxPanel.expandAllTreeItems())
collapseAllButton = QPushButton("Komprimieren", generate_report_window)
collapseAllButton.clicked.connect(lambda: generate_report_window.checkboxPanel.collapseAllTreeItems())
checkboxFilterLayout.addWidget(checkAllButton)
checkboxFilterLayout.addWidget(uncheckAllButton)
checkboxFilterLayout.addWidget(expandAllButton)
checkboxFilterLayout.addWidget(collapseAllButton)
checkboxFilterLabel = QLabel("Filtern:")
checkboxFilterLayout.addWidget(checkboxFilterLabel)
checkboxFilterLineEdit = QLineEdit(generate_report_window)
checkboxFilterLineEdit.setPlaceholderText(" nach Typ, Tooltip oder Kurzbezeichnung filtern...")
checkboxFilterLineEdit.setStyleSheet("""
QLineEdit {
background-color: #3C4043;
color: white;
min-height: 20px;
}
""")
checkboxFilterLayout.addWidget(checkboxFilterLineEdit)
# Connect the textChanged signal of QLineEdit to the filter method
checkboxFilterLineEdit.textChanged.connect(generate_report_window.checkboxPanel.filterCheckboxes)
exportOptionsLayout.addLayout(checkboxFilterLayout)
exportOptionsLayout.addWidget(generate_report_window.checkboxPanel)
# Create a GroupBox for the FileCheckboxPanel
fileSelectionGroupBox = QGroupBox("Dateien - Selektion", generate_report_window)
fileExportOptionsLayout = QVBoxLayout(fileSelectionGroupBox)
generate_report_window.fileCheckboxPanel = FileCheckboxPanel()
# File Checkbox Panel Filter Layout
fileCheckboxFilterLayout = QHBoxLayout()
# Create the "Check All" button
filCheckAllButton = QPushButton("Alle markieren", generate_report_window)
filCheckAllButton.clicked.connect(lambda: generate_report_window.fileCheckboxPanel.checkAllVisible())
# Create the "Uncheck All" button
fileUncheckAllButton = QPushButton("Keine markieren", generate_report_window)
fileUncheckAllButton.clicked.connect(lambda: generate_report_window.fileCheckboxPanel.uncheckAllVisible())
fileCheckboxFilterLayout.addWidget(filCheckAllButton)
fileCheckboxFilterLayout.addWidget(fileUncheckAllButton)
fileFilterLabel = QLabel("Filtern:")
fileCheckboxFilterLayout.addWidget(fileFilterLabel)
fileFilterLineEdit = QLineEdit(generate_report_window)
fileFilterLineEdit.setPlaceholderText(" nach Dateiname filtern...")
fileFilterLineEdit.setStyleSheet("""
QLineEdit {
background-color: #3C4043;
color: white;
min-height: 20px;
}
""")
fileCheckboxFilterLayout.addWidget(fileFilterLineEdit)
# Connect the textChanged signal of QLineEdit to the filter method
fileFilterLineEdit.textChanged.connect(generate_report_window.fileCheckboxPanel.filterCheckboxes)
fileExportOptionsLayout.addLayout(fileCheckboxFilterLayout)
fileExportOptionsLayout.addWidget(generate_report_window.fileCheckboxPanel)
# First Horizontal Layout for Database Query and Export Options
topHBoxLayout = QHBoxLayout()
topHBoxLayout.addWidget(exportOptionsGroupBox)
topHBoxLayout.addWidget(fileSelectionGroupBox)
generate_report_window.mainLayout.addLayout(topHBoxLayout)
# Export Settings as a Grid Layout
exportCustomizationLayout = QGridLayout()
item_height = 20
visible_items = 3
# Set a fixed width for both QListWidgets (adjust the width as needed)
outputFormatGroupBox = QGroupBox("Ausgabeformat - Selektion", generate_report_window)
outputFormatGroupBox.setFixedHeight(200)
outputFormatLayout = QVBoxLayout(outputFormatGroupBox)
generate_report_window.outputFormatList = QListWidget()
generate_report_window.outputFormatList.addItems(['HTML', 'Interactive HTML', 'XLSX'])
generate_report_window.outputFormatList.setCurrentRow(0)
generate_report_window.outputFormatList.setFixedHeight(item_height * visible_items)
outputFormatLayout.addWidget(generate_report_window.outputFormatList)
# Label to display current selection of output format
generate_report_window.outputFormatSelectionLabel = QLabel('')
generate_report_window.outputFormatSelectionLabel.setStyleSheet("QLabel { background-color: #3C4043; color: white; }")
generate_report_window.outputFormatSelectionLabel.setWordWrap(True)
generate_report_window.outputFormatSelectionLabel.setFixedHeight(80)
outputFormatLayout.addWidget(generate_report_window.outputFormatSelectionLabel)
exportCustomizationLayout.addWidget(outputFormatGroupBox, 0, 0)
# Export Context Group Box
exportContextGroupBox = QGroupBox("Ausgabedatei - Aufbau", generate_report_window)
exportContextGroupBox.setFixedHeight(200)
exportContextLayout = QVBoxLayout(exportContextGroupBox)
generate_report_window.exportContextList = QListWidget()
generate_report_window.exportContextList.addItems(['Kontext - gleiche Zeile', 'Kontext - mittelgroß', 'Kontext - umfangreich', 'Kompakte Zusammenfassung ohne Kontext'])
generate_report_window.exportContextList.setCurrentRow(0)
generate_report_window.exportContextList.setFixedHeight(item_height * visible_items)
exportContextLayout.addWidget(generate_report_window.exportContextList)
# Label to display current selection of export context
generate_report_window.exportContextSelectionLabel = QLabel('')
generate_report_window.exportContextSelectionLabel.setStyleSheet("QLabel { background-color: #3C4043; color: white; }")
generate_report_window.exportContextSelectionLabel.setWordWrap(True)
generate_report_window.exportContextSelectionLabel.setFixedHeight(80)
exportContextLayout.addWidget(generate_report_window.exportContextSelectionLabel)
exportCustomizationLayout.addWidget(exportContextGroupBox, 0, 1)
# Connect signals to the update functions
generate_report_window.outputFormatList.currentItemChanged.connect(update_output_format_label)
generate_report_window.exportContextList.currentItemChanged.connect(update_export_context_label)
# Initially update the labels
update_output_format_label(generate_report_window.outputFormatList.currentItem())
update_export_context_label(generate_report_window.exportContextList.currentItem())
# Initially update the label
update_output_format_label(generate_report_window.outputFormatList.currentItem())
# Initially update the label
update_export_context_label(generate_report_window.exportContextList.currentItem())
#exportLayout.addLayout(exportCustomizationLayout)
generate_report_window.mainLayout.addLayout(exportCustomizationLayout)
exportSettingsLayout = QGridLayout()
# Add a checkbox for Crossmatches
generate_report_window.crossmatchesCheckbox = QCheckBox('Nur Kreuztreffer (Gibt Entitäten an, die in mehreren Dateien gefunden wurden)', generate_report_window)
exportSettingsLayout.addWidget(generate_report_window.crossmatchesCheckbox, 0, 0)
generate_report_window.timestampFilterCheckbox = QCheckBox('Nach Zeitstempel filtern:', generate_report_window)
generate_report_window.startDateEdit = QDateTimeEdit(generate_report_window)
generate_report_window.startDateEdit.setCalendarPopup(True)
generate_report_window.startDateEdit.setDate(QDate.currentDate())
generate_report_window.endDateEdit = QDateTimeEdit(generate_report_window)
generate_report_window.endDateEdit.setCalendarPopup(True)
generate_report_window.endDateEdit.setDate(QDate.currentDate())
generate_report_window.timestampFilterQHBoxLayout = QHBoxLayout()
generate_report_window.timestampFilterQHBoxLayout.addWidget(generate_report_window.timestampFilterCheckbox)
generate_report_window.timestampFilterQHBoxLayout.addWidget(generate_report_window.startDateEdit)
generate_report_window.timestampFilterQHBoxLayout.addWidget(generate_report_window.endDateEdit)
exportSettingsLayout.addLayout(generate_report_window.timestampFilterQHBoxLayout, 1, 0)
generate_report_window.flaggedEntriesLayout = QHBoxLayout()
generate_report_window.flaggedEntriesCheckbox = QCheckBox('Markierte Einträge berücksichtigen', generate_report_window)
generate_report_window.flaggedEntriesLayout.addWidget(generate_report_window.flaggedEntriesCheckbox)
generate_report_window.flaggedRadioButtonLayout = QHBoxLayout()
generate_report_window.flaggedRadioButton = QRadioButton('Nur markierte Einträge')
generate_report_window.notflaggedRadioButton = QRadioButton('Nur nicht markierte Einträge')
generate_report_window.flaggedRadioButtonLayout.addWidget(generate_report_window.flaggedRadioButton)
generate_report_window.flaggedRadioButtonLayout.addWidget(generate_report_window.notflaggedRadioButton)
generate_report_window.flaggedRadioButton.setChecked(True)
generate_report_window.flaggedEntriesLayout.addLayout(generate_report_window.flaggedRadioButtonLayout)
exportSettingsLayout.addLayout(generate_report_window.flaggedEntriesLayout, 2, 0)
generate_report_window.openAfterExportCheckbox = QCheckBox('Datei nach dem Export oeffnen', generate_report_window)
exportSettingsLayout.addWidget(generate_report_window.openAfterExportCheckbox, 3, 0)
# Output File Path Label
generate_report_window.outputFilePathLabel = QLabel('', generate_report_window)
generate_report_window.updateOutputFilePathLabel() # Call this method to set the initial text
exportSettingsLayout.addWidget(generate_report_window.outputFilePathLabel, 0, 1)
generate_report_window.setOutputFileNameLineEdit = QLineEdit(generate_report_window)
generate_report_window.setOutputFileNameLineEdit.setPlaceholderText(' Eigenen Dateinamen spezifizieren...')
exportSettingsLayout.addWidget(generate_report_window.setOutputFileNameLineEdit, 3, 1)
generate_report_window.mainLayout.addLayout(exportSettingsLayout)
# Exit Button Layout
bottomLayout = QGridLayout()
#generate_report_window.customizeResultsButton = QPushButton('Customize Results (WiP)', generate_report_window)
#generate_report_window.customizeResultsButton.setDisabled(True)
#generate_report_window.customizeResultsButton.clicked.connect(generate_report_window.openCustomizeResultsDialog)
#bottomLayout.addWidget(generate_report_window.customizeResultsButton, 0, 0)
generate_report_window.openOutputFilepathButton = QPushButton('Ausgabeverzeichnis öffnen...', generate_report_window)
generate_report_window.openOutputFilepathButton.clicked.connect(generate_report_window.openOutputFilepath)
bottomLayout.addWidget(generate_report_window.openOutputFilepathButton, 0, 1)
# Start Export Button
generate_report_window.startExportButton = QPushButton('Report generieren...', generate_report_window)
generate_report_window.startExportButton.clicked.connect(generate_report_window.start_export_process)
generate_report_window.startExportButton.setStyleSheet(highlited_button_style)
bottomLayout.addWidget(generate_report_window.startExportButton, 0, 2)
# Output File Directory
generate_report_window.selectOutputFileButton = QPushButton('Ausgabeverzeichnis setzen...', generate_report_window)
generate_report_window.selectOutputFileButton.clicked.connect(generate_report_window.selectOutputFile)
bottomLayout.addWidget(generate_report_window.selectOutputFileButton, 1, 1)
# Exit Button
generate_report_window.exitButton = QPushButton('Schließen', generate_report_window)
generate_report_window.exitButton.clicked.connect(generate_report_window.close)
bottomLayout.addWidget(generate_report_window.exitButton, 1, 2)
generate_report_window.mainLayout.addLayout(bottomLayout)
#Easteregg
#generate_report_window.extendedLayout.addLayout(generate_report_window.mainLayout)
#generate_report_window.terminalEasterEgg = TerminalEasterEgg(generate_report_window)
#generate_report_window.terminalEasterEgg.hide()
#logoLabel.clicked.connect(generate_report_window.terminalEasterEgg.show)
generate_report_window.setLayout(generate_report_window.mainLayout)

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -0,0 +1,372 @@
import sys
import os
import logging
import shutil
import multiprocessing
import logline_leviathan.gui.versionvars as versionvars
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QLabel
from PyQt5.QtCore import QTimer
from logline_leviathan.file_processor.file_processor_thread import FileProcessorThread
from logline_leviathan.database.database_manager import EntityTypesTable, EntitiesTable, session_scope
from logline_leviathan.database.database_utility import DatabaseUtility
from logline_leviathan.database.database_operations import DatabaseOperations
from logline_leviathan.gui.checkbox_panel import *
from logline_leviathan.gui.initui_mainwindow import initialize_main_window
from logline_leviathan.gui.generate_report import GenerateReportWindow
from logline_leviathan.gui.generate_wordlist import GenerateWordlistWindow
from logline_leviathan.gui.ui_helper import UIHelper, format_time
from logline_leviathan.gui.settings_gui import FileSettingsWindow, AnalysisSettingsWindow
from logline_leviathan.gui.query_window import ResultsWindow
from logline_leviathan.database.query import DatabaseGUIQuery
from sqlalchemy import func
from datetime import datetime
class MainWindow(QWidget):
def __init__(self, app, db_init_func, directory=""):
super().__init__()
logging_level = getattr(logging, versionvars.loglevel, None)
if isinstance(logging_level, int):
logging.basicConfig(level=logging_level)
else:
logging.warning(f"Invalid log level: {versionvars.loglevel}")
self.app = app
self.ui_helper = UIHelper(self)
self.db_init_func = db_init_func
db_init_func()
self.database_operations = DatabaseOperations(self, db_init_func)
self.current_db_path = 'entities.db' # Default database path
self.directory = directory
self.filePaths = []
self.log_dir = os.path.join(os.getcwd(), 'output', 'entities_export', 'log')
os.makedirs(self.log_dir, exist_ok=True)
self.external_db_path = None
self.processing_thread = None
self.generate_report_window = None
self.databaseTree = DatabasePanel()
self.db_query_instance = DatabaseGUIQuery()
self.results_window = ResultsWindow(self.db_query_instance, parent=self)
self.generate_wordlist_window = GenerateWordlistWindow(self.db_query_instance)
self.generate_report_window = GenerateReportWindow(self.app)
self.analysis_settings_window = AnalysisSettingsWindow(self)
self.analysis_settings_window.parsersUpdated.connect(self.refreshApplicationState)
self.file_selection_window = FileSettingsWindow(self.filePaths, self)
self.database_operations.ensureDatabaseExists()
self.initUI()
self.ui_helper = UIHelper(self)
self.database_utility = DatabaseUtility(self)
yaml_data = self.database_operations.loadRegexFromYAML()
self.database_operations.populate_and_update_entities_from_yaml(yaml_data)
# Load data and update checkboxes
self.refreshApplicationState()
self.database_operations.checkScriptPresence()
# Load files from the directory if specified
if self.directory and os.path.isdir(self.directory):
self.loadFilesFromDirectory(self.directory)
self.ui_update_interval = 500
self.needs_tree_update = False
self.needs_checkbox_update = False
self.update_timer = QTimer(self)
self.update_timer.timeout.connect(self.performPeriodicUpdate)
self.update_timer.start(self.ui_update_interval)
def loadFilesFromDirectory(self, directory):
for root, dirs, files in os.walk(directory):
for filename in files:
file_path = os.path.join(root, filename)
self.filePaths.append(file_path)
self.updateFileCountLabel()
def initUI(self):
initialize_main_window(self, self.app)
def openFileNameDialog(self):
self.ui_helper.openFileNameDialog()
self.file_selection_window.populateTable()
self.updateFileCountLabel()
def openDirNameDialog(self):
self.ui_helper.openDirNameDialog()
self.file_selection_window.populateTable()
self.updateFileCountLabel()
def clearFileSelection(self):
self.ui_helper.clearFileSelection()
self.file_selection_window.populateTable()
self.updateFileCountLabel()
def removeSingleFile(self, file):
self.ui_helper.removeSingleFile(file)
self.file_selection_window.populateTable()
self.updateFileCountLabel()
def refreshApplicationState(self):
#yaml_data = self.database_operations.loadRegexFromYAML()
#self.database_operations.populate_and_update_entities_from_yaml(yaml_data)
self.processing_thread = FileProcessorThread(self.filePaths)
self.processing_thread.update_checkboxes_signal.connect(self.generate_report_window.updateCheckboxes)
self.processing_thread.update_checkboxes_signal.connect(self.generate_wordlist_window.updateCheckboxes)
self.generate_report_window.updateCheckboxes()
self.generate_wordlist_window.updateCheckboxes()
self.updateDatabaseStatusLabel()
self.updateTree()
self.updateFileCountLabel()
def updateFileCountLabel(self):
file_count = len(self.filePaths)
file_count_label = f" {file_count} Dateien selektiert"
readable_size = self.ui_helper.calculate_total_size(self.filePaths)
self.fileCountLabel.setText(file_count_label + f' // {readable_size}')
def updateTree(self):
with session_scope() as session:
self.databaseTree.updateTree(session)
def updateDatabaseStatusLabel(self):
with session_scope() as session:
entity_count = session.query(EntitiesTable).count()
db_file_path = self.current_db_path # Replace with your actual database file path
db_file_size = os.path.getsize(db_file_path)
db_file_size_mb = db_file_size / (1024 * 1024) # Convert size to MB
status_text = f"Anzahl Entitäten: {entity_count}\nDatenbank-Größe: {db_file_size_mb:.2f} MB"
self.databaseStatusLabel.setText(status_text)
def onTreeUpdateSignalReceived(self):
self.needs_tree_update = True
def onCheckboxUpdateSignalReceived(self):
self.needs_checkbox_update = True
def performPeriodicUpdate(self):
if self.needs_tree_update:
self.updateTree()
self.needs_tree_update = False
if self.needs_checkbox_update:
self.generate_report_window.updateCheckboxes()
self.generate_wordlist_window.updateCheckboxes()
self.updateTree()
self.needs_checkbox_update = False
def execute_query_wrapper(self, query_text):
self.results_window.show()
self.results_window.set_query_and_execute(query_text)
def quickStartWorkflow(self):
self.clearFileSelection()
self.purgeDatabase()
yaml_data = self.database_operations.loadRegexFromYAML()
self.database_operations.populate_and_update_entities_from_yaml(yaml_data)
self.openDirNameDialog()
self.processFiles()
def purgeDatabase(self):
self.database_utility.purgeDatabase()
def importDatabase(self):
self.database_utility.importDatabase()
def exportDatabase(self):
self.database_utility.exportDatabase()
def processFiles(self):
try:
fileCount = len(self.filePaths)
if fileCount > 0:
self.progressBar.setMaximum(fileCount)
self.db_init_func()
self.processing_thread = FileProcessorThread(self.filePaths) # Assign the thread to processing_thread
self.processing_thread.finished.connect(self.onProcessingFinished)
self.processing_thread.update_progress.connect(self.progressBar.setValue)
self.processing_thread.update_status.connect(self.statusLabel.setText)
self.processing_thread.update_rate.connect(self.updateEntityRate)
#self.processing_thread.update_tree_signal.connect(self.updateTree)
#self.processing_thread.update_checkboxes_signal.connect(self.generate_report_window.updateCheckboxes)
#self.processing_thread.update_checkboxes_signal.connect(self.generate_wordlist_window.updateCheckboxes)
self.processing_thread.update_tree_signal.connect(self.onTreeUpdateSignalReceived)
self.processing_thread.update_checkboxes_signal.connect(self.onCheckboxUpdateSignalReceived)
self.processing_thread.start()
logging.debug(f"Thread started, isRunning: {self.processing_thread.isRunning()}")
else:
self.message("Information", "Keine Dateien Selektiert. Selektion vornehmen.")
except Exception as e:
logging.error(f"Error processing files: {e}")
def abortAnalysis(self):
if self.processing_thread and self.isProcessing():
logging.debug(f"Abort Analysis initiated.")
self.processing_thread.abort()
self.processing_thread.wait()
#self.processing_thread = None
self.statusLabel.setText(" Verarbeitung durch User unterbrochen.")
logging.info(f"Analysis aborted manually.")
self.refreshApplicationState()
def isProcessing(self):
if self.processing_thread is not None:
return self.processing_thread.isRunning()
return False
def onProcessingFinished(self):
if self.processing_thread:
summary = self.getProcessingSummary()
unsupported_files_count = self.processing_thread.getUnsupportedFilesCount()
# Generate CSV files for unprocessed and processed files
current_timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
unprocessed_files_log = os.path.join(self.log_dir, f"{current_timestamp}_unprocessed_files_log.csv")
processed_files_log = os.path.join(self.log_dir, f"{current_timestamp}_processed_files_log.csv")
self.ui_helper.generate_files_log(unprocessed_files_log, self.processing_thread.all_unsupported_files)
processed_files = set(self.processing_thread.file_paths) - set(self.processing_thread.all_unsupported_files)
self.ui_helper.generate_files_log(processed_files_log, list(processed_files))
if unsupported_files_count > 0:
summary += f"\n{unsupported_files_count} nicht unterstützte Dateien übersprungen."
link_label = QLabel(f'<a href="#">Open list of all unsupported files...</a>')
link_label.linkActivated.connect(lambda: self.ui_helper.openFile(unprocessed_files_log))
self.message("Analyse-Zusammenfassung", summary, link_label)
else:
self.message("Analyse-Zusammenfassung", summary)
if self.external_db_path:
try:
shutil.copy('entities.db', self.external_db_path)
self.statusLabel.setText(f" Datenbank gespeichert unter: {self.external_db_path}")
except Exception as e:
logging.error(f"Error exporting database: {e}")
self.statusLabel.setText(f" Fehler beim Exportieren der Datenbank: {e}")
self.refreshApplicationState()
self.processing_thread = None
def openLogDir(self):
self.ui_helper.openFile(self.log_dir)
def getProcessingSummary(self):
with session_scope() as session:
entity_counts = session.query(EntityTypesTable.gui_name, func.count(EntitiesTable.entities_id)) \
.join(EntityTypesTable, EntitiesTable.entity_types_id == EntityTypesTable.entity_type_id) \
.group_by(EntityTypesTable.gui_name) \
.all()
summary = "Analyse-Zusammenfassung:\n\n"
for gui_name, count in entity_counts:
summary += f"{gui_name}: {count} gefunden\n"
return summary
def getUnsupportedFilesCount(self):
if self.processing_thread:
return self.processing_thread.getUnsupportedFilesCount()
return 0
def showProcessingWarning(self):
self.message("Operation unmöglich", "Diese Operation kann nicht durchgeführt werden, während Dateien analysiert werden. Warten oder abbrechen.")
def updateEntityRate(self, entity_rate, total_entities, file_rate, total_files_processed, estimated_time, data_rate_kibs):
formatted_time = format_time(estimated_time)
total_cpu_cores = multiprocessing.cpu_count()
rate_text = (f"{entity_rate:.2f} entities/second, Total: {total_entities} // "
f"{file_rate:.2f} files/second, Total: {total_files_processed} // "
f"{data_rate_kibs:.2f} KiB/s // "
f"ETC: {formatted_time} // "
f"CPU Cores: {total_cpu_cores}")
self.entityRateLabel.setText(rate_text)
def openGenerateReportWindow(self):
if self.isProcessing():
self.showProcessingWarning()
return
if not self.generate_report_window:
self.generate_report_window = GenerateReportWindow(self.app)
self.generate_report_window.show()
def openGenerateWordlistWindow(self):
if self.isProcessing():
self.showProcessingWarning()
return
if not self.generate_wordlist_window:
self.generate_wordlist_window = GenerateWordlistWindow(self.app)
self.generate_wordlist_window.show()
def openFileSettingsWindow(self):
if self.isProcessing():
self.showProcessingWarning()
return
if not self.file_selection_window:
self.file_selection_window = FileSettingsWindow(self.filePaths, self)
self.file_selection_window.show()
def openAnalysisSettingsWindow(self):
if self.isProcessing():
self.showProcessingWarning()
return
if not self.analysis_settings_window: # Use self.analysis_settings_window
self.analysis_settings_window = AnalysisSettingsWindow(self) # Use self.analysis_settings_window
self.analysis_settings_window.show() # Use self.analysis_settings_window
def message(self, title, text, extra_widget=None):
msgBox = QMessageBox()
msgBox.setStyleSheet("""
QMessageBox {
background-color: #282C34; /* Dark grey background */
}
QLabel {
color: white; /* White text */
}
QPushButton {
color: white; /* White text for buttons */
background-color: #4B5563; /* Dark grey background for buttons */
border-style: solid;
border-width: 2px;
border-radius: 5px;
border-color: #4A4A4A;
padding: 6px;
min-width: 80px;
min-height: 30px;
}
""")
msgBox.setIcon(QMessageBox.Warning)
msgBox.setWindowTitle(title)
msgBox.setText(text)
if extra_widget:
msgBox.setInformativeText('')
msgBox.layout().addWidget(extra_widget, 1, 1)
msgBox.exec_()
def main():
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,32 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTextEdit
from PyQt5.QtCore import QTimer
import random
class TerminalEasterEgg(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.initUI()
def initUI(self):
layout = QVBoxLayout(self)
self.terminal_widget = QTextEdit(self)
self.terminal_widget.setStyleSheet("background-color: black; color: green;")
self.terminal_widget.setReadOnly(True)
layout.addWidget(self.terminal_widget)
# Timer for fake prompts
self.terminal_timer = QTimer(self)
self.terminal_timer.timeout.connect(self.update_terminal)
self.terminal_timer.start(1000) # Update every second
def update_terminal(self):
fake_prompts = [
"Decrypting data...",
"Accessing secure server...",
"Running diagnostics...",
"Analyzing patterns...",
"Compiling code...",
"Scanning network...",
# Add more fake prompts as desired
]
self.terminal_widget.append(random.choice(fake_prompts))

View File

@@ -0,0 +1,666 @@
from PyQt5.QtWidgets import QMessageBox, QCheckBox, QGroupBox, QDateTimeEdit,QProgressBar, QMainWindow, QTableWidget, QTableWidgetItem, QLineEdit, QStyledItemDelegate, QTextEdit, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QLabel
from PyQt5.QtCore import pyqtSignal, Qt, pyqtSignal, QDateTime, QThread
from PyQt5.QtGui import QTextDocument, QTextOption
import re
import logging
import html
import datetime
from logline_leviathan.database.query import DatabaseGUIQuery, QueryThread
from logline_leviathan.database.database_manager import EntitiesTable, session_scope
COLUMN_WIDTHS = [200, 100, 250, 100, 120, 600, 80, 100, 40] # Adjust these values as needed
COLUMN_NAMES = ['Distinct Entity', 'Entity Type', 'File Name', 'Line Number', 'Timestamp', 'Context', 'Match Score', 'Flag', 'Identifier']
DEFAULT_ROW_HEIGHT = 120
FILTER_EDIT_WIDTH = 150
class DataProcessor(QThread):
dataProcessed = pyqtSignal(list)
def __init__(self, total_data, search_terms, chunk_size=50):
super().__init__()
self.total_data = total_data
self.search_terms = search_terms
# Ensure chunk_size is an integer
if not isinstance(chunk_size, int):
raise ValueError(f"chunk_size must be an integer, got {type(chunk_size)}")
self.chunk_size = chunk_size
def run(self):
# Process initial chunk
for i in range(0, len(self.total_data), self.chunk_size):
chunk = sorted(self.total_data[i:i+self.chunk_size], key=lambda x: x[1], reverse=True)
self.dataProcessed.emit(chunk)
# Continue processing remaining data
remaining_data = self.total_data[self.chunk_size:]
for i in range(0, len(remaining_data), self.chunk_size):
chunk = sorted(remaining_data[i:i+self.chunk_size], key=lambda x: x[1], reverse=True)
self.dataProcessed.emit(chunk)
class ResultsWindow(QMainWindow):
def __init__(self, db_query_instance, parent=None):
super(ResultsWindow, self).__init__(parent)
self.db_query_instance = db_query_instance
self.total_data = []
self.current_filters = {}
self.query_text = None
self.database_query = DatabaseGUIQuery()
self.query_thread = QueryThread(self.db_query_instance, self.query_text)
self.sorted_results = []
self.setWindowTitle("Suchergebnis")
self.setGeometry(800, 600, 1600, 700) # Adjust size as needed
# Create central widget and set layout
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
mainLayout = QVBoxLayout(centralWidget)
queryFieldLayout = QHBoxLayout()
self.databaseQueryLineEdit = QueryLineEdit(self)
self.databaseQueryLineEdit.setPlaceholderText(" Suchbegriff eingeben...")
self.databaseQueryLineEdit.returnPressed.connect(self.execute_query_from_results_window)
self.databaseQueryLineEdit.setStyleSheet("""
QLineEdit {
background-color: #3C4043;
color: white;
min-height: 20px;
}
""")
queryFieldLayout.addWidget(self.databaseQueryLineEdit)
# Create a progress bar for query in progress
self.query_progress_bar = QProgressBar(self)
self.query_progress_bar.setRange(0, 1) # Indeterminate mode
self.query_progress_bar.setFixedWidth(100) # Initially hidden
queryFieldLayout.addWidget(self.query_progress_bar)
executeQueryButton = QPushButton("Suche ausführen", self)
executeQueryButton.clicked.connect(self.execute_query_from_results_window)
queryFieldLayout.addWidget(executeQueryButton)
mainLayout.addLayout(queryFieldLayout)
mainLayout.addWidget(QLabel(' Die Suche nach mehreren bestimmten Begriffen, Entitätentypen (Kurzform z.B. <ipv4>), Dateinamen und Timestamps ist möglich.\n Nach erfolgreicher Abfrage der Datenbank werden die Ergebnisse tabellarisch dargestellt. Sollte die Anzahl der Suchergebnisse sehr hoch sein, dauert der Prozess einige Sekunden. Die Anzahl der Ergebnisse ist aus Performancegründen auf die besten 512 beschränkt.\n Groß- und Kleinschreibung wird bei Zitatsuchen berücksichtigt; Werden mehrere Suchbegriffe eingegeben, fließt deren Abstand und Reihenfolge ins Ergebnis mit ein.', self))
# Create a horizontal layout for filter options
filterLayout = QHBoxLayout()
mainLayout.addLayout(filterLayout)
# Updated stylesheet for the entire ResultsWindow
stylesheet = """
/* Styles for QTableWidget and headers */
QTableWidget, QHeaderView::section {
background-color: #2A2F35;
color: white;
border: 1px solid #4A4A4A;
}
/* Style for QLineEdit */
QLineEdit {
background-color: #3A3F44;
color: white;
border: 1px solid #4A4A4A;
}
/* Style for QPushButton */
QPushButton {
background-color: #4B5563;
color: white;
border-radius: 4px;
padding: 5px;
margin: 5px;
}
QPushButton:hover {
background-color: #5C677D;
}
QPushButton:pressed {
background-color: #2A2F35;
}
/* Style for empty rows and other areas */
QWidget {
background-color: #2A2F35;
color: white;
}
"""
self.setStyleSheet(stylesheet)
self.resultsTable = QTableWidget(self)
self.clearAllButton = QPushButton("Alle Filteroptionen loeschen", self)
self.clearAllButton.clicked.connect(self.clearAllFilters)
filterLayout.addWidget(self.clearAllButton)
# GroupBox for Entitätenfilter
entitaten_filter_groupbox = QGroupBox("Entitätenfilter", self)
entitaten_filter_layout = QVBoxLayout()
entitaten_filter_groupbox.setLayout(entitaten_filter_layout)
self.distinct_entity_edit = QLineEdit(self)
self.distinct_entity_edit.setPlaceholderText(" Enter distinct entity...")
self.distinct_entity_edit.textChanged.connect(self.applyDistinctEntityTextFilter)
entitaten_filter_layout.addWidget(self.distinct_entity_edit)
self.entityTypeComboBox = QComboBox()
entitaten_filter_layout.addWidget(self.entityTypeComboBox)
filterLayout.addWidget(entitaten_filter_groupbox)
# GroupBox for Fundstelle
fundstelle_groupbox = QGroupBox("Fundstelle", self)
fundstelle_layout = QVBoxLayout()
fundstelle_groupbox.setLayout(fundstelle_layout)
self.file_name_edit = QLineEdit(self)
self.file_name_edit.setPlaceholderText(" Enter file name...")
self.file_name_edit.textChanged.connect(self.applyFileNameTextFilter)
fundstelle_layout.addWidget(self.file_name_edit)
self.line_number_edit = QLineEdit(self)
self.line_number_edit.setPlaceholderText(" Enter line number...")
self.line_number_edit.textChanged.connect(self.applyLineNumberTextFilter)
fundstelle_layout.addWidget(self.line_number_edit)
filterLayout.addWidget(fundstelle_groupbox)
# GroupBox for timestamp filtering
self.timestampFilterGroupbox = QGroupBox("Zeitrahmen", self)
timestampFilterLayout = QVBoxLayout()
self.timestampFilterGroupbox.setLayout(timestampFilterLayout)
filterLayout.addWidget(self.timestampFilterGroupbox)
dateedit_layout = QHBoxLayout()
self.startDateEdit = QDateTimeEdit(self)
self.startDateEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
self.startDateEdit.setCalendarPopup(True)
self.startDateEdit.setDateTime(QDateTime.currentDateTime().addDays(-10000))
dateedit_layout.addWidget(self.startDateEdit)
self.startDateEdit.dateTimeChanged.connect(self.applyTimestampFilter)
self.endDateEdit = QDateTimeEdit(self)
self.endDateEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
self.endDateEdit.setCalendarPopup(True)
self.endDateEdit.setDateTime(QDateTime.currentDateTime().addDays(1))
dateedit_layout.addWidget(self.endDateEdit)
self.endDateEdit.dateTimeChanged.connect(self.applyTimestampFilter)
self.timestamp_edit = QLineEdit(self)
self.timestamp_edit.setPlaceholderText(" Enter timestamp...")
self.timestamp_edit.textChanged.connect(self.applyTimestampTextFilter)
timestampFilterLayout.addWidget(self.timestamp_edit)
timestampFilterLayout.addLayout(dateedit_layout)
# GroupBox for Match Score, Flags, and Identifier
more_filters_groupbox = QGroupBox("Weitere Filter", self)
more_filters_layout = QHBoxLayout()
more_filters_groupbox.setLayout(more_filters_layout)
filterLayout.addWidget(more_filters_groupbox)
flag_layout = QVBoxLayout()
flag_true_checkbox = QCheckBox("Flagged", self)
flag_layout.addWidget(flag_true_checkbox)
flag_false_checkbox = QCheckBox("Not Flagged", self)
flag_layout.addWidget(flag_false_checkbox)
more_filters_layout.addLayout(flag_layout)
meta_layout = QVBoxLayout()
self.match_score_edit = QLineEdit(self)
self.match_score_edit.setPlaceholderText(" Enter match score...")
self.match_score_edit.textChanged.connect(self.applyMatchScoreTextFilter)
meta_layout.addWidget(self.match_score_edit)
self.identifier_edit = QLineEdit(self)
self.identifier_edit.setPlaceholderText(" Enter identifier...")
self.identifier_edit.textChanged.connect(self.applyIdentifierTextFilter)
meta_layout.addWidget(self.identifier_edit)
more_filters_layout.addLayout(meta_layout)
self.resultsTable.setColumnCount(len(COLUMN_NAMES))
self.resultsTable.setHorizontalHeaderLabels(COLUMN_NAMES)
self.resultsTable.setSortingEnabled(True)
mainLayout.addWidget(self.resultsTable)
bottomLayout = QHBoxLayout()
self.query_status_label = QLabel(" Hintergrundsuchprozess läuft...")
bottomLayout.addWidget(self.query_status_label)
self.bottomButtonLayout = QHBoxLayout()
flag_visible_items_button = QPushButton("Sichtbare Objekte markieren", self)
flag_visible_items_button.clicked.connect(self.flagVisibleItems)
self.bottomButtonLayout.addWidget(flag_visible_items_button)
unflag_visible_items_button = QPushButton("Sichtbare Objekte demarkieren", self)
unflag_visible_items_button.clicked.connect(self.unflagVisibleItems)
self.bottomButtonLayout.addWidget(unflag_visible_items_button)
clear_all_flags_button = QPushButton("Sämtliche Markierungen entfernen", self)
clear_all_flags_button.clicked.connect(self.clearAllFlags)
self.bottomButtonLayout.addWidget(clear_all_flags_button)
bottomLayout.addLayout(self.bottomButtonLayout)
mainLayout.addLayout(bottomLayout)
# Create and add the Dismiss button
self.dismissButton = QPushButton("Schließen", self)
self.dismissButton.clicked.connect(self.close)
mainLayout.addWidget(self.dismissButton)
self.populate_entity_type_combobox()
def populate_entity_type_combobox(self):
entity_types = self.database_query.get_entity_types()
self.entityTypeComboBox.addItem("Alle verfügbaren Typen", None) # Default option
for entity_type in entity_types:
self.entityTypeComboBox.addItem(entity_type, entity_type)
self.entityTypeComboBox.currentIndexChanged.connect(self.applyEntityTypeFilter)
def execute_query_from_results_window(self):
self.resultsTable.clear()
self.resultsTable.setRowCount(0)
self.query_text = self.databaseQueryLineEdit.text().strip()
if not self.query_text:
# Handle empty query case
return
self.query_status_label.setText(" Suche wird mit Suchbegriffen " + self.query_text + " durchgeführt...")
self.query_thread = QueryThread(self.database_query, self.query_text)
self.query_thread.queryCompleted.connect(self.on_query_completed)
self.query_thread.start()
self.query_progress_bar.setRange(0, 0)
def set_query_and_execute(self, query_text):
self.databaseQueryLineEdit.setText(query_text)
self.execute_query_from_results_window()
def on_query_completed(self, results_dict):
self.resultsTable.setUpdatesEnabled(False)
self.resultsTable.setSortingEnabled(False) # Disable sorting
self.resultsTable.setRowCount(0)
self.resultsTable.setColumnCount(len(COLUMN_NAMES))
self.sorted_results = sorted(results_dict.items(), key=lambda x: x[1], reverse=True)
self.top_results = self.sorted_results[:512]
self.query_status_label.setText(" Suche abgeschlossen // Anzahl Suchergebnisse: " + str(len(results_dict)) + " // Tabelle wird befüllt (das kann einige Zeit dauern)")
for entities_id, score in self.top_results:
self.insert_row(entities_id, score)
self.query_progress_bar.setRange(0, 1)
self.query_status_label.setText(" Suche abgeschlossen // Anzahl Suchergebnisse: " + str(len(results_dict)) + " (begrenzt auf die besten 512)")
self.resultsTable.setHorizontalHeaderLabels(COLUMN_NAMES)
self.adjust_column_widths()
self.resultsTable.setSortingEnabled(True)
self.resultsTable.sortByColumn(6, Qt.DescendingOrder) # Enable sorting
self.resultsTable.setUpdatesEnabled(True)
self.resultsTable.update()
self.applyAllFilters()
def insert_row(self, entities_id, score):
try:
with session_scope() as session:
# Fetch the entity from the database
entity = session.query(EntitiesTable).filter(EntitiesTable.entities_id == entities_id).first()
if not entity:
return # Skip if the entity is not found
# Fetch related data
distinct_entity = entity.entity.distinct_entity if entity.entity else ""
entity_type = entity.regex_library.gui_name if entity.regex_library else ""
file_name = entity.file.file_name if entity.file else ""
line_number = str(entity.line_number) if entity.line_number is not None else ""
entry_timestamp = entity.entry_timestamp.strftime("%Y-%m-%d %H:%M:%S") if entity.entry_timestamp else ""
context_large = entity.context.context_large if entity.context else ""
# Insert a new row in the table
row_position = self.resultsTable.rowCount()
self.resultsTable.insertRow(row_position)
search_terms = self.query_text.split()
self.highlight_delegate = HighlightDelegate(self.resultsTable, search_terms)
# Set the values for each column
self.resultsTable.setItem(row_position, 0, QTableWidgetItem(distinct_entity))
self.resultsTable.setItem(row_position, 1, QTableWidgetItem(entity_type))
self.resultsTable.setItem(row_position, 2, QTableWidgetItem(file_name))
self.resultsTable.setItem(row_position, 3, QTableWidgetItem(line_number))
self.resultsTable.setItem(row_position, 4, QTableWidgetItem(entry_timestamp))
context_widget = ScrollableTextWidget(context_large, search_terms, distinct_entity)
self.resultsTable.setCellWidget(row_position, 5, context_widget)
match_score_item = NumericTableWidgetItem(str(score))
self.resultsTable.setItem(row_position, 6, match_score_item)
flag_button_widget = FlagButton(entities_id, entity.flag)
self.resultsTable.setCellWidget(row_position, COLUMN_NAMES.index('Flag'), flag_button_widget)
self.resultsTable.setItem(row_position, 8, QTableWidgetItem(str(entities_id)))
self.resultsTable.setRowHeight(row_position, DEFAULT_ROW_HEIGHT)
for column_index in range(self.resultsTable.columnCount()):
if column_index in [0, 1, 2, 3, 4]:
self.resultsTable.setItemDelegateForColumn(column_index, self.highlight_delegate)
self.resultsTable.update()
except Exception as e:
logging.error(f"Error inserting row: {e}")
def adjust_column_widths(self):
for i, width in enumerate(COLUMN_WIDTHS):
self.resultsTable.setColumnWidth(i, width)
def flagVisibleItems(self):
"""Flag all visible items in the table."""
for row in range(self.resultsTable.rowCount()):
if not self.resultsTable.isRowHidden(row):
flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag'))
if flag_button_widget and not flag_button_widget.flag:
flag_button_widget.toggle_flag()
def unflagVisibleItems(self):
"""Unflag all visible items in the table."""
for row in range(self.resultsTable.rowCount()):
if not self.resultsTable.isRowHidden(row):
flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag'))
if flag_button_widget and flag_button_widget.flag:
flag_button_widget.toggle_flag()
def clearAllFlags(self):
"""Ask for confirmation and clear all flags in the table if confirmed."""
reply = QMessageBox.question(self, 'Confirm Action',
"Sollen tatsächlich alle Markierungen entfernt werden?\nDas wirkt sich auf alle Einträge in der Datenbank aus!",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
# Clear all flags in the database
FlagButton.clearAllFlagsInDatabase()
# Update all flag buttons in the table
for row in range(self.resultsTable.rowCount()):
flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag'))
if flag_button_widget and flag_button_widget.flag:
flag_button_widget.flag = False
flag_button_widget.button.setText("_")
flag_button_widget.update_button_style()
# Update all flag buttons in the table
for row in range(self.resultsTable.rowCount()):
flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag'))
if flag_button_widget and flag_button_widget.flag:
flag_button_widget.flag = False
flag_button_widget.button.setText("_")
flag_button_widget.update_button_style()
def applyTextFilter(self, column_index, filter_text):
if filter_text:
for row in range(self.resultsTable.rowCount()):
item = self.resultsTable.item(row, column_index)
show_row = not filter_text or (item and filter_text.lower() in item.text().lower())
self.resultsTable.setRowHidden(row, not show_row)
def applyDistinctEntityTextFilter(self):
filter_text = self.distinct_entity_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Distinct Entity'), filter_text)
def applyFileNameTextFilter(self):
filter_text = self.file_name_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('File Name'), filter_text)
def applyLineNumberTextFilter(self):
filter_text = self.line_number_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Line Number'), filter_text)
def applyMatchScoreTextFilter(self):
filter_text = self.match_score_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Match Score'), filter_text)
def applyTimestampTextFilter(self):
filter_text = self.timestamp_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Timestamp'), filter_text)
def applyIdentifierTextFilter(self):
filter_text = self.identifier_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Identifier'), filter_text)
def applyEntityTypeFilter(self):
selected_type = self.entityTypeComboBox.currentData()
entity_type_column = COLUMN_NAMES.index('Entity Type')
for row in range(self.resultsTable.rowCount()):
item = self.resultsTable.item(row, entity_type_column)
show_row = (selected_type is None or (item and item.text() == selected_type))
self.resultsTable.setRowHidden(row, not show_row)
def applyTimestampFilter(self):
start_date = self.startDateEdit.dateTime().toPyDateTime()
end_date = self.endDateEdit.dateTime().toPyDateTime()
timestamp_column = COLUMN_NAMES.index('Timestamp')
for row in range(self.resultsTable.rowCount()):
item = self.resultsTable.item(row, timestamp_column)
if item and item.text() != "":
row_timestamp = datetime.datetime.strptime(item.text(), "%Y-%m-%d %H:%M:%S")
show_row = start_date <= row_timestamp <= end_date
self.resultsTable.setRowHidden(row, not show_row)
else:
self.resultsTable.setRowHidden(row, False)
def applyAllFilters(self):
self.applyDistinctEntityTextFilter()
self.applyFileNameTextFilter()
self.applyLineNumberTextFilter()
self.applyMatchScoreTextFilter()
self.applyTimestampTextFilter()
self.applyEntityTypeFilter()
self.applyTimestampFilter()
def clearAllFilters(self):
self.entityTypeComboBox.setCurrentIndex(0)
self.startDateEdit.setDateTime(QDateTime(QDateTime(2009, 1, 1, 0, 0, 0)))
self.endDateEdit.setDateTime(QDateTime.currentDateTime())
self.distinct_entity_edit.clear()
self.file_name_edit.clear()
self.line_number_edit.clear()
self.match_score_edit.clear()
self.timestamp_edit.clear()
self.identifier_edit.clear()
self.applyAllFilters()
class QueryLineEdit(QLineEdit):
returnPressed = pyqtSignal()
def keyPressEvent(self, event):
if event.key() == Qt.Key_Return:
self.returnPressed.emit()
else:
super().keyPressEvent(event)
class HighlightDelegate(QStyledItemDelegate):
def __init__(self, parent=None, search_terms=None):
super().__init__(parent)
self.search_terms = search_terms or []
def paint(self, painter, option, index):
painter.save()
# Set text color and other options
options = QTextOption()
options.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
document = QTextDocument()
document.setDefaultTextOption(options)
document.setDefaultFont(option.font)
# Prepare highlighted text
text = index.model().data(index)
highlighted_text = self.get_highlighted_text(text)
document.setHtml(highlighted_text)
# Set the width of the document to the cell width
document.setTextWidth(option.rect.width())
# Draw the contents
painter.translate(option.rect.topLeft())
document.drawContents(painter)
painter.restore()
def get_highlighted_text(self, text):
if text is None:
text = ""
text_with_color = f"<span style='color: white;'>{text}</span>"
for term in self.search_terms:
# Retain the '+' at the beginning and strip other special characters
is_positive = term.startswith('+')
clean_term = re.sub(r'[^\w\s]', '', term.lstrip('+-')).lower()
if is_positive and clean_term.lower() in text.lower():
# Use regex for case-insensitive search and replace
regex = re.compile(re.escape(clean_term), re.IGNORECASE)
highlighted_term = f"<span style='background-color: yellow; color: black;'>{clean_term}</span>"
text_with_color = regex.sub(highlighted_term, text_with_color)
return text_with_color.replace("\n", "<br>")
class ScrollableTextWidget(QWidget):
def __init__(self, text, search_terms, distinct_entity, parent=None):
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.text_edit = CustomTextEdit(self)
self.text_edit.setReadOnly(True)
# Apply styles including scrollbar styles
self.text_edit.setStyleSheet("""
QTextEdit {
background-color: #2A2F35; /* Dark blue-ish background */
color: white; /* White text */
}
QTextEdit QScrollBar:vertical {
border: none;
background-color: #3A3F44; /* Dark scrollbar background */
width: 8px; /* Width of the scrollbar */
}
QTextEdit QScrollBar::handle:vertical {
background-color: #6E6E6E; /* Scroll handle color */
border-radius: 4px; /* Rounded corners for the handle */
}
QTextEdit QScrollBar::add-line:vertical, QTextEdit QScrollBar::sub-line:vertical {
background: none;
}
""")
# Set the text with highlighting
self.setHighlightedText(text, search_terms, distinct_entity)
layout.addWidget(self.text_edit)
# Scroll to the distinct entity
self.scroll_to_text(distinct_entity)
def setHighlightedText(self, text, search_terms, distinct_entity):
if text is None:
text = ""
# Wrap the original text in a span to maintain color
text_with_color = f"<span style='color: white;'>{text}</span>"
# Highlight distinct entity in a different color
if distinct_entity:
distinct_entity_escaped = html.escape(distinct_entity)
text_with_color = re.sub(
re.escape(distinct_entity_escaped),
lambda match: f"<span style='background-color: blue; color: white;'>{match.group()}</span>",
text_with_color,
flags=re.IGNORECASE
)
for term in search_terms:
# Check if the term starts with '+'
is_positive = term.startswith('+')
clean_term = re.sub(r'[^\w\s]', '', term.lstrip('+-'))
# If the term starts with '+', highlight all matches regardless of case
if is_positive or clean_term.lower() in text.lower():
regex = re.compile(re.escape(clean_term), re.IGNORECASE)
highlighted_term = f"<span style='background-color: yellow; color: black;'>{clean_term}</span>"
text_with_color = regex.sub(highlighted_term, text_with_color)
self.text_edit.setHtml(text_with_color.replace("\n", "<br>"))
def scroll_to_text(self, text):
if text:
cursor = self.text_edit.document().find(text)
self.text_edit.setTextCursor(cursor)
class CustomTextEdit(QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # Enable vertical scrollbar as needed
def wheelEvent(self, event):
# Always handle the wheel event within QTextEdit
super().wheelEvent(event)
# Stop propagation of the event to parent
if self.verticalScrollBar().isVisible():
event.accept()
else:
event.ignore()
class FlagButton(QWidget):
def __init__(self, entities_id, flag, parent=None):
super().__init__(parent)
self.entities_id = entities_id
self.flag = flag
self.layout = QHBoxLayout(self)
self.button = QPushButton("FLAG" if flag else "_", self)
self.update_button_style()
self.button.clicked.connect(self.toggle_flag)
self.layout.addWidget(self.button)
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
def toggle_flag(self):
# Toggle the flag
self.flag = not self.flag
self.button.setText("FLAG" if self.flag else "_")
self.update_button_style()
# Update the flag in the database
with session_scope() as session:
entity = session.query(EntitiesTable).filter(EntitiesTable.entities_id == self.entities_id).first()
if entity:
entity.flag = self.flag
session.commit()
@staticmethod
def clearAllFlagsInDatabase():
with session_scope() as session:
entities = session.query(EntitiesTable).all()
for entity in entities:
entity.flag = False
session.commit()
def update_button_style(self):
if self.flag:
self.button.setStyleSheet("QPushButton { background-color: yellow; color: black; }")
else:
self.button.setStyleSheet("")
class NumericTableWidgetItem(QTableWidgetItem):
def __lt__(self, other):
return float(self.text()) < float(other.text())

View File

@@ -0,0 +1,255 @@
from PyQt5.QtWidgets import QGroupBox, QCheckBox, QLineEdit, QDialog, QTableWidget, QVBoxLayout, QTableWidgetItem, QPushButton, QHBoxLayout, QFileDialog
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPalette, QColor
from logline_leviathan.gui.ui_helper import UIHelper
from logline_leviathan.file_processor.file_processor_thread import FileProcessorThread
from logline_leviathan.database.database_manager import EntityTypesTable, session_scope
import os
class FileSettingsWindow(QDialog):
def __init__(self, file_paths, main_window=None):
super().__init__(main_window)
self.file_paths = file_paths
self.main_window = main_window # Reference to MainWindow
self.ui_helper = UIHelper(self)
self.processing_thread = None
self.initUI()
def initUI(self):
self.layout = QVBoxLayout(self)
# Button layout
self.buttonLayout = QHBoxLayout()
self.removeSelectedButton = QPushButton("Remove Selected Files")
self.removeAllButton = QPushButton("Remove All")
self.addFilesButton = QPushButton("Add Files to Selection")
self.addDirButton = QPushButton("Add Directory to Selection")
self.buttonLayout.addWidget(self.removeSelectedButton)
self.buttonLayout.addWidget(self.removeAllButton)
self.buttonLayout.addWidget(self.addFilesButton)
self.buttonLayout.addWidget(self.addDirButton)
# Connect buttons to functions
self.removeSelectedButton.clicked.connect(self.removeSelected)
self.removeAllButton.clicked.connect(self.removeAll)
self.addFilesButton.clicked.connect(self.openFileNameDialog)
self.addDirButton.clicked.connect(self.openDirNameDialog)
self.layout.addLayout(self.buttonLayout)
self.filterLineEdit = QLineEdit()
self.filterLineEdit.setPlaceholderText("Filter files...")
self.filterLineEdit.textChanged.connect(self.filterTableItems)
self.layout.insertWidget(1, self.filterLineEdit) # Inserting QLineEdit above the table
# Table widget
self.tableWidget = QTableWidget()
self.tableWidget.setColumnCount(1)
self.tableWidget.setHorizontalHeaderLabels(["File Path"])
self.layout.addWidget(self.tableWidget)
self.populateTable()
# Close button
self.closeButton = QPushButton("Close")
self.layout.addWidget(self.closeButton)
self.closeButton.clicked.connect(self.close)
def populateTable(self):
self.tableWidget.clearContents()
self.tableWidget.setRowCount(len(self.file_paths))
for row, file_path in enumerate(self.file_paths):
self.tableWidget.setItem(row, 0, QTableWidgetItem(file_path))
self.tableWidget.resizeColumnsToContents()
def filterTableItems(self, text):
for row in range(self.tableWidget.rowCount()):
item = self.tableWidget.item(row, 0)
self.tableWidget.setRowHidden(row, text.lower() not in item.text().lower())
def removeSelected(self):
if self.isProcessing():
self.main_window.showProcessingWarning()
return
# Get the selected rows from the table
selected_rows = self.tableWidget.selectionModel().selectedRows()
# Extract file paths from the selected rows
selected_files = [self.tableWidget.item(row.row(), 0).text() for row in selected_rows]
# Remove each selected file
for file_path in selected_files:
if file_path in self.file_paths:
self.file_paths.remove(file_path) # Remove from local file_paths list
self.main_window.removeSingleFile(file_path) # Call method in main_window
# Refresh the table and update the file count
self.populateTable()
self.main_window.updateFileCountLabel()
def removeAll(self):
if self.isProcessing():
self.main_window.showProcessingWarning()
return
self.main_window.clearFileSelection()
self.main_window.updateFileCountLabel()
def openFileNameDialog(self):
if self.isProcessing():
self.main_window.showProcessingWarning()
return
self.main_window.openFileNameDialog()
self.main_window.updateFileCountLabel()
def openDirNameDialog(self):
if self.isProcessing():
self.main_window.showProcessingWarning()
return
self.main_window.openDirNameDialog()
self.main_window.updateFileCountLabel()
def isProcessing(self):
return self.processing_thread and self.processing_thread.isRunning()
class AnalysisSettingsWindow(QDialog):
parsersUpdated = pyqtSignal()
def __init__(self, main_window=None):
super().__init__(main_window)
self.main_window = main_window
self.ui_helper = UIHelper(self)
self.initUI()
def initUI(self):
self.layout = QHBoxLayout(self)
# Create the QGroupBox
self.parserSettingsGroupBox = QGroupBox("Parser Settings")
self.parserSettingsGroupBoxLayout = QVBoxLayout()
self.toggleAllButtonsLayout = QHBoxLayout()
self.parserSettingsGroupBoxLayout.addLayout(self.toggleAllButtonsLayout)
self.enableAllButton = QPushButton("Enable All")
self.disableAllButton = QPushButton("Disable All")
self.toggleAllButtonsLayout.addWidget(self.enableAllButton)
self.enableAllButton.clicked.connect(self.enableAllCheckboxes)
self.toggleAllButtonsLayout.addWidget(self.disableAllButton)
self.disableAllButton.clicked.connect(self.disableAllCheckboxes)
self.parserSettingsGroupBox.setLayout(self.parserSettingsGroupBoxLayout)
self.layout.addWidget(self.parserSettingsGroupBox)
# Populate the QGroupBox
self.populateGroupBox()
self.configDirectoriesGroupBox = QGroupBox("Config Directories")
self.configDirectoriesGroupBoxLayout = QVBoxLayout()
self.configDirectoriesGroupBox.setLayout(self.configDirectoriesGroupBoxLayout)
self.configDirectoriesGroupBoxLayout.setAlignment(Qt.AlignTop)
self.layout.addWidget(self.configDirectoriesGroupBox)
# Inspect Regex Button
self.inspectRegexButton = QPushButton('YAML-Konfigurationsdatei inspizieren...', self)
self.inspectRegexButton.clicked.connect(self.openRegexLibrary)
self.configDirectoriesGroupBoxLayout.addWidget(self.inspectRegexButton)
self.openWordlistPathButton = QPushButton('Wordlist-Verzeichnis oeffnen...', self)
self.openWordlistPathButton.clicked.connect(self.openWordlistPath)
self.configDirectoriesGroupBoxLayout.addWidget(self.openWordlistPathButton)
self.openScriptsPathButton = QPushButton('Scripts-Verzeichnis oeffnen...', self)
self.openScriptsPathButton.clicked.connect(self.openScriptsPath)
self.configDirectoriesGroupBoxLayout.addWidget(self.openScriptsPathButton)
# Close Button
self.closeButton = QPushButton("Close")
self.layout.addWidget(self.closeButton)
self.closeButton.clicked.connect(self.close)
def populateGroupBox(self):
self.parserSettingsGroupBoxLayout.setAlignment(Qt.AlignTop)
with session_scope() as db_session:
entity_types = db_session.query(EntityTypesTable).all()
self.checkboxes = []
for et in entity_types:
# Omit entries that start with "category_"
if et.entity_type.startswith("category_"):
continue
parser_info = []
if et.regex_pattern:
parser_info.append("regex")
if et.script_parser:
parser_info.append("script")
checkBoxText = f"{et.gui_name} ({', '.join(parser_info)})" if parser_info else et.gui_name
checkBox = QCheckBox(checkBoxText)
# Fetch the current state of parser_enabled from the database for each entity type
current_parser_enabled = db_session.query(EntityTypesTable).filter(EntityTypesTable.entity_type_id == et.entity_type_id).first().parser_enabled
checkBox.setChecked(current_parser_enabled)
color = 'green' if current_parser_enabled else 'red'
checkBox.setStyleSheet(f"QCheckBox {{ color: {color}; }}")
self.parserSettingsGroupBoxLayout.addWidget(checkBox)
self.checkboxes.append((checkBox, et.entity_type_id))
checkBox.toggled.connect(lambda checked, et_id=et.entity_type_id: self.updateParserEnabled(checked, et_id))
def enableAllCheckboxes(self):
for checkBox, _ in self.checkboxes:
checkBox.setChecked(True)
def disableAllCheckboxes(self):
for checkBox, _ in self.checkboxes:
checkBox.setChecked(False)
def updateParserEnabled(self, checked, entity_type_id):
with session_scope() as db_session: # Using a session context manager
et = db_session.query(EntityTypesTable).filter_by(entity_type_id=entity_type_id).first()
if et:
et.parser_enabled = checked
db_session.commit() # Commit changes
if self.main_window:
self.main_window.refreshApplicationState() # Refresh the UI state
self.refreshGroupBox() # Refresh the group box to reflect changes
def refreshGroupBox(self):
# Clear and repopulate the group box
for checkBox, _ in self.checkboxes:
checkBox.deleteLater() # Properly delete the checkbox
self.populateGroupBox()
self.parsersUpdated.emit()
def openWordlistPath(self):
wordlist_path = os.path.join(os.getcwd(), 'data', 'wordlist')
if os.path.exists(wordlist_path):
self.ui_helper.openFile(wordlist_path) # Call openFile method on the UIHelper instance
else:
#self.statusLabel.setText(" wordlist nicht unter ./daten/ gefunden.")
pass
def openRegexLibrary(self):
path_to_yaml = os.path.join(os.getcwd(), 'data', 'entities.yaml')
if os.path.exists(path_to_yaml):
self.ui_helper.openFile(path_to_yaml) # Call openFile method on the UIHelper instance
else:
#self.statusLabel.setText(" entities.yaml nicht unter ./daten/ gefunden.")
pass
def openScriptsPath(self):
scripts_path = os.path.join(os.getcwd(), 'data', 'parser')
if os.path.exists(scripts_path):
self.ui_helper.openFile(scripts_path) # Call openFile method on the UIHelper instance
else:
#self.statusLabel.setText(" scripts nicht unter ./daten/ gefunden.")
pass

View File

@@ -0,0 +1,119 @@
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import QUrl
import os
import math
import logging
import csv
import sys
import subprocess
class UIHelper():
def __init__(self, main_window):
self.main_window = main_window
def openFileNameDialog(self):
if self.main_window.isProcessing():
self.main_window.showProcessingWarning()
return
options = QFileDialog.Options()
files, _ = QFileDialog.getOpenFileNames(self.main_window, "Dateien selektieren", "", "All Files (*)", options=options)
if files:
for file in files:
if file not in self.main_window.filePaths:
self.main_window.filePaths.append(file)
self.main_window.updateFileCountLabel()
def openDirNameDialog(self):
if self.main_window.isProcessing():
self.main_window.showProcessingWarning()
return
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
fileDialog = QFileDialog(self.main_window, "Ordner selektieren", "", options=options)
fileDialog.setFileMode(QFileDialog.Directory)
fileDialog.setOption(QFileDialog.ShowDirsOnly, True)
fileDialog.setOption(QFileDialog.DontResolveSymlinks, True)
# Store previously selected directories
selected_directories = []
while True:
if fileDialog.exec_() == QFileDialog.Accepted:
directory = fileDialog.selectedFiles()[0]
if directory and directory not in selected_directories:
selected_directories.append(directory)
self.addAllFilesFromDirectory(directory)
else:
break # Exit loop if user cancels
self.main_window.updateFileCountLabel()
def calculate_total_size(self, file_paths):
total_size = sum(os.path.getsize(f) for f in file_paths if os.path.exists(f))
return self.format_size(total_size)
def format_size(self, size_bytes):
if size_bytes == 0:
return "0B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return f"{s} {size_name[i]}"
def addAllFilesFromDirectory(self, directory):
for root, dirs, files in os.walk(directory):
for filename in files:
file_path = os.path.join(root, filename)
if file_path not in self.main_window.filePaths:
self.main_window.filePaths.append(file_path)
def clearFileSelection(self):
if self.main_window.isProcessing():
self.main_window.showProcessingWarning()
return
self.main_window.filePaths.clear()
self.main_window.updateFileCountLabel()
self.main_window.fileCountLabel.setText(' Keine Dateien selektiert')
def removeSingleFile(self, file):
if self.main_window.isProcessing():
self.main_window.showProcessingWarning()
return
if self.main_window.filePaths:
# Remove the file by value
if file in self.main_window.filePaths:
self.main_window.filePaths.remove(file)
self.main_window.updateFileCountLabel()
def generate_files_log(self, file_path, files_list):
try:
with open(file_path, mode='w', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
for file in files_list:
writer.writerow([file])
except Exception as e:
logging.error(f"Error generating log file {file_path}: {e}")
def openFile(self, file_path):
if sys.platform == 'win32':
os.startfile(file_path)
elif sys.platform == 'darwin': # macOS
subprocess.Popen(['open', file_path])
else: # Linux and other Unix-like systems
subprocess.Popen(['xdg-open', file_path])
def format_time(seconds):
if seconds != seconds or seconds == float('inf'): # Check for NaN and inf
return "N/A"
minutes = int(seconds // 60)
seconds = int(seconds % 60)
return f"{minutes} min {seconds} sec"

View File

@@ -0,0 +1,4 @@
repo_link = "https://cloud.mikoshi.de/call/qhtkcnmn#/"
repo_link_text = "Feedback // Support (öffnet externen Link)"
version_string = "2024-02-08 - Version: 0.4.4 // TESTING // UPDATE REGULARLY"
loglevel = "INFO"