initial commit
This commit is contained in:
0
logline_leviathan/gui/__init__.py
Normal file
0
logline_leviathan/gui/__init__.py
Normal file
398
logline_leviathan/gui/checkbox_panel.py
Normal file
398
logline_leviathan/gui/checkbox_panel.py
Normal 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)
|
||||
77
logline_leviathan/gui/customize_results.py
Normal file
77
logline_leviathan/gui/customize_results.py
Normal 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()
|
||||
|
||||
328
logline_leviathan/gui/db_browser.py
Normal file
328
logline_leviathan/gui/db_browser.py
Normal 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_()
|
||||
188
logline_leviathan/gui/generate_report.py
Normal file
188
logline_leviathan/gui/generate_report.py
Normal 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_()
|
||||
416
logline_leviathan/gui/generate_wordlist.py
Normal file
416
logline_leviathan/gui/generate_wordlist.py
Normal 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_()
|
||||
306
logline_leviathan/gui/initui_mainwindow.py
Normal file
306
logline_leviathan/gui/initui_mainwindow.py
Normal 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()
|
||||
|
||||
|
||||
339
logline_leviathan/gui/initui_report_window.py
Normal file
339
logline_leviathan/gui/initui_report_window.py
Normal 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)
|
||||
BIN
logline_leviathan/gui/logo.png
Normal file
BIN
logline_leviathan/gui/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
372
logline_leviathan/gui/mainwindow.py
Normal file
372
logline_leviathan/gui/mainwindow.py
Normal 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()
|
||||
|
||||
32
logline_leviathan/gui/presentation_mode.py
Normal file
32
logline_leviathan/gui/presentation_mode.py
Normal 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))
|
||||
666
logline_leviathan/gui/query_window.py
Normal file
666
logline_leviathan/gui/query_window.py
Normal 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())
|
||||
|
||||
255
logline_leviathan/gui/settings_gui.py
Normal file
255
logline_leviathan/gui/settings_gui.py
Normal 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
|
||||
|
||||
|
||||
119
logline_leviathan/gui/ui_helper.py
Normal file
119
logline_leviathan/gui/ui_helper.py
Normal 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"
|
||||
|
||||
4
logline_leviathan/gui/versionvars.py
Normal file
4
logline_leviathan/gui/versionvars.py
Normal 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"
|
||||
Reference in New Issue
Block a user