manager/qubesmanager/table_widgets.py
Marta Marczykowska-Górecka e79724f9db
Update Qt version used in qubesmanager to Qt5
Fixed dependencies, places where obsolete functions stopped working,
code fragments that started throwing warnings and an .ui file that
stopped being readable after the update.
2019-05-30 21:33:49 +02:00

497 lines
17 KiB
Python

#!/usr/bin/python3
# -*- coding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2014 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
import datetime
from PyQt5 import QtWidgets, QtCore, QtGui # pylint: disable=import-error
# pylint: disable=too-few-public-methods
power_order = QtCore.Qt.DescendingOrder
update_order = QtCore.Qt.AscendingOrder
row_height = 30
class VmIconWidget(QtWidgets.QWidget):
def __init__(self, icon_path, enabled=True, size_multiplier=0.7,
tooltip=None, parent=None,
icon_sz=(32, 32)): # pylint: disable=unused-argument
super(VmIconWidget, self).__init__(parent)
self.enabled = enabled
self.size_multiplier = size_multiplier
self.label_icon = QtWidgets.QLabel()
self.set_icon(icon_path)
if tooltip is not None:
self.label_icon.setToolTip(tooltip)
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.label_icon)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
def setToolTip(self, tooltip): # pylint: disable=invalid-name
if tooltip is not None:
self.label_icon.setToolTip(tooltip)
else:
self.label_icon.setToolTip('')
def set_icon(self, icon_path):
if icon_path[0] in ':/':
icon = QtGui.QIcon(icon_path)
else:
icon = QtGui.QIcon.fromTheme(icon_path)
icon_sz = QtCore.QSize(row_height * self.size_multiplier,
row_height * self.size_multiplier)
icon_pixmap = icon.pixmap(
icon_sz,
QtGui.QIcon.Disabled if not self.enabled else QtGui.QIcon.Normal)
self.label_icon.setPixmap(icon_pixmap)
self.label_icon.setFixedSize(icon_sz)
class VmTypeWidget(VmIconWidget):
class VmTypeItem(QtWidgets.QTableWidgetItem):
def __init__(self, value, vm):
super(VmTypeWidget.VmTypeItem, self).__init__()
self.value = value
self.qid = vm.qid
self.name = vm.name
def set_value(self, value):
self.value = value
# pylint: disable=too-many-return-statements
def __lt__(self, other):
if self.qid == 0:
return True
if other.qid == 0:
return False
if self.value == other.value:
return self.name < other.name
return self.value < other.value
def __init__(self, vm, parent=None):
(icon_path, tooltip) = self.get_vm_icon(vm)
super(VmTypeWidget, self).__init__(
icon_path, True, 0.8, tooltip, parent)
self.vm = vm
self.table_item = self.VmTypeItem(self.value, vm)
self.value = None
# TODO: add "provides network" column
def get_vm_icon(self, vm):
if vm.klass == 'AdminVM':
self.value = 0
icon_name = "dom0"
elif vm.klass == 'TemplateVM':
self.value = 3
icon_name = "templatevm"
elif vm.klass == 'StandaloneVM':
self.value = 4
icon_name = "standalonevm"
else:
self.value = 5 + vm.label.index
icon_name = "appvm"
return ":/" + icon_name + ".png", vm.klass
class VmLabelWidget(VmIconWidget):
class VmLabelItem(QtWidgets.QTableWidgetItem):
def __init__(self, value, vm):
super(VmLabelWidget.VmLabelItem, self).__init__()
self.value = value
self.qid = vm.qid
self.name = vm.name
def set_value(self, value):
self.value = value
# pylint: disable=too-many-return-statements
def __lt__(self, other):
if self.qid == 0:
return True
if other.qid == 0:
return False
if self.value == other.value:
return self.name < other.name
return self.value < other.value
def __init__(self, vm, parent=None):
self.icon_path = self.get_vm_icon_path(vm)
super(VmLabelWidget, self).__init__(self.icon_path,
True, 0.8, None, parent)
self.vm = vm
self.table_item = self.VmLabelItem(self.value, vm)
self.value = None
def get_vm_icon_path(self, vm):
self.value = vm.label.index
return vm.label.icon
def update(self):
icon_path = self.get_vm_icon_path(self.vm)
if icon_path != self.icon_path:
self.icon_path = icon_path
self.set_icon(icon_path)
class VmStatusIcon(QtWidgets.QLabel):
def __init__(self, vm, parent=None):
super(VmStatusIcon, self).__init__(parent)
self.vm = vm
self.status = None
self.set_on_icon()
self.previous_power_state = self.vm.get_power_state()
def update(self):
if self.previous_power_state != self.vm.get_power_state():
self.set_on_icon()
self.previous_power_state = self.vm.get_power_state()
def set_on_icon(self):
if self.vm.get_power_state() == "Running":
icon = QtGui.QIcon(":/on.png")
self.status = 3
elif self.vm.get_power_state() in ["Paused", "Suspended"]:
icon = QtGui.QIcon(":/paused.png")
self.status = 2
elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]:
icon = QtGui.QIcon(":/transient.png")
self.status = 1
else:
icon = QtGui.QIcon(":/off.png")
self.status = 0
icon_sz = QtCore.QSize(row_height * 0.5, row_height * 0.5)
icon_pixmap = icon.pixmap(icon_sz)
self.setPixmap(icon_pixmap)
self.setFixedSize(icon_sz)
class VmInfoWidget(QtWidgets.QWidget):
class VmInfoItem(QtWidgets.QTableWidgetItem):
def __init__(self, on_icon, upd_info_item, vm):
super(VmInfoWidget.VmInfoItem, self).__init__()
self.on_icon = on_icon
self.upd_info_item = upd_info_item
self.vm = vm
self.qid = vm.qid
self.name = vm.name
def __lt__(self, other):
# pylint: disable=too-many-return-statements
if self.qid == 0:
return True
if other.qid == 0:
return False
self_val = self.upd_info_item.value
other_val = other.upd_info_item.value
if self.tableWidget().\
horizontalHeader().sortIndicatorOrder() == update_order:
# the result will be sorted by upd, sorting order: Ascending
self_val += 1 if self.on_icon.status > 0 else 0
other_val += 1 if other.on_icon.status > 0 else 0
if self_val == other_val:
return self.name < other.name
return self_val > other_val
if self.tableWidget().\
horizontalHeader().sortIndicatorOrder() == power_order:
# the result will be sorted by power state,
# sorting order: Descending
if self.on_icon.status == other.on_icon.status:
return self.name < other.name
return self_val > other_val
# it would be strange if this happened
return
def __init__(self, vm, parent=None):
super(VmInfoWidget, self).__init__(parent)
self.vm = vm
layout = QtWidgets.QHBoxLayout()
self.on_icon = VmStatusIcon(vm)
self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
self.error_icon = VmIconWidget(":/warning.png")
self.blk_icon = VmIconWidget(":/mount.png")
self.rec_icon = VmIconWidget(":/mic.png")
layout.addWidget(self.on_icon)
layout.addWidget(self.upd_info)
layout.addWidget(self.error_icon)
layout.addItem(QtWidgets.QSpacerItem(0, 10,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding))
layout.addWidget(self.blk_icon)
layout.addWidget(self.rec_icon)
layout.setContentsMargins(5, 0, 5, 0)
self.setLayout(layout)
self.rec_icon.setVisible(False)
self.blk_icon.setVisible(False)
self.error_icon.setVisible(False)
self.table_item = self.VmInfoItem(self.on_icon,
self.upd_info.table_item, vm)
def update_vm_state(self):
self.on_icon.update()
self.upd_info.update_outdated()
class VMPropertyItem(QtWidgets.QTableWidgetItem):
def __init__(self, vm, property_name, empty_function=(lambda x: False),
check_default=False):
"""
Class used to represent Qube Manager table widget.
:param vm: vm object
:param property_name: name of the property the widget represents
:param empty_function: a function that, when applied to values of
vm.property_name, returns True when the property value should be
represented as an empty string and False otherwise; by default this
function always returns false (vm.property_name is represented by an
empty string only when it actually is one)
:param check_default: if True, the widget will prepend its text with
"default" if the if the property is set to DEFAULT
"""
super(VMPropertyItem, self).__init__()
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.setTextAlignment(QtCore.Qt.AlignVCenter)
self.vm = vm
self.qid = vm.qid
self.property_name = property_name
self.name = vm.name
self.empty_function = empty_function
self.check_default = check_default
self.update()
def update(self):
val = getattr(self.vm, self.property_name, None)
if self.empty_function(val):
text = ""
elif val is None:
text = "n/a"
elif val is True:
text = "Yes"
else:
text = str(val)
if self.check_default and hasattr(self.vm, self.property_name) and \
self.vm.property_is_default(self.property_name):
text = 'default (' + text + ')'
self.setText(text)
def __lt__(self, other):
if self.qid == 0:
return True
if other.qid == 0:
return False
if self.text() == other.text():
return self.name < other.name
return super(VMPropertyItem, self).__lt__(other)
class VmTemplateItem(VMPropertyItem):
def __init__(self, vm):
super(VmTemplateItem, self).__init__(vm, "template")
def update(self):
if getattr(self.vm, 'template', None) is not None:
self.setText(self.vm.template.name)
else:
font = QtGui.QFont()
font.setStyle(QtGui.QFont.StyleItalic)
self.setFont(font)
self.setForeground(QtGui.QBrush(QtGui.QColor("gray")))
self.setText(self.vm.klass)
class VmInternalItem(VMPropertyItem):
def __init__(self, vm):
super(VmInternalItem, self).__init__(vm, None)
def update(self):
internal = self.vm.features.get('internal', False)
self.setText("Yes" if internal else "")
# features man qvm-features
class VmUpdateInfoWidget(QtWidgets.QWidget):
class VmUpdateInfoItem(QtWidgets.QTableWidgetItem):
def __init__(self, value, vm):
super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
self.value = 0
self.vm = vm
self.qid = vm.qid
self.name = vm.name
self.set_value(value)
def set_value(self, value):
if value in ("outdated", "to-be-outdated"):
self.value = 30
elif value == "update":
self.value = 20
else:
self.value = 0
def __lt__(self, other):
if self.qid == 0:
return True
if other.qid == 0:
return False
if self.value == other.value:
return self.name < other.name
return self.value < other.value
def __init__(self, vm, show_text=True, parent=None):
super(VmUpdateInfoWidget, self).__init__(parent)
layout = QtWidgets.QHBoxLayout()
self.show_text = show_text
if self.show_text:
self.label = QtWidgets.QLabel("")
layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
else:
self.icon = QtWidgets.QLabel("")
layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
self.setLayout(layout)
self.vm = vm
self.previous_outdated_state = None
self.previous_update_recommended = None
self.value = None
self.table_item = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
self.update_outdated()
def update_outdated(self):
outdated_state = False
if self.vm.is_running():
if hasattr(self.vm, 'template') and self.vm.template.is_running():
outdated_state = "to-be-outdated"
if not outdated_state:
for vol in self.vm.volumes.values():
if vol.is_outdated():
outdated_state = "outdated"
break
if self.vm.klass in {'TemplateVM', 'StandaloneVM'} and \
self.vm.features.get('updates-available', False):
outdated_state = 'update'
self.update_status_widget(outdated_state)
def update_status_widget(self, state):
if state == self.previous_outdated_state:
return
self.previous_outdated_state = state
self.value = state
self.table_item.set_value(state)
if state == "update":
label_text = "<font color=\"#CCCC00\">Check updates</font>"
icon_path = ":/update-recommended.png"
tooltip_text = self.tr("Updates pending!")
elif state == "outdated":
label_text = "<font color=\"red\">Qube outdated</font>"
icon_path = ":/outdated.png"
tooltip_text = self.tr(
"The qube must be restarted for its filesystem to reflect the "
"template's recent committed changes.")
elif state == "to-be-outdated":
label_text = "<font color=\"#800000\">Template running</font>"
icon_path = ":/to-be-outdated.png"
tooltip_text = self.tr(
"The Template must be stopped before changes from its "
"current session can be picked up by this qube.")
else:
label_text = None
tooltip_text = None
icon_path = None
if hasattr(self, 'icon'):
self.icon.setVisible(False)
self.layout().removeWidget(self.icon)
del self.icon
if self.show_text:
self.label.setText(label_text)
else:
if icon_path is not None:
self.icon = VmIconWidget(icon_path, True, 0.7)
self.icon.setToolTip(tooltip_text)
self.layout().addWidget(self.icon,
alignment=QtCore.Qt.AlignCenter)
self.icon.setVisible(True)
class VmSizeOnDiskItem(QtWidgets.QTableWidgetItem):
def __init__(self, vm):
super(VmSizeOnDiskItem, self).__init__()
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.qid = vm.qid
self.name = vm.name
self.value = 0
self.update()
self.setTextAlignment(QtCore.Qt.AlignVCenter)
def update(self):
if self.vm.qid == 0:
self.setText("n/a")
else:
self.value = 10
self.value = round(self.vm.get_disk_utilization()/(1024*1024), 2)
self.setText(str(self.value) + " MiB")
def __lt__(self, other):
if self.qid == 0:
return True
if other.qid == 0:
return False
if self.value == other.value:
return self.name < other.name
return self.value < other.value
class VmLastBackupItem(VMPropertyItem):
def __init__(self, vm, property_name):
super(VmLastBackupItem, self).__init__(vm, property_name)
def update(self):
backup_timestamp = getattr(self.vm, 'backup_timestamp', None)
if backup_timestamp:
self.setText(
str(datetime.datetime.fromtimestamp(backup_timestamp)))
else:
self.setText("")