ui: added Actions

Added ability to perform actions on different parts of the GUI, based on
conditions defined in json files.

There's only one Action of type Highlight for now, to colorize cells and
rows.

There're 3 Highlight actions defined by default:
 - rules: applied to the rules view to colorize the columns Enabled and
   Action.
 - firewall: applied to the fw rules to colorize the columns Action and
   Enabled.
 - common: applied to the rest of the views to colorize the column
   Action.

Users can add new actions to the directory
~/.config/opensnitch/actions/, as .json files. The format is defined
below.

Example of a Highlight action to colorize cells and rows, based on
different texts (simple texts/strings for now):
{
  "name": "commonDelegateConfig",
  "actions": {
    "highlight": {
      "cells": [
        {
          "text": ["allow", "✓ online"],
          "cols": [1, 2, 3],
          "color": "green",
          "bgcolor": "",
          "alignment": ["center"]
        }
      ],
      "rows": [
        {
          "text": ["block-domains"],
          "cols": [8],
          "color": "white",
          "bgcolor": "darkMagenta",
          "alignment": []
        }
      ]
    }
}

Closes: #555
This commit is contained in:
Gustavo Iñiguez Goia 2023-01-21 00:27:31 +01:00
parent fa87353746
commit cba52cf3d8
Failed to generate hash of commit
8 changed files with 766 additions and 231 deletions

View file

@ -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

View file

@ -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": []
}
}
}

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -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(

View file

@ -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)

View file

@ -897,6 +897,9 @@
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>