table_widgets.py 19 KB

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