table_widgets.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  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 PyQt4 import QtGui # pylint: disable=import-error
  23. from PyQt4 import QtCore # pylint: disable=import-error
  24. # pylint: disable=too-few-public-methods
  25. power_order = QtCore.Qt.DescendingOrder
  26. update_order = QtCore.Qt.AscendingOrder
  27. row_height = 30
  28. class VmIconWidget(QtGui.QWidget):
  29. def __init__(self, icon_path, enabled=True, size_multiplier=0.7,
  30. tooltip=None, parent=None, icon_sz=(32, 32)):
  31. super(VmIconWidget, self).__init__(parent)
  32. self.label_icon = QtGui.QLabel()
  33. if icon_path[0] in ':/':
  34. icon = QtGui.QIcon(icon_path)
  35. else:
  36. icon = QtGui.QIcon.fromTheme(icon_path)
  37. icon_sz = QtCore.QSize(row_height * size_multiplier,
  38. row_height * size_multiplier)
  39. icon_pixmap = icon.pixmap(
  40. icon_sz,
  41. QtGui.QIcon.Disabled if not enabled else QtGui.QIcon.Normal)
  42. self.label_icon.setPixmap(icon_pixmap)
  43. self.label_icon.setFixedSize(icon_sz)
  44. if tooltip is not None:
  45. self.label_icon.setToolTip(tooltip)
  46. layout = QtGui.QHBoxLayout()
  47. layout.addWidget(self.label_icon)
  48. layout.setContentsMargins(0, 0, 0, 0)
  49. self.setLayout(layout)
  50. def setToolTip(self, tooltip): # pylint: disable=invalid-name
  51. if tooltip is not None:
  52. self.label_icon.setToolTip(tooltip)
  53. else:
  54. self.label_icon.setToolTip('')
  55. class VmTypeWidget(VmIconWidget):
  56. class VmTypeItem(QtGui.QTableWidgetItem):
  57. def __init__(self, value, vm):
  58. super(VmTypeWidget.VmTypeItem, self).__init__()
  59. self.value = value
  60. self.qid = vm.qid
  61. self.name = vm.name
  62. def set_value(self, value):
  63. self.value = value
  64. #pylint: disable=too-many-return-statements
  65. def __lt__(self, other):
  66. if self.qid == 0:
  67. return True
  68. if other.qid == 0:
  69. return False
  70. if self.value == other.value:
  71. return self.name < other.name
  72. return self.value < other.value
  73. def __init__(self, vm, parent=None):
  74. (icon_path, tooltip) = self.get_vm_icon(vm)
  75. super(VmTypeWidget, self).__init__(
  76. icon_path, True, 0.8, tooltip, parent)
  77. self.vm = vm
  78. self.table_item = self.VmTypeItem(self.value, vm)
  79. self.value = None
  80. # TODO: add "provides network" column
  81. def get_vm_icon(self, vm):
  82. if vm.klass == 'AdminVM':
  83. self.value = 0
  84. icon_name = "dom0"
  85. elif vm.klass == 'TemplateVM':
  86. self.value = 3
  87. icon_name = "templatevm"
  88. elif vm.klass == 'StandaloneVM':
  89. self.value = 4
  90. icon_name = "standalonevm"
  91. else:
  92. self.value = 5 + vm.label.index
  93. icon_name = "appvm"
  94. return ":/" + icon_name + ".png", vm.klass
  95. class VmLabelWidget(VmIconWidget):
  96. class VmLabelItem(QtGui.QTableWidgetItem):
  97. def __init__(self, value, vm):
  98. super(VmLabelWidget.VmLabelItem, self).__init__()
  99. self.value = value
  100. self.qid = vm.qid
  101. self.name = vm.name
  102. def set_value(self, value):
  103. self.value = value
  104. #pylint: disable=too-many-return-statements
  105. def __lt__(self, other):
  106. if self.qid == 0:
  107. return True
  108. if other.qid == 0:
  109. return False
  110. if self.value == other.value:
  111. return self.name < other.name
  112. return self.value < other.value
  113. def __init__(self, vm, parent=None):
  114. icon_path = self.get_vm_icon_path(vm)
  115. super(VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent)
  116. self.vm = vm
  117. self.table_item = self.VmLabelItem(self.value, vm)
  118. self.value = None
  119. def get_vm_icon_path(self, vm):
  120. self.value = vm.label.index
  121. return vm.label.icon
  122. class VmNameItem(QtGui.QTableWidgetItem):
  123. def __init__(self, vm):
  124. super(VmNameItem, self).__init__()
  125. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  126. self.setText(vm.name)
  127. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  128. self.qid = vm.qid
  129. def __lt__(self, other):
  130. if self.qid == 0:
  131. return True
  132. if other.qid == 0:
  133. return False
  134. return super(VmNameItem, self).__lt__(other)
  135. class VmStatusIcon(QtGui.QLabel):
  136. def __init__(self, vm, parent=None):
  137. super(VmStatusIcon, self).__init__(parent)
  138. self.vm = vm
  139. self.set_on_icon()
  140. self.previous_power_state = self.vm.get_power_state()
  141. def update(self):
  142. if self.previous_power_state != self.vm.get_power_state():
  143. self.set_on_icon()
  144. self.previous_power_state = self.vm.get_power_state()
  145. def set_on_icon(self):
  146. if self.vm.get_power_state() == "Running":
  147. icon = QtGui.QIcon(":/on.png")
  148. self.status = 3
  149. elif self.vm.get_power_state() in ["Paused", "Suspended"]:
  150. icon = QtGui.QIcon(":/paused.png")
  151. self.status = 2
  152. elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]:
  153. icon = QtGui.QIcon(":/transient.png")
  154. self.status = 1
  155. else:
  156. icon = QtGui.QIcon(":/off.png")
  157. self.status = 0
  158. icon_sz = QtCore.QSize(row_height * 0.5, row_height * 0.5)
  159. icon_pixmap = icon.pixmap(icon_sz)
  160. self.setPixmap(icon_pixmap)
  161. self.setFixedSize(icon_sz)
  162. class VmInfoWidget(QtGui.QWidget):
  163. class VmInfoItem(QtGui.QTableWidgetItem):
  164. def __init__(self, on_icon, upd_info_item, vm):
  165. super(VmInfoWidget.VmInfoItem, self).__init__()
  166. self.on_icon = on_icon
  167. self.upd_info_item = upd_info_item
  168. self.vm = vm
  169. self.qid = vm.qid
  170. self.name = vm.name
  171. def __lt__(self, other):
  172. # pylint: disable=too-many-return-statements
  173. if self.qid == 0:
  174. return True
  175. if other.qid == 0:
  176. return False
  177. self_val = self.upd_info_item.value
  178. other_val = other.upd_info_item.value
  179. if self.tableWidget().\
  180. horizontalHeader().sortIndicatorOrder() == update_order:
  181. # the result will be sorted by upd, sorting order: Ascending
  182. self_val += 1 if self.on_icon.status > 0 else 0
  183. other_val += 1 if other.on_icon.status > 0 else 0
  184. if self_val == other_val:
  185. return self.name < other.name
  186. return self_val > other_val
  187. if self.tableWidget().\
  188. horizontalHeader().sortIndicatorOrder() == power_order:
  189. # the result will be sorted by power state,
  190. # sorting order: Descending
  191. if self.on_icon.status == other.on_icon.status:
  192. return self.name < other.name
  193. return self_val > other_val
  194. # it would be strange if this happened
  195. return
  196. def __init__(self, vm, parent=None):
  197. super(VmInfoWidget, self).__init__(parent)
  198. self.vm = vm
  199. layout = QtGui.QHBoxLayout()
  200. self.on_icon = VmStatusIcon(vm)
  201. self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
  202. self.error_icon = VmIconWidget(":/warning.png")
  203. self.blk_icon = VmIconWidget(":/mount.png")
  204. self.rec_icon = VmIconWidget(":/mic.png")
  205. layout.addWidget(self.on_icon)
  206. layout.addWidget(self.upd_info)
  207. layout.addWidget(self.error_icon)
  208. layout.addItem(QtGui.QSpacerItem(0, 10,
  209. QtGui.QSizePolicy.Expanding,
  210. QtGui.QSizePolicy.Expanding))
  211. layout.addWidget(self.blk_icon)
  212. layout.addWidget(self.rec_icon)
  213. layout.setContentsMargins(5, 0, 5, 0)
  214. self.setLayout(layout)
  215. self.rec_icon.setVisible(False)
  216. self.blk_icon.setVisible(False)
  217. self.error_icon.setVisible(False)
  218. self.table_item = self.VmInfoItem(self.on_icon,\
  219. self.upd_info.table_item, vm)
  220. def update_vm_state(self):
  221. self.on_icon.update()
  222. self.upd_info.update_outdated()
  223. class VmTemplateItem(QtGui.QTableWidgetItem):
  224. def __init__(self, vm):
  225. super(VmTemplateItem, self).__init__()
  226. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  227. self.vm = vm
  228. self.qid = vm.qid
  229. self.name = vm.name
  230. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  231. self.update()
  232. def update(self):
  233. if getattr(self.vm, 'template', None) is not None:
  234. self.setText(self.vm.template.name)
  235. else:
  236. font = QtGui.QFont()
  237. font.setStyle(QtGui.QFont.StyleItalic)
  238. self.setFont(font)
  239. self.setTextColor(QtGui.QColor("gray"))
  240. self.setText(self.vm.klass)
  241. def __lt__(self, other):
  242. if self.qid == 0:
  243. return True
  244. if other.qid == 0:
  245. return False
  246. if self.text() == other.text():
  247. return self.name < other.name
  248. return super(VmTemplateItem, self).__lt__(other)
  249. class VmNetvmItem(QtGui.QTableWidgetItem):
  250. def __init__(self, vm):
  251. super(VmNetvmItem, self).__init__()
  252. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  253. self.vm = vm
  254. self.qid = vm.qid
  255. self.name = vm.name
  256. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  257. self.update()
  258. def update(self):
  259. if getattr(self.vm, 'netvm', None) is None:
  260. self.setText("n/a")
  261. else:
  262. if self.vm.property_is_default('netvm'):
  263. text = 'default (' + self.vm.netvm.name +')'
  264. else:
  265. text = self.vm.netvm.name
  266. self.setText(text)
  267. def __lt__(self, other):
  268. if self.qid == 0:
  269. return True
  270. if other.qid == 0:
  271. return False
  272. if self.text() == other.text():
  273. return self.name < other.name
  274. return super(VmNetvmItem, self).__lt__(other)
  275. class VmInternalItem(QtGui.QTableWidgetItem):
  276. def __init__(self, vm):
  277. super(VmInternalItem, self).__init__()
  278. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  279. self.vm = vm
  280. self.qid = vm.qid
  281. self.name = vm.name
  282. self.update()
  283. def update(self):
  284. self.internal = self.vm.features.get('internal', False)
  285. self.setText("Yes" if self.internal else "")
  286. def __lt__(self, other):
  287. if self.qid == 0:
  288. return True
  289. if other.qid == 0:
  290. return False
  291. if self.internal == other.internal:
  292. return self.name < other.name
  293. return super(VmInternalItem, self).__lt__(other)
  294. # features man qvm-features
  295. class VmUpdateInfoWidget(QtGui.QWidget):
  296. class VmUpdateInfoItem(QtGui.QTableWidgetItem):
  297. def __init__(self, value, vm):
  298. super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
  299. self.value = 0
  300. self.vm = vm
  301. self.qid = vm.qid
  302. self.name = vm.name
  303. self.set_value(value)
  304. def set_value(self, value):
  305. if value in ("outdated", "to-be-outdated"):
  306. self.value = 30
  307. elif value == "update":
  308. self.value = 20
  309. else:
  310. self.value = 0
  311. def __lt__(self, other):
  312. if self.qid == 0:
  313. return True
  314. if other.qid == 0:
  315. return False
  316. if self.value == other.value:
  317. return self.name < other.name
  318. return self.value < other.value
  319. def __init__(self, vm, show_text=True, parent=None):
  320. super(VmUpdateInfoWidget, self).__init__(parent)
  321. layout = QtGui.QHBoxLayout()
  322. self.show_text = show_text
  323. if self.show_text:
  324. self.label = QtGui.QLabel("")
  325. layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
  326. else:
  327. self.icon = QtGui.QLabel("")
  328. layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
  329. self.setLayout(layout)
  330. self.vm = vm
  331. self.previous_outdated_state = None
  332. self.previous_update_recommended = None
  333. self.value = None
  334. self.table_item = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
  335. self.update_outdated()
  336. def update_outdated(self):
  337. outdated_state = False
  338. if self.vm.is_running():
  339. if hasattr(self.vm, 'template') and self.vm.template.is_running():
  340. outdated_state = "to-be-outdated"
  341. if not outdated_state:
  342. for vol in self.vm.volumes.values():
  343. if vol.is_outdated():
  344. outdated_state = "outdated"
  345. break
  346. if self.vm.klass in {'TemplateVM', 'StandaloneVM'} and \
  347. self.vm.features.get('updates-available', False):
  348. outdated_state = 'update'
  349. self.update_status_widget(outdated_state)
  350. def update_status_widget(self, state):
  351. if state == self.previous_outdated_state:
  352. return
  353. self.previous_outdated_state = state
  354. self.value = state
  355. self.table_item.set_value(state)
  356. if state == "update":
  357. label_text = "<font color=\"#CCCC00\">Check updates</font>"
  358. icon_path = ":/update-recommended.png"
  359. tooltip_text = self.tr("Updates pending!")
  360. elif state == "outdated":
  361. label_text = "<font color=\"red\">Qube outdated</font>"
  362. icon_path = ":/outdated.png"
  363. tooltip_text = self.tr(
  364. "The qube must be restarted for its filesystem to reflect the "
  365. "template's recent committed changes.")
  366. elif state == "to-be-outdated":
  367. label_text = "<font color=\"#800000\">Template running</font>"
  368. icon_path = ":/to-be-outdated.png"
  369. tooltip_text = self.tr(
  370. "The Template must be stopped before changes from its "
  371. "current session can be picked up by this qube.")
  372. else:
  373. icon_path = None
  374. if hasattr(self, 'icon'):
  375. self.icon.setVisible(False)
  376. self.layout().removeWidget(self.icon)
  377. del self.icon
  378. if self.show_text:
  379. self.label.setText(label_text)
  380. else:
  381. if icon_path is not None:
  382. self.icon = VmIconWidget(icon_path, True, 0.7)
  383. self.icon.setToolTip(tooltip_text)
  384. self.layout().addWidget(self.icon,\
  385. alignment=QtCore.Qt.AlignCenter)
  386. self.icon.setVisible(True)
  387. class VmSizeOnDiskItem(QtGui.QTableWidgetItem):
  388. def __init__(self, vm):
  389. super(VmSizeOnDiskItem, self).__init__()
  390. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  391. self.vm = vm
  392. self.qid = vm.qid
  393. self.name = vm.name
  394. self.value = 0
  395. self.update()
  396. self.setTextAlignment(QtCore.Qt.AlignVCenter)
  397. def update(self):
  398. if self.vm.qid == 0:
  399. self.setText("n/a")
  400. else:
  401. self.value = 10
  402. self.value = round(self.vm.get_disk_utilization()/(1024*1024), 2)
  403. self.setText(str(self.value) + " MiB")
  404. def __lt__(self, other):
  405. if self.qid == 0:
  406. return True
  407. if other.qid == 0:
  408. return False
  409. if self.value == other.value:
  410. return self.name < other.name
  411. return self.value < other.value
  412. class VmIPItem(QtGui.QTableWidgetItem):
  413. def __init__(self, vm):
  414. super(VmIPItem, self).__init__()
  415. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  416. self.vm = vm
  417. self.qid = vm.qid
  418. self.name = vm.name
  419. self.update()
  420. def update(self):
  421. self.ip = getattr(self.vm, 'ip', None)
  422. self.setText(self.ip if self.ip is not None else 'n/a')
  423. def __lt__(self, other):
  424. if self.qid == 0:
  425. return True
  426. if other.qid == 0:
  427. return False
  428. if self.ip == other.ip:
  429. return self.name < other.name
  430. return super(VmIPItem, self).__lt__(other)
  431. class VmIncludeInBackupsItem(QtGui.QTableWidgetItem):
  432. def __init__(self, vm):
  433. super(VmIncludeInBackupsItem, self).__init__()
  434. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  435. self.vm = vm
  436. self.name = vm.name
  437. self.qid = vm.qid
  438. self.update()
  439. def update(self):
  440. if getattr(self.vm, 'include_in_backups', None):
  441. self.setText("Yes")
  442. self.include_in_backups = True
  443. else:
  444. self.setText("")
  445. self.include_in_backups = False
  446. def __lt__(self, other):
  447. if self.qid == 0:
  448. return True
  449. if other.qid == 0:
  450. return False
  451. if self.include_in_backups == other.include_in_backups:
  452. return self.name < other.name
  453. return self.include_in_backups < other.include_in_backups
  454. class VmLastBackupItem(QtGui.QTableWidgetItem):
  455. def __init__(self, vm):
  456. super(VmLastBackupItem, self).__init__()
  457. self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
  458. self.vm = vm
  459. self.qid = vm.qid
  460. self.name = vm.name
  461. self.update()
  462. def update(self):
  463. self.backup_timestamp = getattr(self.vm, 'backup_timestamp', None)
  464. if self.backup_timestamp:
  465. self.setText(
  466. str(datetime.datetime.fromtimestamp(self.backup_timestamp)))
  467. else:
  468. self.setText("")
  469. #pylint: disable=too-many-return-statements
  470. def __lt__(self, other):
  471. if self.qid == 0:
  472. return True
  473. if other.qid == 0:
  474. return False
  475. if self.backup_timestamp == other.backup_timestamp:
  476. return self.name < other.name
  477. if not self.backup_timestamp:
  478. return False
  479. if not other.backup_timestamp:
  480. return True
  481. return self.backup_timestamp < other.backup_timestamp