table_widgets.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. #!/usr/bin/python3
  2. # -*- coding: utf8 -*-
  3. #
  4. # The Qubes OS Project, http://www.qubes-os.org
  5. #
  6. # Copyright (C) 2014 Marek Marczykowski-Górecki
  7. # <marmarek@invisiblethingslab.com>
  8. #
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Lesser General Public License along
  20. # with this program; if not, see <http://www.gnu.org/licenses/>.
  21. import datetime
  22. from PyQt5 import QtWidgets, QtCore, QtGui # pylint: disable=import-error
  23. # pylint: disable=too-few-public-methods
  24. power_order = QtCore.Qt.DescendingOrder
  25. update_order = QtCore.Qt.AscendingOrder
  26. row_height = 30
  27. class VmIconWidget(QtWidgets.QWidget):
  28. def __init__(self, icon_path, enabled=True, size_multiplier=0.7,
  29. tooltip=None, parent=None,
  30. icon_sz=(32, 32)): # pylint: disable=unused-argument
  31. super(VmIconWidget, self).__init__(parent)
  32. self.enabled = enabled
  33. self.size_multiplier = size_multiplier
  34. self.label_icon = QtWidgets.QLabel()
  35. self.set_icon(icon_path)
  36. if tooltip is not None:
  37. self.label_icon.setToolTip(tooltip)
  38. layout = QtWidgets.QHBoxLayout()
  39. layout.addWidget(self.label_icon)
  40. layout.setContentsMargins(0, 0, 0, 0)
  41. self.setLayout(layout)
  42. def setToolTip(self, tooltip): # pylint: disable=invalid-name
  43. if tooltip is not None:
  44. self.label_icon.setToolTip(tooltip)
  45. else:
  46. self.label_icon.setToolTip('')
  47. def set_icon(self, icon_path):
  48. if icon_path[0] in ':/':
  49. icon = QtGui.QIcon(icon_path)
  50. else:
  51. icon = QtGui.QIcon.fromTheme(icon_path)
  52. icon_sz = QtCore.QSize(row_height * self.size_multiplier,
  53. row_height * self.size_multiplier)
  54. icon_pixmap = icon.pixmap(
  55. icon_sz,
  56. QtGui.QIcon.Disabled if not self.enabled else QtGui.QIcon.Normal)
  57. self.label_icon.setPixmap(icon_pixmap)
  58. self.label_icon.setFixedSize(icon_sz)
  59. class VmTypeWidget(VmIconWidget):
  60. class VmTypeItem(QtWidgets.QTableWidgetItem):
  61. def __init__(self, value, vm):
  62. super(VmTypeWidget.VmTypeItem, self).__init__()
  63. self.value = value
  64. self.qid = vm.qid
  65. self.name = vm.name
  66. def set_value(self, value):
  67. self.value = value
  68. # pylint: disable=too-many-return-statements
  69. def __lt__(self, other):
  70. if self.qid == 0:
  71. return True
  72. if other.qid == 0:
  73. return False
  74. if self.value == other.value:
  75. return self.name < other.name
  76. return self.value < other.value
  77. def __init__(self, vm, parent=None):
  78. (icon_path, tooltip) = self.get_vm_icon(vm)
  79. super(VmTypeWidget, self).__init__(
  80. icon_path, True, 0.8, tooltip, parent)
  81. self.vm = vm
  82. self.table_item = self.VmTypeItem(self.value, vm)
  83. self.value = None
  84. # TODO: add "provides network" column
  85. def get_vm_icon(self, vm):
  86. if vm.klass == 'AdminVM':
  87. self.value = 0
  88. icon_name = "dom0"
  89. elif vm.klass == 'TemplateVM':
  90. self.value = 3
  91. icon_name = "templatevm"
  92. elif vm.klass == 'StandaloneVM':
  93. self.value = 4
  94. icon_name = "standalonevm"
  95. else:
  96. self.value = 5 + vm.label.index
  97. icon_name = "appvm"
  98. return ":/" + icon_name + ".png", vm.klass
  99. class VmLabelWidget(VmIconWidget):
  100. class VmLabelItem(QtWidgets.QTableWidgetItem):
  101. def __init__(self, value, vm):
  102. super(VmLabelWidget.VmLabelItem, self).__init__()
  103. self.value = value
  104. self.qid = vm.qid
  105. self.name = vm.name
  106. def set_value(self, value):
  107. self.value = value
  108. # pylint: disable=too-many-return-statements
  109. def __lt__(self, other):
  110. if self.qid == 0:
  111. return True
  112. if other.qid == 0:
  113. return False
  114. if self.value == other.value:
  115. return self.name < other.name
  116. return self.value < other.value
  117. def __init__(self, vm, parent=None):
  118. self.icon_path = self.get_vm_icon_path(vm)
  119. super(VmLabelWidget, self).__init__(self.icon_path,
  120. True, 0.8, None, parent)
  121. self.vm = vm
  122. self.table_item = self.VmLabelItem(self.value, vm)
  123. self.value = None
  124. def get_vm_icon_path(self, vm):
  125. self.value = vm.label.index
  126. return vm.label.icon
  127. def update(self):
  128. icon_path = self.get_vm_icon_path(self.vm)
  129. if icon_path != self.icon_path:
  130. self.icon_path = icon_path
  131. self.set_icon(icon_path)
  132. class VmStatusIcon(QtWidgets.QLabel):
  133. def __init__(self, vm, parent=None):
  134. super(VmStatusIcon, self).__init__(parent)
  135. self.vm = vm
  136. self.status = None
  137. self.set_on_icon()
  138. self.previous_power_state = self.vm.get_power_state()
  139. def update(self):
  140. if self.previous_power_state != self.vm.get_power_state():
  141. self.set_on_icon()
  142. self.previous_power_state = self.vm.get_power_state()
  143. def set_on_icon(self):
  144. if self.vm.get_power_state() == "Running":
  145. icon = QtGui.QIcon(":/on.png")
  146. self.status = 3
  147. elif self.vm.get_power_state() in ["Paused", "Suspended"]:
  148. icon = QtGui.QIcon(":/paused.png")
  149. self.status = 2
  150. elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]:
  151. icon = QtGui.QIcon(":/transient.png")
  152. self.status = 1
  153. else:
  154. icon = QtGui.QIcon(":/off.png")
  155. self.status = 0
  156. icon_sz = QtCore.QSize(row_height * 0.5, row_height * 0.5)
  157. icon_pixmap = icon.pixmap(icon_sz)
  158. self.setPixmap(icon_pixmap)
  159. self.setFixedSize(icon_sz)
  160. class VmInfoWidget(QtWidgets.QWidget):
  161. class VmInfoItem(QtWidgets.QTableWidgetItem):
  162. def __init__(self, on_icon, upd_info_item, vm):
  163. super(VmInfoWidget.VmInfoItem, self).__init__()
  164. self.on_icon = on_icon
  165. self.upd_info_item = upd_info_item
  166. self.vm = vm
  167. self.qid = vm.qid
  168. self.name = vm.name
  169. def __lt__(self, other):
  170. # pylint: disable=too-many-return-statements
  171. if self.qid == 0:
  172. return True
  173. if other.qid == 0:
  174. return False
  175. self_val = self.upd_info_item.value
  176. other_val = other.upd_info_item.value
  177. if self.tableWidget().\
  178. horizontalHeader().sortIndicatorOrder() == update_order:
  179. # the result will be sorted by upd, sorting order: Ascending
  180. self_val += 1 if self.on_icon.status > 0 else 0
  181. other_val += 1 if other.on_icon.status > 0 else 0
  182. if self_val == other_val:
  183. return self.name < other.name
  184. return self_val > other_val
  185. if self.tableWidget().\
  186. horizontalHeader().sortIndicatorOrder() == power_order:
  187. # the result will be sorted by power state,
  188. # sorting order: Descending
  189. if self.on_icon.status == other.on_icon.status:
  190. return self.name < other.name
  191. return self_val > other_val
  192. # it would be strange if this happened
  193. return
  194. def __init__(self, vm, parent=None):
  195. super(VmInfoWidget, self).__init__(parent)
  196. self.vm = vm
  197. layout = QtWidgets.QHBoxLayout()
  198. self.on_icon = VmStatusIcon(vm)
  199. self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
  200. self.error_icon = VmIconWidget(":/warning.png")
  201. self.blk_icon = VmIconWidget(":/mount.png")
  202. self.rec_icon = VmIconWidget(":/mic.png")
  203. layout.addWidget(self.on_icon)
  204. layout.addWidget(self.upd_info)
  205. layout.addWidget(self.error_icon)
  206. layout.addItem(QtWidgets.QSpacerItem(0, 10,
  207. QtWidgets.QSizePolicy.Expanding,
  208. QtWidgets.QSizePolicy.Expanding))
  209. layout.addWidget(self.blk_icon)
  210. layout.addWidget(self.rec_icon)
  211. layout.setContentsMargins(5, 0, 5, 0)
  212. self.setLayout(layout)
  213. self.rec_icon.setVisible(False)
  214. self.blk_icon.setVisible(False)
  215. self.error_icon.setVisible(False)
  216. self.table_item = self.VmInfoItem(self.on_icon,
  217. self.upd_info.table_item, vm)
  218. def update_vm_state(self):
  219. self.on_icon.update()
  220. self.upd_info.update_outdated()
  221. class VMPropertyItem(QtWidgets.QTableWidgetItem):
  222. def __init__(self, vm, property_name, empty_function=(lambda x: False),
  223. check_default=False):
  224. """
  225. Class used to represent Qube Manager table widget.
  226. :param vm: vm object
  227. :param property_name: name of the property the widget represents
  228. :param empty_function: a function that, when applied to values of
  229. vm.property_name, returns True when the property value should be
  230. represented as an empty string and False otherwise; by default this
  231. function always returns false (vm.property_name is represented by an
  232. empty string only when it actually is one)
  233. :param check_default: if True, the widget will prepend its text with
  234. "default" if the if the property is set to DEFAULT
  235. """
  236. super(VMPropertyItem, self).__init__()
  237. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  238. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  239. self.vm = vm
  240. self.qid = vm.qid
  241. self.property_name = property_name
  242. self.name = vm.name
  243. self.empty_function = empty_function
  244. self.check_default = check_default
  245. self.update()
  246. def update(self):
  247. val = getattr(self.vm, self.property_name, None)
  248. if self.empty_function(val):
  249. text = ""
  250. elif val is None:
  251. text = QtCore.QCoreApplication.translate("QubeManager", "n/a")
  252. elif val is True:
  253. text = QtCore.QCoreApplication.translate("QubeManager", "Yes")
  254. else:
  255. text = str(val)
  256. if self.check_default and hasattr(self.vm, self.property_name) and \
  257. self.vm.property_is_default(self.property_name):
  258. text = QtCore.QCoreApplication.translate(
  259. "QubeManager", 'default ({})').format(text)
  260. self.setText(text)
  261. def __lt__(self, other):
  262. if self.qid == 0:
  263. return True
  264. if other.qid == 0:
  265. return False
  266. if self.text() == other.text():
  267. return self.name < other.name
  268. return super(VMPropertyItem, self).__lt__(other)
  269. class VmTemplateItem(VMPropertyItem):
  270. def __init__(self, vm):
  271. super(VmTemplateItem, self).__init__(vm, "template")
  272. def update(self):
  273. if getattr(self.vm, 'template', None) is not None:
  274. self.setText(self.vm.template.name)
  275. else:
  276. font = QtGui.QFont()
  277. font.setStyle(QtGui.QFont.StyleItalic)
  278. self.setFont(font)
  279. self.setForeground(QtGui.QBrush(QtGui.QColor("gray")))
  280. self.setText(self.vm.klass)
  281. class VmInternalItem(VMPropertyItem):
  282. def __init__(self, vm):
  283. super(VmInternalItem, self).__init__(vm, None)
  284. def update(self):
  285. internal = self.vm.features.get('internal', False)
  286. self.setText(QtCore.QCoreApplication.translate(
  287. "QubeManager", "Yes") if internal else "")
  288. # features man qvm-features
  289. class VmUpdateInfoWidget(QtWidgets.QWidget):
  290. class VmUpdateInfoItem(QtWidgets.QTableWidgetItem):
  291. def __init__(self, value, vm):
  292. super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
  293. self.value = 0
  294. self.vm = vm
  295. self.qid = vm.qid
  296. self.name = vm.name
  297. self.set_value(value)
  298. def set_value(self, value):
  299. if value in ("outdated", "to-be-outdated"):
  300. self.value = 30
  301. elif value == "update":
  302. self.value = 20
  303. else:
  304. self.value = 0
  305. def __lt__(self, other):
  306. if self.qid == 0:
  307. return True
  308. if other.qid == 0:
  309. return False
  310. if self.value == other.value:
  311. return self.name < other.name
  312. return self.value < other.value
  313. def __init__(self, vm, show_text=True, parent=None):
  314. super(VmUpdateInfoWidget, self).__init__(parent)
  315. layout = QtWidgets.QHBoxLayout()
  316. self.show_text = show_text
  317. if self.show_text:
  318. self.label = QtWidgets.QLabel("")
  319. layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
  320. else:
  321. self.icon = QtWidgets.QLabel("")
  322. layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
  323. self.setLayout(layout)
  324. self.vm = vm
  325. self.previous_outdated_state = None
  326. self.previous_update_recommended = None
  327. self.value = None
  328. self.table_item = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
  329. self.update_outdated()
  330. def update_outdated(self):
  331. outdated_state = False
  332. is_disposable = getattr(self.vm, 'auto_cleanup', False)
  333. if not is_disposable and self.vm.is_running():
  334. if hasattr(self.vm, 'template') and self.vm.template.is_running():
  335. outdated_state = "to-be-outdated"
  336. if not outdated_state:
  337. for vol in self.vm.volumes.values():
  338. if vol.is_outdated():
  339. outdated_state = "outdated"
  340. break
  341. if not is_disposable and \
  342. self.vm.klass in {'TemplateVM', 'StandaloneVM'} and \
  343. self.vm.features.get('updates-available', False):
  344. outdated_state = 'update'
  345. self.update_status_widget(outdated_state)
  346. def update_status_widget(self, state):
  347. if state == self.previous_outdated_state:
  348. return
  349. self.previous_outdated_state = state
  350. self.value = state
  351. self.table_item.set_value(state)
  352. if state == "update":
  353. label_text = "<font color=\"#CCCC00\">{}</font>".format(
  354. QtCore.QCoreApplication.translate("QubeManager",
  355. "Check updates"))
  356. icon_path = ":/update-recommended.png"
  357. tooltip_text = QtCore.QCoreApplication.translate("QubeManager",
  358. "Updates pending!")
  359. elif state == "outdated":
  360. label_text = "<font color=\"red\">{}</font>".format(
  361. QtCore.QCoreApplication.translate("QubeManager",
  362. "Qube outdated"))
  363. icon_path = ":/outdated.png"
  364. tooltip_text = QtCore.QCoreApplication.translate(
  365. "QubeManager",
  366. "The qube must be restarted for its filesystem to reflect the "
  367. "template's recent committed changes.")
  368. elif state == "to-be-outdated":
  369. label_text = "<font color=\"#800000\">{}</font>".format(
  370. QtCore.QCoreApplication.translate("QubeManager",
  371. "Template running"))
  372. icon_path = ":/to-be-outdated.png"
  373. tooltip_text = QtCore.QCoreApplication.translate(
  374. "QubeManager",
  375. "The Template must be stopped before changes from its "
  376. "current session can be picked up by this qube.")
  377. else:
  378. label_text = None
  379. tooltip_text = None
  380. icon_path = None
  381. if hasattr(self, 'icon'):
  382. self.icon.setVisible(False)
  383. self.layout().removeWidget(self.icon)
  384. del self.icon
  385. if self.show_text:
  386. self.label.setText(label_text)
  387. else:
  388. if icon_path is not None:
  389. self.icon = VmIconWidget(icon_path, True, 0.7)
  390. self.icon.setToolTip(tooltip_text)
  391. self.layout().addWidget(self.icon,
  392. alignment=QtCore.Qt.AlignCenter)
  393. self.icon.setVisible(True)
  394. class VmSizeOnDiskItem(QtWidgets.QTableWidgetItem):
  395. def __init__(self, vm):
  396. super(VmSizeOnDiskItem, self).__init__()
  397. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  398. self.vm = vm
  399. self.qid = vm.qid
  400. self.name = vm.name
  401. self.value = 0
  402. self.update()
  403. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  404. def update(self):
  405. if self.vm.qid == 0:
  406. self.setText(QtCore.QCoreApplication.translate("QubeManager",
  407. "n/a"))
  408. else:
  409. self.value = 10
  410. self.value = round(self.vm.get_disk_utilization()/(1024*1024), 2)
  411. self.setText(str(self.value) + " MiB")
  412. def __lt__(self, other):
  413. if self.qid == 0:
  414. return True
  415. if other.qid == 0:
  416. return False
  417. if self.value == other.value:
  418. return self.name < other.name
  419. return self.value < other.value
  420. class VmLastBackupItem(VMPropertyItem):
  421. def __init__(self, vm, property_name):
  422. super(VmLastBackupItem, self).__init__(vm, property_name)
  423. def update(self):
  424. backup_timestamp = getattr(self.vm, 'backup_timestamp', None)
  425. if backup_timestamp:
  426. self.setText(
  427. str(datetime.datetime.fromtimestamp(backup_timestamp)))
  428. else:
  429. self.setText("")