mirror of
https://github.com/evilsocket/opensnitch.git
synced 2025-03-04 00:24:40 +01:00
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:
parent
fa87353746
commit
cba52cf3d8
8 changed files with 766 additions and 231 deletions
157
ui/opensnitch/actions/__init__.py
Normal file
157
ui/opensnitch/actions/__init__.py
Normal 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
|
128
ui/opensnitch/actions/default_configs.py
Normal file
128
ui/opensnitch/actions/default_configs.py
Normal 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": []
|
||||
}
|
||||
}
|
||||
}
|
234
ui/opensnitch/actions/highlight.py
Normal file
234
ui/opensnitch/actions/highlight.py
Normal 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
|
8
ui/opensnitch/actions/utils.py
Normal file
8
ui/opensnitch/actions/utils.py
Normal 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()
|
77
ui/opensnitch/customwidgets/colorizeddelegate.py
Normal file
77
ui/opensnitch/customwidgets/colorizeddelegate.py
Normal 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)
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue