diff --git a/icons/ban.svg b/icons/ban.svg new file mode 100644 index 00000000..1296e51a --- /dev/null +++ b/icons/ban.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py index 1ddb7424..93f8cf46 100644 --- a/qubesmanager/qube_manager.py +++ b/qubesmanager/qube_manager.py @@ -103,6 +103,7 @@ def __init__(self): "Halted" : QIcon(":/blank") } self.outdatedIcons = { + "blocked" : QIcon(":/ban"), "update" : QIcon(":/updateable"), "outdated" : QIcon(":/outdated"), "to-be-outdated" : QIcon(":/outdated"), @@ -110,6 +111,8 @@ def __init__(self): "skipped": QIcon(':/skipped') } self.outdatedTooltips = { + "blocked" : self.tr( + "The qube is prohibited from being started"), "update" : self.tr("Updates available"), "outdated" : self.tr( "The qube must be restarted for recent changes in " @@ -239,6 +242,12 @@ def update_power_state(self): self.state['outdated'] = "" try: + if self.vm.klass != "AdminVM" and manager_utils.get_feature( + self.vm, 'prohibit-start', False): + # Special case where being outdated, eol & skipped is irrelevant + self.state['outdated'] = 'blocked' + return + if manager_utils.is_running(self.vm, False): if hasattr(self.vm, 'template') and \ manager_utils.is_running(self.vm.template, False): @@ -879,6 +888,10 @@ def __init__(self, qt_app, qubes_app, dispatcher, _parent=None): self.on_domain_updates_available) dispatcher.add_handler('domain-feature-delete:skip-update', self.on_domain_updates_available) + dispatcher.add_handler('domain-feature-set:prohibit-start', + self.on_domain_updates_available) + dispatcher.add_handler('domain-feature-delete:prohibit-start', + self.on_domain_updates_available) self.installEventFilter(self) @@ -1125,6 +1138,9 @@ def check_updates(self, info=None): try: if info.vm.klass in {'TemplateVM', 'StandaloneVM'}: if manager_utils.get_feature( + info.vm, 'prohibit-start', False): + info.state['outdated'] = 'blocked' + elif manager_utils.get_feature( info.vm, 'skip-update', False): info.state['outdated'] = 'skipped' elif manager_utils.get_feature( @@ -1352,6 +1368,17 @@ def table_selection_changed(self): if not vm.updateable and vm.klass != 'AdminVM': self.action_updatevm.setEnabled(False) + if vm.vm.features.get('prohibit-start', False): + self.action_open_console.setEnabled(False) + self.action_resumevm.setEnabled(False) + self.action_startvm_tools_install.setEnabled(False) + self.action_pausevm.setEnabled(False) + self.action_restartvm.setEnabled(False) + self.action_killvm.setEnabled(False) + self.action_shutdownvm.setEnabled(False) + self.action_updatevm.setEnabled(False) + self.action_run_command_in_vm.setEnabled(False) + self.update_template_menu() self.update_network_menu() diff --git a/qubesmanager/tests/conftest.py b/qubesmanager/tests/conftest.py index d303e7f8..e39f071b 100644 --- a/qubesmanager/tests/conftest.py +++ b/qubesmanager/tests/conftest.py @@ -27,6 +27,7 @@ def test_qubes_app(): test_qapp = MockQubesComplete() test_qapp._qubes['sys-usb'].features[ 'supported-feature.keyboard-layout'] = '1' + test_qapp._qubes['sys-usb'].features['prohibit-start'] = None test_qapp.update_vm_calls() return test_qapp diff --git a/qubesmanager/tests/test_qube_manager.py b/qubesmanager/tests/test_qube_manager.py index d9a93385..3a8f383f 100644 --- a/qubesmanager/tests/test_qube_manager.py +++ b/qubesmanager/tests/test_qube_manager.py @@ -1604,3 +1604,13 @@ def test_704_check_later(mock_timer, mock_question): assert mock_question.call_count == 0 assert mock_timer.call_count == 1 + + +def test_705_prohibit_start_vms(qubes_manager): + qubes_manager.qubes_app._qubes['test-old'].features[ \ + 'prohibit_start'] = \ + 'Do not start - Qube is compromised by Gremlins!' + _select_vm(qubes_manager, 'test-old') + qubes_manager.qubes_app._qubes['test-standalone'].features[ \ + 'prohibit_start'] = 'Ancient qube which breaks on update' + _select_vm(qubes_manager, 'test-standalone') diff --git a/resources.qrc b/resources.qrc index 9c4d87d2..524d6496 100644 --- a/resources.qrc +++ b/resources.qrc @@ -3,6 +3,7 @@ icons/add.svg icons/apps.svg icons/backup.svg + icons/ban.svg icons/blank.svg icons/checked.svg icons/checkmark.svg