table_widgets.py 20 KB

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