Merge remote-tracking branch 'origin/pr/273'

* origin/pr/273: (24 commits)
  Fix coherence in network menu when adding/removing domains
  Fix too long line
  Add warning if trying to change template VM
  Disable network menu for templates
  Display default netvm
  Fix possible 'None' default error
  Added default option for network change
  Wrap warnings message in self.tr()
  Added wait argument to start_vm
  Better dialog creation
  Added try/except for starting netvm
  Added error message to dialogs
  Added proper error handling and Check netvm_name is not None
  Changed checkboxes to icons
  Added change network confirmation
  Added Template Change Confirmation
  Moved change_* funcs after __init__()
  Added QMessageBox if netvm is halted and user wants to start it
  Added network_menu updates
  Added try/except for change_network
  ...
This commit is contained in:
Marek Marczykowski-Górecki 2021-02-24 02:29:13 +01:00
commit d253b50545
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
3 changed files with 193 additions and 2 deletions

View File

@ -709,6 +709,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.frame_width = 0
self.frame_height = 0
self.init_template_menu()
self.init_network_menu()
self.__init_context_menu()
self.tools_context_menu = QMenu(self)
@ -740,6 +742,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.proxy.setFilterKeyColumn(2)
self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.proxy.layoutChanged.connect(self.save_sorting)
self.proxy.layoutChanged.connect(self.update_template_menu)
self.proxy.layoutChanged.connect(self.update_network_menu)
self.show_running.stateChanged.connect(self.invalidate)
self.show_halted.stateChanged.connect(self.invalidate)
@ -762,7 +766,6 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
column = self.qubes_model.columns_indices[col_no]
action = self.menu_view.addAction(column)
action.setData(column)
action.setCheckable(True)
action.toggled.connect(partial(self.showhide_column, col_no))
self.menu_view.addSeparator()
@ -822,9 +825,77 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.check_updates()
def change_template(self, template):
selected_vms = self.get_selected_vms()
reply = QMessageBox.question(
self, self.tr("Template Change Confirmation"),
self.tr("Do you want to change '{0}'<br>"
"to Template <b>'{1}'</b>?").format(
', '.join(vm.name for vm in selected_vms), template),
QMessageBox.Yes | QMessageBox.Cancel)
if reply == QMessageBox.Yes:
errors = []
for info in selected_vms:
try:
info.vm.template = template
except exc.QubesValueError as ex:
errors.append((info.name, str(ex)))
for error in errors:
QMessageBox.warning(self, self.tr("{0} template change failed!")
.format(error[0]), error[1])
def change_network(self, netvm_name):
selected_vms = self.get_selected_vms()
reply = QMessageBox.question(
self, self.tr("Network Change Confirmation"),
self.tr("Do you want to change '{0}'<br>"
"to Network <b>'{1}'</b>?").format(
', '.join(vm.name for vm in selected_vms), netvm_name),
QMessageBox.Yes | QMessageBox.Cancel)
if reply != QMessageBox.Yes:
return
if netvm_name not in [None, 'default']:
check_power = any(info.state['power'] == 'Running' for info
in self.get_selected_vms())
netvm = self.qubes_cache.get_vm(name=netvm_name)
if check_power and netvm.state['power'] != 'Running':
reply = QMessageBox.question(
self, self.tr("Qube Start Confirmation"),
self.tr("<br>Can not change netvm to a halted Qube.<br>"
"Do you want to start the Qube <b>'{0}'</b>?").format(
netvm_name),
QMessageBox.Yes | QMessageBox.Cancel)
if reply == QMessageBox.Yes:
self.start_vm(netvm.vm, True)
else:
return
errors = []
for info in self.get_selected_vms():
try:
if netvm_name == 'default':
delattr(info.vm, 'netvm')
else:
info.vm.netvm = netvm_name
except exc.QubesValueError as ex:
errors.append((info.name, str(ex)))
for error in errors:
QMessageBox.warning(self, self.tr("{0} network change failed!")
.format(error[0]), error[1])
def __init_context_menu(self):
self.context_menu = QMenu(self)
self.context_menu.addAction(self.action_settings)
self.context_menu.addAction(self.template_menu.menuAction())
self.context_menu.addAction(self.network_menu.menuAction())
self.context_menu.addAction(self.action_editfwrules)
self.context_menu.addAction(self.action_appmenus)
self.context_menu.addAction(self.action_set_keyboard_layout)
@ -885,6 +956,33 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
progress.setValue(row_no)
def init_template_menu(self):
self.template_menu.clear()
for vm in self.qubes_app.domains:
if vm.klass == 'TemplateVM':
action = self.template_menu.addAction(vm.name)
action.setData(vm.name)
action.triggered.connect(partial(self.change_template, vm.name))
def _get_default_netvm(self):
for vm in self.qubes_app.domains:
if vm.klass == 'AppVM':
return vm.property_get_default('netvm')
def init_network_menu(self):
default = self._get_default_netvm()
self.network_menu.clear()
action = self.network_menu.addAction("None")
action.triggered.connect(partial(self.change_network, None))
action = self.network_menu.addAction("default ({0})".format(default))
action.triggered.connect(partial(self.change_network, 'default'))
for vm in self.qubes_app.domains:
if vm.qid != 0 and vm.provides_network:
action = self.network_menu.addAction(vm.name)
action.setData(vm.name)
action.triggered.connect(partial(self.change_network, vm.name))
def setup_application(self):
self.qt_app.setApplicationName(self.tr("Qube Manager"))
self.qt_app.setWindowIcon(QIcon.fromTheme("qubes-manager"))
@ -942,12 +1040,16 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
domain = self.qubes_app.domains[vm]
self.qubes_cache.add_vm(domain)
self.proxy.invalidate()
if domain.klass == 'TemplateVM':
self.init_template_menu()
except (exc.QubesException, KeyError):
pass
def on_domain_removed(self, _submitter, _event, **kwargs):
self.qubes_cache.remove_vm(name=kwargs['vm'])
self.proxy.invalidate()
self.init_template_menu()
self.init_network_menu()
def on_domain_status_changed(self, vm, event, **_kwargs):
try:
@ -977,6 +1079,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
return
try:
if event.endswith(':provides_network'):
self.init_network_menu()
self.qubes_cache.get_vm(qid=vm.qid).update(event=event)
self.proxy.invalidate()
except exc.QubesDaemonAccessError:
@ -1061,6 +1165,8 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
def table_selection_changed(self):
# Since selection could have multiple domains
# enable all first and then filter them
self.template_menu.setEnabled(True)
self.network_menu.setEnabled(True)
for action in self.toolbar.actions() + self.context_menu.actions():
action.setEnabled(True)
@ -1071,17 +1177,20 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
['Running', 'Transient', 'Halting', 'Dying']:
self.action_resumevm.setEnabled(False)
self.action_removevm.setEnabled(False)
self.template_menu.setEnabled(False)
elif vm.state['power'] == 'Paused':
self.action_removevm.setEnabled(False)
self.action_pausevm.setEnabled(False)
self.action_set_keyboard_layout.setEnabled(False)
self.action_restartvm.setEnabled(False)
self.action_open_console.setEnabled(False)
self.template_menu.setEnabled(False)
elif vm.state['power'] == 'Suspend':
self.action_set_keyboard_layout.setEnabled(False)
self.action_removevm.setEnabled(False)
self.action_pausevm.setEnabled(False)
self.action_open_console.setEnabled(False)
self.template_menu.setEnabled(False)
elif vm.state['power'] == 'Halted':
self.action_set_keyboard_layout.setEnabled(False)
self.action_pausevm.setEnabled(False)
@ -1104,9 +1213,15 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.action_editfwrules.setEnabled(False)
self.action_set_keyboard_layout.setEnabled(False)
self.action_run_command_in_vm.setEnabled(False)
self.template_menu.setEnabled(False)
self.network_menu.setEnabled(False)
elif vm.klass == 'DispVM':
self.action_appmenus.setEnabled(False)
self.action_restartvm.setEnabled(False)
self.template_menu.setEnabled(False)
elif vm.klass == 'TemplateVM':
self.template_menu.setEnabled(False)
self.network_menu.setEnabled(False)
if vm.vm.features.get('internal', False):
self.action_appmenus.setEnabled(False)
@ -1114,6 +1229,47 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
if not vm.updateable and vm.klass != 'AdminVM':
self.action_updatevm.setEnabled(False)
self.update_template_menu()
self.update_network_menu()
def update_template_menu(self):
if not self.template_menu.isEnabled():
return
for entry in self.template_menu.actions():
entry.setIcon(QIcon())
vms = self.get_selected_vms()
for vm in vms:
for entry in self.template_menu.actions():
if entry.data() == vm.template:
if len(vms) == 1:
entry.setIcon(QIcon(":/on.png"))
else:
entry.setIcon(QIcon(":/transient.png"))
def update_network_menu(self):
if not self.network_menu.isEnabled():
return
for entry in self.network_menu.actions():
entry.setIcon(QIcon())
if len(self.get_selected_vms()) == 1:
icon = QIcon(":/on.png")
else:
icon = QIcon(":/transient.png")
for vm in self.get_selected_vms():
if vm.netvm == "n/a":
self.network_menu.actions()[0].setIcon(QIcon(icon))
elif vm.vm.property_is_default("netvm"):
self.network_menu.actions()[1].setIcon(QIcon(icon))
else:
for entry in self.network_menu.actions():
if entry.data() == vm.netvm:
entry.setIcon(icon)
# noinspection PyArgumentList
@pyqtSlot(name='on_action_createvm_triggered')
def action_createvm_triggered(self):
@ -1202,7 +1358,7 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
self.start_vm(vm)
def start_vm(self, vm):
def start_vm(self, vm, wait=False):
if manager_utils.is_running(vm, False):
return
@ -1211,6 +1367,10 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QMainWindow):
thread.finished.connect(self.clear_threads)
thread.start()
if wait:
with common_threads.busy_cursor():
thread.wait()
# noinspection PyArgumentList
@pyqtSlot(name='on_action_startvm_tools_install_triggered')
# TODO: replace with boot from device

