diff --git a/ui/opensnitch/actions/__init__.py b/ui/opensnitch/actions/__init__.py new file mode 100644 index 00000000..7348be4b --- /dev/null +++ b/ui/opensnitch/actions/__init__.py @@ -0,0 +1,157 @@ +from PyQt5.QtCore import QObject + +import json +import os +import glob +import sys + +from opensnitch.actions import highlight +from opensnitch.actions.default_configs import commonDelegateConfig, rulesDelegateConfig, fwDelegateConfig + +class Actions(QObject): + """List of actions to perform on the data that is displayed on the GUI. + Whenever an item matches a condition an action is applied, for example: + - if the text of a cell matches a condition for the given columns, + then the properties of the cell/row and the text are customized. + + There's only 1 action supported right now: + - highlight: for customizing rows and cells appearance. + + There're 3 actions by default of type Highlight: + - rules: applied to the rules to colorize the columns Enabled and + Action + - firewall: applied to the fw rules to colorize the columns Action and + Target. + - common: applied to the rest of the views to colorize the column + Action. + + Users can modify the default actions, by adding more patterns to colorize. + At the same time they can also create new actions to be applied on certain views. + + The format of the actions is JSON: + { + "created": "....", + "name": "...", + "actions": { + "highlight": { + "cells": [ + { + "text": ["allow", "True", "online"], + "cols": [3,5,6], + "color": "green", + }, + { + "text": ["deny", "False", "offline"], + "cols": [3,5,6], + "color": "red", + } + ], + "rows": [] + } + } + + """ + __instance = None + + # list of loaded actions + _actions = None + + + KEY_ACTIONS = "actions" + KEY_NAME = "name" + KEY_TYPE = "type" + + # TODO: emit a signal when the actions are (re)loaded + # reloaded_signal = pyQtSignal() + + # default paths to look for actions + _paths = [ + os.path.dirname(sys.modules[__name__].__file__) + "/data/", + os.path.expanduser("~/.config/opensnitch/actions/") + ] + + @staticmethod + def instance(): + if Actions.__instance == None: + Actions.__instance = Actions() + return Actions.__instance + + def __init__(self, parent=None): + QObject.__init__(self) + self._actions_list = {} + try: + os.makedirs(os.path.expanduser("~/.config/opensnitch/actions/"), 0o700) + except: + pass + + def _load_default_configs(self): + self._actions_list[commonDelegateConfig[Actions.KEY_NAME]] = self.compile(commonDelegateConfig) + self._actions_list[rulesDelegateConfig[Actions.KEY_NAME]] = self.compile(rulesDelegateConfig) + self._actions_list[fwDelegateConfig[Actions.KEY_NAME]] = self.compile(fwDelegateConfig) + + def loadAll(self): + """look for actions firstly on default system path, secondly on + user's home. + If a user customizes existing configurations, they'll be saved under + the user's home directory. + + Action files are .json files. + """ + self._load_default_configs() + + for path in self._paths: + for jfile in glob.glob(os.path.join(path, '*.json')): + self.load(jfile) + + def load(self, action_file): + """read a json file from disk and create the action.""" + with open(action_file, 'r') as fd: + data=fd.read() + obj = json.loads(data) + self._actions_list[obj[Actions.KEY_NAME]] = self.compile(obj) + + def compile(self, obj): + try: + if Actions.KEY_NAME not in obj or obj[Actions.KEY_NAME] == "": + return None + if obj.get(Actions.KEY_ACTIONS) == None: + return None + + for action in obj[Actions.KEY_ACTIONS]: + if action == highlight.Highlight.NAME: + h = highlight.Highlight(obj[Actions.KEY_ACTIONS][action]) + h.compile() + obj[Actions.KEY_ACTIONS][action]= h + else: + print("Actions exception: Action '{0}' not supported yet".format(obj[Actions.KEY_NAME])) + + return obj + except Exception as e: + print("Actions.compile() exception:", e) + return None + + + + def getAll(self): + return self._actions_list + + def deleteAll(self): + self._actions_list = {} + + def get(self, name): + try: + return self._actions_list[name] + except Exception as e: + print("get() exception:", e) + return None + + def delete(self, name): + try: + del self._actions_list[name] + # TODO: + # self.reloaded_signal.emit() + except: + pass + + def isValid(self): + pass diff --git a/ui/opensnitch/actions/default_configs.py b/ui/opensnitch/actions/default_configs.py new file mode 100644 index 00000000..fae8b8b3 --- /dev/null +++ b/ui/opensnitch/actions/default_configs.py @@ -0,0 +1,128 @@ + +# common configuration to highlight Action column +commonDelegateConfig = { + "name": "commonDelegateConfig", + "created": "", + "updated": "", + "actions": { + "highlight": { + "cells": [ + { + "text": ["allow", "\u2713 online"], + "operator": "==", + "cols": [1, 2, 3], + "color": "green", + "bgcolor": "", + "alignment": ["center"] + }, + { + "text": ["deny", "\u2613 offline"], + "cols": [1, 2, 3], + "color": "red", + "bgcolor": "", + "alignment": ["center"] + }, + { + "text": ["reject"], + "cols": [1, 2, 3], + "color": "purple", + "bgcolor": "", + "alignment": ["center"] + } + ], + "rows": [] + } + } +} + +# firewall rules configuration to highlight Enabled and Action columns +fwDelegateConfig = { + "name": "defaultFWDelegateConfig", + "created": "", + "updated": "", + "actions": { + "highlight": { + "cells": [ + { + "text": [ + "allow", + "True", + "accept", + "jump", + "masquerade", + "snat", + "dnat", + "tproxy", + "queue", + "redirect", + "True", + "ACCEPT" + ], + "cols": [7, 10], + "color": "green", + "bgcolor": "", + "alignment": ["center"] + }, + { + "text": [ + "deny", + "False", + "drop", + "DROP", + "stop" + ], + "cols": [7, 10], + "color": "red", + "bgcolor": "", + "alignment": ["center"] + }, + { + "text": [ + "reject", + "return" + ], + "cols": [7, 10], + "color": "purple", + "bgcolor": "", + "alignment": ["center"] + } + ], + "rows": [] + } + } +} + +# rules configuration to highlight Enabled and Action columns +rulesDelegateConfig = { + "name": "defaultRulesDelegateConfig", + "created": "", + "updated": "", + "actions": { + "highlight": { + "cells": [ + { + "text": ["allow", "True"], + "cols": [3, 4], + "color": "green", + "bgcolor": "", + "alignment": ["center"] + }, + { + "text": ["deny", "False"], + "cols": [3, 4], + "color": "red", + "bgcolor": "", + "alignment": ["center"] + }, + { + "text": ["reject"], + "cols": [3, 4], + "color": "purple", + "bgcolor": "", + "alignment": ["center"] + } + ], + "rows": [] + } + } +} diff --git a/ui/opensnitch/actions/highlight.py b/ui/opensnitch/actions/highlight.py new file mode 100644 index 00000000..e373702a --- /dev/null +++ b/ui/opensnitch/actions/highlight.py @@ -0,0 +1,234 @@ +from PyQt5 import Qt, QtCore +from PyQt5.QtGui import QColor, QStandardItemModel, QStandardItem + +class Highlight(): + """Customizes QTablewView cells via QItemDelegates. + Format: + [ + { + 'text': {"allow", "True", "online"}, + 'cols': {1,4,5}, + 'color': "green", + 'bgcolor': None, + 'alignment': ["center"], + #"margins': [0, 0] + #'font': {} + }, + ] + + text: will match any of the given texts. + cols: look for patterns on these columns. + color: colorizes the color of the text. + bgcolor: colorizes the background color of the cell. + etc. + """ + + NAME = "highlight" + + MARGINS = "margins" + ALIGNMENT = "alignment" + # QtCore.Qt.AlignCenter + ALIGN_CENTER = "center" + # QtCore.Qt.AlignHCenter + ALIGN_HCENTER = "hcenter" + # QtCore.Qt.AlignVCenter + ALIGN_VCENTER = "vcenter" + + COLOR = "color" + BGCOLOR = "bgcolor" + FONT = "font" + CELLS = "cells" + ROWS = "rows" + COLS = "cols" + TEXT = "text" + + def __init__(self, config): + # original json config received + self._config = config + self._last_visited_row = -1 + self._rowcells = "" + + def compile(self): + """transform json items to Qt objects. + These items are transformed: + - color (to QColor), bgcolor (to QColor), alignment (to Qt.Align*), + font (to QFont TODO) + + Return the original json object transformed. + """ + # cells, rows + for idx in self._config: + cells = self._config[idx] + for cell in cells: + for item in cell: + # colors + if (item == Highlight.COLOR or item == Highlight.BGCOLOR): + if cell[item] != "" and cell[item] is not None: + cell[item] = QColor(cell[item]) + else: + cell[item] = None + + # alignments + if item == Highlight.ALIGNMENT: + cell[item] = self.getAlignment(cell[item]) + + # fonts + if item == Highlight.FONT: + self.getFont(cell[item]) + + return self._config + + + def run(self, args): + """Highlight cells or rows based on patterns. + + Return if the cell was modified. + + Keyword arguments: + args -- tuple of options. + """ + painter = args[0] + option = args[1] + index = args[2] + style = args[3] + modelColumns = args[4] + curRow = args[5] + curColumn = args[6] + defaultPen = args[7] + defaultBrush = args[8] + cellAlignment = args[9] + cellRect = args[10] + cellValue = args[11] + + # signal that this cell has been modified + modified = False + + cells = self._config.get(Highlight.CELLS) + rows = self._config.get(Highlight.ROWS) + + if cells: + for cell in cells: + if curColumn not in cell[Highlight.COLS]: + continue + if cellValue not in cell[Highlight.TEXT]: + continue +# TODO +# if cell['operator'] == 'simple' and cellValue != cell['text']: +# continue +# elif cell['text'] not in cellValue: +# continue + + cellColor = cell.get(Highlight.COLOR) + cellBgColor = cell.get(Highlight.BGCOLOR) + if cell.get(Highlight.ALIGNMENT) != None: + cellAlignment = cell[Highlight.ALIGNMENT] + if cell.get(Highlight.MARGINS) != None: + cellRect.adjust( + int(cell[Highlight.MARGINS][self.HMARGIN]), + int(cell[Highlight.MARGINS][self.VMARGIN]), + -defaultPen.width(), + -defaultPen.width() + ) + + modified=True + self.paintCell( + style, + painter, + option, + index, + defaultPen, + defaultBrush, + cellAlignment, + cellRect, + cellColor, + cellBgColor, + cellValue) + + if len(rows) == 0: + return (modified,) + + # get row's cells only for the first cell of the row, + # then reuse them for the rest of the cells of the current row. + if curRow != self._last_visited_row: + self._rowcells = " ".join( + [index.sibling(curRow, col).data() for col in range(0, modelColumns)] + ) + self._last_visited_row = curRow + + for row in rows: + skip = True + for text in row[Highlight.TEXT]: + if text in self._rowcells: + skip = False + if skip: + continue + + cellColor = row.get(Highlight.COLOR) + cellBgColor = row.get(Highlight.BGCOLOR) + if row.get(Highlight.ALIGNMENT) != None: + cellAlignment = row[Highlight.ALIGNMENT] + if row.get(Highlight.MARGINS) != None: + cellRect.adjust( + int(row[Highlight.MARGINS][self.HMARGIN]), + int(row[Highlight.MARGINS][self.VMARGIN]), + -defaultPen.width(), + -defaultPen.width() + ) + + modified=True + self.paintCell( + style, + painter, + option, + index, + defaultPen, + defaultBrush, + cellAlignment, + cellRect, + cellColor, + cellBgColor, + cellValue) + + return (modified,) + + def paintCell(self, style, painter, option, index, defaultPen, defaultBrush, cellAlignment, cellRect, cellColor, cellBgColor, cellValue): + cellSelected = option.state & Qt.QStyle.State_Selected + + painter.save() + # don't customize selected state + if not cellSelected: + if cellBgColor != None: + painter.fillRect(option.rect, cellBgColor) + + if cellColor is not None: + defaultPen.setColor(cellColor) + painter.setPen(defaultPen) + + # setting option.displayAlignment has no effect here, so we need to + # draw the text. + # FIXME: Drawing the text though, the background color of the SelectedState is + # altered. + # If we called super().paint(), modifying option.palette.* would be + # enough to change the text color, but it wouldn't be aligned: + # option.palette.setColor(QPalette.Text, cellColor) + style.drawItemText(painter, cellRect, cellAlignment, option.palette, True, cellValue) + painter.restore() + + def getAlignment(self, alignments): + alignFlags = 0 + for align in alignments: + if align == Highlight.ALIGN_CENTER: + alignFlags |= QtCore.Qt.AlignCenter + elif align == Highlight.ALIGN_HCENTER: + alignFlags |= QtCore.Qt.AlignHCenter + elif align == Highlight.ALIGN_VCENTER: + alignFlags |= QtCore.Qt.AlignVCenter + + if alignFlags == 0: + return None + + return alignFlags + + def getFont(self, font): + # TODO + pass diff --git a/ui/opensnitch/actions/utils.py b/ui/opensnitch/actions/utils.py new file mode 100644 index 00000000..5c435bed --- /dev/null +++ b/ui/opensnitch/actions/utils.py @@ -0,0 +1,8 @@ +from PyQt5.QtGui import QColor + +def getColorNames(): + """Return the built-in color names that can be used to choose new colors: + https://doc.qt.io/qtforpython-5/PySide2/QtGui/QColor.html#predefined-colors + https://www.w3.org/TR/SVG11/types.html#ColorKeywords + """ + return QColor.colorNames() diff --git a/ui/opensnitch/customwidgets/colorizeddelegate.py b/ui/opensnitch/customwidgets/colorizeddelegate.py new file mode 100644 index 00000000..24e0910b --- /dev/null +++ b/ui/opensnitch/customwidgets/colorizeddelegate.py @@ -0,0 +1,77 @@ +from PyQt5 import Qt, QtCore +from PyQt5.QtWidgets import QApplication + +class ColorizedDelegate(Qt.QItemDelegate): + HMARGIN = 0 + VMARGIN = 1 + + def __init__(self, parent=None, *args, actions={}): + Qt.QItemDelegate.__init__(self, parent, *args) + self._actions = actions + self.modelColumns = parent.model().columnCount() + self._style = QApplication.style() + + def setConfig(self, actions): + self._actions = actions + + #@profile_each_line + def paint(self, painter, option, index): + """Override default widget style to personalize it with our own. + """ + if self._actions.get('actions') == None: + return super().paint(painter, option, index) + if not index.isValid(): + return super().paint(painter, option, index) + cellValue = index.data(QtCore.Qt.DisplayRole) + if cellValue == None: + return super().paint(painter, option, index) + + # initialize new QStyleOptionViewItem with the default options of this + # cell. + option = Qt.QStyleOptionViewItem(option) + + # by default use item's default attributes. + # if we modify any of them, set it to False + nocolor=True + + # don't call these functions in for-loops + cellRect = Qt.QRect(option.rect) + curColumn = index.column() + curRow = index.row() + cellAlignment = option.displayAlignment + defaultPen = painter.pen() + defaultBrush = painter.brush() + + # get default margins in order to respect them. + # option.widget is the QTableView + hmargin = self._style.pixelMetric( + self._style.PM_FocusFrameHMargin, None, option.widget + ) + 1 + vmargin = self._style.pixelMetric( + self._style.PM_FocusFrameVMargin, None, option.widget + ) + 1 + + # set default margins for this cell + cellRect.adjust(hmargin, vmargin, -painter.pen().width(), -painter.pen().width()) + + for a in self._actions['actions']: + action = self._actions['actions'][a] + modified = action.run( + (painter, + option, + index, + self._style, + self.modelColumns, + curRow, + curColumn, + defaultPen, + defaultBrush, + cellAlignment, + cellRect, + cellValue) + ) + if modified[0]: + nocolor=False + + if nocolor: + super().paint(painter, option, index) diff --git a/ui/opensnitch/customwidgets/main.py b/ui/opensnitch/customwidgets/main.py index 4d096f00..4812c49d 100644 --- a/ui/opensnitch/customwidgets/main.py +++ b/ui/opensnitch/customwidgets/main.py @@ -1,4 +1,4 @@ -from PyQt5 import Qt, QtCore +from PyQt5 import QtCore from PyQt5.QtGui import QColor, QStandardItemModel, QStandardItem from PyQt5.QtSql import QSqlQueryModel, QSqlQuery, QSql from PyQt5.QtWidgets import QTableView @@ -7,37 +7,6 @@ import time import math from PyQt5.QtCore import QCoreApplication as QC - -class ColorizedDelegate(Qt.QItemDelegate): - def __init__(self, parent=None, *args, config=None): - Qt.QItemDelegate.__init__(self, parent, *args) - self._config = config - self._alignment = QtCore.Qt.AlignLeft | QtCore.Qt.AlignHCenter - - def paint(self, painter, option, index): - if not index.isValid(): - return super().paint(painter, option, index) - - nocolor=True - - value = index.data(QtCore.Qt.DisplayRole) - for _, what in enumerate(self._config): - if what == value: - nocolor=False - painter.save() - painter.setPen(self._config[what]) - if 'alignment' in self._config: - self._alignment = self._config['alignment'] - - if option.state & Qt.QStyle.State_Selected: - painter.setBrush(painter.brush()) - painter.setPen(painter.pen()) - painter.drawText(option.rect, self._alignment, value) - painter.restore() - - if nocolor == True: - super().paint(painter, option, index) - class ColorizedQSqlQueryModel(QSqlQueryModel): """ model=CustomQSqlQueryModel( diff --git a/ui/opensnitch/dialogs/stats.py b/ui/opensnitch/dialogs/stats.py index 3fdc0c3f..0520b6a5 100644 --- a/ui/opensnitch/dialogs/stats.py +++ b/ui/opensnitch/dialogs/stats.py @@ -17,17 +17,15 @@ from opensnitch.dialogs.firewall import FirewallDialog from opensnitch.dialogs.preferences import PreferencesDialog from opensnitch.dialogs.ruleseditor import RulesEditorDialog from opensnitch.dialogs.processdetails import ProcessDetailsDialog -from opensnitch.customwidgets.main import ColorizedDelegate, ConnectionsTableModel +from opensnitch.customwidgets.colorizeddelegate import ColorizedDelegate from opensnitch.customwidgets.firewalltableview import FirewallTableModel from opensnitch.customwidgets.generictableview import GenericTableModel from opensnitch.customwidgets.addresstablemodel import AddressTableModel from opensnitch.utils import Message, QuickHelp, AsnDB, Icons +from opensnitch.actions import Actions DIALOG_UI_PATH = "%s/../res/stats.ui" % os.path.dirname(sys.modules[__name__].__file__) class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): - RED = QtGui.QColor(0xff, 0x63, 0x47) - GREEN = QtGui.QColor(0x2e, 0x90, 0x59) - PURPLE = QtGui.QColor(0x7f, 0x00, 0xff) _trigger = QtCore.pyqtSignal(bool, bool) settings_saved = QtCore.pyqtSignal() @@ -120,205 +118,162 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): # try to restore last selection LAST_SELECTED_ITEM = "" - commonDelegateConf = { - Config.ACTION_DENY: RED, - Config.ACTION_REJECT: PURPLE, - Config.ACTION_ALLOW: GREEN, - 'alignment': QtCore.Qt.AlignCenter | QtCore.Qt.AlignHCenter - } - - commonTableConf = { - "name": "", + TABLES = { + TAB_MAIN: { + "name": "connections", "label": None, "cmd": None, + "cmdCleanStats": None, "view": None, + "filterLine": None, "model": None, - "delegate": commonDelegateConf, - "display_fields": "*" - } - - TABLES = { - TAB_MAIN: { - "name": "connections", - "label": None, - "cmd": None, - "cmdCleanStats": None, - "view": None, - "filterLine": None, - "model": None, - "delegate": commonDelegateConf, - "display_fields": "time as Time, " \ - "node as Node, " \ - "action as Action, " \ - "CASE dst_host WHEN ''" \ - " THEN dst_ip || ' -> ' || dst_port " \ - " ELSE dst_host || ' -> ' || dst_port " \ - "END Destination, " \ - "protocol as Protocol, " \ - "process as Process, " \ - "process_args as Cmdline, " \ - "rule as Rule", - "group_by": LAST_GROUP_BY, - "last_order_by": "1", - "last_order_to": 1 - }, - TAB_NODES: { - "name": "nodes", - "label": None, - "cmd": None, - "cmdCleanStats": None, - "view": None, - "filterLine": None, - "model": None, - "delegate": { - Config.ACTION_DENY: RED, - Config.ACTION_REJECT: PURPLE, - Config.ACTION_ALLOW: GREEN, - Nodes.OFFLINE: RED, - Nodes.ONLINE: GREEN, - 'alignment': QtCore.Qt.AlignCenter | QtCore.Qt.AlignHCenter - }, - "display_fields": "last_connection as LastConnection, "\ - "addr as Addr, " \ - "status as Status, " \ - "hostname as Hostname, " \ - "daemon_version as Version, " \ - "daemon_uptime as Uptime, " \ - "daemon_rules as Rules," \ - "cons as Connections," \ - "cons_dropped as Dropped," \ - "version as Version", - "header_labels": [], - "last_order_by": "1", - "last_order_to": 1 - }, - TAB_RULES: { - "name": "rules", - "label": None, - "cmd": None, - "cmdCleanStats": None, - "view": None, - "filterLine": None, - "model": None, - "delegate": commonDelegateConf, - "display_fields": "time as Time," \ - "node as Node," \ - "name as Name," \ - "enabled as Enabled," \ - "action as Action," \ - "duration as Duration," \ - "description as Description", - "header_labels": [], - "last_order_by": "2", - "last_order_to": 0 - }, - TAB_FIREWALL: { - "name": "firewall", - "label": None, - "cmd": None, - "cmdCleanStats": None, - "view": None, - "filterLine": None, - "model": None, - "delegate": { - Config.ACTION_DENY: RED, - Config.ACTION_DROP: RED, - Config.ACTION_STOP: RED, - "DROP": RED, - "ACCEPT": GREEN, - Config.ACTION_REJECT: PURPLE, - Config.ACTION_RETURN: PURPLE, - Config.ACTION_ACCEPT: GREEN, - Config.ACTION_JUMP: GREEN, - Config.ACTION_MASQUERADE: GREEN, - Config.ACTION_SNAT: GREEN, - Config.ACTION_DNAT: GREEN, - Config.ACTION_TPROXY: GREEN, - Config.ACTION_QUEUE: GREEN, - "True": GREEN, - "False": RED, - 'alignment': QtCore.Qt.AlignCenter | QtCore.Qt.AlignHCenter - }, - "display_fields": "*", - "header_labels": [], - "last_order_by": "2", - "last_order_to": 0 - }, - TAB_HOSTS: { - "name": "hosts", - "label": None, - "cmd": None, - "cmdCleanStats": None, - "view": None, - "filterLine": None, - "model": None, - "delegate": commonDelegateConf, - "display_fields": "*", - "header_labels": [], - "last_order_by": "2", - "last_order_to": 1 - }, - TAB_PROCS: { - "name": "procs", - "label": None, - "cmd": None, - "cmdCleanStats": None, - "view": None, - "filterLine": None, - "model": None, - "delegate": commonDelegateConf, - "display_fields": "*", - "header_labels": [], - "last_order_by": "2", - "last_order_to": 1 - }, - TAB_ADDRS: { - "name": "addrs", - "label": None, - "cmd": None, - "cmdCleanStats": None, - "view": None, - "filterLine": None, - "model": None, - "delegate": commonDelegateConf, - "display_fields": "*", - "header_labels": [], - "last_order_by": "2", - "last_order_to": 1 - }, - TAB_PORTS: { - "name": "ports", - "label": None, - "cmd": None, - "cmdCleanStats": None, - "view": None, - "filterLine": None, - "model": None, - "delegate": commonDelegateConf, - "display_fields": "*", - "header_labels": [], - "last_order_by": "2", - "last_order_to": 1 - }, - TAB_USERS: { - "name": "users", - "label": None, - "cmd": None, - "cmdCleanStats": None, - "view": None, - "filterLine": None, - "model": None, - "delegate": commonDelegateConf, - "display_fields": "*", - "header_labels": [], - "last_order_by": "2", - "last_order_to": 1 - } - } + "delegate": "commonDelegateConfig", + "display_fields": "time as Time, " \ + "node as Node, " \ + "action as Action, " \ + "CASE dst_host WHEN ''" \ + " THEN dst_ip || ' -> ' || dst_port " \ + " ELSE dst_host || ' -> ' || dst_port " \ + "END Destination, " \ + "protocol as Protocol, " \ + "process as Process, " \ + "process_args as Cmdline, " \ + "rule as Rule", + "group_by": LAST_GROUP_BY, + "last_order_by": "1", + "last_order_to": 1 + }, + TAB_NODES: { + "name": "nodes", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": "commonDelegateConfig", + "display_fields": "last_connection as LastConnection, "\ + "addr as Addr, " \ + "status as Status, " \ + "hostname as Hostname, " \ + "daemon_version as Version, " \ + "daemon_uptime as Uptime, " \ + "daemon_rules as Rules," \ + "cons as Connections," \ + "cons_dropped as Dropped," \ + "version as Version", + "header_labels": [], + "last_order_by": "1", + "last_order_to": 1 + }, + TAB_RULES: { + "name": "rules", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": "defaultRulesDelegateConfig", + "display_fields": "time as Time," \ + "node as Node," \ + "name as Name," \ + "enabled as Enabled," \ + "action as Action," \ + "duration as Duration," \ + "description as Description", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 0 + }, + TAB_FIREWALL: { + "name": "firewall", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": "defaultFWDelegateConfig", + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 0 + }, + TAB_HOSTS: { + "name": "hosts", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": "commonDelegateConfig", + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1 + }, + TAB_PROCS: { + "name": "procs", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": "commonDelegateConfig", + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1 + }, + TAB_ADDRS: { + "name": "addrs", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": "commonDelegateConfig", + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1 + }, + TAB_PORTS: { + "name": "ports", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": "commonDelegateConfig", + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1 + }, + TAB_USERS: { + "name": "users", + "label": None, + "cmd": None, + "cmdCleanStats": None, + "view": None, + "filterLine": None, + "model": None, + "delegate": "commonDelegateConfig", + "display_fields": "*", + "header_labels": [], + "last_order_by": "2", + "last_order_to": 1 + } + } def __init__(self, parent=None, address=None, db=None, dbname="db", appicon=None): super(StatsDialog, self).__init__(parent) - QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint) self._current_desktop = os.environ['XDG_CURRENT_DESKTOP'] if os.environ.get("XDG_CURRENT_DESKTOP") != None else None @@ -367,6 +322,8 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._nodes = Nodes.instance() self._fw = Firewall().instance() self._fw.rules.rulesUpdated.connect(self._cb_fw_rules_updated) + self._actions = Actions().instance() + self._actions.loadAll() # TODO: allow to display multiples dialogs self._proc_details_dialog = ProcessDetailsDialog(appicon=appicon) @@ -2474,9 +2431,6 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): tableWidget.setSortingEnabled(True) if model == None: model = self._db.get_new_qsql_model() - if delegate != None: - tableWidget.setItemDelegate(ColorizedDelegate(self, config=delegate)) - if verticalScrollBar != None: tableWidget.setVerticalScrollBar(verticalScrollBar) tableWidget.verticalScrollBar().sliderPressed.connect(self._cb_scrollbar_pressed) @@ -2485,6 +2439,11 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.setQuery(model, "SELECT " + fields + " FROM " + table_name + group_by + " ORDER BY " + order_by + " " + sort_direction + limit) tableWidget.setModel(model) + if delegate != None: + action = self._actions.get(delegate) + if action != None: + tableWidget.setItemDelegate(ColorizedDelegate(tableWidget, actions=action)) + header = tableWidget.horizontalHeader() if header != None: header.sortIndicatorChanged.connect(self._cb_table_header_clicked) diff --git a/ui/opensnitch/res/stats.ui b/ui/opensnitch/res/stats.ui index ef093d40..25c9f5f8 100644 --- a/ui/opensnitch/res/stats.ui +++ b/ui/opensnitch/res/stats.ui @@ -897,6 +897,9 @@ QAbstractItemView::SelectRows + + QAbstractItemView::ScrollPerPixel + false