table_widgets.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. #!/usr/bin/python2
  2. # -*- coding: utf8 -*-
  3. #
  4. # The Qubes OS Project, http://www.qubes-os.org
  5. #
  6. # Copyright (C) 2014 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License
  10. # as published by the Free Software Foundation; either version 2
  11. # of the License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. import os
  22. from PyQt4 import QtGui
  23. from PyQt4.QtCore import QSize, Qt
  24. from PyQt4.QtGui import QTableWidgetItem, QHBoxLayout, QIcon, QLabel, QWidget, \
  25. QSizePolicy, QSpacerItem, QFont, QColor, QProgressBar, QPainter, QPen
  26. import time
  27. from qubes.qubes import vm_files
  28. import main
  29. qubes_dom0_updates_stat_file = '/var/lib/qubes/updates/dom0-updates-available'
  30. power_order = Qt.DescendingOrder
  31. update_order = Qt.AscendingOrder
  32. row_height = 30
  33. class VmIconWidget (QWidget):
  34. def __init__(self, icon_path, enabled=True, size_multiplier=0.7,
  35. tooltip = None, parent=None, icon_sz = (32, 32)):
  36. super(VmIconWidget, self).__init__(parent)
  37. self.label_icon = QLabel()
  38. icon = QIcon (icon_path)
  39. icon_sz = QSize (row_height * size_multiplier, row_height * size_multiplier)
  40. icon_pixmap = icon.pixmap(icon_sz, QIcon.Disabled if not enabled else QIcon.Normal)
  41. self.label_icon.setPixmap (icon_pixmap)
  42. self.label_icon.setFixedSize (icon_sz)
  43. if tooltip != None:
  44. self.label_icon.setToolTip(tooltip)
  45. layout = QHBoxLayout()
  46. layout.addWidget(self.label_icon)
  47. layout.setContentsMargins(0,0,0,0)
  48. self.setLayout(layout)
  49. def setToolTip(self, tooltip):
  50. if tooltip is not None:
  51. self.label_icon.setToolTip(tooltip)
  52. else:
  53. self.label_icon.setToolTip('')
  54. class VmTypeWidget(VmIconWidget):
  55. class VmTypeItem(QTableWidgetItem):
  56. def __init__(self, value, vm):
  57. super(VmTypeWidget.VmTypeItem, self).__init__()
  58. self.value = value
  59. self.vm = vm
  60. def set_value(self, value):
  61. self.value = value
  62. def __lt__(self, other):
  63. if self.value == other.value:
  64. return self.vm.qid < other.vm.qid
  65. else:
  66. return self.value < other.value
  67. def __init__(self, vm, parent=None):
  68. (icon_path, tooltip) = self.get_vm_icon(vm)
  69. super (VmTypeWidget, self).__init__(icon_path, True, 0.8, tooltip, parent)
  70. self.vm = vm
  71. self.tableItem = self.VmTypeItem(self.value, vm)
  72. def get_vm_icon(self, vm):
  73. if vm.qid == 0:
  74. self.value = 0
  75. return (":/dom0.png", "Dom0")
  76. elif vm.is_netvm() and not vm.is_proxyvm():
  77. self.value = 1
  78. return (":/netvm.png", "NetVM")
  79. elif vm.is_proxyvm():
  80. self.value = 2
  81. return (":/proxyvm.png", "ProxyVM")
  82. elif vm.is_appvm() and vm.template is None:
  83. self.value = 4
  84. return (":/standalonevm.png", "StandaloneVM")
  85. elif vm.is_template():
  86. self.value = 3
  87. return (":/templatevm.png", "TemplateVM")
  88. elif vm.is_appvm() or vm.is_disposablevm():
  89. self.value = 5 + vm.label.index
  90. return (":/appvm.png", "AppVM")
  91. class VmLabelWidget(VmIconWidget):
  92. class VmLabelItem(QTableWidgetItem):
  93. def __init__(self, value, vm):
  94. super(VmLabelWidget.VmLabelItem, self).__init__()
  95. self.value = value
  96. self.vm = vm
  97. def set_value(self, value):
  98. self.value = value
  99. def __lt__(self, other):
  100. if self.value == other.value:
  101. return self.vm.qid < other.vm.qid
  102. else:
  103. return self.value < other.value
  104. def __init__(self, vm, parent=None):
  105. icon_path = self.get_vm_icon_path(vm)
  106. super (VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent)
  107. self.vm = vm
  108. self.tableItem = self.VmLabelItem(self.value, vm)
  109. def get_vm_icon_path(self, vm):
  110. if vm.qid == 0:
  111. self.value = 100
  112. return ":/off.png"
  113. else:
  114. self.value = vm.label.index
  115. return vm.label.icon_path
  116. class VmNameItem (QTableWidgetItem):
  117. def __init__(self, vm):
  118. super(VmNameItem, self).__init__()
  119. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  120. self.setText(vm.name)
  121. self.setTextAlignment(Qt.AlignVCenter)
  122. self.qid = vm.qid
  123. class VmStatusIcon(QLabel):
  124. def __init__(self, vm, parent=None):
  125. super (VmStatusIcon, self).__init__(parent)
  126. self.vm = vm
  127. self.set_on_icon()
  128. self.previous_power_state = vm.last_power_state
  129. def update(self):
  130. if self.previous_power_state != self.vm.last_power_state:
  131. self.set_on_icon()
  132. self.previous_power_state = self.vm.last_power_state
  133. def set_on_icon(self):
  134. if self.vm.last_power_state == "Running":
  135. icon = QIcon (":/on.png")
  136. elif self.vm.last_power_state in ["Paused"]:
  137. icon = QIcon (":/paused.png")
  138. elif self.vm.last_power_state in ["Transient", "Halting", "Dying"]:
  139. icon = QIcon (":/transient.png")
  140. else:
  141. icon = QIcon (":/off.png")
  142. icon_sz = QSize (row_height * 0.5, row_height *0.5)
  143. icon_pixmap = icon.pixmap(icon_sz)
  144. self.setPixmap (icon_pixmap)
  145. self.setFixedSize (icon_sz)
  146. class VmInfoWidget (QWidget):
  147. class VmInfoItem (QTableWidgetItem):
  148. def __init__(self, upd_info_item, vm):
  149. super(VmInfoWidget.VmInfoItem, self).__init__()
  150. self.upd_info_item = upd_info_item
  151. self.vm = vm
  152. def __lt__(self, other):
  153. self_val = self.upd_info_item.value
  154. other_val = other.upd_info_item.value
  155. if self.tableWidget().horizontalHeader().sortIndicatorOrder() == update_order:
  156. # the result will be sorted by upd, sorting order: Ascending
  157. self_val += 1 if self.vm.is_running() else 0
  158. other_val += 1 if other.vm.is_running() else 0
  159. if self_val == other_val:
  160. return self.vm.qid < other.vm.qid
  161. else:
  162. return self_val > other_val
  163. elif self.tableWidget().horizontalHeader().sortIndicatorOrder() == power_order:
  164. #the result will be sorted by power state, sorting order: Descending
  165. self_val = -(self_val/10 + 10*(1 if self.vm.is_running() else 0))
  166. other_val = -(other_val/10 + 10*(1 if other.vm.is_running() else 0))
  167. if self_val == other_val:
  168. return self.vm.qid < other.vm.qid
  169. else:
  170. return self_val > other_val
  171. else:
  172. #it would be strange if this happened
  173. return
  174. def __init__(self, vm, parent = None):
  175. super (VmInfoWidget, self).__init__(parent)
  176. self.vm = vm
  177. layout = QHBoxLayout ()
  178. self.on_icon = VmStatusIcon(vm)
  179. self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
  180. self.error_icon = VmIconWidget(":/warning.png")
  181. self.blk_icon = VmIconWidget(":/mount.png")
  182. self.rec_icon = VmIconWidget(":/mic.png")
  183. layout.addWidget(self.on_icon)
  184. layout.addWidget(self.upd_info)
  185. layout.addWidget(self.error_icon)
  186. layout.addItem(QSpacerItem(0, 10, QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
  187. layout.addWidget(self.blk_icon)
  188. layout.addWidget(self.rec_icon)
  189. layout.setContentsMargins(5,0,5,0)
  190. self.setLayout(layout)
  191. self.rec_icon.setVisible(False)
  192. self.blk_icon.setVisible(False)
  193. self.error_icon.setVisible(False)
  194. self.tableItem = self.VmInfoItem(self.upd_info.tableItem, vm)
  195. def update_vm_state(self, vm, blk_visible, rec_visible=None):
  196. self.on_icon.update()
  197. self.upd_info.update_outdated(vm)
  198. if blk_visible != None:
  199. self.blk_icon.setVisible(blk_visible)
  200. if rec_visible != None:
  201. self.rec_icon.setVisible(rec_visible)
  202. self.error_icon.setToolTip(vm.qubes_manager_state[main.QMVmState
  203. .ErrorMsg])
  204. self.error_icon.setVisible(vm.qubes_manager_state[main.QMVmState
  205. .ErrorMsg] is not None)
  206. class VmTemplateItem (QTableWidgetItem):
  207. def __init__(self, vm):
  208. super(VmTemplateItem, self).__init__()
  209. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  210. self.vm = vm
  211. if vm.template is not None:
  212. self.setText(vm.template.name)
  213. else:
  214. font = QFont()
  215. font.setStyle(QFont.StyleItalic)
  216. self.setFont(font)
  217. self.setTextColor(QColor("gray"))
  218. if vm.is_appvm(): # and vm.template is None
  219. self.setText("StandaloneVM")
  220. elif vm.is_template():
  221. self.setText("TemplateVM")
  222. elif vm.qid == 0:
  223. self.setText("AdminVM")
  224. elif vm.is_netvm():
  225. self.setText("NetVM")
  226. else:
  227. self.setText("---")
  228. self.setTextAlignment(Qt.AlignVCenter)
  229. def __lt__(self, other):
  230. if self.text() == other.text():
  231. return self.vm.qid < other.vm.qid
  232. else:
  233. return super(VmTemplateItem, self).__lt__(other)
  234. class VmNetvmItem (QTableWidgetItem):
  235. def __init__(self, vm):
  236. super(VmNetvmItem, self).__init__()
  237. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  238. self.vm = vm
  239. if vm.is_netvm() and not vm.is_proxyvm():
  240. self.setText("n/a")
  241. elif vm.netvm is not None:
  242. self.setText(vm.netvm.name)
  243. else:
  244. self.setText("---")
  245. self.setTextAlignment(Qt.AlignVCenter)
  246. def __lt__(self, other):
  247. if self.text() == other.text():
  248. return self.vm.qid < other.vm.qid
  249. else:
  250. return super(VmNetvmItem, self).__lt__(other)
  251. class VmInternalItem(QTableWidgetItem):
  252. def __init__(self, vm):
  253. super(VmInternalItem, self).__init__()
  254. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  255. self.internal = vm.internal
  256. if self.internal:
  257. self.setText("Yes")
  258. else:
  259. self.setText("")
  260. class VmUsageBarWidget (QWidget):
  261. class VmUsageBarItem (QTableWidgetItem):
  262. def __init__(self, value, vm):
  263. super(VmUsageBarWidget.VmUsageBarItem, self).__init__()
  264. self.value = value
  265. self.vm = vm
  266. def set_value(self, value):
  267. self.value = value
  268. def __lt__(self, other):
  269. if self.value == other.value:
  270. return self.vm.qid < other.vm.qid
  271. else:
  272. return int(self.value) < int(other.value)
  273. def __init__(self, min, max, format, update_func, vm, load, hue=210, parent = None):
  274. super (VmUsageBarWidget, self).__init__(parent)
  275. self.min = min
  276. self.max = max
  277. self.update_func = update_func
  278. self.value = min
  279. self.widget = QProgressBar()
  280. self.widget.setMinimum(min)
  281. self.widget.setMaximum(max)
  282. self.widget.setFormat(format)
  283. self.widget.setStyleSheet(
  284. "QProgressBar:horizontal{" +\
  285. "border: 1px solid hsv({0}, 100, 250);".format(hue) +\
  286. "border-radius: 4px;\
  287. background: white;\
  288. text-align: center;\
  289. }\
  290. QProgressBar::chunk:horizontal {\
  291. background: qlineargradient(x1: 0, y1: 0.5, x2: 1, y2: 0.5, " +\
  292. "stop: 0 hsv({0}, 170, 207),".format(hue) +
  293. " stop: 1 white); \
  294. }"
  295. )
  296. layout = QHBoxLayout()
  297. layout.addWidget(self.widget)
  298. self.setLayout(layout)
  299. self.tableItem = self.VmUsageBarItem(min, vm)
  300. self.update_load(vm, load)
  301. def update_load(self, vm, load):
  302. self.value = self.update_func(vm, load)
  303. self.widget.setValue(self.value)
  304. self.tableItem.set_value(self.value)
  305. class ChartWidget (QWidget):
  306. class ChartItem (QTableWidgetItem):
  307. def __init__(self, value, vm):
  308. super(ChartWidget.ChartItem, self).__init__()
  309. self.value = value
  310. self.vm = vm
  311. def set_value(self, value):
  312. self.value = value
  313. def __lt__(self, other):
  314. if self.value == other.value:
  315. return self.vm.qid < other.vm.qid
  316. else:
  317. return self.value < other.value
  318. def __init__(self, vm, update_func, hue, load = 0, parent = None):
  319. super (ChartWidget, self).__init__(parent)
  320. self.update_func = update_func
  321. self.hue = hue
  322. if hue < 0 or hue > 255:
  323. self.hue = 255
  324. self.load = load
  325. assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
  326. self.load_history = [self.load]
  327. self.tableItem = ChartWidget.ChartItem(self.load, vm)
  328. def update_load (self, vm, load):
  329. self.load = self.update_func(vm, load)
  330. assert self.load >= 0, "load = {0}".format(self.load)
  331. # assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
  332. if self.load > 100:
  333. # FIXME: This is an ugly workaround for cpu_load:/
  334. self.load = 100
  335. self.load_history.append (self.load)
  336. self.tableItem.set_value(self.load)
  337. self.repaint()
  338. def paintEvent (self, Event = None):
  339. p = QPainter (self)
  340. dx = 4
  341. W = self.width()
  342. H = self.height() - 5
  343. N = len(self.load_history)
  344. if N > W/dx:
  345. tail = N - W/dx
  346. N = W/dx
  347. self.load_history = self.load_history[tail:]
  348. assert len(self.load_history) == N
  349. for i in range (0, N-1):
  350. val = self.load_history[N- i - 1]
  351. sat = 70 + val*(255-70)/100
  352. color = QColor.fromHsv (self.hue, sat, 255)
  353. pen = QPen (color)
  354. pen.setWidth(dx-1)
  355. p.setPen(pen)
  356. if val > 0:
  357. p.drawLine (W - i*dx - dx, H , W - i*dx - dx, H - (H - 5) * val/100)
  358. class VmUpdateInfoWidget(QWidget):
  359. class VmUpdateInfoItem (QTableWidgetItem):
  360. def __init__(self, value, vm):
  361. super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
  362. self.value = 0
  363. self.vm = vm
  364. self.set_value(value)
  365. def set_value(self, value):
  366. if value == "outdated":
  367. self.value = 30
  368. elif value == "update":
  369. self.value = 20
  370. else:
  371. self.value = 0
  372. def __lt__(self, other):
  373. if self.value == other.value:
  374. return self.vm.qid < other.vm.qid
  375. else:
  376. return self.value < other.value
  377. def __init__(self, vm, show_text=True, parent = None):
  378. super (VmUpdateInfoWidget, self).__init__(parent)
  379. layout = QHBoxLayout ()
  380. self.show_text = show_text
  381. if self.show_text:
  382. self.label=QLabel("")
  383. layout.addWidget(self.label, alignment=Qt.AlignCenter)
  384. else:
  385. self.icon = QLabel("")
  386. layout.addWidget(self.icon, alignment=Qt.AlignCenter)
  387. self.setLayout(layout)
  388. self.previous_outdated = False
  389. self.previous_update_recommended = None
  390. self.value = None
  391. self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
  392. def update_outdated(self, vm):
  393. if vm.type == "HVM":
  394. return
  395. outdated = vm.is_outdated()
  396. if outdated and not self.previous_outdated:
  397. self.update_status_widget("outdated")
  398. elif not outdated and self.previous_outdated:
  399. self.update_status_widget(None)
  400. self.previous_outdated = outdated
  401. if not vm.is_updateable():
  402. return
  403. if vm.qid == 0:
  404. update_recommended = self.previous_update_recommended
  405. if os.path.exists(qubes_dom0_updates_stat_file):
  406. update_recommended = True
  407. else:
  408. update_recommended = False
  409. else:
  410. update_recommended = self.previous_update_recommended
  411. stat_file_path = vm.dir_path + '/' + vm_files["updates_stat_file"]
  412. if not os.path.exists(stat_file_path):
  413. update_recommended = False
  414. else:
  415. if (not hasattr(vm, "updates_stat_file_read_time")) or vm.updates_stat_file_read_time <= os.path.getmtime(stat_file_path):
  416. stat_file = open(stat_file_path, "r")
  417. updates = stat_file.read().strip()
  418. stat_file.close()
  419. if updates.isdigit():
  420. updates = int(updates)
  421. else:
  422. updates = 0
  423. if updates == 0:
  424. update_recommended = False
  425. else:
  426. update_recommended = True
  427. vm.updates_stat_file_read_time = time.time()
  428. if update_recommended and not self.previous_update_recommended:
  429. self.update_status_widget("update")
  430. elif self.previous_update_recommended and not update_recommended:
  431. self.update_status_widget(None)
  432. self.previous_update_recommended = update_recommended
  433. def update_status_widget(self, state):
  434. self.value = state
  435. self.tableItem.set_value(state)
  436. if state == "update":
  437. label_text = "<font color=\"#CCCC00\">Check updates</font>"
  438. icon_path = ":/update-recommended.png"
  439. tooltip_text = "Updates pending!"
  440. elif state == "outdated":
  441. label_text = "<font color=\"red\">VM outdated</font>"
  442. icon_path = ":/outdated.png"
  443. tooltip_text = "The VM must be restarted for its filesystem to reflect the template's recent changes."
  444. elif state == None:
  445. label_text = ""
  446. icon_path = None
  447. tooltip_text = None
  448. if self.show_text:
  449. self.label.setText(label_text)
  450. else:
  451. self.layout().removeWidget(self.icon)
  452. self.icon.deleteLater()
  453. if icon_path != None:
  454. self.icon = VmIconWidget(icon_path, True, 0.7)
  455. self.icon.setToolTip(tooltip_text)
  456. else:
  457. self.icon = QLabel(label_text)
  458. self.layout().addWidget(self.icon, alignment=Qt.AlignCenter)
  459. class VmSizeOnDiskItem (QTableWidgetItem):
  460. def __init__(self, vm):
  461. super(VmSizeOnDiskItem, self).__init__()
  462. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  463. self.vm = vm
  464. self.value = 0
  465. self.update()
  466. self.setTextAlignment(Qt.AlignVCenter)
  467. def update(self):
  468. if self.vm.qid == 0:
  469. self.setText("n/a")
  470. else:
  471. self.value = self.vm.get_disk_utilization()/(1024*1024)
  472. self.setText( str(self.value) + " MiB")
  473. def __lt__(self, other):
  474. if self.value == other.value:
  475. return self.vm.qid < other.vm.qid
  476. else:
  477. return self.value < other.value
  478. class VmIPItem(QTableWidgetItem):
  479. def __init__(self, vm):
  480. super(VmIPItem, self).__init__()
  481. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  482. self.ip = vm.ip
  483. if self.ip:
  484. self.setText(self.ip)
  485. else:
  486. self.setText("n/a")
  487. class VmIncludeInBackupsItem(QTableWidgetItem):
  488. def __init__(self, vm):
  489. super(VmIncludeInBackupsItem, self).__init__()
  490. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  491. self.vm = vm
  492. if self.vm.include_in_backups:
  493. self.setText("Yes")
  494. else:
  495. self.setText("")
  496. def __lt__(self, other):
  497. if self.vm.include_in_backups == other.vm.include_in_backups:
  498. return self.vm.qid < other.vm.qid
  499. else:
  500. return self.vm.include_in_backups < other.vm.include_in_backups
  501. class VmLastBackupItem(QTableWidgetItem):
  502. def __init__(self, vm):
  503. super(VmLastBackupItem, self).__init__()
  504. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  505. self.vm = vm
  506. if self.vm.backup_timestamp:
  507. self.setText(str(self.vm.backup_timestamp.date()))
  508. else:
  509. self.setText("")
  510. def __lt__(self, other):
  511. if self.vm.backup_timestamp == other.vm.backup_timestamp:
  512. return self.vm.qid < other.vm.qid
  513. elif not self.vm.backup_timestamp:
  514. return False
  515. elif not other.vm.backup_timestamp:
  516. return True
  517. else:
  518. return self.vm.backup_timestamp < other.vm.backup_timestamp