View File

@ -432,6 +432,7 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog):
self.netVM.setCurrentIndex(-1)
self.netVM.currentIndexChanged.connect(self.check_warn_dispvmnetvm)
self.netVM.currentIndexChanged.connect(self.check_warn_templatenetvm)
try:
self.include_in_backups.setChecked(self.vm.include_in_backups)
@ -625,6 +626,16 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtWidgets.QDialog):
self.init_mem.value() * 10 < self.max_mem_size.value():
self.warn_too_much_mem_label.setVisible(True)
def check_warn_templatenetvm(self):
if self.vm.klass == 'TemplateVM':
QtWidgets.QMessageBox.warning(
self,
self.tr("Warning!"),
self.tr("Connecting a TemplateVM directly to a network is higly"
" discouraged! <br> <small>You are breaking a basic par"
"t of Qubes security and there is probably no real need"
" to do so. Continue at your own risk.</small>"))
def check_warn_dispvmnetvm(self):
if not hasattr(self.vm, 'default_dispvm'):
self.warn_netvm_dispvm.setVisible(False)

View File

@ -350,6 +350,24 @@ Template</string>
<property name="title">
<string>&amp;Qube</string>
</property>
<widget class="QMenu" name="template_menu">
<property name="title">
<string>Template</string>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/templatevm.png</normaloff>:/templatevm.png</iconset>
</property>
</widget>
<widget class="QMenu" name="network_menu">
<property name="title">
<string>Network</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/netvm.png</normaloff>:/netvm.png</iconset>
</property>
</widget>
<addaction name="action_createvm"/>
<addaction name="action_removevm"/>
<addaction name="action_clonevm"/>
@ -361,6 +379,8 @@ Template</string>
<addaction name="action_killvm"/>
<addaction name="separator"/>
<addaction name="action_settings"/>
<addaction name="template_menu"/>
<addaction name="network_menu"/>
<addaction name="action_editfwrules"/>
<addaction name="action_appmenus"/>
<addaction name="action_updatevm"/>