manager/qubesmanager/table_widgets.py

511 lines
18 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 = QtCore.QCoreApplication.translate("QubeManager", "n/a")
elif val is True:
text = QtCore.QCoreApplication.translate("QubeManager", "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 = QtCore.QCoreApplication.translate(
"QubeManager", 'default ({})').format(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(QtCore.QCoreApplication.translate(
"QubeManager", "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
is_disposable = getattr(self.vm, 'auto_cleanup', False)
if not is_disposable and 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 not is_disposable and \
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\">{}</font>".format(
QtCore.QCoreApplication.translate("QubeManager",
"Check updates"))
icon_path = ":/update-recommended.png"
tooltip_text = QtCore.QCoreApplication.translate("QubeManager",
"Updates pending!")
elif state == "outdated":
label_text = "<font color=\"red\">{}</font>".format(
QtCore.QCoreApplication.translate("QubeManager",
"Qube outdated"))
icon_path = ":/outdated.png"
tooltip_text = QtCore.QCoreApplication.translate(
"QubeManager",
"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\">{}</font>".format(
QtCore.QCoreApplication.translate("QubeManager",
"Template running"))
icon_path = ":/to-be-outdated.png"
tooltip_text = QtCore.QCoreApplication.translate(
"QubeManager",
"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(QtCore.QCoreApplication.translate("QubeManager",
"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("")