diff --git a/.travis.yml b/.travis.yml
index 66bde29..e868fbe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,13 @@
sudo: required
dist: trusty
-language: generic
-install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
-script: ~/qubes-builder/scripts/travis-build
+language: python
+python:
+ - '3.5'
+install:
+ - pip install --quiet -r ci/requirements.txt
+ - git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
+script:
+ - PYTHONPATH=test-packages pylint --rcfile=ci/pylintrc qubesmanager
+ - ~/qubes-builder/scripts/travis-build
env:
- - DIST_DOM0=fc23 USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1
+ - DIST_DOM0=fc25 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
diff --git a/ci/pylintrc b/ci/pylintrc
new file mode 100644
index 0000000..85eefb5
--- /dev/null
+++ b/ci/pylintrc
@@ -0,0 +1,211 @@
+[MASTER]
+persistent=no
+ignore=tests,
+ ui_about.py,
+ ui_backupdlg.py,
+ ui_bootfromdevice.py,
+ ui_globalsettingsdlg.py,
+ ui_informationnotes.py,
+ ui_logdlg.py,
+ ui_multiselectwidget.py,
+ ui_newappvmdlg.py,
+ ui_newfwruledlg.py,
+ ui_releasenotes.py,
+ ui_restoredlg.py,
+ ui_settingsdlg.py,
+ resources_rc.py
+
+[MESSAGES CONTROL]
+# abstract-class-little-used: see http://www.logilab.org/ticket/111138
+# deprecated-method:
+# enable again after disabling py-3.4.3 asyncio.ensure_future compat hack
+disable=
+ abstract-class-little-used,
+ bad-continuation,
+ cyclic-import,
+ deprecated-method,
+ duplicate-code,
+ file-ignored,
+ fixme,
+ inconsistent-return-statements,
+ locally-disabled,
+ locally-enabled,
+ logging-format-interpolation,
+ missing-docstring,
+ star-args,
+ useless-super-delegation,
+ wrong-import-order
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=colorized
+
+#files-output=no
+reports=yes
+
+[TYPECHECK]
+#ignored-classes=
+
+ignore-mixin-members=yes
+generated-members=
+ iter_entry_points,
+ Element,ElementTree,QName,SubElement,fromstring,parse,tostring,
+
+[BASIC]
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Za-z_][A-Za-z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=([A-Z_][a-zA-Z0-9]+|TC_\d\d_[a-zA-Z0-9_]+)$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=e,i,j,k,m,p,v,ex,Run,_,log,vm,ok,ip
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,FIX,XXX,TODO
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=3000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the beginning of the name of dummy variables
+# (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=35
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+# Let's have max-args + 5
+max-locals=40
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+# 4x the default value
+max-branches=48
+
+# Maximum number of statements in function / method body
+# Double default
+max-statements=100
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=15
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=100
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception,EnvironmentError
+
+# vim: ft=conf
diff --git a/ci/requirements.txt b/ci/requirements.txt
new file mode 100644
index 0000000..ac54cec
--- /dev/null
+++ b/ci/requirements.txt
@@ -0,0 +1,11 @@
+# WARNING: those requirements are used only for travis-ci.org
+# they SHOULD NOT be used under normal conditions; use system package manager
+coverage
+codecov
+docutils
+jinja2
+lxml
+pylint
+sphinx
+pydbus
+PyYAML
\ No newline at end of file
diff --git a/qubes-backup-restore.desktop b/qubes-backup-restore.desktop
new file mode 100644
index 0000000..3983f9f
--- /dev/null
+++ b/qubes-backup-restore.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Exec=qubes-backup-restore
+Icon=qubes-manager
+Terminal=false
+Name=Restore Backup
+GenericName=Restore Backup
+StartupNotify=false
+Categories=System;
diff --git a/qubes-backup.desktop b/qubes-backup.desktop
new file mode 100644
index 0000000..7e93247
--- /dev/null
+++ b/qubes-backup.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Exec=qubes-backup
+Icon=qubes-manager
+Terminal=false
+Name=Backup Qubes
+GenericName=Backup Qubes
+StartupNotify=false
+Categories=System;
diff --git a/qubes-global-settings.desktop b/qubes-global-settings.desktop
index 110d418..74a0d53 100644
--- a/qubes-global-settings.desktop
+++ b/qubes-global-settings.desktop
@@ -1,7 +1,6 @@
[Desktop Entry]
Type=Application
Exec=qubes-global-settings
-Path=/user/bin
Icon=qubes-manager
Terminal=false
Name=Qubes Global Settings
diff --git a/qubes-qube-manager.desktop b/qubes-qube-manager.desktop
new file mode 100644
index 0000000..220f8d5
--- /dev/null
+++ b/qubes-qube-manager.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Exec=qubes-qube-manager
+Icon=qubes-manager
+Terminal=false
+Name=Qube Manager
+GenericName=Qube Manager
+StartupNotify=false
+Categories=System;
diff --git a/qubesmanager/about.py b/qubesmanager/about.py
index fb06102..b12ae36 100644
--- a/qubesmanager/about.py
+++ b/qubesmanager/about.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
# coding=utf-8
#
# The Qubes OS Project, http://www.qubes-os.org
@@ -16,21 +16,19 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
-from PyQt4.QtCore import SIGNAL, SLOT
-from PyQt4.QtGui import QDialog, QIcon
+from PyQt4.QtCore import SIGNAL, SLOT # pylint: disable=import-error
+from PyQt4.QtGui import QDialog, QIcon # pylint: disable=import-error
from qubesmanager.releasenotes import ReleaseNotesDialog
from qubesmanager.informationnotes import InformationNotesDialog
-from .ui_about import *
+from . import ui_about # pylint: disable=no-name-in-module
-
-class AboutDialog(Ui_AboutDialog, QDialog):
+class AboutDialog(ui_about.Ui_AboutDialog, QDialog):
def __init__(self):
super(AboutDialog, self).__init__()
diff --git a/qubesmanager/appmenu_select.py b/qubesmanager/appmenu_select.py
index c161f56..f042011 100755
--- a/qubesmanager/appmenu_select.py
+++ b/qubesmanager/appmenu_select.py
@@ -14,28 +14,19 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
-import os
import subprocess
-import sys
-import time
-from operator import itemgetter
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
-
-import qubesmanager.resources_rc
+import PyQt4.QtGui # pylint: disable=import-error
# TODO description in tooltip
# TODO icon
-class AppListWidgetItem(QListWidgetItem):
+# pylint: disable=too-few-public-methods
+class AppListWidgetItem(PyQt4.QtGui.QListWidgetItem):
def __init__(self, name, ident, parent=None):
super(AppListWidgetItem, self).__init__(name, parent)
# self.setToolTip(command)
@@ -43,12 +34,12 @@ class AppListWidgetItem(QListWidgetItem):
@classmethod
def from_line(cls, line):
- ident, icon_name, name = line.strip().split(maxsplit=2)
+ ident, _icon_name, name = line.strip().split(maxsplit=2)
return cls(name=name, ident=ident)
class AppmenuSelectManager:
- def __init__(self, vm, apps_multiselect, parent=None):
+ def __init__(self, vm, apps_multiselect):
self.vm = vm
self.app_list = apps_multiselect # this is a multiselect wiget
self.whitelisted = None
@@ -60,7 +51,9 @@ class AppmenuSelectManager:
).decode().strip().split('\n') if line]
# Check if appmenu entry is really installed
-# whitelisted = [a for a in whitelisted if os.path.exists('%s/apps/%s-%s' % (self.vm.dir_path, self.vm.name, a))]
+ # whitelisted = [a for a in whitelisted
+ # if os.path.exists('%s/apps/%s-%s' %
+ # (self.vm.dir_path, self.vm.name, a))]
self.app_list.clear()
@@ -69,11 +62,11 @@ class AppmenuSelectManager:
'--get-available', '--i-understand-format-is-unstable',
self.vm.name]).decode().splitlines()]
- for a in available_appmenus:
- if a.ident in self.whitelisted:
- self.app_list.selected_list.addItem(a)
+ for app in available_appmenus:
+ if app.ident in self.whitelisted:
+ self.app_list.selected_list.addItem(app)
else:
- self.app_list.available_list.addItem(a)
+ self.app_list.available_list.addItem(app)
self.app_list.available_list.sortItems()
self.app_list.selected_list.sortItems()
diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py
index bde407b..27dcea1 100644
--- a/qubesmanager/backup.py
+++ b/qubesmanager/backup.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
#
# The Qubes OS Project, http://www.qubes-os.org
#
@@ -15,385 +15,324 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
+import traceback
+
+import signal
+
+from qubesadmin import Qubes, exc
+from qubesadmin import utils as admin_utils
+from qubes.storage.file import get_disk_usage
+
+from PyQt4 import QtCore # pylint: disable=import-error
+from PyQt4 import QtGui # pylint: disable=import-error
+from . import ui_backupdlg # pylint: disable=no-name-in-module
+from . import multiselectwidget
+
+from . import backup_utils
+from . import utils
+import grp
+import pwd
import sys
import os
-import signal
-import shutil
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from qubes.qubes import QubesVmCollection
-from qubes.qubes import QubesException
-from qubes.qubes import QubesDaemonPidfile
-from qubes.qubes import QubesHost
-from qubes import backup
-from qubes import qubesutils
-
-import qubesmanager.resources_rc
-
-from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
-
+from . import thread_monitor
+import threading
import time
-from .thread_monitor import *
-from operator import itemgetter
-
-from datetime import datetime
-from string import replace
-
-from .ui_backupdlg import *
-from .multiselectwidget import *
-
-from .backup_utils import *
-import main
-import grp,pwd
-class BackupVMsWindow(Ui_Backup, QWizard):
+class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
- __pyqtSignals__ = ("backup_progress(int)",)
-
- def __init__(self, app, qvm_collection, blk_manager, shutdown_vm_func, parent=None):
+ def __init__(self, qt_app, qubes_app, parent=None):
super(BackupVMsWindow, self).__init__(parent)
- self.app = app
- self.qvm_collection = qvm_collection
- self.blk_manager = blk_manager
- self.shutdown_vm_func = shutdown_vm_func
+ self.qt_app = qt_app
+ self.qubes_app = qubes_app
+ self.backup_settings = QtCore.QSettings()
- self.func_output = []
self.selected_vms = []
- self.tmpdir_to_remove = None
self.canceled = False
-
- self.vm = self.qvm_collection[0]
- self.files_to_backup = None
-
- assert self.vm != None
+ self.thread_monitor = None
self.setupUi(self)
self.progress_status.text = self.tr("Backup in progress...")
- self.show_running_vms_warning(False)
self.dir_line_edit.setReadOnly(False)
- self.select_vms_widget = MultiSelectWidget(self)
+ self.select_vms_widget = multiselectwidget.MultiSelectWidget(self)
self.verticalLayout.insertWidget(1, self.select_vms_widget)
- self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
- self.connect(self.select_vms_widget, SIGNAL("selected_changed()"), self.check_running)
- self.connect(self.select_vms_widget, SIGNAL("items_removed(PyQt_PyObject)"), self.vms_removed)
- self.connect(self.select_vms_widget, SIGNAL("items_added(PyQt_PyObject)"), self.vms_added)
- self.refresh_button.clicked.connect(self.check_running)
- self.shutdown_running_vms_button.clicked.connect(self.shutdown_all_running_selected)
- self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
- self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed)
+ self.connect(self, QtCore.SIGNAL("currentIdChanged(int)"),
+ self.current_page_changed)
+ self.connect(self.select_vms_widget,
+ QtCore.SIGNAL("items_removed(PyQt_PyObject)"),
+ self.vms_removed)
+ self.connect(self.select_vms_widget,
+ QtCore.SIGNAL("items_added(PyQt_PyObject)"),
+ self.vms_added)
+ self.dir_line_edit.connect(self.dir_line_edit,
+ QtCore.SIGNAL("textChanged(QString)"),
+ self.backup_location_changed)
self.select_vms_page.isComplete = self.has_selected_vms
self.select_dir_page.isComplete = self.has_selected_dir_and_pass
- #FIXME
- #this causes to run isComplete() twice, I don't know why
+ # FIXME
+ # this causes to run isComplete() twice, I don't know why
self.select_vms_page.connect(
self.select_vms_widget,
- SIGNAL("selected_changed()"),
- SIGNAL("completeChanged()"))
+ QtCore.SIGNAL("selected_changed()"),
+ QtCore.SIGNAL("completeChanged()"))
self.passphrase_line_edit.connect(
self.passphrase_line_edit,
- SIGNAL("textChanged(QString)"),
+ QtCore.SIGNAL("textChanged(QString)"),
self.backup_location_changed)
self.passphrase_line_edit_verify.connect(
self.passphrase_line_edit_verify,
- SIGNAL("textChanged(QString)"),
+ QtCore.SIGNAL("textChanged(QString)"),
self.backup_location_changed)
self.total_size = 0
- self.__fill_vms_list__()
- fill_appvms_list(self)
- self.load_settings()
+ self.target_vm_list, self.target_vm_idx = utils.prepare_vm_choice(
+ self.appvm_combobox,
+ self.qubes_app,
+ None,
+ self.qubes_app.domains['dom0'],
+ (lambda vm: vm.klass != 'TemplateVM' and vm.is_running()),
+ allow_internal=False,
+ allow_default=False,
+ allow_none=False
+ )
+
+ selected = self.load_settings()
+ self.__fill_vms_list__(selected)
def load_settings(self):
- dest_vm_name = main.manager_window.manager_settings.value(
- 'backup/vmname', defaultValue="")
- dest_vm_idx = self.appvm_combobox.findText(dest_vm_name.toString())
- if dest_vm_idx > -1:
- self.appvm_combobox.setCurrentIndex(dest_vm_idx)
+ """
+ Helper function that tries to load existing backup profile
+ (default path: /etc/qubes/backup/qubes-manager-backup.conf )
+ and then apply its contents to the Backup window.
+ :return: list of vms to include in backup, if it exists in the profile,
+ or None if it does not
+ """
+ try:
+ profile_data = backup_utils.load_backup_profile()
+ except FileNotFoundError:
+ return
+ except exc.QubesException:
+ QtGui.QMessageBox.information(
+ None, self.tr("Error loading backup profile"),
+ self.tr("Unable to load saved backup profile."))
+ return
+ if not profile_data:
+ return
- if main.manager_window.manager_settings.contains('backup/path'):
- dest_path = main.manager_window.manager_settings.value(
- 'backup/path', defaultValue=None)
- self.dir_line_edit.setText(dest_path.toString())
+ if 'destination_vm' in profile_data:
+ dest_vm_name = profile_data['destination_vm']
+ dest_vm_idx = self.appvm_combobox.findText(dest_vm_name)
+ if dest_vm_idx > -1:
+ self.appvm_combobox.setCurrentIndex(dest_vm_idx)
- if main.manager_window.manager_settings.contains('backup/encrypt'):
- encrypt = main.manager_window.manager_settings.value(
- 'backup/encrypt', defaultValue=None)
- self.encryption_checkbox.setChecked(encrypt.toBool())
+ if 'destination_path' in profile_data:
+ dest_path = profile_data['destination_path']
+ self.dir_line_edit.setText(dest_path)
- def save_settings(self):
- main.manager_window.manager_settings.setValue(
- 'backup/vmname', self.appvm_combobox.currentText())
- main.manager_window.manager_settings.setValue(
- 'backup/path', self.dir_line_edit.text())
- main.manager_window.manager_settings.setValue(
- 'backup/encrypt', self.encryption_checkbox.isChecked())
+ if 'passphrase_text' in profile_data:
+ self.passphrase_line_edit.setText(profile_data['passphrase_text'])
+ self.passphrase_line_edit_verify.setText(
+ profile_data['passphrase_text'])
- def show_running_vms_warning(self, show):
- self.running_vms_warning.setVisible(show)
- self.shutdown_running_vms_button.setVisible(show)
- self.refresh_button.setVisible(show)
+ if 'compression' in profile_data:
+ self.compress_checkbox.setChecked(profile_data['compression'])
- class VmListItem(QListWidgetItem):
+ if 'include' in profile_data:
+ return profile_data['include']
+
+ return None
+
+ def save_settings(self, use_temp):
+ """
+ Helper function that saves backup profile to either
+ /etc/qubes/backup/qubes-manager-backup.conf or
+ /etc/qubes/backup/qubes-manager-backup-tmp.conf
+ :param use_temp: whether to use temporary profile (True) or the default
+ backup profile (False)
+ """
+ settings = {'destination_vm': self.appvm_combobox.currentText(),
+ 'destination_path': self.dir_line_edit.text(),
+ 'include': [vm.name for vm in self.selected_vms],
+ 'passphrase_text': self.passphrase_line_edit.text(),
+ 'compression': self.compress_checkbox.isChecked()}
+
+ backup_utils.write_backup_profile(settings, use_temp)
+
+ class VmListItem(QtGui.QListWidgetItem):
+ # pylint: disable=too-few-public-methods
def __init__(self, vm):
self.vm = vm
if vm.qid == 0:
local_user = grp.getgrnam('qubes').gr_mem[0]
home_dir = pwd.getpwnam(local_user).pw_dir
- self.size = qubesutils.get_disk_usage(home_dir)
+ self.size = get_disk_usage(home_dir)
else:
- self.size = self.get_vm_size(vm)
- super(BackupVMsWindow.VmListItem, self).__init__(vm.name+ " (" + qubesutils.size_to_human(self.size) + ")")
+ self.size = vm.get_disk_utilization()
+ super(BackupVMsWindow.VmListItem, self).__init__(
+ vm.name + " (" + admin_utils.size_to_human(self.size) + ")")
- def get_vm_size(self, vm):
- size = 0
- if vm.private_img is not None:
- size += qubesutils.get_disk_usage (vm.private_img)
-
- if vm.updateable:
- size += qubesutils.get_disk_usage(vm.root_img)
-
- return size
-
-
- def __fill_vms_list__(self):
- for vm in self.qvm_collection.values():
- if vm.internal:
+ def __fill_vms_list__(self, selected=None):
+ for vm in self.qubes_app.domains:
+ if vm.features.get('internal', False):
continue
item = BackupVMsWindow.VmListItem(vm)
- if vm.include_in_backups == True:
+ if (selected is None and
+ getattr(vm, 'include_in_backups', True)) \
+ or (selected and vm.name in selected):
self.select_vms_widget.selected_list.addItem(item)
self.total_size += item.size
else:
self.select_vms_widget.available_list.addItem(item)
self.select_vms_widget.available_list.sortItems()
self.select_vms_widget.selected_list.sortItems()
- self.check_running()
- self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
+
+ self.unrecognized_config_label.setVisible(
+ selected is not None and
+ len(selected) != len(self.select_vms_widget.selected_list))
+ self.total_size_label.setText(
+ admin_utils.size_to_human(self.total_size))
def vms_added(self, items):
for i in items:
self.total_size += i.size
- self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
+ self.total_size_label.setText(
+ admin_utils.size_to_human(self.total_size))
def vms_removed(self, items):
for i in items:
self.total_size -= i.size
- self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
+ self.total_size_label.setText(
+ admin_utils.size_to_human(self.total_size))
- def check_running(self):
- some_selected_vms_running = False
- for i in range(self.select_vms_widget.selected_list.count()):
- item = self.select_vms_widget.selected_list.item(i)
- if item.vm.is_running() and item.vm.qid != 0:
- item.setForeground(QBrush(QColor(255, 0, 0)))
- some_selected_vms_running = True
- else:
- item.setForeground(QBrush(QColor(0, 0, 0)))
-
- self.show_running_vms_warning(some_selected_vms_running)
-
- for i in range(self.select_vms_widget.available_list.count()):
- item = self.select_vms_widget.available_list.item(i)
- if item.vm.is_running() and item.vm.qid != 0:
- item.setForeground(QBrush(QColor(255, 0, 0)))
- else:
- item.setForeground(QBrush(QColor(0, 0, 0)))
-
- return some_selected_vms_running
-
- def shutdown_all_running_selected(self):
- (names, vms) = self.get_running_vms()
- if len(vms) == 0:
- return
-
- for vm in vms:
- self.blk_manager.check_if_serves_as_backend(vm)
-
- reply = QMessageBox.question(None, self.tr("VM Shutdown Confirmation"),
- self.tr(
- "Are you sure you want to power down the following VMs: "
- "{0}?
"
- "This will shutdown all the running applications "
- "within them.").format(', '.join(names)),
- QMessageBox.Yes | QMessageBox.Cancel)
-
- self.app.processEvents()
-
- if reply == QMessageBox.Yes:
-
- wait_time = 60.0
- for vm in vms:
- self.shutdown_vm_func(vm, wait_time*1000)
-
- progress = QProgressDialog ("Shutting down VMs {0}...".format(', '.join(names)), "", 0, 0)
- progress.setModal(True)
- progress.show()
-
- wait_for = wait_time
- while self.check_running() and wait_for > 0:
- self.app.processEvents()
- time.sleep (0.5)
- wait_for -= 0.5
-
- progress.hide()
-
- def get_running_vms(self):
- names = []
- vms = []
- for i in range(self.select_vms_widget.selected_list.count()):
- item = self.select_vms_widget.selected_list.item(i)
- if item.vm.is_running() and item.vm.qid != 0:
- names.append(item.vm.name)
- vms.append(item.vm)
- return (names, vms)
-
- @pyqtSlot(name='on_select_path_button_clicked')
+ @QtCore.pyqtSlot(name='on_select_path_button_clicked')
def select_path_button_clicked(self):
- select_path_button_clicked(self)
+ backup_utils.select_path_button_clicked(self)
def validateCurrentPage(self):
+ # pylint: disable=invalid-name
if self.currentPage() is self.select_vms_page:
- if self.check_running():
- QMessageBox.information(None,
- self.tr("Wait!"),
- self.tr("Some selected VMs are running. "
- "Running VMs can not be backuped. "
- "Please shut them down or remove them from the list."))
- return False
self.selected_vms = []
for i in range(self.select_vms_widget.selected_list.count()):
- self.selected_vms.append(self.select_vms_widget.selected_list.item(i).vm)
+ self.selected_vms.append(
+ self.select_vms_widget.selected_list.item(i).vm)
elif self.currentPage() is self.select_dir_page:
backup_location = str(self.dir_line_edit.text())
if not backup_location:
- QMessageBox.information(None, self.tr("Wait!"),
+ QtGui.QMessageBox.information(
+ None, self.tr("Wait!"),
self.tr("Enter backup target location first."))
return False
- if self.appvm_combobox.currentIndex() == 0 and \
- not os.path.isdir(backup_location):
- QMessageBox.information(None, self.tr("Wait!"),
+ if self.appvm_combobox.currentIndex() == 0 \
+ and not os.path.isdir(backup_location):
+ QtGui.QMessageBox.information(
+ None, self.tr("Wait!"),
self.tr("Selected directory do not exists or "
"not a directory (%s).") % backup_location)
return False
- if not len(self.passphrase_line_edit.text()):
- QMessageBox.information(None, self.tr("Wait!"),
- self.tr("Enter passphrase for backup encryption/verification first."))
+ if not self.passphrase_line_edit.text():
+ QtGui.QMessageBox.information(
+ None, self.tr("Wait!"),
+ self.tr("Enter passphrase for backup "
+ "encryption/verification first."))
return False
- if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text():
- QMessageBox.information(None,
- self.tr("Wait!"),
+ if self.passphrase_line_edit.text() !=\
+ self.passphrase_line_edit_verify.text():
+ QtGui.QMessageBox.information(
+ None, self.tr("Wait!"),
self.tr("Enter the same passphrase in both fields."))
return False
return True
- def gather_output(self, s):
- self.func_output.append(s)
-
- def update_progress_bar(self, value):
- self.emit(SIGNAL("backup_progress(int)"), value)
-
- def __do_backup__(self, thread_monitor):
+ def __do_backup__(self, t_monitor):
msg = []
try:
- backup.backup_do(self.dir_line_edit.text(),
- self.files_to_backup,
- self.passphrase_line_edit.text(),
- progress_callback=self.update_progress_bar,
- encrypted=self.encryption_checkbox.isChecked(),
- appvm=self.target_appvm)
- #simulate_long_lasting_proces(10, self.update_progress_bar)
- except backup.BackupCanceledError as ex:
- msg.append(str(ex))
- self.canceled = True
- if ex.tmpdir:
- self.tmpdir_to_remove = ex.tmpdir
- except Exception as ex:
- print("Exception:", ex)
+ vm = self.qubes_app.domains[
+ self.appvm_combobox.currentText()]
+ if not vm.is_running():
+ vm.start()
+ self.qubes_app.qubesd_call(
+ 'dom0', 'admin.backup.Execute',
+ backup_utils.get_profile_name(True))
+ except Exception as ex: # pylint: disable=broad-except
msg.append(str(ex))
- if len(msg) > 0 :
- thread_monitor.set_error_msg('\n'.join(msg))
+ if msg:
+ t_monitor.set_error_msg('\n'.join(msg))
- thread_monitor.set_finished()
+ t_monitor.set_finished()
+ @staticmethod
+ def cleanup_temporary_files():
+ try:
+ os.remove(backup_utils.get_profile_path(use_temp=True))
+ except FileNotFoundError:
+ pass
- def current_page_changed(self, id):
+ def current_page_changed(self, page_id): # pylint: disable=unused-argument
old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if self.currentPage() is self.confirm_page:
- self.target_appvm = None
- if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
- self.target_appvm = self.qvm_collection.get_vm_by_name(
- self.appvm_combobox.currentText())
-
- del self.func_output[:]
- try:
- self.files_to_backup = backup.backup_prepare(
- self.selected_vms,
- print_callback = self.gather_output,
- hide_vm_names=self.encryption_checkbox.isChecked())
- except Exception as ex:
- print("Exception:", ex)
- QMessageBox.critical(None,
- self.tr("Error while preparing backup."),
- self.tr("ERROR: {0}").format(ex))
+ self.save_settings(use_temp=True)
+ backup_summary = self.qubes_app.qubesd_call(
+ 'dom0', 'admin.backup.Info',
+ backup_utils.get_profile_name(True))
self.textEdit.setReadOnly(True)
self.textEdit.setFontFamily("Monospace")
- self.textEdit.setText("\n".join(self.func_output))
- self.save_settings()
+ self.textEdit.setText(backup_summary.decode())
elif self.currentPage() is self.commit_page:
+
+ if self.save_profile_checkbox.isChecked():
+ self.save_settings(use_temp=False)
+
self.button(self.FinishButton).setDisabled(True)
self.showFileDialog.setEnabled(
self.appvm_combobox.currentIndex() != 0)
self.showFileDialog.setChecked(self.showFileDialog.isEnabled()
and str(self.dir_line_edit.text())
.count("media/") > 0)
- self.thread_monitor = ThreadMonitor()
- thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,))
+ self.thread_monitor = thread_monitor.ThreadMonitor()
+ thread = threading.Thread(
+ target=self.__do_backup__,
+ args=(self.thread_monitor,))
thread.daemon = True
thread.start()
- counter = 0
while not self.thread_monitor.is_finished():
- self.app.processEvents()
- time.sleep (0.1)
+ self.qt_app.processEvents()
+ time.sleep(0.1)
if not self.thread_monitor.success:
if self.canceled:
- self.progress_status.setText(self.tr("Backup aborted."))
- if self.tmpdir_to_remove:
- if QMessageBox.warning(None, self.tr("Backup aborted"),
- self.tr("Do you want to remove temporary files from "
- "%s?") % self.tmpdir_to_remove,
- QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes:
- shutil.rmtree(self.tmpdir_to_remove)
+ self.progress_status.setText(
+ self.tr(
+ "Backup aborted. "
+ "Temporary file may be left at backup location."))
else:
self.progress_status.setText(self.tr("Backup error."))
- QMessageBox.warning(self, self.tr("Backup error!"),
+ QtGui.QMessageBox.warning(
+ self, self.tr("Backup error!"),
self.tr("ERROR: {}").format(
- self.thread_monitor.error_msg))
+ self.thread_monitor.error_msg))
else:
+ self.progress_bar.setMaximum(100)
self.progress_bar.setValue(100)
self.progress_status.setText(self.tr("Backup finished."))
if self.showFileDialog.isChecked():
@@ -402,84 +341,78 @@ class BackupVMsWindow(Ui_Backup, QWizard):
orig_text + self.tr(
" Please unmount your backup volume and cancel "
"the file selection dialog."))
- if self.target_appvm:
- self.target_appvm.run("QUBESRPC %s dom0" % "qubes"
- ".SelectDirectory")
+ backup_utils.select_path_button_clicked(self, False, True)
self.button(self.CancelButton).setEnabled(False)
self.button(self.FinishButton).setEnabled(True)
self.showFileDialog.setEnabled(False)
+ self.cleanup_temporary_files()
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def reject(self):
- #cancell clicked while the backup is in progress.
- #calling kill on tar.
if self.currentPage() is self.commit_page:
- if backup.backup_cancel():
- self.button(self.CancelButton).setDisabled(True)
+ self.canceled = True
+ self.qubes_app.qubesd_call(
+ 'dom0', 'admin.backup.Cancel',
+ backup_utils.get_profile_name(True))
+ self.progress_bar.setMaximum(100)
+ self.progress_bar.setValue(0)
+ self.button(self.CancelButton).setDisabled(True)
+ self.cleanup_temporary_files()
else:
+ self.cleanup_temporary_files()
self.done(0)
def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0
def has_selected_dir_and_pass(self):
- if not len(self.passphrase_line_edit.text()):
+ if not self.passphrase_line_edit.text():
return False
- if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text():
+ if self.passphrase_line_edit.text() != \
+ self.passphrase_line_edit_verify.text():
return False
return len(self.dir_line_edit.text()) > 0
- def backup_location_changed(self, new_dir = None):
- self.select_dir_page.emit(SIGNAL("completeChanged()"))
+ def backup_location_changed(self, new_dir=None):
+ # pylint: disable=unused-argument
+ self.select_dir_page.emit(QtCore.SIGNAL("completeChanged()"))
# Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet
-def handle_exception(exc_type, exc_value, exc_traceback ):
- import sys
- import os.path
- import traceback
+def handle_exception(exc_type, exc_value, exc_traceback):
+ filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
+ filename = os.path.basename(filename)
+ error = "%s: %s" % (exc_type.__name__, exc_value)
- filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop()
- filename = os.path.basename( filename )
- error = "%s: %s" % ( exc_type.__name__, exc_value )
-
- QMessageBox.critical(None, "Houston, we have a problem...",
- "Whoops. A critical error has occured. This is most likely a bug "
- "in Qubes Restore VMs application.
"
- "%s" % error +
- "at line %d of file %s.
"
- % ( line, filename ))
+ QtGui.QMessageBox.critical(
+ None,
+ "Houston, we have a problem...",
+ "Whoops. A critical error has occured. This is most likely a bug "
+ "in Qubes Global Settings application.
%s" %
+ error + "at line %d of file %s.
"
+ % (line, filename))
-def app_main():
+def main():
- global qubes_host
- qubes_host = QubesHost()
-
- global app
- app = QApplication(sys.argv)
- app.setOrganizationName("The Qubes Project")
- app.setOrganizationDomain("http://qubes-os.org")
- app.setApplicationName("Qubes Backup VMs")
+ qt_app = QtGui.QApplication(sys.argv)
+ qt_app.setOrganizationName("The Qubes Project")
+ qt_app.setOrganizationDomain("http://qubes-os.org")
+ qt_app.setApplicationName("Qubes Backup VMs")
sys.excepthook = handle_exception
- qvm_collection = QubesVmCollection()
- qvm_collection.lock_db_for_reading()
- qvm_collection.load()
- qvm_collection.unlock_db()
+ app = Qubes()
- global backup_window
- backup_window = BackupVMsWindow()
+ backup_window = BackupVMsWindow(qt_app, app)
backup_window.show()
- app.exec_()
- app.exit()
-
+ qt_app.exec_()
+ qt_app.exit()
if __name__ == "__main__":
- app_main()
+ main()
diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py
index 38b5931..9efd8b2 100644
--- a/qubesmanager/backup_utils.py
+++ b/qubesmanager/backup_utils.py
@@ -14,96 +14,117 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
import re
-import sys
-import os
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
+from PyQt4 import QtGui # pylint: disable=import-error
+from PyQt4 import QtCore # pylint: disable=import-error
import subprocess
-import time
-
-from .thread_monitor import *
+from . import utils
+import yaml
path_re = re.compile(r"[a-zA-Z0-9/:.,_+=() -]*")
path_max_len = 512
+
def fill_appvms_list(dialog):
+ """
+ Helper function, designed to fill the destination vm combobox in both backup
+ and restore GUI tools.
+ :param dialog: QtGui.QWizard with a combobox called appvm_combobox
+ """
dialog.appvm_combobox.clear()
dialog.appvm_combobox.addItem("dom0")
- dialog.appvm_combobox.setCurrentIndex(0) #current selected is null ""
+ dialog.appvm_combobox.setCurrentIndex(0) # current selected is null ""
- for vm in dialog.qvm_collection.values():
- if vm.is_appvm() and vm.internal:
- continue
- if vm.is_template() and vm.installed_by_rpm:
+ for vm in dialog.qubes_app.domains:
+ if vm.features.get('internal', False) or vm.klass == 'TemplateVM':
continue
if vm.is_running() and vm.qid != 0:
dialog.appvm_combobox.addItem(vm.name)
+
def enable_dir_line_edit(dialog, boolean):
dialog.dir_line_edit.setEnabled(boolean)
dialog.select_path_button.setEnabled(boolean)
-def get_path_for_vm(vm, service_name):
- if not vm:
- return None
- proc = vm.run("QUBESRPC %s dom0" % service_name, passio_popen=True)
- proc.stdin.close()
- untrusted_path = proc.stdout.readline(path_max_len)
- if len(untrusted_path) == 0:
- return None
- if path_re.match(untrusted_path):
- assert '../' not in untrusted_path
- assert '\0' not in untrusted_path
- return untrusted_path.strip()
- else:
- return None
-def select_path_button_clicked(dialog, select_file = False):
+def select_path_button_clicked(dialog, select_file=False, read_only=False):
+ """
+ Helper function that displays a file/directory selection wizard. Used by
+ backup and restore GUI tools.
+ :param dialog: QtGui.QWizard with a dir_line_edit text box that wants to
+ receive a file/directory path and appvm_combobox with VM to use
+ :param select_file: True: select file dialog; False: select directory
+ dialog
+ :param read_only: should the dir_line_edit be changed after selecting a file
+ or directory
+ :return:
+ """
backup_location = str(dialog.dir_line_edit.text())
- file_dialog = QFileDialog()
+ file_dialog = QtGui.QFileDialog()
file_dialog.setReadOnly(True)
- if select_file:
- file_dialog_function = file_dialog.getOpenFileName
- else:
- file_dialog_function = file_dialog.getExistingDirectory
-
- new_appvm = None
new_path = None
- if dialog.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
- new_appvm = str(dialog.appvm_combobox.currentText())
- vm = dialog.qvm_collection.get_vm_by_name(new_appvm)
- if vm:
- new_path = get_path_for_vm(vm, "qubes.SelectFile" if select_file
- else "qubes.SelectDirectory")
- else:
- new_path = file_dialog_function(dialog,
- dialog.tr("Select backup location."),
- backup_location if backup_location else '/')
- if new_path != None:
- if os.path.basename(new_path) == 'qubes.xml':
- backup_location = os.path.dirname(new_path)
+ new_appvm = str(dialog.appvm_combobox.currentText())
+ vm = dialog.qubes_app.domains[new_appvm]
+ try:
+ new_path = utils.get_path_from_vm(
+ vm,
+ "qubes.SelectFile" if select_file
+ else "qubes.SelectDirectory")
+ except subprocess.CalledProcessError:
+ if not read_only:
+ QtGui.QMessageBox.warning(
+ None,
+ dialog.tr("Nothing selected!"),
+ dialog.tr("No file or directory selected."))
else:
- backup_location = new_path
- dialog.dir_line_edit.setText(backup_location)
+ return
- if (new_path or new_appvm) and len(backup_location) > 0:
- dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
+ if new_path and not read_only:
+ dialog.dir_line_edit.setText(new_path)
-def simulate_long_lasting_proces(period, progress_callback):
- for i in range(period):
- progress_callback((i*100)/period)
- time.sleep(1)
+ if new_path and backup_location and not read_only:
+ dialog.select_dir_page.emit(QtCore.SIGNAL("completeChanged()"))
- progress_callback(100)
- return 0
+
+def get_profile_name(use_temp):
+ backup_profile_name = 'qubes-manager-backup'
+ temp_backup_profile_name = 'qubes-manager-backup-tmp'
+
+ return temp_backup_profile_name if use_temp else backup_profile_name
+
+
+def get_profile_path(use_temp):
+ path = '/etc/qubes/backup/' + get_profile_name(use_temp) + '.conf'
+ return path
+
+
+def load_backup_profile(use_temp=False):
+
+ path = get_profile_path(use_temp)
+
+ with open(path) as profile_file:
+ profile_data = yaml.safe_load(profile_file)
+ return profile_data
+
+
+def write_backup_profile(args, use_temp=False):
+
+ acceptable_fields = ['include', 'passphrase_text', 'compression',
+ 'destination_vm', 'destination_path']
+
+ profile_data = {key: value for key, value in args.items()
+ if key in acceptable_fields}
+
+ path = get_profile_path(use_temp)
+
+ with open(path, 'w') as profile_file:
+ yaml.safe_dump(profile_data, profile_file)
diff --git a/qubesmanager/block.py b/qubesmanager/block.py
index a826a1e..ed4ff0a 100644
--- a/qubesmanager/block.py
+++ b/qubesmanager/block.py
@@ -1,5 +1,6 @@
#!/usr/bin/python2
# -*- coding: utf8 -*-
+# pylint: skip-file
#
# The Qubes OS Project, http://www.qubes-os.org
#
@@ -15,9 +16,8 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
import threading
import time
diff --git a/qubesmanager/bootfromdevice.py b/qubesmanager/bootfromdevice.py
index d17e8fc..458abef 100644
--- a/qubesmanager/bootfromdevice.py
+++ b/qubesmanager/bootfromdevice.py
@@ -12,20 +12,21 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
+import sys
import subprocess
from . import utils
-from .firewall import *
-from .ui_bootfromdevice import *
-import qubesadmin.tools.qvm_start as qvm_start
+from . import ui_bootfromdevice # pylint: disable=no-name-in-module
+from PyQt4 import QtGui, QtCore # pylint: disable=import-error
+from qubesadmin import tools
+from qubesadmin.tools import qvm_start
-class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
+class VMBootFromDeviceWindow(ui_bootfromdevice.Ui_BootDialog, QtGui.QDialog):
def __init__(self, vm, qapp, parent=None):
super(VMBootFromDeviceWindow, self).__init__(parent)
@@ -33,14 +34,19 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
self.qapp = qapp
self.setupUi(self)
- self.setWindowTitle(self.tr("Boot {vm} from device").format(vm=self.vm.name))
+ self.setWindowTitle(
+ self.tr("Boot {vm} from device").format(vm=self.vm.name))
- self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply)
- self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
+ self.connect(
+ self.buttonBox,
+ QtCore.SIGNAL("accepted()"),
+ self.save_and_apply)
+ self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject)
# populate buttons and such
self.__init_buttons__()
-
+ # warn user if the VM is currently running
+ self.__warn_if_running__()
def reject(self):
self.done(0)
@@ -49,15 +55,30 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
if self.blockDeviceRadioButton.isChecked():
cdrom_location = self.blockDeviceComboBox.currentText()
elif self.fileRadioButton.isChecked():
- cdrom_location = str(self.vm_list[self.fileVM.currentIndex()]) + ":" + self.pathText.text()
+ cdrom_location = str(
+ self.vm_list[self.fileVM.currentIndex()]) + \
+ ":" + self.pathText.text()
else:
- QMessageBox.warning(None,
- self.tr(
- "ERROR!"),
- self.tr("No file or block device selected; please select one."))
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("ERROR!"),
+ self.tr("No file or block device selected; please select one."))
return
+
+ # warn user if the VM is currently running
+ self.__warn_if_running__()
+
qvm_start.main(['--cdrom', cdrom_location, self.vm.name])
+ def __warn_if_running__(self):
+ if self.vm.is_running():
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Warning!"),
+ self.tr("Qube must be turned off before booting it from"
+ "device. Please turn off the qube.")
+ )
+
def __init_buttons__(self):
self.fileVM.setEnabled(False)
self.selectFileButton.setEnabled(False)
@@ -86,7 +107,8 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
)
def radio_button_clicked(self):
- self.blockDeviceComboBox.setEnabled(self.blockDeviceRadioButton.isChecked())
+ self.blockDeviceComboBox.setEnabled(
+ self.blockDeviceRadioButton.isChecked())
self.fileVM.setEnabled(self.fileRadioButton.isChecked())
self.selectFileButton.setEnabled(self.fileRadioButton.isChecked())
self.pathText.setEnabled(self.fileRadioButton.isChecked())
@@ -103,18 +125,17 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
self.pathText.setText(new_path)
-parser = qubesadmin.tools.QubesArgumentParser(vmname_nargs=1)
+parser = tools.QubesArgumentParser(vmname_nargs=1)
+
def main(args=None):
- global bootfromdevice_window
-
args = parser.parse_args(args)
vm = args.domains.pop()
- qapp = QApplication(sys.argv)
+ qapp = QtGui.QApplication(sys.argv)
qapp.setOrganizationName('Invisible Things Lab')
qapp.setOrganizationDomain("https://www.qubes-os.org/")
- qapp.setApplicationName("Qubes VM Settings")
+ qapp.setApplicationName("Boot Qube From Device")
# if not utils.is_debug(): #FIXME
# sys.excepthook = handle_exception
diff --git a/qubesmanager/clipboard.py b/qubesmanager/clipboard.py
index 00fe9f7..f4c5785 100644
--- a/qubesmanager/clipboard.py
+++ b/qubesmanager/clipboard.py
@@ -1,4 +1,5 @@
#!/usr/bin/python2
+# pylint: skip-file
#
# The Qubes OS Project, http://www.qubes-os.org
#
@@ -16,9 +17,8 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
diff --git a/qubesmanager/create_new_vm.py b/qubesmanager/create_new_vm.py
index f75debc..d31c5db 100644
--- a/qubesmanager/create_new_vm.py
+++ b/qubesmanager/create_new_vm.py
@@ -16,34 +16,30 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
-import os
import sys
import threading
import time
import subprocess
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
+from PyQt4 import QtCore, QtGui # pylint: disable=import-error
import qubesadmin
import qubesadmin.tools
-
-import qubesmanager.resources_rc
+import qubesadmin.exc
from . import utils
-from .ui_newappvmdlg import Ui_NewVMDlg
+from .ui_newappvmdlg import Ui_NewVMDlg # pylint: disable=import-error
from .thread_monitor import ThreadMonitor
-class NewVmDlg(QDialog, Ui_NewVMDlg):
- def __init__(self, qtapp, app, parent = None):
+class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
+ def __init__(self, qtapp, app, parent=None):
super(NewVmDlg, self).__init__(parent)
self.setupUi(self)
@@ -51,7 +47,8 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
self.app = app
# Theoretically we should be locking for writing here and unlock
- # only after the VM creation finished. But the code would be more messy...
+ # only after the VM creation finished. But the code would be
+ # more messy...
# Instead we lock for writing in the actual worker thread
self.label_list, self.label_idx = utils.prepare_label_choice(
self.label,
@@ -73,13 +70,13 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
(lambda vm: vm.provides_network),
allow_internal=False, allow_default=True, allow_none=True)
- self.name.setValidator(QRegExpValidator(
- QRegExp("[a-zA-Z0-9-]*", Qt.CaseInsensitive), None))
+ self.name.setValidator(QtGui.QRegExpValidator(
+ QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None))
self.name.selectAll()
self.name.setFocus()
- if len(self.template_list) == 0:
- QMessageBox.warning(None,
+ if not self.template_list:
+ QtGui.QMessageBox.warning(None,
self.tr('No template available!'),
self.tr('Cannot create a qube when no template exists.'))
@@ -99,7 +96,8 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
self.done(0)
def accept(self):
- vmclass = ('AppVM' if self.vm_type.currentIndex() == 0 else 'StandaloneVM')
+ vmclass = ('AppVM' if self.vm_type.currentIndex() == 0
+ else 'StandaloneVM')
name = str(self.name.text())
try:
@@ -107,7 +105,7 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
except LookupError:
pass
else:
- QMessageBox.warning(None,
+ QtGui.QMessageBox.warning(None,
self.tr('Incorrect qube name!'),
self.tr('A qube with the name {} already exists in the '
'system!').format(name))
@@ -132,7 +130,7 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
thread.daemon = True
thread.start()
- progress = QProgressDialog(
+ progress = QtGui.QProgressDialog(
self.tr("Creating new qube {}...").format(name), "", 0, 0)
progress.setCancelButton(None)
progress.setModal(True)
@@ -140,12 +138,12 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
while not thread_monitor.is_finished():
self.qtapp.processEvents()
- time.sleep (0.1)
+ time.sleep(0.1)
progress.hide()
if not thread_monitor.success:
- QMessageBox.warning(None,
+ QtGui.QMessageBox.warning(None,
self.tr("Error creating the qube!"),
self.tr("ERROR: {}").format(thread_monitor.error_msg))
@@ -177,8 +175,10 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
for k, v in properties.items():
setattr(vm, k, v)
- except Exception as ex:
- thread_monitor.set_error_msg(str(ex))
+ except qubesadmin.exc.QubesException as qex:
+ thread_monitor.set_error_msg(str(qex))
+ except Exception as ex: # pylint: disable=broad-except
+ thread_monitor.set_error_msg(repr(ex))
thread_monitor.set_finished()
@@ -218,7 +218,7 @@ parser = qubesadmin.tools.QubesArgumentParser()
def main(args=None):
args = parser.parse_args(args)
- qtapp = QApplication(sys.argv)
+ qtapp = QtGui.QApplication(sys.argv)
qtapp.setOrganizationName('Invisible Things Lab')
qtapp.setOrganizationDomain('https://www.qubes-os.org/')
qtapp.setApplicationName('Create qube')
diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py
index d482b3f..14b69dc 100644
--- a/qubesmanager/firewall.py
+++ b/qubesmanager/firewall.py
@@ -13,91 +13,90 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
import datetime
-import ipaddress
-import os
import re
-import sys
-import xml.etree.ElementTree
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
+from PyQt4 import QtCore, QtGui # pylint: disable=import-error
import qubesadmin.firewall
-from . import ui_newfwruledlg
+from . import ui_newfwruledlg # pylint: disable=no-name-in-module
class FirewallModifiedOutsideError(ValueError):
pass
-class QIPAddressValidator(QValidator):
- def __init__(self, parent = None):
- super (QIPAddressValidator, self).__init__(parent)
+class QIPAddressValidator(QtGui.QValidator):
+ # pylint: disable=too-few-public-methods
+ def __init__(self, parent=None):
+ super(QIPAddressValidator, self).__init__(parent)
- def validate(self, input, pos):
- hostname = str(input)
+ def validate(self, input_string, pos):
+ # pylint: disable=too-many-return-statements,no-self-use
+ hostname = str(input_string)
- if len(hostname) > 255 or len(hostname) == 0:
- return (QValidator.Intermediate, input, pos)
+ if len(hostname) > 255 or not hostname:
+ return (QtGui.QValidator.Intermediate, input_string, pos)
if hostname == "*":
- return (QValidator.Acceptable, input, pos)
+ return (QtGui.QValidator.Acceptable, input_string, pos)
unmask = hostname.split("/", 1)
if len(unmask) == 2:
hostname = unmask[0]
mask = unmask[1]
if mask.isdigit() or mask == "":
- if re.match("^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None:
- return (QValidator.Invalid, input, pos)
+ if re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None:
+ return (QtGui.QValidator.Invalid, input_string, pos)
if mask != "":
mask = int(unmask[1])
if mask < 0 or mask > 32:
- return (QValidator.Invalid, input, pos)
+ return (QtGui.QValidator.Invalid, input_string, pos)
else:
- return (QValidator.Invalid, input, pos)
+ return (QtGui.QValidator.Invalid, input_string, pos)
if hostname[-1:] == ".":
hostname = hostname[:-1]
if hostname[-1:] == "-":
- return (QValidator.Intermediate, input, pos)
+ return (QtGui.QValidator.Intermediate, input_string, pos)
- allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?[a-z][a-z0-9-]+)\s+(?P[0-9]+)/(?P[a-z]+)", re.IGNORECASE)
- f = open('/etc/services', 'r')
- for line in f:
- match = pattern.match(line)
- if match is not None:
- service = match.groupdict()
- self.__services.append( (service["name"], int(service["port"]),) )
- f.close()
+ pattern = re.compile(
+ r"(?P[a-z][a-z0-9-]+)\s+(?P[0-9]+)/"
+ r"(?P[a-z]+)",
+ re.IGNORECASE)
+ with open('/etc/services', 'r') as file:
+ for line in file:
+ match = pattern.match(line)
+ if match is not None:
+ service = match.groupdict()
+ self.__services.append(
+ (service["name"], int(service["port"]),))
self.fw_changed = False
+ self.allow = None # is the default policy allow or deny
+ self.temp_full_access_expire_time = None # temporary full access time
+ self.__vm = None # VM that the model applies to
+ self.__children = None # list of rules in the FW
def sort(self, idx, order):
- from operator import attrgetter
+ rev = (order == QtCore.Qt.AscendingOrder)
+ self.children.sort(key=lambda x: self.get_column_string(idx, x),
+ reverse=rev)
- rev = (order == Qt.AscendingOrder)
- if idx==0:
- self.children.sort(key=lambda x: x['address'], reverse = rev)
- if idx==1:
- self.children.sort(key=lambda x: self.get_service_name(x[
- "portBegin"]) if x["portEnd"] == None else x["portBegin"],
- reverse = rev)
- if idx==2:
- self.children.sort(key=lambda x: x['proto'], reverse
- = rev)
index1 = self.createIndex(0, 0)
- index2 = self.createIndex(len(self)-1, len(self.__columnValues)-1)
+ index2 = self.createIndex(len(self) - 1, len(self.__column_names) - 1)
self.dataChanged.emit(index1, index2)
def get_service_name(self, port):
for service in self.__services:
- if service[1] == port:
+ if str(service[1]) == str(port):
return service[0]
return str(port)
@@ -197,256 +184,292 @@ class QubesFirewallRulesModel(QAbstractItemModel):
return service[1]
return None
- def get_column_string(self, col, row):
- return self.__columnValues[col](row)
+ def get_column_string(self, col, rule):
+ # pylint: disable=too-many-return-statements
+ # Address
+ if col == 0:
+ if rule.dsthost is None:
+ return "*"
+ if rule.dsthost.type == 'dst4' and rule.dsthost.prefixlen == '32':
+ return str(rule.dsthost)[:-3]
+ if rule.dsthost.type == 'dst6' and rule.dsthost.prefixlen == '128':
+ return str(rule.dsthost)[:-4]
+ return str(rule.dsthost)
-
- def rule_to_dict(self, rule):
- if rule.dsthost is None:
- raise FirewallModifiedOutsideError('no dsthost')
-
- d = {}
-
- if not rule.proto:
- d['proto'] = 'any'
- d['portBegin'] = 'any'
- d['portEnd'] = None
-
- else:
- d['proto'] = rule.proto
+ # Service
+ if col == 1:
if rule.dstports is None:
- raise FirewallModifiedOutsideError('no dstport')
- d['portBegin'] = rule.dstports.range[0]
- d['portEnd'] = rule.dstports.range[1] \
- if rule.dstports.range[0] != rule.dstports.range[1] \
- else None
+ return "any"
+ if rule.dstports.range[0] != rule.dstports.range[1]:
+ return str(rule.dstports)
+ return self.get_service_name(rule.dstports)
- if rule.dsthost.type == 'dsthost':
- d['address'] = str(rule.dsthost)
- d['netmask'] = 32
- elif rule.dsthost.type == 'dst4':
- network = ipaddress.IPv4Network(rule.dsthost)
- d['address'] = str(network.network_address)
- d['netmask'] = int(network.prefixlen)
- else:
- raise FirewallModifiedOutsideError(
- 'cannot map dsthost.type={!s}'.format(rule.dsthost))
+ # Protocol
+ if col == 2:
+ if rule.proto is None:
+ return "any"
+ return str(rule.proto)
+ return "unknown"
- if rule.expire is not None:
- d['expire'] = int(rule.expire)
-
- return d
-
- def get_firewall_conf(self, vm):
+ @staticmethod
+ def get_firewall_conf(vm):
conf = {
'allow': None,
- 'allowDns': False,
- 'allowIcmp': False,
- 'allowYumProxy': False,
+ 'expire': 0,
'rules': [],
}
+ allow_dns = False
+ allow_icmp = False
common_action = None
- tentative_action = None
reversed_rules = list(reversed(vm.firewall.rules))
+ last_rule = reversed_rules.pop(0)
+ if last_rule == qubesadmin.firewall.Rule('action=accept') \
+ or last_rule == qubesadmin.firewall.Rule('action=drop'):
+ common_action = last_rule.action
+ else:
+ FirewallModifiedOutsideError('Last rule must be either '
+ 'drop all or accept all.')
+
+ dns_rule = qubesadmin.firewall.Rule(None,
+ action='accept', specialtarget='dns')
+ icmp_rule = qubesadmin.firewall.Rule(None,
+ action='accept', proto='icmp')
while reversed_rules:
- rule = reversed_rules[0]
- if rule.dsthost is not None or rule.proto is not None:
- break
- tentative_action = reversed_rules.pop(0).action
+ rule = reversed_rules.pop(0)
- if not reversed_rules:
- conf['allow'] = tentative_action == 'accept'
- return conf
-
- for rule in reversed_rules:
- if rule.specialtarget == 'dns':
- conf['allowDns'] = (rule.action == 'accept')
+ if rule == dns_rule:
+ allow_dns = True
continue
- if rule.proto == 'icmp':
- if rule.icmptype is not None:
- raise FirewallModifiedOutsideError(
- 'cannot map icmptype != None')
- conf['allowIcmp'] = (rule.action == 'accept')
+ if rule == icmp_rule:
+ allow_icmp = True
continue
- if common_action is None:
- common_action = rule.action
- elif common_action != rule.action:
- raise FirewallModifiedOutsideError('incoherent action')
+ if rule.specialtarget is not None or rule.icmptype is not None:
+ raise FirewallModifiedOutsideError("Rule type unknown!")
- conf['rules'].insert(0, self.rule_to_dict(rule))
+ if (rule.dsthost is not None or rule.proto is not None) \
+ and rule.expire is None:
+ if rule.action == 'accept':
+ conf['rules'].insert(0, rule)
+ continue
+ else:
+ raise FirewallModifiedOutsideError('No blacklist support.')
- if common_action is None or common_action != tentative_action:
- # we've got only specialtarget and/or icmp
- conf['allow'] = tentative_action == 'accept'
- return conf
+ if rule.expire is not None and rule.dsthost is None \
+ and rule.proto is None:
+ conf['expire'] = int(str(rule.expire))
+ continue
- raise FirewallModifiedOutsideError('it does not add up')
+ raise FirewallModifiedOutsideError('it does not add up.')
- def write_firewall_conf(self, vm, conf):
- common_action = qubesadmin.firewall.Action(
- 'drop' if conf['allow'] else 'accept')
+ conf['allow'] = (common_action == 'accept')
+ if not allow_icmp and not conf['allow']:
+ raise FirewallModifiedOutsideError('ICMP must be allowed.')
+
+ if not allow_dns and not conf['allow']:
+ raise FirewallModifiedOutsideError('DNS must be allowed')
+
+ return conf
+
+ @staticmethod
+ def write_firewall_conf(vm, conf):
rules = []
for rule in conf['rules']:
- kwargs = {}
- if rule['proto'] != 'any':
- kwargs['proto'] = rule['proto']
- if rule['portBegin'] != 'any':
- kwargs['dstports'] = '-'.join(map(str, filter((lambda x: x),
- (rule['portBegin'], rule['portEnd']))))
+ rules.append(rule)
- netmask = str(rule['netmask']) if rule['netmask'] != 32 else None
-
- rules.append(qubesadmin.firewall.Rule(None,
- action=common_action,
- dsthost='/'.join(map(str, filter((lambda x: x),
- (rule['address'], netmask)))),
- **kwargs))
-
- if conf['allowDns']:
+ if not conf['allow']:
rules.append(qubesadmin.firewall.Rule(None,
action='accept', specialtarget='dns'))
- if conf['allowIcmp']:
+ if not conf['allow']:
rules.append(qubesadmin.firewall.Rule(None,
action='accept', proto='icmp'))
- if common_action == 'drop':
+ if conf['allow']:
rules.append(qubesadmin.firewall.Rule(None,
action='accept'))
+ else:
+ rules.append(qubesadmin.firewall.Rule(None,
+ action='drop'))
vm.firewall.rules = rules
def set_vm(self, vm):
self.__vm = vm
- self.clearChildren()
+ self.clear_children()
conf = self.get_firewall_conf(vm)
self.allow = conf["allow"]
- self.allowDns = conf["allowDns"]
- self.allowIcmp = conf["allowIcmp"]
- self.allowYumProxy = conf["allowYumProxy"]
- self.tempFullAccessExpireTime = 0
+
+ self.temp_full_access_expire_time = conf['expire']
for rule in conf["rules"]:
- self.appendChild(rule)
- if "expire" in rule and rule["address"] == "0.0.0.0":
- self.tempFullAccessExpireTime = rule["expire"]
+ self.append_child(rule)
def get_vm_name(self):
return self.__vm.name
- def apply_rules(self, allow, dns, icmp, yumproxy, tempFullAccess=False,
- tempFullAccessTime=None):
+ def apply_rules(self, allow, temp_full_access=False,
+ temp_full_access_time=None):
assert self.__vm is not None
- if self.allow != allow or self.allowDns != dns or \
- self.allowIcmp != icmp or self.allowYumProxy != yumproxy or \
- (self.tempFullAccessExpireTime != 0) != tempFullAccess:
+ if self.allow != allow or \
+ (self.temp_full_access_expire_time != 0) != temp_full_access:
self.fw_changed = True
- conf = { "allow": allow,
- "allowDns": dns,
- "allowIcmp": icmp,
- "allowYumProxy": yumproxy,
+ conf = {"allow": allow,
"rules": list()
}
- for rule in self.children:
- if "expire" in rule and rule["address"] == "0.0.0.0" and \
- rule["netmask"] == 0 and rule["proto"] == "any":
- # rule already present, update its time
- if tempFullAccess:
- rule["expire"] = \
- int(datetime.datetime.now().strftime("%s")) + \
- tempFullAccessTime*60
- tempFullAccess = False
- conf["rules"].append(rule)
+ conf['rules'].extend(self.children)
- if tempFullAccess and not allow:
- conf["rules"].append({"address": "0.0.0.0",
- "netmask": 0,
- "proto": "any",
- "expire": int(
- datetime.datetime.now().strftime("%s"))+\
- tempFullAccessTime*60
- })
+ if temp_full_access and not allow:
+ conf["rules"].append(qubesadmin.firewall.Rule(
+ None,
+ action='accept',
+ expire=int(datetime.datetime.now().strftime("%s")) +
+ temp_full_access_time * 60))
if self.fw_changed:
self.write_firewall_conf(self.__vm, conf)
- def index(self, row, column, parent=QModelIndex()):
+ def populate_edit_dialog(self, dialog, row):
+ address = self.get_column_string(0, self.children[row])
+ dialog.addressComboBox.setItemText(0, address)
+ dialog.addressComboBox.setCurrentIndex(0)
+ service = self.get_column_string(1, self.children[row])
+ if service == "any":
+ service = ""
+ dialog.serviceComboBox.setItemText(0, service)
+ dialog.serviceComboBox.setCurrentIndex(0)
+ protocol = self.get_column_string(2, self.children[row])
+ if protocol == "tcp":
+ dialog.tcp_radio.setChecked(True)
+ elif protocol == "udp":
+ dialog.udp_radio.setChecked(True)
+ else:
+ dialog.any_radio.setChecked(True)
+
+ def run_rule_dialog(self, dialog, row=None):
+ if dialog.exec_():
+
+ address = str(dialog.addressComboBox.currentText())
+ service = str(dialog.serviceComboBox.currentText())
+
+ rule = qubesadmin.firewall.Rule(None, action='accept')
+
+ if address is not None and address != "*":
+ try:
+ rule.dsthost = address
+ except ValueError:
+ QtGui.QMessageBox.warning(None, self.tr("Invalid address"),
+ self.tr("Address '{0}' is invalid.").format(address))
+
+ if dialog.tcp_radio.isChecked():
+ rule.proto = 'tcp'
+ elif dialog.udp_radio.isChecked():
+ rule.proto = 'udp'
+
+ if '-' in service:
+ try:
+ rule.dstports = service
+ except ValueError:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Invalid port or service"),
+ self.tr("Port number or service '{0}' is invalid.")
+ .format(service))
+ elif service:
+ try:
+ rule.dstports = service
+ except (TypeError, ValueError):
+ if self.get_service_port(service) is not None:
+ rule.dstports = self.get_service_port(service)
+ else:
+ QtGui.QMessageBox.warning(None,
+ self.tr("Invalid port or service"),
+ self.tr("Port number or service '{0}' is invalid.")
+ .format(service))
+
+ if row is not None:
+ self.set_child(row, rule)
+ else:
+ self.append_child(rule)
+
+ def index(self, row, column, parent=QtCore.QModelIndex()):
if not self.hasIndex(row, column, parent):
- return QModelIndex()
+ return QtCore.QModelIndex()
return self.createIndex(row, column, self.children[row])
- def parent(self, child):
- return QModelIndex()
+ def parent(self, child): # pylint: disable=unused-argument,no-self-use
+ return QtCore.QModelIndex()
- def rowCount(self, parent=QModelIndex()):
+ # pylint: disable=invalid-name,unused-argument
+ def rowCount(self, parent=QtCore.QModelIndex()):
return len(self)
- def columnCount(self, parent=QModelIndex()):
- return len(self.__columnValues)
+ # pylint: disable=invalid-name,unused-argument
+ def columnCount(self, parent=QtCore.QModelIndex()):
+ return len(self.__column_names)
- def hasChildren(self, index=QModelIndex()):
- parentItem = index.internalPointer()
- if parentItem is not None:
- return False
- else:
- return True
+ # pylint: disable=invalid-name,no-self-use
+ def hasChildren(self, index=QtCore.QModelIndex()):
+ parent_item = index.internalPointer()
+ return parent_item is None
- def data(self, index, role=Qt.DisplayRole):
- if index.isValid() and role == Qt.DisplayRole:
- return self.__columnValues[index.column()](index.row())
+ def data(self, index, role=QtCore.Qt.DisplayRole):
+ if index.isValid() and role == QtCore.Qt.DisplayRole:
+ return self.get_column_string(index.column(),
+ self.children[index.row()])
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- if section < len(self.__columnNames) \
- and orientation == Qt.Horizontal and role == Qt.DisplayRole:
- return self.__columnNames[section]
+ # pylint: disable=invalid-name
+ def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
+ if section < len(self.__column_names) \
+ and orientation == QtCore.Qt.Horizontal \
+ and role == QtCore.Qt.DisplayRole:
+ return self.__column_names[section]
@property
def children(self):
return self.__children
- def appendChild(self, child):
+ def append_child(self, child):
row = len(self)
- self.beginInsertRows(QModelIndex(), row, row)
+ self.beginInsertRows(QtCore.QModelIndex(), row, row)
self.children.append(child)
self.endInsertRows()
index = self.createIndex(row, 0, child)
self.dataChanged.emit(index, index)
self.fw_changed = True
- def removeChild(self, i):
+ def remove_child(self, i):
if i >= len(self):
return
- self.beginRemoveRows(QModelIndex(), i, i)
+ self.beginRemoveRows(QtCore.QModelIndex(), i, i)
del self.children[i]
self.endRemoveRows()
index = self.createIndex(i, 0)
self.dataChanged.emit(index, index)
self.fw_changed = True
- def setChild(self, i, child):
+ def set_child(self, i, child):
self.children[i] = child
index = self.createIndex(i, 0, child)
self.dataChanged.emit(index, index)
self.fw_changed = True
- def clearChildren(self):
+ def clear_children(self):
self.__children = list()
def __len__(self):
return len(self.children)
-
diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py
index eaf009f..c779326 100644
--- a/qubesmanager/global_settings.py
+++ b/qubesmanager/global_settings.py
@@ -15,28 +15,29 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
import sys
import os
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
+import os.path
+import traceback
+from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from qubesadmin import Qubes
+from qubesadmin.utils import parse_size, updates_vms_status
-from .ui_globalsettingsdlg import *
+from . import ui_globalsettingsdlg # pylint: disable=no-name-in-module
from configparser import ConfigParser
-from qubesadmin.utils import parse_size, updates_vms_status
qmemman_config_path = '/etc/qubes/qmemman.conf'
-class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
+class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
+ QtGui.QDialog):
def __init__(self, app, qvm_collection, parent=None):
super(GlobalSettingsWindow, self).__init__(parent)
@@ -45,10 +46,13 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.qvm_collection = qvm_collection
self.setupUi(self)
-
- self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply)
- self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
-
+
+ self.connect(
+ self.buttonBox,
+ QtCore.SIGNAL("accepted()"),
+ self.save_and_apply)
+ self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject)
+
self.__init_system_defaults__()
self.__init_kernel_defaults__()
self.__init_mem_defaults__()
@@ -89,7 +93,8 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.clock_vm_combo.setCurrentIndex(self.clockvm_idx)
# default netvm
- netvms = [vm for vm in all_vms if getattr(vm, 'provides_network', False)]
+ netvms = [vm for vm in all_vms
+ if getattr(vm, 'provides_network', False)]
self.netvm_idx = -1
current_netvm = self.qvm_collection.default_netvm
@@ -122,7 +127,7 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
updatevm_name = str(self.update_vm_combo.currentText())
updatevm_name = updatevm_name.split(' ')[0]
updatevm = self.qvm_collection.domains[updatevm_name]
-
+
self.qvm_collection.updatevm = updatevm
#clockvm
@@ -130,7 +135,7 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
clockvm_name = str(self.clock_vm_combo.currentText())
clockvm_name = clockvm_name.split(' ')[0]
clockvm = self.qvm_collection.domains[clockvm_name]
-
+
self.qvm_collection.clockvm = clockvm
#default netvm
@@ -138,7 +143,7 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
name = str(self.default_netvm_combo.currentText())
name = name.split(' ')[0]
vm = self.qvm_collection.domains[name]
-
+
self.qvm_collection.default_netvm = vm
#default template
@@ -146,13 +151,14 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
name = str(self.default_template_combo.currentText())
name = name.split(' ')[0]
vm = self.qvm_collection.domains[name]
-
+
self.qvm_collection.default_template = vm
def __init_kernel_defaults__(self):
kernel_list = []
- # TODO system_path["qubes_kernels_base_dir"] idea: qubes.pulls['linux-kernel'].volumes
+ # TODO system_path["qubes_kernels_base_dir"]
+ # idea: qubes.pools['linux-kernel'].volumes
for k in os.listdir('/var/lib/qubes/vm-kernels'):
kernel_list.append(k)
@@ -170,21 +176,22 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
if self.default_kernel_combo.currentIndex() != self.kernel_idx:
kernel = str(self.default_kernel_combo.currentText())
kernel = kernel.split(' ')[0]
-
+
self.qvm_collection.default_kernel = kernel
-
- def __init_mem_defaults__(self):
+ def __init_mem_defaults__(self):
#qmemman settings
self.qmemman_config = ConfigParser()
- self.vm_min_mem_val = '200MiB' #str(qmemman_algo.MIN_PREFMEM)
+ self.vm_min_mem_val = '200MiB' #str(qmemman_algo.MIN_PREFMEM)
self.dom0_mem_boost_val = '350MiB' #str(qmemman_algo.DOM0_MEM_BOOST)
-
+
self.qmemman_config.read(qmemman_config_path)
if self.qmemman_config.has_section('global'):
- self.vm_min_mem_val = self.qmemman_config.get('global', 'vm-min-mem')
- self.dom0_mem_boost_val = self.qmemman_config.get('global', 'dom0-mem-boost')
+ self.vm_min_mem_val = \
+ self.qmemman_config.get('global', 'vm-min-mem')
+ self.dom0_mem_boost_val = \
+ self.qmemman_config.get('global', 'dom0-mem-boost')
self.vm_min_mem_val = parse_size(self.vm_min_mem_val)
self.dom0_mem_boost_val = parse_size(self.dom0_mem_boost_val)
@@ -199,7 +206,8 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
current_min_vm_mem = self.min_vm_mem.value()
current_dom0_mem_boost = self.dom0_mem_boost.value()
- if current_min_vm_mem*1024*1024 != self.vm_min_mem_val or current_dom0_mem_boost*1024*1024 != self.dom0_mem_boost_val:
+ if current_min_vm_mem*1024*1024 != self.vm_min_mem_val \
+ or current_dom0_mem_boost*1024*1024 != self.dom0_mem_boost_val:
current_min_vm_mem = str(current_min_vm_mem)+'M'
current_dom0_mem_boost = str(current_dom0_mem_boost)+'M'
@@ -207,38 +215,46 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
if not self.qmemman_config.has_section('global'):
#add the whole section
self.qmemman_config.add_section('global')
- self.qmemman_config.set('global', 'vm-min-mem', current_min_vm_mem)
- self.qmemman_config.set('global', 'dom0-mem-boost', current_dom0_mem_boost)
- self.qmemman_config.set('global', 'cache-margin-factor', str(1.3)) # removed qmemman_algo.CACHE_FACTOR
+ self.qmemman_config.set(
+ 'global', 'vm-min-mem', current_min_vm_mem)
+ self.qmemman_config.set(
+ 'global', 'dom0-mem-boost', current_dom0_mem_boost)
+ self.qmemman_config.set(
+ 'global', 'cache-margin-factor', str(1.3))
+ # removed qmemman_algo.CACHE_FACTOR
qmemman_config_file = open(qmemman_config_path, 'a')
self.qmemman_config.write(qmemman_config_file)
qmemman_config_file.close()
else:
- #If there already is a 'global' section, we don't use SafeConfigParser.write() - it would get rid of all the comments...
-
+ #If there already is a 'global' section, we don't use
+ # SafeConfigParser.write() - it would get rid of
+ # all the comments...
+
lines_to_add = {}
- lines_to_add['vm-min-mem'] = "vm-min-mem = " + current_min_vm_mem + "\n"
- lines_to_add['dom0-mem-boost'] = "dom0-mem-boost = " + current_dom0_mem_boost +"\n"
+ lines_to_add['vm-min-mem'] = \
+ "vm-min-mem = " + current_min_vm_mem + "\n"
+ lines_to_add['dom0-mem-boost'] = \
+ "dom0-mem-boost = " + current_dom0_mem_boost +"\n"
config_lines = []
qmemman_config_file = open(qmemman_config_path, 'r')
- for l in qmemman_config_file:
- if l.strip().startswith('vm-min-mem'):
+ for line in qmemman_config_file:
+ if line.strip().startswith('vm-min-mem'):
config_lines.append(lines_to_add['vm-min-mem'])
del lines_to_add['vm-min-mem']
- elif l.strip().startswith('dom0-mem-boost'):
+ elif line.strip().startswith('dom0-mem-boost'):
config_lines.append(lines_to_add['dom0-mem-boost'])
del lines_to_add['dom0-mem-boost']
else:
- config_lines.append(l)
-
+ config_lines.append(line)
+
qmemman_config_file.close()
- for l in lines_to_add:
- config_lines.append(l)
+ for line in lines_to_add:
+ config_lines.append(line)
qmemman_config_file = open(qmemman_config_path, 'w')
qmemman_config_file.writelines(config_lines)
@@ -252,24 +268,26 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.updates_dom0.setChecked(self.updates_dom0_val)
updates_vms = updates_vms_status(self.qvm_collection)
if updates_vms is None:
- self.updates_vm.setCheckState(Qt.PartiallyChecked)
+ self.updates_vm.setCheckState(QtCore.Qt.PartiallyChecked)
else:
self.updates_vm.setCheckState(updates_vms)
def __apply_updates__(self):
if self.updates_dom0.isChecked() != self.updates_dom0_val:
- # TODO updates_dom0_toggle(self.qvm_collection, self.updates_dom0.isChecked())
+ # TODO updates_dom0_toggle(
+ # self.qvm_collection, self.updates_dom0.isChecked())
raise NotImplementedError('Toggle dom0 updates not implemented')
- if self.updates_vm.checkState() != Qt.PartiallyChecked:
+ if self.updates_vm.checkState() != QtCore.Qt.PartiallyChecked:
for vm in self.qvm_collection.domains:
- vm.features['check-updates'] = bool(self.updates_vm.checkState())
+ vm.features['check-updates'] = \
+ bool(self.updates_vm.checkState())
def reject(self):
self.done(0)
def save_and_apply(self):
- self.__apply_system_defaults__()
+ self.__apply_system_defaults__()
self.__apply_kernel_defaults__()
self.__apply_mem_defaults__()
self.__apply_updates__()
@@ -279,26 +297,22 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
# Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet
-def handle_exception( exc_type, exc_value, exc_traceback ):
- import os.path
- import traceback
+def handle_exception(exc_type, exc_value, exc_traceback):
+ filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
+ filename = os.path.basename(filename)
+ error = "%s: %s" % (exc_type.__name__, exc_value)
- filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop()
- filename = os.path.basename( filename )
- error = "%s: %s" % ( exc_type.__name__, exc_value )
-
- QMessageBox.critical(None, "Houston, we have a problem...",
- "Whoops. A critical error has occured. This is most likely a bug "
- "in Qubes Global Settings application.
"
- "%s" % error +
- "at line %d of file %s.
"
- % ( line, filename ))
+ QtGui.QMessageBox.critical(
+ None,
+ "Houston, we have a problem...",
+ "Whoops. A critical error has occured. This is most likely a bug "
+ "in Qubes Global Settings application.
%s" %
+ error + "at line %d of file %s.
"
+ % (line, filename))
def main():
-
- global qtapp
- qtapp = QApplication(sys.argv)
+ qtapp = QtGui.QApplication(sys.argv)
qtapp.setOrganizationName("The Qubes Project")
qtapp.setOrganizationDomain("http://qubes-os.org")
qtapp.setApplicationName("Qubes Global Settings")
@@ -307,7 +321,6 @@ def main():
app = Qubes()
- global global_window
global_window = GlobalSettingsWindow(qtapp, app)
global_window.show()
diff --git a/qubesmanager/informationnotes.py b/qubesmanager/informationnotes.py
index b6aa946..b512d3f 100644
--- a/qubesmanager/informationnotes.py
+++ b/qubesmanager/informationnotes.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
# coding=utf-8
#
# The Qubes OS Project, http://www.qubes-os.org
@@ -16,22 +16,23 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
-from PyQt4.QtCore import SIGNAL
-from PyQt4.QtGui import QDialog, QIcon
+from PyQt4.QtGui import QDialog # pylint: disable=import-error
-from .ui_informationnotes import *
+from . import ui_informationnotes # pylint: disable=no-name-in-module
import subprocess
-class InformationNotesDialog(Ui_InformationNotesDialog, QDialog):
+class InformationNotesDialog(ui_informationnotes.Ui_InformationNotesDialog,
+ QDialog):
+ # pylint: disable=too-few-public-methods
def __init__(self):
super(InformationNotesDialog, self).__init__()
self.setupUi(self)
- details = subprocess.check_output(['/usr/libexec/qubes-manager/qvm_about.sh'])
- self.informationNotes.setText(details)
+ details = subprocess.check_output(
+ ['/usr/libexec/qubes-manager/qvm_about.sh'])
+ self.informationNotes.setText(details.decode())
diff --git a/qubesmanager/log_dialog.py b/qubesmanager/log_dialog.py
index 6e7ced9..0f4232b 100644
--- a/qubesmanager/log_dialog.py
+++ b/qubesmanager/log_dialog.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
#
# The Qubes OS Project, http://www.qubes-os.org
#
@@ -15,28 +15,24 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
-import sys
+from PyQt4 import QtCore # pylint: disable=import-error
+from PyQt4 import QtGui # pylint: disable=import-error
+
+from . import ui_logdlg # pylint: disable=no-name-in-module
+from . import clipboard
import os
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from qubes.qubes import QubesException
-
-import qubesmanager.resources_rc
-
-from .ui_logdlg import *
-from .clipboard import *
# Display only this size of log
LOG_DISPLAY_SIZE = 1024*1024
-class LogDialog(Ui_LogDialog, QDialog):
+
+class LogDialog(ui_logdlg.Ui_LogDialog, QtGui.QDialog):
+ # pylint: disable=too-few-public-methods
def __init__(self, app, log_path, parent=None):
super(LogDialog, self).__init__(parent)
@@ -46,9 +42,11 @@ class LogDialog(Ui_LogDialog, QDialog):
self.setupUi(self)
self.setWindowTitle(log_path)
-
- self.connect(self.copy_to_qubes_clipboard, SIGNAL("clicked()"), self.copy_to_qubes_clipboard_triggered)
-
+
+ self.connect(self.copy_to_qubes_clipboard,
+ QtCore.SIGNAL("clicked()"),
+ self.copy_to_clipboard_triggered)
+
self.__init_log_text__()
def __init_log_text__(self):
@@ -56,7 +54,8 @@ class LogDialog(Ui_LogDialog, QDialog):
log = open(self.log_path)
log.seek(0, os.SEEK_END)
if log.tell() > LOG_DISPLAY_SIZE:
- self.displayed_text = self.tr("(Showing only last %d bytes of file)\n") % LOG_DISPLAY_SIZE
+ self.displayed_text = self.tr(
+ "(Showing only last %d bytes of file)\n") % LOG_DISPLAY_SIZE
log.seek(-LOG_DISPLAY_SIZE, os.SEEK_END)
else:
log.seek(0, os.SEEK_SET)
@@ -64,6 +63,5 @@ class LogDialog(Ui_LogDialog, QDialog):
log.close()
self.log_text.setPlainText(self.displayed_text)
-
- def copy_to_qubes_clipboard_triggered(self):
- copy_text_to_qubes_clipboard(self.displayed_text)
+ def copy_to_clipboard_triggered(self):
+ clipboard.copy_text_to_qubes_clipboard(self.displayed_text)
diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py
index a1ab7c2..c733889 100644
--- a/qubesmanager/multiselectwidget.py
+++ b/qubesmanager/multiselectwidget.py
@@ -1,46 +1,47 @@
-import sys
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from .ui_multiselectwidget import *
+from PyQt4 import QtCore, QtGui # pylint: disable=import-error
+from . import ui_multiselectwidget # pylint: disable=no-name-in-module
-class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
+class MultiSelectWidget(
+ ui_multiselectwidget.Ui_MultiSelectWidget, QtGui.QWidget):
__pyqtSignals__ = ("selected_changed()",)
__pyqtSignals__ = ("items_added(PyQt_PyObject)",)
__pyqtSignals__ = ("items_removed(PyQt_PyObject)",)
def __init__(self, parent=None):
- super(MultiSelectWidget, self).__init__()
+ super(MultiSelectWidget, self).__init__(parent)
self.setupUi(self)
self.add_selected_button.clicked.connect(self.add_selected)
self.add_all_button.clicked.connect(self.add_all)
self.remove_selected_button.clicked.connect(self.remove_selected)
self.remove_all_button.clicked.connect(self.remove_all)
- self.available_list.setSelectionMode(QAbstractItemView.ExtendedSelection)
- self.selected_list.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.available_list.setSelectionMode(
+ QtGui.QAbstractItemView.ExtendedSelection)
+ self.selected_list.setSelectionMode(
+ QtGui.QAbstractItemView.ExtendedSelection)
def switch_selected(self, src, dst):
selected = src.selectedItems()
items = []
- for s in selected:
- row = src.indexFromItem(s).row()
+ for selected_item in selected:
+ row = src.indexFromItem(selected_item).row()
item = src.takeItem(row)
dst.addItem(item)
items.append(item)
dst.sortItems()
- self.emit(SIGNAL("selected_changed()"))
- if src is self.selected_list:
- self.emit(SIGNAL("items_removed(PyQt_PyObject)"), items)
+ self.emit(QtCore.SIGNAL("selected_changed()"))
+ if src is self.selected_list:
+ self.emit(QtCore.SIGNAL("items_removed(PyQt_PyObject)"), items)
else:
- self.emit(SIGNAL("items_added(PyQt_PyObject)"), items)
+ self.emit(QtCore.SIGNAL("items_added(PyQt_PyObject)"), items)
def add_selected(self):
self.switch_selected(self.available_list, self.selected_list)
def remove_selected(self):
- self.switch_selected(self.selected_list, self.available_list)
-
+ self.switch_selected(self.selected_list, self.available_list)
+
def move_all(self, src, dst):
items = []
while src.count() > 0:
@@ -48,11 +49,11 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
dst.addItem(item)
items.append(item)
dst.sortItems()
- self.emit(SIGNAL("selected_changed()"))
- if src is self.selected_list:
- self.emit(SIGNAL("items_removed(PyQt_PyObject)"), items)
+ self.emit(QtCore.SIGNAL("selected_changed()"))
+ if src is self.selected_list:
+ self.emit(QtCore.SIGNAL("items_removed(PyQt_PyObject)"), items)
else:
- self.emit(SIGNAL("items_added(PyQt_PyObject)"), items)
+ self.emit(QtCore.SIGNAL("items_added(PyQt_PyObject)"), items)
def add_all(self):
@@ -64,4 +65,3 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
def clear(self):
self.available_list.clear()
self.selected_list.clear()
-
diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py
new file mode 100755
index 0000000..9cb6ef1
--- /dev/null
+++ b/qubesmanager/qube_manager.py
@@ -0,0 +1,1188 @@
+#!/usr/bin/python3
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2012 Agnieszka Kostrzewa
+# Copyright (C) 2012 Marek Marczykowski-Górecki
+#
+# Copyright (C) 2017 Wojtek Porczyk
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
+#
+#
+
+import sys
+import os
+import os.path
+import subprocess
+import time
+from datetime import datetime, timedelta
+import traceback
+
+from qubesadmin import Qubes
+from qubesadmin import exc
+
+from PyQt4 import QtGui # pylint: disable=import-error
+from PyQt4 import QtCore # pylint: disable=import-error
+
+from . import ui_qubemanager # pylint: disable=no-name-in-module
+from . import thread_monitor
+from . import table_widgets
+from . import settings
+from . import global_settings
+from . import restore
+from . import backup
+from . import log_dialog
+import threading
+
+from qubesmanager.about import AboutDialog
+
+
+class SearchBox(QtGui.QLineEdit):
+ def __init__(self, parent=None):
+ super(SearchBox, self).__init__(parent)
+ self.focusing = False
+
+ def focusInEvent(self, e): # pylint: disable=invalid-name
+ super(SearchBox, self).focusInEvent(e)
+ self.selectAll()
+ self.focusing = True
+
+ def mousePressEvent(self, e): # pylint: disable=invalid-name
+ super(SearchBox, self).mousePressEvent(e)
+ if self.focusing:
+ self.selectAll()
+ self.focusing = False
+
+
+class VmRowInTable(object):
+ # pylint: disable=too-few-public-methods
+
+ def __init__(self, vm, row_no, table):
+ self.vm = vm
+ self.row_no = row_no
+ # TODO: replace a various different widgets with a more generic
+ # VmFeatureWidget or VMPropertyWidget
+
+ table_widgets.row_height = VmManagerWindow.row_height
+ table.setRowHeight(row_no, VmManagerWindow.row_height)
+
+ self.type_widget = table_widgets.VmTypeWidget(vm)
+ table.setCellWidget(row_no, VmManagerWindow.columns_indices['Type'],
+ self.type_widget)
+ table.setItem(row_no, VmManagerWindow.columns_indices['Type'],
+ self.type_widget.table_item)
+
+ self.label_widget = table_widgets.VmLabelWidget(vm)
+ table.setCellWidget(row_no, VmManagerWindow.columns_indices['Label'],
+ self.label_widget)
+ table.setItem(row_no, VmManagerWindow.columns_indices['Label'],
+ self.label_widget.table_item)
+
+ self.name_widget = table_widgets.VmNameItem(vm)
+ table.setItem(row_no, VmManagerWindow.columns_indices['Name'],
+ self.name_widget)
+
+ self.info_widget = table_widgets.VmInfoWidget(vm)
+ table.setCellWidget(row_no, VmManagerWindow.columns_indices['State'],
+ self.info_widget)
+ table.setItem(row_no, VmManagerWindow.columns_indices['State'],
+ self.info_widget.table_item)
+
+ self.template_widget = table_widgets.VmTemplateItem(vm)
+ table.setItem(row_no, VmManagerWindow.columns_indices['Template'],
+ self.template_widget)
+
+ self.netvm_widget = table_widgets.VmNetvmItem(vm)
+ table.setItem(row_no, VmManagerWindow.columns_indices['NetVM'],
+ self.netvm_widget)
+
+ self.size_widget = table_widgets.VmSizeOnDiskItem(vm)
+ table.setItem(row_no, VmManagerWindow.columns_indices['Size'],
+ self.size_widget)
+
+ self.internal_widget = table_widgets.VmInternalItem(vm)
+ table.setItem(row_no, VmManagerWindow.columns_indices['Internal'],
+ self.internal_widget)
+
+ self.ip_widget = table_widgets.VmIPItem(vm)
+ table.setItem(row_no, VmManagerWindow.columns_indices['IP'],
+ self.ip_widget)
+
+ self.include_in_backups_widget = \
+ table_widgets.VmIncludeInBackupsItem(vm)
+ table.setItem(row_no, VmManagerWindow.columns_indices[
+ 'Backups'], self.include_in_backups_widget)
+
+ self.last_backup_widget = table_widgets.VmLastBackupItem(vm)
+ table.setItem(row_no, VmManagerWindow.columns_indices[
+ 'Last backup'], self.last_backup_widget)
+
+ def update(self, update_size_on_disk=False):
+ """
+ Update info in a single VM row
+ :param update_size_on_disk: should disk utilization be updated? the
+ widget will extract the data from VM object
+ :return: None
+ """
+ self.info_widget.update_vm_state(self.vm)
+ if update_size_on_disk:
+ self.size_widget.update()
+
+
+vm_shutdown_timeout = 20000 # in msec
+vm_restart_check_timeout = 1000 # in msec
+
+
+class VmShutdownMonitor(QtCore.QObject):
+ def __init__(self, vm, shutdown_time=vm_shutdown_timeout,
+ check_time=vm_restart_check_timeout,
+ and_restart=False, caller=None):
+ QtCore.QObject.__init__(self)
+ self.vm = vm
+ self.shutdown_time = shutdown_time
+ self.check_time = check_time
+ self.and_restart = and_restart
+ self.shutdown_started = datetime.now()
+ self.caller = caller
+
+ def restart_vm_if_needed(self):
+ if self.and_restart and self.caller:
+ self.caller.start_vm(self.vm)
+
+ def check_again_later(self):
+ # noinspection PyTypeChecker,PyCallByClass
+ QtCore.QTimer.singleShot(self.check_time, self.check_if_vm_has_shutdown)
+
+ def timeout_reached(self):
+ actual = datetime.now() - self.shutdown_started
+ allowed = timedelta(milliseconds=self.shutdown_time)
+
+ return actual > allowed
+
+ def check_if_vm_has_shutdown(self):
+ vm = self.vm
+ vm_is_running = vm.is_running()
+ try:
+ vm_start_time = datetime.fromtimestamp(float(vm.start_time))
+ except AttributeError:
+ vm_start_time = None
+
+ if vm_is_running and vm_start_time \
+ and vm_start_time < self.shutdown_started:
+ if self.timeout_reached():
+ reply = QtGui.QMessageBox.question(
+ None, self.tr("Qube Shutdown"),
+ self.tr(
+ "The Qube '{0}' hasn't shutdown within the last "
+ "{1} seconds, do you want to kill it?
").format(
+ vm.name, self.shutdown_time / 1000),
+ self.tr("Kill it!"),
+ self.tr("Wait another {0} seconds...").format(
+ self.shutdown_time / 1000))
+ if reply == 0:
+ vm.force_shutdown()
+ self.restart_vm_if_needed()
+ else:
+ self.shutdown_started = datetime.now()
+ self.check_again_later()
+ else:
+ self.check_again_later()
+ else:
+ if vm_is_running:
+ # Due to unknown reasons, Xen sometimes reports that a domain
+ # is running even though its start-up timestamp is not valid.
+ # Make sure that "restart_vm_if_needed" is not called until
+ # the domain has been completely shut down according to Xen.
+ self.check_again_later()
+ return
+
+ self.restart_vm_if_needed()
+
+
+class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
+ # pylint: disable=too-many-instance-attributes
+ row_height = 30
+ column_width = 200
+ min_visible_rows = 10
+ search = ""
+ # suppress saving settings while initializing widgets
+ settings_loaded = False
+ columns_indices = {"Type": 0,
+ "Label": 1,
+ "Name": 2,
+ "State": 3,
+ "Template": 4,
+ "NetVM": 5,
+ "Size": 6,
+ "Internal": 7,
+ "IP": 8,
+ "Backups": 9,
+ "Last backup": 10,
+ }
+
+ def __init__(self, qubes_app, qt_app, parent=None):
+ # pylint: disable=unused-argument
+ super(VmManagerWindow, self).__init__()
+ self.setupUi(self)
+
+ self.manager_settings = QtCore.QSettings(self)
+
+ self.qubes_app = qubes_app
+ self.qt_app = qt_app
+
+ self.searchbox = SearchBox()
+ self.searchbox.setValidator(QtGui.QRegExpValidator(
+ QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None))
+ self.searchContainer.addWidget(self.searchbox)
+
+ self.connect(self.table, QtCore.SIGNAL("itemSelectionChanged()"),
+ self.table_selection_changed)
+
+ self.table.setColumnWidth(0, self.column_width)
+
+ self.sort_by_column = "Type"
+ self.sort_order = QtCore.Qt.AscendingOrder
+
+ self.vms_list = []
+ self.vms_in_table = {}
+ self.reload_table = False
+
+ self.frame_width = 0
+ self.frame_height = 0
+
+ self.move(self.x(), 0)
+
+ self.columns_actions = {
+ self.columns_indices["Type"]: self.action_vm_type,
+ self.columns_indices["Label"]: self.action_label,
+ self.columns_indices["Name"]: self.action_name,
+ self.columns_indices["State"]: self.action_state,
+ self.columns_indices["Template"]: self.action_template,
+ self.columns_indices["NetVM"]: self.action_netvm,
+ self.columns_indices["Size"]: self.action_size_on_disk,
+ self.columns_indices["Internal"]: self.action_internal,
+ self.columns_indices["IP"]: self
+ .action_ip, self.columns_indices["Backups"]: self
+ .action_backups, self.columns_indices["Last backup"]: self
+ .action_last_backup
+ }
+
+ self.visible_columns_count = len(self.columns_indices)
+
+ self.table.setColumnWidth(self.columns_indices["State"], 80)
+ self.table.setColumnWidth(self.columns_indices["Name"], 150)
+ self.table.setColumnWidth(self.columns_indices["Label"], 40)
+ self.table.setColumnWidth(self.columns_indices["Type"], 40)
+ self.table.setColumnWidth(self.columns_indices["Size"], 100)
+ self.table.setColumnWidth(self.columns_indices["Internal"], 60)
+ self.table.setColumnWidth(self.columns_indices["IP"], 100)
+ self.table.setColumnWidth(self.columns_indices["Backups"], 60)
+ self.table.setColumnWidth(self.columns_indices["Last backup"], 90)
+
+ self.table.horizontalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
+
+ self.table.sortItems(self.columns_indices[self.sort_by_column],
+ self.sort_order)
+
+ self.context_menu = QtGui.QMenu(self)
+
+ self.context_menu.addAction(self.action_settings)
+ self.context_menu.addAction(self.action_editfwrules)
+ self.context_menu.addAction(self.action_appmenus)
+ self.context_menu.addAction(self.action_set_keyboard_layout)
+ self.context_menu.addSeparator()
+
+ self.context_menu.addAction(self.action_updatevm)
+ self.context_menu.addAction(self.action_run_command_in_vm)
+ self.context_menu.addAction(self.action_resumevm)
+ self.context_menu.addAction(self.action_startvm_tools_install)
+ self.context_menu.addAction(self.action_pausevm)
+ self.context_menu.addAction(self.action_shutdownvm)
+ self.context_menu.addAction(self.action_restartvm)
+ self.context_menu.addAction(self.action_killvm)
+ self.context_menu.addSeparator()
+
+ self.context_menu.addAction(self.action_clonevm)
+ self.context_menu.addAction(self.action_removevm)
+ self.context_menu.addSeparator()
+
+ self.context_menu.addMenu(self.logs_menu)
+ self.context_menu.addSeparator()
+
+ self.tools_context_menu = QtGui.QMenu(self)
+ self.tools_context_menu.addAction(self.action_toolbar)
+ self.tools_context_menu.addAction(self.action_menubar)
+
+ self.connect(
+ self.table.horizontalHeader(),
+ QtCore.SIGNAL("sortIndicatorChanged(int, Qt::SortOrder)"),
+ self.sort_indicator_changed)
+ self.connect(self.table,
+ QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"),
+ self.open_context_menu)
+ self.connect(self.menubar,
+ QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"),
+ lambda pos: self.open_tools_context_menu(self.menubar,
+ pos))
+ self.connect(self.toolbar,
+ QtCore.SIGNAL("customContextMenuRequested(const QPoint&)"),
+ lambda pos: self.open_tools_context_menu(self.toolbar,
+ pos))
+ self.connect(self.logs_menu, QtCore.SIGNAL("triggered(QAction *)"),
+ self.show_log)
+
+ self.connect(self.searchbox,
+ QtCore.SIGNAL("textChanged(const QString&)"),
+ self.do_search)
+
+ self.table.setContentsMargins(0, 0, 0, 0)
+ self.centralwidget.layout().setContentsMargins(0, 0, 0, 0)
+ self.layout().setContentsMargins(0, 0, 0, 0)
+
+ self.connect(self.action_menubar, QtCore.SIGNAL("toggled(bool)"),
+ self.showhide_menubar)
+ self.connect(self.action_toolbar, QtCore.SIGNAL("toggled(bool)"),
+ self.showhide_toolbar)
+
+ self.load_manager_settings()
+
+ self.fill_table()
+
+ self.counter = 0
+ self.update_size_on_disk = False
+ self.shutdown_monitor = {}
+
+ def load_manager_settings(self):
+ # visible columns
+ self.visible_columns_count = 0
+ for col in self.columns_indices:
+ col_no = self.columns_indices[col]
+ visible = self.manager_settings.value(
+ 'columns/%s' % col,
+ defaultValue="true")
+ self.columns_actions[col_no].setChecked(visible == "true")
+ self.visible_columns_count += 1
+
+ self.sort_by_column = str(
+ self.manager_settings.value("view/sort_column",
+ defaultValue=self.sort_by_column))
+ self.sort_order = QtCore.Qt.SortOrder(
+ self.manager_settings.value("view/sort_order",
+ defaultValue=self.sort_order))
+ self.table.sortItems(self.columns_indices[self.sort_by_column],
+ self.sort_order)
+ if not self.manager_settings.value("view/menubar_visible",
+ defaultValue=True):
+ self.action_menubar.setChecked(False)
+ if not self.manager_settings.value("view/toolbar_visible",
+ defaultValue=True):
+ self.action_toolbar.setChecked(False)
+ self.settings_loaded = True
+
+ def get_vms_list(self):
+ return [vm for vm in self.qubes_app.domains]
+
+ def fill_table(self):
+ # save current selection
+ row_index = self.table.currentRow()
+ selected_qid = -1
+ if row_index != -1:
+ vm_item = self.table.item(row_index, self.columns_indices["Name"])
+ if vm_item:
+ selected_qid = vm_item.qid
+
+ self.table.setSortingEnabled(False)
+ self.table.clearContents()
+ vms_list = self.get_vms_list()
+
+ vms_in_table = {}
+
+ row_no = 0
+ for vm in vms_list:
+ vm_row = VmRowInTable(vm, row_no, self.table)
+ vms_in_table[vm.qid] = vm_row
+
+ row_no += 1
+
+ self.table.setRowCount(row_no)
+ self.vms_list = vms_list
+ self.vms_in_table = vms_in_table
+ self.reload_table = False
+ if selected_qid in vms_in_table.keys():
+ self.table.setCurrentItem(
+ self.vms_in_table[selected_qid].name_widget)
+ self.table.setSortingEnabled(True)
+
+ self.showhide_vms()
+
+ def showhide_vms(self):
+ if not self.search:
+ for row_no in range(self.table.rowCount()):
+ self.table.setRowHidden(row_no, False)
+ else:
+ for row_no in range(self.table.rowCount()):
+ widget = self.table.cellWidget(row_no,
+ self.columns_indices["State"])
+ show = (self.search in widget.vm.name)
+ self.table.setRowHidden(row_no, not show)
+
+ @QtCore.pyqtSlot(str)
+ def do_search(self, search):
+ self.search = str(search)
+ self.showhide_vms()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_search_triggered')
+ def action_search_triggered(self):
+ self.searchbox.setFocus()
+
+ def mark_table_for_update(self):
+ self.reload_table = True
+
+ def update_table(self):
+
+ self.fill_table()
+ # TODO: instead of manually refreshing the entire table, use dbus events
+
+ # reapply sorting
+ if self.sort_by_column:
+ self.table.sortByColumn(self.columns_indices[self.sort_by_column])
+
+ self.table_selection_changed()
+
+ # noinspection PyPep8Naming
+ def sort_indicator_changed(self, column, order):
+ self.sort_by_column = [name for name in self.columns_indices if
+ self.columns_indices[name] == column][0]
+ self.sort_order = order
+ if self.settings_loaded:
+ self.manager_settings.setValue('view/sort_column',
+ self.sort_by_column)
+ self.manager_settings.setValue('view/sort_order', self.sort_order)
+ self.manager_settings.sync()
+
+ def table_selection_changed(self):
+
+ vm = self.get_selected_vm()
+
+ if vm is not None and vm in self.qubes_app.domains:
+
+ # TODO: add boot from device to menu and add windows tools there
+ # Update available actions:
+ self.action_settings.setEnabled(vm.klass != 'AdminVM')
+ self.action_removevm.setEnabled(
+ vm.klass != 'AdminVM' and not vm.is_running())
+ self.action_clonevm.setEnabled(vm.klass != 'AdminVM')
+ self.action_resumevm.setEnabled(
+ not vm.is_running() or vm.get_power_state() == "Paused")
+ self.action_pausevm.setEnabled(
+ vm.is_running() and vm.get_power_state() != "Paused"
+ and vm.klass != 'AdminVM')
+ self.action_shutdownvm.setEnabled(
+ vm.is_running() and vm.get_power_state() != "Paused"
+ and vm.klass != 'AdminVM')
+ self.action_restartvm.setEnabled(
+ vm.is_running() and vm.get_power_state() != "Paused"
+ and vm.klass != 'AdminVM' and vm.klass != 'DispVM')
+ self.action_killvm.setEnabled(
+ (vm.get_power_state() == "Paused" or vm.is_running())
+ and vm.klass != 'AdminVM')
+
+ self.action_appmenus.setEnabled(
+ vm.klass != 'AdminVM' and vm.klass != 'DispVM'
+ and not vm.features.get('internal', False))
+ self.action_editfwrules.setEnabled(vm.klass != 'AdminVM')
+ self.action_updatevm.setEnabled(getattr(vm, 'updateable', False)
+ or vm.qid == 0)
+ self.action_run_command_in_vm.setEnabled(
+ not vm.get_power_state() == "Paused" and vm.qid != 0)
+ self.action_set_keyboard_layout.setEnabled(
+ vm.qid != 0 and
+ vm.get_power_state() != "Paused" and vm.is_running())
+ else:
+ self.action_settings.setEnabled(False)
+ self.action_removevm.setEnabled(False)
+ self.action_clonevm.setEnabled(False)
+ self.action_resumevm.setEnabled(False)
+ self.action_pausevm.setEnabled(False)
+ self.action_shutdownvm.setEnabled(False)
+ self.action_restartvm.setEnabled(False)
+ self.action_killvm.setEnabled(False)
+ self.action_appmenus.setEnabled(False)
+ self.action_editfwrules.setEnabled(False)
+ self.action_updatevm.setEnabled(False)
+ self.action_run_command_in_vm.setEnabled(False)
+ self.action_set_keyboard_layout.setEnabled(False)
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_createvm_triggered')
+ def action_createvm_triggered(self): # pylint: disable=no-self-use
+ subprocess.check_call('qubes-vm-create')
+
+ def get_selected_vm(self):
+ # vm selection relies on the VmInfo widget's value used
+ # for sorting by VM name
+ row_index = self.table.currentRow()
+ if row_index != -1:
+ vm_item = self.table.item(row_index, self.columns_indices["Name"])
+ # here is possible race with update_table timer so check
+ # if really got the item
+ if vm_item is None:
+ return None
+ qid = vm_item.qid
+ assert self.vms_in_table[qid] is not None
+ vm = self.vms_in_table[qid].vm
+ return vm
+ else:
+ return None
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_removevm_triggered')
+ def action_removevm_triggered(self):
+
+ vm = self.get_selected_vm()
+
+ if vm.klass == 'TemplateVM':
+ dependent_vms = 0
+ for single_vm in self.qubes_app.domains:
+ if getattr(single_vm, 'template', None) == vm:
+ dependent_vms += 1
+ if dependent_vms > 0:
+ QtGui.QMessageBox.warning(
+ None, self.tr("Warning!"),
+ self.tr("This Template Qube cannot be removed, "
+ "because there is at least one Qube that is based "
+ "on it.
If you want to remove this "
+ "Template Qube and all the Qubes based on it, you "
+ "should first remove each individual Qube that "
+ "uses this template."))
+ return
+
+ (requested_name, ok) = QtGui.QInputDialog.getText(
+ None, self.tr("Qube Removal Confirmation"),
+ self.tr("Are you sure you want to remove the Qube '{0}'"
+ "?
All data on this Qube's private storage will be "
+ "lost!
Type the name of the Qube ({1}) below "
+ "to confirm:").format(vm.name, vm.name))
+
+ if not ok:
+ # user clicked cancel
+ return
+
+ elif requested_name != vm.name:
+ # name did not match
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Qube removal confirmation failed"),
+ self.tr(
+ "Entered name did not match! Not removing "
+ "{0}.").format(vm.name))
+ return
+
+ else:
+ # remove the VM
+ t_monitor = thread_monitor.ThreadMonitor()
+ thread = threading.Thread(target=self.do_remove_vm,
+ args=(vm, self.qubes_app, t_monitor))
+ thread.daemon = True
+ thread.start()
+
+ progress = QtGui.QProgressDialog(
+ self.tr(
+ "Removing Qube: {0}...").format(vm.name), "", 0, 0)
+ progress.setCancelButton(None)
+ progress.setModal(True)
+ progress.show()
+
+ while not t_monitor.is_finished():
+ self.qt_app.processEvents()
+ time.sleep(0.1)
+
+ progress.hide()
+
+ if t_monitor.success:
+ pass
+ else:
+ QtGui.QMessageBox.warning(None, self.tr("Error removing Qube!"),
+ self.tr("ERROR: {0}").format(
+ t_monitor.error_msg))
+
+ self.update_table()
+
+ @staticmethod
+ def do_remove_vm(vm, qubes_app, t_monitor):
+ try:
+ del qubes_app.domains[vm.name]
+ except exc.QubesException as ex:
+ t_monitor.set_error_msg(str(ex))
+
+ t_monitor.set_finished()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_clonevm_triggered')
+ def action_clonevm_triggered(self):
+ vm = self.get_selected_vm()
+
+ name_number = 1
+ name_format = vm.name + '-clone-%d'
+ while name_format % name_number in self.qubes_app.domains.keys():
+ name_number += 1
+
+ (clone_name, ok) = QtGui.QInputDialog.getText(
+ self, self.tr('Qubes clone Qube'),
+ self.tr('Enter name for Qube {} clone:').format(vm.name),
+ text=(name_format % name_number))
+ if not ok or clone_name == "":
+ return
+
+ t_monitor = thread_monitor.ThreadMonitor()
+ thread = threading.Thread(target=self.do_clone_vm,
+ args=(vm, self.qubes_app,
+ clone_name, t_monitor))
+ thread.daemon = True
+ thread.start()
+
+ progress = QtGui.QProgressDialog(
+ self.tr("Cloning Qube {0} to {1}...").format(
+ vm.name, clone_name), "", 0, 0)
+ progress.setCancelButton(None)
+ progress.setModal(True)
+ progress.show()
+
+ while not t_monitor.is_finished():
+ self.qt_app.processEvents()
+ time.sleep(0.2)
+
+ progress.hide()
+
+ if not t_monitor.success:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Error while cloning Qube"),
+ self.tr("Exception while cloning:
{0}").format(
+ t_monitor.error_msg))
+
+ self.update_table()
+
+ @staticmethod
+ def do_clone_vm(src_vm, qubes_app, dst_name, t_monitor):
+ dst_vm = None
+ try:
+ dst_vm = qubes_app.clone_vm(src_vm, dst_name)
+ except exc.QubesException as ex:
+ t_monitor.set_error_msg(str(ex))
+ if dst_vm:
+ pass
+ t_monitor.set_finished()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_resumevm_triggered')
+ def action_resumevm_triggered(self):
+ vm = self.get_selected_vm()
+
+ if vm.get_power_state() in ["Paused", "Suspended"]:
+ try:
+ vm.unpause()
+ except exc.QubesException as ex:
+ QtGui.QMessageBox.warning(
+ None, self.tr("Error unpausing Qube!"),
+ self.tr("ERROR: {0}").format(ex))
+ return
+
+ self.start_vm(vm)
+ self.update_table()
+
+ def start_vm(self, vm):
+ if vm.is_running():
+ return
+ t_monitor = thread_monitor.ThreadMonitor()
+ thread = threading.Thread(target=self.do_start_vm,
+ args=(vm, t_monitor))
+ thread.daemon = True
+ thread.start()
+
+ while not t_monitor.is_finished():
+ self.qt_app.processEvents()
+ time.sleep(0.1)
+
+ if not t_monitor.success:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Error starting Qube!"),
+ self.tr("ERROR: {0}").format(t_monitor.error_msg))
+
+ self.update_table()
+
+ @staticmethod
+ def do_start_vm(vm, t_monitor):
+ try:
+ vm.start()
+ except exc.QubesException as ex:
+ t_monitor.set_error_msg(str(ex))
+ t_monitor.set_finished()
+ return
+
+ t_monitor.set_finished()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_startvm_tools_install_triggered')
+ # TODO: replace with boot from device
+ def action_startvm_tools_install_triggered(self):
+ # pylint: disable=invalid-name
+ pass
+
+ @QtCore.pyqtSlot(name='on_action_pausevm_triggered')
+ def action_pausevm_triggered(self):
+ vm = self.get_selected_vm()
+ assert vm.is_running()
+ try:
+ vm.pause()
+ self.update_table()
+ except exc.QubesException as ex:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Error pausing Qube!"),
+ self.tr("ERROR: {0}").format(ex))
+ return
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_shutdownvm_triggered')
+ def action_shutdownvm_triggered(self):
+ vm = self.get_selected_vm()
+ assert vm.is_running()
+
+ reply = QtGui.QMessageBox.question(
+ None, self.tr("Qube Shutdown Confirmation"),
+ self.tr("Are you sure you want to power down the Qube"
+ " '{0}'?
This will shutdown all the "
+ "running applications within this Qube.").format(
+ vm.name), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
+
+ self.qt_app.processEvents()
+
+ if reply == QtGui.QMessageBox.Yes:
+ self.shutdown_vm(vm)
+
+ self.update_table()
+
+ def shutdown_vm(self, vm, shutdown_time=vm_shutdown_timeout,
+ check_time=vm_restart_check_timeout, and_restart=False):
+ try:
+ vm.shutdown()
+ except exc.QubesException as ex:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Error shutting down Qube!"),
+ self.tr("ERROR: {0}").format(ex))
+ return
+
+ self.shutdown_monitor[vm.qid] = VmShutdownMonitor(vm, shutdown_time,
+ check_time,
+ and_restart, self)
+ # noinspection PyCallByClass,PyTypeChecker
+ QtCore.QTimer.singleShot(check_time, self.shutdown_monitor[
+ vm.qid].check_if_vm_has_shutdown)
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_restartvm_triggered')
+ def action_restartvm_triggered(self):
+ vm = self.get_selected_vm()
+ assert vm.is_running()
+
+ reply = QtGui.QMessageBox.question(
+ None, self.tr("Qube Restart Confirmation"),
+ self.tr("Are you sure you want to restart the Qube '{0}'?"
+ "
This will shutdown all the running "
+ "applications within this Qube.").format(vm.name),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
+
+ self.qt_app.processEvents()
+
+ if reply == QtGui.QMessageBox.Yes:
+ self.shutdown_vm(vm, and_restart=True)
+
+ self.update_table()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_killvm_triggered')
+ def action_killvm_triggered(self):
+ vm = self.get_selected_vm()
+ assert vm.is_running() or vm.is_paused()
+
+ reply = QtGui.QMessageBox.question(
+ None, self.tr("Qube Kill Confirmation"),
+ self.tr("Are you sure you want to kill the Qube '{0}'?
"
+ "This will end (not shutdown!) all the "
+ "running applications within this Qube.").format(
+ vm.name),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel,
+ QtGui.QMessageBox.Cancel)
+
+ self.qt_app.processEvents()
+
+ if reply == QtGui.QMessageBox.Yes:
+ try:
+ vm.force_shutdown()
+ except exc.QubesException as ex:
+ QtGui.QMessageBox.critical(
+ None, self.tr("Error while killing Qube!"),
+ self.tr(
+ "An exception ocurred while killing {0}.
"
+ "ERROR: {1}").format(vm.name, ex))
+ return
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_settings_triggered')
+ def action_settings_triggered(self):
+ vm = self.get_selected_vm()
+ if vm:
+ settings_window = settings.VMSettingsWindow(
+ vm, self.qt_app, "basic")
+ settings_window.exec_()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_appmenus_triggered')
+ def action_appmenus_triggered(self):
+ vm = self.get_selected_vm()
+ if vm:
+ settings_window = settings.VMSettingsWindow(
+ vm, self.qt_app, "applications")
+ settings_window.exec_()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_refresh_list_triggered')
+ def action_refresh_list_triggered(self):
+ self.update_table()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_updatevm_triggered')
+ def action_updatevm_triggered(self):
+ vm = self.get_selected_vm()
+
+ if not vm.is_running():
+ reply = QtGui.QMessageBox.question(
+ None, self.tr("Qube Update Confirmation"),
+ self.tr(
+ "{0}
The Qube has to be running to be updated."
+ "
Do you want to start it?
").format(vm.name),
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
+ if reply != QtGui.QMessageBox.Yes:
+ return
+
+ self.qt_app.processEvents()
+
+ t_monitor = thread_monitor.ThreadMonitor()
+ thread = threading.Thread(target=self.do_update_vm,
+ args=(vm, t_monitor))
+ thread.daemon = True
+ thread.start()
+
+ progress = QtGui.QProgressDialog(
+ self.tr(
+ "{0}
Please wait for the updater to "
+ "launch...").format(vm.name), "", 0, 0)
+ progress.setCancelButton(None)
+ progress.setModal(True)
+ progress.show()
+
+ while not t_monitor.is_finished():
+ self.qt_app.processEvents()
+ time.sleep(0.2)
+
+ progress.hide()
+
+ if vm.qid != 0:
+ if not t_monitor.success:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Error on Qube update!"),
+ self.tr("ERROR: {0}").format(t_monitor.error_msg))
+
+ self.update_table()
+
+ @staticmethod
+ def do_update_vm(vm, t_monitor):
+ try:
+ if vm.qid == 0:
+ subprocess.check_call(
+ ["/usr/bin/qubes-dom0-update", "--clean", "--gui"])
+ else:
+ if not vm.is_running():
+ vm.start()
+ vm.run_service("qubes.InstallUpdatesGUI",
+ user="root", wait=False)
+ except (ChildProcessError, exc.QubesException) as ex:
+ t_monitor.set_error_msg(str(ex))
+ t_monitor.set_finished()
+ return
+ t_monitor.set_finished()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_run_command_in_vm_triggered')
+ def action_run_command_in_vm_triggered(self):
+ # pylint: disable=invalid-name
+ vm = self.get_selected_vm()
+
+ (command_to_run, ok) = QtGui.QInputDialog.getText(
+ self, self.tr('Qubes command entry'),
+ self.tr('Run command in {}:').format(vm.name))
+ if not ok or command_to_run == "":
+ return
+ t_monitor = thread_monitor.ThreadMonitor()
+ thread = threading.Thread(target=self.do_run_command_in_vm, args=(
+ vm, command_to_run, t_monitor))
+ thread.daemon = True
+ thread.start()
+
+ while not t_monitor.is_finished():
+ self.qt_app.processEvents()
+ time.sleep(0.2)
+
+ if not t_monitor.success:
+ QtGui.QMessageBox.warning(
+ None, self.tr("Error while running command"),
+ self.tr("Exception while running command:
{0}").format(
+ t_monitor.error_msg))
+
+ @staticmethod
+ def do_run_command_in_vm(vm, command_to_run, t_monitor):
+ try:
+ vm.run(command_to_run)
+ except (ChildProcessError, exc.QubesException) as ex:
+ t_monitor.set_error_msg(str(ex))
+ t_monitor.set_finished()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_set_keyboard_layout_triggered')
+ def action_set_keyboard_layout_triggered(self):
+ # pylint: disable=invalid-name
+ vm = self.get_selected_vm()
+ vm.run('qubes-change-keyboard-layout')
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_editfwrules_triggered')
+ def action_editfwrules_triggered(self):
+ vm = self.get_selected_vm()
+ settings_window = settings.VMSettingsWindow(vm, self.qt_app, "firewall")
+ settings_window.exec_()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_global_settings_triggered')
+ def action_global_settings_triggered(self): # pylint: disable=invalid-name
+ global_settings_window = global_settings.GlobalSettingsWindow(
+ self.qt_app,
+ self.qubes_app)
+ global_settings_window.exec_()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_show_network_triggered')
+ def action_show_network_triggered(self):
+ pass
+ # TODO: revive for 4.1
+ # network_notes_dialog = NetworkNotesDialog()
+ # network_notes_dialog.exec_()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_restore_triggered')
+ def action_restore_triggered(self):
+ restore_window = restore.RestoreVMsWindow(self.qt_app, self.qubes_app)
+ restore_window.exec_()
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_backup_triggered')
+ def action_backup_triggered(self):
+ backup_window = backup.BackupVMsWindow(self.qt_app, self.qubes_app)
+ backup_window.exec_()
+
+ def showhide_menubar(self, checked):
+ self.menubar.setVisible(checked)
+ if not checked:
+ self.context_menu.addAction(self.action_menubar)
+ else:
+ self.context_menu.removeAction(self.action_menubar)
+ if self.settings_loaded:
+ self.manager_settings.setValue('view/menubar_visible', checked)
+ self.manager_settings.sync()
+
+ def showhide_toolbar(self, checked):
+ self.toolbar.setVisible(checked)
+ if not checked:
+ self.context_menu.addAction(self.action_toolbar)
+ else:
+ self.context_menu.removeAction(self.action_toolbar)
+ if self.settings_loaded:
+ self.manager_settings.setValue('view/toolbar_visible', checked)
+ self.manager_settings.sync()
+
+ def showhide_column(self, col_num, show):
+ self.table.setColumnHidden(col_num, not show)
+
+ val = 1 if show else -1
+ self.visible_columns_count += val
+
+ if self.visible_columns_count == 1:
+ # disable hiding the last one
+ for col in self.columns_actions:
+ if self.columns_actions[col].isChecked():
+ self.columns_actions[col].setEnabled(False)
+ break
+ elif self.visible_columns_count == 2 and val == 1:
+ # enable hiding previously disabled column
+ for col in self.columns_actions:
+ if not self.columns_actions[col].isEnabled():
+ self.columns_actions[col].setEnabled(True)
+ break
+
+ if self.settings_loaded:
+ col_name = [name for name in self.columns_indices if
+ self.columns_indices[name] == col_num][0]
+ self.manager_settings.setValue('columns/%s' % col_name, show)
+ self.manager_settings.sync()
+
+ def on_action_vm_type_toggled(self, checked):
+ self.showhide_column(self.columns_indices['Type'], checked)
+
+ def on_action_label_toggled(self, checked):
+ self.showhide_column(self.columns_indices['Label'], checked)
+
+ def on_action_name_toggled(self, checked):
+ self.showhide_column(self.columns_indices['Name'], checked)
+
+ def on_action_state_toggled(self, checked):
+ self.showhide_column(self.columns_indices['State'], checked)
+
+ def on_action_internal_toggled(self, checked):
+ self.showhide_column(self.columns_indices['Internal'], checked)
+
+ def on_action_ip_toggled(self, checked):
+ self.showhide_column(self.columns_indices['IP'], checked)
+
+ def on_action_backups_toggled(self, checked):
+ self.showhide_column(self.columns_indices['Backups'], checked)
+
+ def on_action_last_backup_toggled(self, checked):
+ self.showhide_column(self.columns_indices['Last backup'], checked)
+
+ def on_action_template_toggled(self, checked):
+ self.showhide_column(self.columns_indices['Template'], checked)
+
+ def on_action_netvm_toggled(self, checked):
+ self.showhide_column(self.columns_indices['NetVM'], checked)
+
+ def on_action_size_on_disk_toggled(self, checked):
+ self.showhide_column(self.columns_indices['Size'], checked)
+
+ # noinspection PyArgumentList
+ @QtCore.pyqtSlot(name='on_action_about_qubes_triggered')
+ def action_about_qubes_triggered(self): # pylint: disable=no-self-use
+ about = AboutDialog()
+ about.exec_()
+
+ def createPopupMenu(self): # pylint: disable=invalid-name
+ menu = QtGui.QMenu()
+ menu.addAction(self.action_toolbar)
+ menu.addAction(self.action_menubar)
+ return menu
+
+ def open_tools_context_menu(self, widget, point):
+ self.tools_context_menu.exec_(widget.mapToGlobal(point))
+
+ @QtCore.pyqtSlot('const QPoint&')
+ def open_context_menu(self, point):
+ vm = self.get_selected_vm()
+
+ # logs menu
+ self.logs_menu.clear()
+
+ if vm.qid == 0:
+ logfiles = ["/var/log/xen/console/hypervisor.log"]
+ else:
+ logfiles = [
+ "/var/log/xen/console/guest-" + vm.name + ".log",
+ "/var/log/xen/console/guest-" + vm.name + "-dm.log",
+ "/var/log/qubes/guid." + vm.name + ".log",
+ "/var/log/qubes/qrexec." + vm.name + ".log",
+ ]
+
+ menu_empty = True
+ for logfile in logfiles:
+ if os.path.exists(logfile):
+ action = self.logs_menu.addAction(QtGui.QIcon(":/log.png"),
+ logfile)
+ action.setData(logfile)
+ menu_empty = False
+
+ self.logs_menu.setEnabled(not menu_empty)
+ self.context_menu.exec_(self.table.mapToGlobal(point))
+
+ @QtCore.pyqtSlot('QAction *')
+ def show_log(self, action):
+ log = str(action.data())
+ log_dlg = log_dialog.LogDialog(self.qt_app, log)
+ log_dlg.exec_()
+
+
+# Bases on the original code by:
+# Copyright (c) 2002-2007 Pascal Varet
+
+def handle_exception(exc_type, exc_value, exc_traceback):
+
+ filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
+ filename = os.path.basename(filename)
+ error = "%s: %s" % (exc_type.__name__, exc_value)
+
+ strace = ""
+ stacktrace = traceback.extract_tb(exc_traceback)
+ while stacktrace:
+ (filename, line, func, txt) = stacktrace.pop()
+ strace += "----\n"
+ strace += "line: %s\n" % txt
+ strace += "func: %s\n" % func
+ strace += "line no.: %d\n" % line
+ strace += "file: %s\n" % filename
+
+ msg_box = QtGui.QMessageBox()
+ msg_box.setDetailedText(strace)
+ msg_box.setIcon(QtGui.QMessageBox.Critical)
+ msg_box.setWindowTitle("Houston, we have a problem...")
+ msg_box.setText("Whoops. A critical error has occured. "
+ "This is most likely a bug in Qubes Manager.
"
+ "%s" % error +
+ "
at line %d
of file %s.
"
+ % (line, filename))
+
+ msg_box.exec_()
+
+
+def main():
+ qt_app = QtGui.QApplication(sys.argv)
+ qt_app.setOrganizationName("The Qubes Project")
+ qt_app.setOrganizationDomain("http://qubes-os.org")
+ qt_app.setApplicationName("Qube Manager")
+ qt_app.setWindowIcon(QtGui.QIcon.fromTheme("qubes-manager"))
+
+ sys.excepthook = handle_exception
+
+ qubes_app = Qubes()
+
+ manager_window = VmManagerWindow(qubes_app, qt_app)
+
+ manager_window.show()
+ manager_window.update_table()
+ qt_app.exec_()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/qubesmanager/releasenotes.py b/qubesmanager/releasenotes.py
index 53b8752..fd781d5 100644
--- a/qubesmanager/releasenotes.py
+++ b/qubesmanager/releasenotes.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
# coding=utf-8
#
# The Qubes OS Project, http://www.qubes-os.org
@@ -16,18 +16,17 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
-from PyQt4.QtCore import SIGNAL
-from PyQt4.QtGui import QDialog, QIcon
+from PyQt4.QtGui import QDialog # pylint: disable=import-error
-from .ui_releasenotes import *
+from . import ui_releasenotes # pylint: disable=no-name-in-module
-class ReleaseNotesDialog(Ui_ReleaseNotesDialog, QDialog):
+class ReleaseNotesDialog(ui_releasenotes.Ui_ReleaseNotesDialog, QDialog):
+ # pylint: disable=too-few-public-methods
def __init__(self):
super(ReleaseNotesDialog, self).__init__()
diff --git a/qubesmanager/restore.py b/qubesmanager/restore.py
index 552e3eb..b36ec52 100644
--- a/qubesmanager/restore.py
+++ b/qubesmanager/restore.py
@@ -15,104 +15,90 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
import sys
+from PyQt4 import QtCore # pylint: disable=import-error
+from PyQt4 import QtGui # pylint: disable=import-error
+import threading
+import time
import os
-import shutil
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
+import os.path
+import traceback
+import logging
+import logging.handlers
-from qubes.qubes import QubesVmCollection
-from qubes.qubes import QubesException
-from qubes.qubes import QubesDaemonPidfile
-from qubes.qubes import QubesHost
-from qubes.qubes import qubes_base_dir
-import qubesmanager.resources_rc
import signal
-from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
-
-import time
-from operator import itemgetter
-from .thread_monitor import *
-
from qubes import backup
-from qubes import qubesutils
-from .ui_restoredlg import *
-from .multiselectwidget import *
+from . import ui_restoredlg # pylint: disable=no-name-in-module
+from . import multiselectwidget
+from . import backup_utils
+from . import thread_monitor
-from .backup_utils import *
from multiprocessing import Queue, Event
from multiprocessing.queues import Empty
+from qubesadmin import Qubes, exc
+from qubesadmin.backup import restore
-class RestoreVMsWindow(Ui_Restore, QWizard):
- __pyqtSignals__ = ("restore_progress(int)","backup_progress(int)")
+class RestoreVMsWindow(ui_restoredlg.Ui_Restore, QtGui.QWizard):
- def __init__(self, app, qvm_collection, blk_manager, parent=None):
+ def __init__(self, qt_app, qubes_app, parent=None):
super(RestoreVMsWindow, self).__init__(parent)
- self.app = app
- self.qvm_collection = qvm_collection
- self.blk_manager = blk_manager
+ self.qt_app = qt_app
+ self.qubes_app = qubes_app
- self.restore_options = None
self.vms_to_restore = None
self.func_output = []
+
+ # Set up logging
self.feedback_queue = Queue()
+ handler = logging.handlers.QueueHandler(self.feedback_queue)
+ logger = logging.getLogger('qubesadmin.backup')
+ logger.addHandler(handler)
+ logger.setLevel(logging.INFO)
+
self.canceled = False
- self.tmpdir_to_remove = None
self.error_detected = Event()
-
- self.excluded = {}
-
- self.vm = self.qvm_collection[0]
-
- assert self.vm != None
+ self.thread_monitor = None
+ self.backup_restore = None
+ self.target_appvm = None
self.setupUi(self)
- self.select_vms_widget = MultiSelectWidget(self)
+ self.select_vms_widget = multiselectwidget.MultiSelectWidget(self)
self.select_vms_layout.insertWidget(1, self.select_vms_widget)
- self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
- self.connect(self, SIGNAL("restore_progress(QString)"), self.commit_text_edit.append)
- self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
- self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed)
- self.connect(self.verify_only, SIGNAL("stateChanged(int)"),
- self.on_verify_only_toogled)
+ self.connect(self,
+ QtCore.SIGNAL("currentIdChanged(int)"),
+ self.current_page_changed)
+ self.dir_line_edit.connect(self.dir_line_edit,
+ QtCore.SIGNAL("textChanged(QString)"),
+ self.backup_location_changed)
self.select_dir_page.isComplete = self.has_selected_dir
self.select_vms_page.isComplete = self.has_selected_vms
self.confirm_page.isComplete = self.all_vms_good
- #FIXME
- #this causes to run isComplete() twice, I don't know why
- self.select_vms_page.connect(self.select_vms_widget, SIGNAL("selected_changed()"), SIGNAL("completeChanged()"))
+ # FIXME
+ # this causes to run isComplete() twice, I don't know why
+ self.select_vms_page.connect(
+ self.select_vms_widget,
+ QtCore.SIGNAL("selected_changed()"),
+ QtCore.SIGNAL("completeChanged()"))
- fill_appvms_list(self)
- self.__init_restore_options__()
+ backup_utils.fill_appvms_list(self)
- @pyqtSlot(name='on_select_path_button_clicked')
+ @QtCore.pyqtSlot(name='on_select_path_button_clicked')
def select_path_button_clicked(self):
- select_path_button_clicked(self, True)
+ backup_utils.select_path_button_clicked(self, True)
- def on_ignore_missing_toggled(self, checked):
- self.restore_options['use-default-template'] = checked
- self.restore_options['use-default-netvm'] = checked
-
- def on_ignore_uname_mismatch_toggled(self, checked):
- self.restore_options['ignore-username-mismatch'] = checked
-
- def on_verify_only_toogled(self, checked):
- self.restore_options['verify-only'] = bool(checked)
-
- def cleanupPage(self, p_int):
+ def cleanupPage(self, p_int): # pylint: disable=invalid-name
if self.page(p_int) is self.select_vms_page:
self.vms_to_restore = None
else:
@@ -126,119 +112,95 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
self.select_vms_widget.available_list.clear()
self.target_appvm = None
- if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
- self.target_appvm = self.qvm_collection.get_vm_by_name(
- str(self.appvm_combobox.currentText()))
+ if self.appvm_combobox.currentIndex() != 0: # An existing appvm chosen
+ self.target_appvm = self.qubes_app.domains[
+ str(self.appvm_combobox.currentText())]
try:
- self.vms_to_restore = backup.backup_restore_prepare(
- self.dir_line_edit.text(),
- self.passphrase_line_edit.text(),
- options=self.restore_options,
- host_collection=self.qvm_collection,
- encrypted=self.encryption_checkbox.isChecked(),
- appvm=self.target_appvm)
+ self.backup_restore = restore.BackupRestore(
+ self.qubes_app,
+ self.dir_line_edit.text(),
+ self.target_appvm,
+ self.passphrase_line_edit.text()
+ )
+
+ if self.ignore_missing.isChecked():
+ self.backup_restore.options.use_default_template = True
+ self.backup_restore.options.use_default_netvm = True
+
+ if self.ignore_uname_mismatch.isChecked():
+ self.backup_restore.options.ignore_username_mismatch = True
+
+ if self.verify_only.isChecked():
+ self.backup_restore.options.verify_only = True
+
+ self.vms_to_restore = self.backup_restore.get_restore_info()
for vmname in self.vms_to_restore:
if vmname.startswith('$'):
# Internal info
continue
self.select_vms_widget.available_list.addItem(vmname)
- except QubesException as ex:
- QMessageBox.warning (None, self.tr("Restore error!"), str(ex))
+ except exc.QubesException as ex:
+ QtGui.QMessageBox.warning(None, self.tr("Restore error!"), str(ex))
- def __init_restore_options__(self):
- if not self.restore_options:
- self.restore_options = {}
- backup.backup_restore_set_defaults(self.restore_options)
+ def append_output(self, text):
+ self.commit_text_edit.append(text)
- if 'use-default-template' in self.restore_options and 'use-default-netvm' in self.restore_options:
- val = self.restore_options['use-default-template'] and self.restore_options['use-default-netvm']
- self.ignore_missing.setChecked(val)
- else:
- self.ignore_missing.setChecked(False)
-
- if 'ignore-username-mismatch' in self.restore_options:
- self.ignore_uname_mismatch.setChecked(self.restore_options['ignore-username-mismatch'])
-
- def gather_output(self, s):
- self.func_output.append(s)
-
- def restore_error_output(self, s):
- self.error_detected.set()
- self.feedback_queue.put((SIGNAL("restore_progress(QString)"),
- u'{0}'.format(s)))
-
- def restore_output(self, s):
- self.feedback_queue.put((SIGNAL("restore_progress(QString)"),
- u'{0}'.format(s)))
-
- def update_progress_bar(self, value):
- self.feedback_queue.put((SIGNAL("backup_progress(int)"), value))
-
- def __do_restore__(self, thread_monitor):
+ def __do_restore__(self, t_monitor):
err_msg = []
- self.qvm_collection.lock_db_for_writing()
try:
- backup.backup_restore_do(self.vms_to_restore,
- self.qvm_collection,
- print_callback=self.restore_output,
- error_callback=self.restore_error_output,
- progress_callback=self.update_progress_bar)
+ self.backup_restore.restore_do(self.vms_to_restore)
+
except backup.BackupCanceledError as ex:
self.canceled = True
- self.tmpdir_to_remove = ex.tmpdir
err_msg.append(str(ex))
- except Exception as ex:
- print ("Exception:", ex)
+ except Exception as ex: # pylint: disable=broad-except
err_msg.append(str(ex))
err_msg.append(
- self.tr("Partially restored files left in "
- "/var/tmp/restore_*, investigate them and/or clean them up"))
+ self.tr("Partially restored files left in /var/tmp/restore_*, "
+ "investigate them and/or clean them up"))
- self.qvm_collection.unlock_db()
if self.canceled:
- self.emit(SIGNAL("restore_progress(QString)"),
- '{0}'
- .format(self.tr("Restore aborted!")))
- elif len(err_msg) > 0 or self.error_detected.is_set():
- if len(err_msg) > 0:
- thread_monitor.set_error_msg('\n'.join(err_msg))
- self.emit(SIGNAL("restore_progress(QString)"),
- '{0}'
- .format(self.tr("Finished with errors!")))
+ self.append_output('{0}'.format(
+ self.tr("Restore aborted!")))
+ elif err_msg or self.error_detected.is_set():
+ if err_msg:
+ t_monitor.set_error_msg('\n'.join(err_msg))
+ self.append_output('{0}'.format(
+ self.tr("Finished with errors!")))
else:
- self.emit(SIGNAL("restore_progress(QString)"),
- '{0}'
- .format(self.tr("Finished successfully!")))
+ self.append_output('{0}'.format(
+ self.tr("Finished successfully!")))
- thread_monitor.set_finished()
+ t_monitor.set_finished()
- def current_page_changed(self, id):
+ def current_page_changed(self, page_id): # pylint: disable=unused-argument
old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if self.currentPage() is self.select_vms_page:
self.__fill_vms_list__()
elif self.currentPage() is self.confirm_page:
- for v in self.excluded:
- self.vms_to_restore[v] = self.excluded[v]
- self.excluded = {}
+
+ self.vms_to_restore = self.backup_restore.get_restore_info()
+
for i in range(self.select_vms_widget.available_list.count()):
- vmname = self.select_vms_widget.available_list.item(i).text()
- self.excluded[str(vmname)] = self.vms_to_restore[str(vmname)]
+ vmname = self.select_vms_widget.available_list.item(i).text()
del self.vms_to_restore[str(vmname)]
- del self.func_output[:]
- self.vms_to_restore = backup.restore_info_verify(self.vms_to_restore,
- self.qvm_collection)
- backup.backup_restore_print_summary(
- self.vms_to_restore, print_callback = self.gather_output)
+ self.vms_to_restore = self.backup_restore.restore_info_verify(
+ self.vms_to_restore)
+
+ self.func_output = self.backup_restore.get_restore_summary(
+ self.vms_to_restore
+ )
+
self.confirm_text_edit.setReadOnly(True)
self.confirm_text_edit.setFontFamily("Monospace")
- self.confirm_text_edit.setText("\n".join(self.func_output))
+ self.confirm_text_edit.setText(self.func_output)
- self.confirm_page.emit(SIGNAL("completeChanged()"))
+ self.confirm_page.emit(QtCore.SIGNAL("completeChanged()"))
elif self.currentPage() is self.commit_page:
self.button(self.FinishButton).setDisabled(True)
@@ -247,50 +209,46 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
and str(self.dir_line_edit.text())
.count("media/") > 0)
- self.thread_monitor = ThreadMonitor()
- thread = threading.Thread (target= self.__do_restore__ , args=(self.thread_monitor,))
+ self.thread_monitor = thread_monitor.ThreadMonitor()
+ thread = threading.Thread(target=self.__do_restore__,
+ args=(self.thread_monitor,))
thread.daemon = True
thread.start()
-
while not self.thread_monitor.is_finished():
- self.app.processEvents()
- time.sleep (0.1)
+ self.qt_app.processEvents()
+ time.sleep(0.1)
try:
- for (signal_to_emit,data) in iter(self.feedback_queue.get_nowait,None):
- self.emit(signal_to_emit,data)
+ log_record = self.feedback_queue.get_nowait()
+ while log_record:
+ if log_record.levelno == logging.ERROR or\
+ log_record.levelno == logging.CRITICAL:
+ output = '{0}'.format(
+ log_record.getMessage())
+ else:
+ output = log_record.getMessage()
+ self.append_output(output)
+ log_record = self.feedback_queue.get_nowait()
except Empty:
pass
if not self.thread_monitor.success:
- if self.canceled:
- if self.tmpdir_to_remove and \
- QMessageBox.warning(None, self.tr("Restore aborted"),
- self.tr("Do you want to remove temporary files "
- "from %s?") % self.tmpdir_to_remove,
- QMessageBox.Yes, QMessageBox.No) == \
- QMessageBox.Yes:
- shutil.rmtree(self.tmpdir_to_remove)
- else:
- QMessageBox.warning(None,
- self.tr("Backup error!"), self.tr("ERROR: {0}")
- .format(self.thread_monitor.error_msg))
+ if not self.canceled:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Backup error!"),
+ self.tr("ERROR: {0}").format(
+ self.thread_monitor.error_msg))
+ self.progress_bar.setMaximum(100)
+ self.progress_bar.setValue(100)
if self.showFileDialog.isChecked():
- self.emit(SIGNAL("restore_progress(QString)"),
- '{0}'.format(
- self.tr(
- "Please unmount your backup volume and cancel"
- " the file selection dialog.")))
- if self.target_appvm:
- self.target_appvm.run("QUBESRPC %s dom0" %
- "qubes.SelectDirectory")
- else:
- file_dialog = QFileDialog()
- file_dialog.setReadOnly(True)
- file_dialog.getExistingDirectory(
- self, self.tr("Detach backup device"),
- os.path.dirname(self.dir_line_edit.text()))
- self.progress_bar.setValue(100)
+ self.append_output(
+ '{0}'.format(
+ self.tr("Please unmount your backup volume and cancel "
+ "the file selection dialog.")))
+ self.qt_app.processEvents()
+ backup_utils.select_path_button_clicked(self, False, True)
+
self.button(self.FinishButton).setEnabled(True)
self.button(self.CancelButton).setEnabled(False)
self.showFileDialog.setEnabled(False)
@@ -298,20 +256,19 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def all_vms_good(self):
- for vminfo in self.vms_to_restore.values():
- if not vminfo.has_key('vm'):
+ for vm_info in self.vms_to_restore.values():
+ if not vm_info.vm:
continue
- if not vminfo['good-to-go']:
+ if not vm_info.good_to_go:
return False
return True
def reject(self):
if self.currentPage() is self.commit_page:
- if backup.backup_cancel():
- self.emit(SIGNAL("restore_progress(QString)"),
- '{0}'
- .format(self.tr("Aborting the operation...")))
- self.button(self.CancelButton).setDisabled(True)
+ self.backup_restore.canceled = True
+ self.append_output('{0}'.format(
+ self.tr("Aborting the operation...")))
+ self.button(self.CancelButton).setDisabled(True)
else:
self.done(0)
@@ -331,58 +288,46 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0
- def backup_location_changed(self, new_dir = None):
- self.select_dir_page.emit(SIGNAL("completeChanged()"))
+ def backup_location_changed(self, new_dir=None):
+ # pylint: disable=unused-argument
+ self.select_dir_page.emit(QtCore.SIGNAL("completeChanged()"))
# Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet
-def handle_exception( exc_type, exc_value, exc_traceback ):
- import sys
- import os.path
- import traceback
+def handle_exception(exc_type, exc_value, exc_traceback):
- filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop()
- filename = os.path.basename( filename )
- error = "%s: %s" % ( exc_type.__name__, exc_value )
+ filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
+ filename = os.path.basename(filename)
+ error = "%s: %s" % (exc_type.__name__, exc_value)
- QMessageBox.critical(None, "Houston, we have a problem...",
- "Whoops. A critical error has occured. This is most likely a bug "
+ QtGui.QMessageBox.critical(None, "Houston, we have a problem...",
+ "Whoops. A critical error has occured. "
+ "This is most likely a bug "
"in Qubes Restore VMs application.
"
"%s" % error +
"at line %d of file %s.
"
- % ( line, filename ))
-
-
+ % (line, filename))
def main():
- global qubes_host
- qubes_host = QubesHost()
-
- global app
- app = QApplication(sys.argv)
- app.setOrganizationName("The Qubes Project")
- app.setOrganizationDomain("http://qubes-os.org")
- app.setApplicationName("Qubes Restore VMs")
+ qt_app = QtGui.QApplication(sys.argv)
+ qt_app.setOrganizationName("The Qubes Project")
+ qt_app.setOrganizationDomain("http://qubes-os.org")
+ qt_app.setApplicationName("Qubes Restore VMs")
sys.excepthook = handle_exception
- qvm_collection = QubesVmCollection()
- qvm_collection.lock_db_for_reading()
- qvm_collection.load()
- qvm_collection.unlock_db()
+ qubes_app = Qubes()
- global restore_window
- restore_window = RestoreVMsWindow()
+ restore_window = RestoreVMsWindow(qt_app, qubes_app)
restore_window.show()
- app.exec_()
- app.exit()
-
+ qt_app.exec_()
+ qt_app.exit()
if __name__ == "__main__":
diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py
index 2c4ed15..64765e3 100755
--- a/qubesmanager/settings.py
+++ b/qubesmanager/settings.py
@@ -17,39 +17,37 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
import collections
-import copy
-import os
import os.path
import re
import subprocess
-import sys
import threading
import time
import traceback
-
-import qubesadmin
-import qubesadmin.tools
+import os
+import sys
+from qubesadmin.tools import QubesArgumentParser
+from qubesadmin import devices
+import qubesadmin.exc
from . import utils
from . import multiselectwidget
from . import thread_monitor
from .appmenu_select import AppmenuSelectManager
-from .backup_utils import get_path_for_vm
-from .firewall import *
+from . import firewall
+from PyQt4 import QtCore, QtGui # pylint: disable=import-error
-from .ui_settingsdlg import *
-from .bootfromdevice import main as bootfromdevice
+from . import ui_settingsdlg #pylint: disable=no-name-in-module
-class VMSettingsWindow(Ui_SettingsDialog, QDialog):
+# pylint: disable=too-many-instance-attributes
+class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
tabs_indices = collections.OrderedDict((
('basic', 0),
('advanced', 1),
@@ -73,42 +71,59 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.setWindowTitle(self.tr("Settings: {vm}").format(vm=self.vm.name))
if init_page in self.tabs_indices:
idx = self.tabs_indices[init_page]
- assert (idx in range(self.tabWidget.count()))
+ assert idx in range(self.tabWidget.count())
self.tabWidget.setCurrentIndex(idx)
- self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply)
- self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
+ self.connect(self.buttonBox,
+ QtCore.SIGNAL("accepted()"),
+ self.save_and_apply)
+ self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject)
self.tabWidget.currentChanged.connect(self.current_tab_changed)
-# self.tabWidget.setTabEnabled(self.tabs_indices["firewall"], vm.is_networked() and not vm.provides_network)
-
###### basic tab
self.__init_basic_tab__()
self.rename_vm_button.clicked.connect(self.rename_vm)
+ self.delete_vm_button.clicked.connect(self.remove_vm)
+ self.clone_vm_button.clicked.connect(self.clone_vm)
###### advanced tab
self.__init_advanced_tab__()
- self.include_in_balancing.stateChanged.connect(self.include_in_balancing_state_changed)
- self.connect(self.init_mem, SIGNAL("editingFinished()"), self.check_mem_changes)
- self.connect(self.max_mem_size, SIGNAL("editingFinished()"), self.check_mem_changes)
- self.bootFromDeviceButton.clicked.connect(self.boot_from_cdrom_button_pressed)
+ self.include_in_balancing.stateChanged.connect(
+ self.include_in_balancing_changed)
+ self.connect(self.init_mem,
+ QtCore.SIGNAL("editingFinished()"),
+ self.check_mem_changes)
+ self.connect(self.max_mem_size,
+ QtCore.SIGNAL("editingFinished()"),
+ self.check_mem_changes)
+ self.boot_from_device_button.clicked.connect(
+ self.boot_from_cdrom_button_pressed)
###### firewall tab
if self.tabWidget.isTabEnabled(self.tabs_indices['firewall']):
- model = QubesFirewallRulesModel()
- model.set_vm(vm)
- self.set_fw_model(model)
+ model = firewall.QubesFirewallRulesModel()
+ try:
+ model.set_vm(vm)
+ self.set_fw_model(model)
+ self.firewall_modified_outside_label.setVisible(False)
+ except firewall.FirewallModifiedOutsideError:
+ self.disable_all_fw_conf()
- self.newRuleButton.clicked.connect(self.new_rule_button_pressed)
- self.editRuleButton.clicked.connect(self.edit_rule_button_pressed)
- self.deleteRuleButton.clicked.connect(self.delete_rule_button_pressed)
- self.policyDenyRadioButton.clicked.connect(self.policy_changed)
- self.policyAllowRadioButton.clicked.connect(self.policy_changed)
+ self.new_rule_button.clicked.connect(self.new_rule_button_pressed)
+ self.edit_rule_button.clicked.connect(self.edit_rule_button_pressed)
+ self.delete_rule_button.clicked.connect(
+ self.delete_rule_button_pressed)
+ self.policy_deny_radio_button.clicked.connect(self.policy_changed)
+ self.policy_allow_radio_button.clicked.connect(self.policy_changed)
+ if init_page == 'firewall':
+ self.check_network_availability()
####### devices tab
self.__init_devices_tab__()
- self.connect(self.dev_list, SIGNAL("selected_changed()"), self.devices_selection_changed)
+ self.connect(self.dev_list,
+ QtCore.SIGNAL("selected_changed()"),
+ self.devices_selection_changed)
####### services tab
self.__init_services_tab__()
@@ -119,8 +134,9 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]):
self.app_list = multiselectwidget.MultiSelectWidget(self)
self.apps_layout.addWidget(self.app_list)
- self.AppListManager = AppmenuSelectManager(self.vm, self.app_list)
- self.refresh_apps_button.clicked.connect(self.refresh_apps_button_pressed)
+ self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list)
+ self.refresh_apps_button.clicked.connect(
+ self.refresh_apps_button_pressed)
def reject(self):
self.done(0)
@@ -131,91 +147,112 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def save_and_apply(self):
t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self.__save_changes__, args=(t_monitor,))
+ thread = threading.Thread(target=self.__save_changes__,
+ args=(t_monitor,))
thread.daemon = True
thread.start()
- progress = QProgressDialog(
- self.tr("Applying settings to {0}...").format(self.vm.name), "", 0, 0)
+ progress = QtGui.QProgressDialog(
+ self.tr("Applying settings to {0}...").format(self.vm.name),
+ "", 0, 0)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
while not t_monitor.is_finished():
self.qapp.processEvents()
- time.sleep (0.1)
+ time.sleep(0.1)
progress.hide()
if not t_monitor.success:
- QMessageBox.warning(None,
- self.tr("Error while changing settings for {0}!").format(self.vm.name),
- self.tr("ERROR: {0}").format(t_monitor.error_msg))
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Error while changing settings for {0}!"
+ ).format(self.vm.name),
+ self.tr("ERROR: {0}").format(t_monitor.error_msg))
self.done(0)
def __save_changes__(self, t_monitor):
- self.anything_changed = False
-
ret = []
try:
ret_tmp = self.__apply_basic_tab__()
- if len(ret_tmp) > 0:
+ if ret_tmp:
ret += ["Basic tab:"] + ret_tmp
ret_tmp = self.__apply_advanced_tab__()
- if len(ret_tmp) > 0:
+ if ret_tmp:
ret += ["Advanced tab:"] + ret_tmp
ret_tmp = self.__apply_devices_tab__()
- if len(ret_tmp) > 0:
+ if ret_tmp:
ret += ["Devices tab:"] + ret_tmp
ret_tmp = self.__apply_services_tab__()
- if len(ret_tmp) > 0:
+ if ret_tmp:
ret += ["Sevices tab:"] + ret_tmp
- except Exception as ex:
- ret.append(self.tr('Error while saving changes: ') + str(ex))
+ except qubesadmin.exc.QubesException as qex:
+ ret.append(self.tr('Error while saving changes: ') + str(qex))
+ except Exception as ex: # pylint: disable=broad-except
+ ret.append(repr(ex))
try:
- if self.tabWidget.isTabEnabled(self.tabs_indices["firewall"]):
- self.fw_model.apply_rules(self.policyAllowRadioButton.isChecked(),
- self.dnsCheckBox.isChecked(),
- self.icmpCheckBox.isChecked(),
- self.yumproxyCheckBox.isChecked(),
- self.tempFullAccess.isChecked(),
- self.tempFullAccessTime.value())
- if self.fw_model.fw_changed:
- # might modified vm.services
- self.anything_changed = True
- except Exception as ex:
- ret += [self.tr("Firewall tab:"), str(ex)]
+ if self.policy_allow_radio_button.isEnabled():
+ self.fw_model.apply_rules(
+ self.policy_allow_radio_button.isChecked(),
+ self.temp_full_access.isChecked(),
+ self.temp_full_access_time.value())
+ except qubesadmin.exc.QubesException as qex:
+ ret += [self.tr("Firewall tab:"), str(qex)]
+ except Exception as ex: # pylint: disable=broad-except
+ ret += [self.tr("Firewall tab:"), repr(ex)]
try:
if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]):
- self.AppListManager.save_appmenu_select_changes()
- except Exception as ex:
- ret += [self.tr("Applications tab:"), str(ex)]
+ self.app_list_manager.save_appmenu_select_changes()
+ except qubesadmin.exc.QubesException as qex:
+ ret += [self.tr("Applications tab:"), str(qex)]
+ except Exception as ex: # pylint: disable=broad-except
+ ret += [self.tr("Applications tab:"), repr(ex)]
- if len(ret) > 0 :
+ if ret:
t_monitor.set_error_msg('\n'.join(ret))
utils.debug('\n'.join(ret))
t_monitor.set_finished()
- def current_tab_changed(self, idx):
- if idx == self.tabs_indices["firewall"]:
- netvm = self.vm.netvm
- if netvm is not None and \
- not netvm.features.check_with_template('qubes-firewall', False):
- QMessageBox.warning(None,
- self.tr("VM configuration problem!"),
- self.tr("The '{vm}' AppVM is network connected to "
+ def check_network_availability(self):
+ netvm = self.vm.netvm
+ self.no_netvm_label.setVisible(netvm is None)
+ self.netvm_no_firewall_label.setVisible(
+ netvm is not None and
+ not netvm.features.check_with_template('qubes-firewall', False))
+ if netvm is None:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Qube configuration problem!"),
+ self.tr('This qube has networking disabled '
+ '(Basic -> Networking) - network will be disabled. '
+ 'If you want to use firewall, '
+ 'please enable networking.')
+ )
+ if netvm is not None and \
+ not netvm.features.check_with_template(
+ 'qubes-firewall',
+ False):
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Qube configuration problem!"),
+ self.tr("The '{vm}' AppVM is network connected to "
"'{netvm}', which does not support firewall!
"
"You may edit the '{vm}' VM firewall rules, but these "
"will not take any effect until you connect it to "
"a working Firewall VM.").format(
- vm=self.vm.name, netvm=netvm.name))
+ vm=self.vm.name, netvm=netvm.name))
+ def current_tab_changed(self, idx):
+ if idx == self.tabs_indices["firewall"]:
+ self.check_network_availability()
######### basic tab
@@ -233,9 +270,17 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def __init_basic_tab__(self):
self.vmname.setText(self.vm.name)
- self.vmname.setValidator(QRegExpValidator(QRegExp("[a-zA-Z0-9-]*", Qt.CaseInsensitive), None))
+ self.vmname.setValidator(
+ QtGui.QRegExpValidator(
+ QtCore.QRegExp("[a-zA-Z0-9-]*",
+ QtCore.Qt.CaseInsensitive), None))
self.vmname.setEnabled(False)
self.rename_vm_button.setEnabled(not self.vm.is_running())
+ self.delete_vm_button.setEnabled(not self.vm.is_running())
+
+ if self.vm.is_running():
+ self.delete_vm_button.setText(self.tr('Delete VM '
+ '(cannot delete a running VM)'))
if self.vm.qid == 0:
self.vmlabel.setVisible(False)
@@ -282,7 +327,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.autostart_vm.setVisible(False)
#type
- self.type_label.setText(type(self.vm).__name__)
+ self.type_label.setText(self.vm.klass)
#installed by rpm
self.rpm_label.setText('Yes' if self.vm.installed_by_rpm else 'No')
@@ -316,8 +361,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.vmlabel.currentIndex() != self.label_idx:
label = self.label_list[self.vmlabel.currentIndex()]
self.vm.label = label
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
#vm template changed
@@ -325,24 +369,22 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.template_name.currentIndex() != self.template_idx:
self.vm.template = \
self.template_list[self.template_name.currentIndex()]
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
#vm netvm changed
try:
if self.netVM.currentIndex() != self.netvm_idx:
self.vm.netvm = self.netvm_list[self.netVM.currentIndex()]
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
#include in backups
try:
- if self.vm.include_in_backups != self.include_in_backups.isChecked():
+ if self.vm.include_in_backups != \
+ self.include_in_backups.isChecked():
self.vm.include_in_backups = self.include_in_backups.isChecked()
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
#run_in_debug_mode
@@ -350,8 +392,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.run_in_debug_mode.isVisible():
if self.vm.debug != self.run_in_debug_mode.isChecked():
self.vm.debug = self.run_in_debug_mode.isChecked()
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
#autostart_vm
@@ -359,8 +400,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.autostart_vm.isVisible():
if self.vm.autostart != self.autostart_vm.isChecked():
self.vm.autostart = self.autostart_vm.isChecked()
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
#max priv storage
@@ -368,8 +408,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.priv_img_size != priv_size:
try:
self.vm.volumes['private'].resize(priv_size * 1024**2)
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
#max sys storage
@@ -377,8 +416,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.root_img_size != sys_size:
try:
self.vm.volumes['root'].resize(sys_size * 1024**2)
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
return msg
@@ -386,54 +424,122 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def check_mem_changes(self):
if self.max_mem_size.value() < self.init_mem.value():
- QMessageBox.warning(None,
+ QtGui.QMessageBox.warning(
+ None,
self.tr("Warning!"),
self.tr("Max memory can not be less than initial memory.
"
"Setting max memory to equal initial memory."))
self.max_mem_size.setValue(self.init_mem.value())
- # Linux specific limit: init memory must not be below max_mem_size/10.79 in order to allow scaling up to max_mem_size (or else "add_memory() failed: -17" problem)
+ # Linux specific limit: init memory must not be below
+ # max_mem_size/10.79 in order to allow scaling up to
+ # max_mem_size (or else "add_memory() failed: -17" problem)
if self.init_mem.value() * 10 < self.max_mem_size.value():
- QMessageBox.warning(None,
+ QtGui.QMessageBox.warning(
+ None,
self.tr("Warning!"),
self.tr("Initial memory can not be less than one tenth "
"Max memory.
Setting initial memory to the minimum "
"allowed value."))
self.init_mem.setValue(self.max_mem_size.value() / 10)
+ def _run_in_thread(self, func, *args):
+ t_monitor = thread_monitor.ThreadMonitor()
+ thread = threading.Thread(target=func, args=(t_monitor, *args,))
+ thread.daemon = True
+ thread.start()
+
+ while not t_monitor.is_finished():
+ self.qapp.processEvents()
+ time.sleep(0.1)
+
+ if not t_monitor.success:
+ QtGui.QMessageBox.warning(None,
+ self.tr("Error!"),
+ self.tr("ERROR: {}").format(
+ t_monitor.error_msg))
+
+
def _rename_vm(self, t_monitor, name):
try:
self.vm.app.clone_vm(self.vm, name)
del self.vm.app.domains[self.vm.name]
- except Exception as ex:
- t_monitor.set_error_msg(str(ex))
+ except qubesadmin.exc.QubesException as qex:
+ t_monitor.set_error_msg(str(qex))
+ except Exception as ex: # pylint: disable=broad-except
+ t_monitor.set_error_msg(repr(ex))
t_monitor.set_finished()
def rename_vm(self):
- new_vm_name, ok = QInputDialog.getText(self, self.tr('Rename VM'), self.tr('New name: (WARNING: all other changes will be discarded)'))
+ new_vm_name, ok = QtGui.QInputDialog.getText(
+ self,
+ self.tr('Rename VM'),
+ self.tr('New name: (WARNING: all other changes will be discarded)'))
if ok:
-
- t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self._rename_vm, args=(t_monitor, new_vm_name,))
- thread.daemon = True
- thread.start()
-
- while not t_monitor.is_finished():
- self.qapp.processEvents()
- time.sleep (0.1)
-
- if not t_monitor.success:
- QMessageBox.warning(None,
- self.tr("Error renaming the VM!"),
- self.tr("ERROR: {}").format(
- t_monitor.error_msg))
-
+ self._run_in_thread(self._rename_vm, new_vm_name)
self.done(0)
+ def _remove_vm(self, t_monitor):
+ try:
+ del self.vm.app.domains[self.vm.name]
+
+ except qubesadmin.exc.QubesException as qex:
+ t_monitor.set_error_msg(str(qex))
+ except Exception as ex: # pylint: disable=broad-except
+ t_monitor.set_error_msg(repr(ex))
+
+ t_monitor.set_finished()
+
+ def remove_vm(self):
+
+ answer, ok = QtGui.QInputDialog.getText(
+ self,
+ self.tr('Delete VM'),
+ self.tr('Are you absolutely sure you want to delete this VM? '
+ '
All VM settings and data will be irrevocably'
+ ' deleted.
If you are sure, please enter this '
+ 'VM\'s name below.'))
+
+
+ if ok and answer == self.vm.name:
+ self._run_in_thread(self._remove_vm)
+ self.done(0)
+
+ elif ok:
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Removal cancelled"),
+ self.tr("The VM will not be removed."))
+
+ def _clone_vm(self, t_monitor, name):
+ try:
+ self.vm.app.clone_vm(self.vm, name)
+
+ except qubesadmin.exc.QubesException as qex:
+ t_monitor.set_error_msg(str(qex))
+ except Exception as ex: # pylint: disable=broad-except
+ t_monitor.set_error_msg(repr(ex))
+
+ t_monitor.set_finished()
+
+ def clone_vm(self):
+
+ cloned_vm_name, ok = QtGui.QInputDialog.getText(
+ self,
+ self.tr('Clone VM'),
+ self.tr('Name for the cloned VM:'))
+
+ if ok:
+ self._run_in_thread(self._clone_vm, cloned_vm_name)
+ QtGui.QMessageBox.warning(
+ None,
+ self.tr("Success"),
+ self.tr("The VM was cloned successfully."))
+
######### advanced tab
def __init_advanced_tab__(self):
@@ -453,7 +559,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.include_in_balancing.setEnabled(True)
self.include_in_balancing.setChecked(
- self.vm.features.get('services.meminfo-writer', True))
+ bool(self.vm.features.get('service.meminfo-writer', True)))
self.max_mem_size.setEnabled(self.include_in_balancing.isChecked())
#in case VM is HVM
@@ -490,16 +596,13 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
try:
if self.init_mem.value() != int(self.vm.memory):
self.vm.memory = self.init_mem.value()
- self.anything_changed = True
if self.max_mem_size.value() != int(self.vm.maxmem):
self.vm.maxmem = self.max_mem_size.value()
- self.anything_changed = True
if self.vcpus.value() != int(self.vm.vcpus):
self.vm.vcpus = self.vcpus.value()
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
#include_in_memory_balancing applied in services tab
@@ -508,9 +611,9 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if hasattr(self.vm, "kernel") and self.kernel_groupbox.isVisible():
try:
if self.kernel.currentIndex() != self.kernel_idx:
- self.vm.kernel = self.kernel_list[self.kernel.currentIndex()]
- self.anything_changed = True
- except Exception as ex:
+ self.vm.kernel = self.kernel_list[
+ self.kernel.currentIndex()]
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
#vm default_dispvm changed
@@ -518,8 +621,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.default_dispvm.currentIndex() != self.default_dispvm_idx:
self.vm.default_dispvm = \
self.default_dispvm_list[self.default_dispvm.currentIndex()]
- self.anything_changed = True
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
try:
@@ -632,11 +734,11 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
for dev in lspci.splitlines():
devs.append((dev.rstrip(), dev.split(' ')[0]))
- class DevListWidgetItem(QListWidgetItem):
- def __init__(self, name, ident, parent = None):
+ # pylint: disable=too-few-public-methods
+ class DevListWidgetItem(QtGui.QListWidgetItem):
+ def __init__(self, name, ident, parent=None):
super(DevListWidgetItem, self).__init__(name, parent)
self.ident = ident
- self.Type
persistent = [ass.ident.replace('_', ':')
for ass in self.vm.devices['pci'].persistent()]
@@ -649,7 +751,8 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.dev_list.available_list.addItem(
DevListWidgetItem(name, ident))
- if self.dev_list.selected_list.count() > 0 and self.include_in_balancing.isChecked():
+ if self.dev_list.selected_list.count() > 0\
+ and self.include_in_balancing.isChecked():
self.dmm_warning_adv.show()
self.dmm_warning_dev.show()
else:
@@ -677,7 +780,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
for i in range(self.dev_list.selected_list.count())]
for ident in new:
if ident not in old:
- ass = qubesadmin.devices.DeviceAssignment(
+ ass = devices.DeviceAssignment(
self.vm.app.domains['dom0'],
ident.replace(':', '_'),
persistent=True)
@@ -686,24 +789,22 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if ass.ident.replace('_', ':') not in new:
self.vm.devices['pci'].detach(ass)
- self.anything_changed = True
-
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
if utils.is_debug():
traceback.print_exc()
msg.append(str(ex))
return msg
- def include_in_balancing_state_changed(self, state):
- for r in range (self.services_list.count()):
- item = self.services_list.item(r)
+ def include_in_balancing_changed(self, state):
+ for i in range(self.services_list.count()):
+ item = self.services_list.item(i)
if str(item.text()) == 'meminfo-writer':
item.setCheckState(state)
break
if self.dev_list.selected_list.count() > 0:
- if state == QtCore.Qt.Checked:
+ if state == ui_settingsdlg.QtCore.Qt.Checked:
self.dmm_warning_adv.show()
self.dmm_warning_dev.show()
else:
@@ -713,7 +814,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def devices_selection_changed(self):
if self.include_in_balancing.isChecked():
- if self.dev_list.selected_list.count() > 0 :
+ if self.dev_list.selected_list.count() > 0:
self.dmm_warning_adv.show()
self.dmm_warning_dev.show()
else:
@@ -757,15 +858,17 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.refresh_apps_button.setText(self.tr('Refresh in progress...'))
t_monitor = thread_monitor.ThreadMonitor()
- thread = threading.Thread(target=self.refresh_apps_in_vm, args=(t_monitor,))
+ thread = threading.Thread(
+ target=self.refresh_apps_in_vm,
+ args=(t_monitor,))
thread.daemon = True
thread.start()
while not t_monitor.is_finished():
self.qapp.processEvents()
- time.sleep (0.1)
+ time.sleep(0.1)
- self.AppListManager = AppmenuSelectManager(self.vm, self.app_list)
+ self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list)
self.refresh_apps_button.setEnabled(True)
self.refresh_apps_button.setText(self.tr('Refresh Applications'))
@@ -778,23 +881,29 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if not feature.startswith('service.'):
continue
service = feature[len('service.'):]
- item = QListWidgetItem(service)
- item.setCheckState(QtCore.Qt.Checked
- if self.vm.features[feature] else QtCore.Qt.Unchecked)
+ item = QtGui.QListWidgetItem(service)
+ item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked
+ if self.vm.features[feature]
+ else ui_settingsdlg.QtCore.Qt.Unchecked)
self.services_list.addItem(item)
self.new_srv_dict[service] = self.vm.features[feature]
- self.connect(self.services_list, SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked)
+ self.connect(
+ self.services_list,
+ QtCore.SIGNAL("itemClicked(QListWidgetItem *)"),
+ self.services_item_clicked)
def __add_service__(self):
srv = str(self.service_line_edit.text()).strip()
if srv != "":
if srv in self.new_srv_dict:
- QMessageBox.information(None, '',
+ QtGui.QMessageBox.information(
+ None,
+ '',
self.tr('Service already on the list!'))
else:
- item = QListWidgetItem(srv)
- item.setCheckState(QtCore.Qt.Checked)
+ item = QtGui.QListWidgetItem(srv)
+ item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked)
self.services_list.addItem(item)
self.new_srv_dict[srv] = True
@@ -804,9 +913,11 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if not item:
return
if str(item.text()) == 'meminfo-writer':
- QMessageBox.information(None,
+ QtGui.QMessageBox.information(
+ None,
self.tr('Service can not be removed'),
- self.tr('Service meminfo-writer can not be removed from the list.'))
+ self.tr('Service meminfo-writer can not '
+ 'be removed from the list.'))
return
row = self.services_list.currentRow()
@@ -816,10 +927,10 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def services_item_clicked(self, item):
if str(item.text()) == 'meminfo-writer':
- if item.checkState() == QtCore.Qt.Checked:
+ if item.checkState() == ui_settingsdlg.QtCore.Qt.Checked:
if not self.include_in_balancing.isChecked():
self.include_in_balancing.setChecked(True)
- elif item.checkState() == QtCore.Qt.Unchecked:
+ elif item.checkState() == ui_settingsdlg.QtCore.Qt.Unchecked:
if self.include_in_balancing.isChecked():
self.include_in_balancing.setChecked(False)
@@ -828,13 +939,16 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
msg = []
try:
- for r in range(self.services_list.count()):
- item = self.services_list.item(r)
- self.new_srv_dict[str(item.text())] = (item.checkState() == QtCore.Qt.Checked)
+ for i in range(self.services_list.count()):
+ item = self.services_list.item(i)
+ self.new_srv_dict[str(item.text())] = \
+ (item.checkState() == ui_settingsdlg.QtCore.Qt.Checked)
- balancing_was_checked = self.vm.features.get('service.meminfo-writer', True)
+ balancing_was_checked = self.vm.features.get(
+ 'service.meminfo-writer', True)
balancing_is_checked = self.include_in_balancing.isChecked()
- meminfo_writer_checked = self.new_srv_dict.get('meminfo-writer', True)
+ meminfo_writer_checked = self.new_srv_dict.get(
+ 'meminfo-writer', True)
if balancing_is_checked != meminfo_writer_checked:
if balancing_is_checked != balancing_was_checked:
@@ -844,7 +958,6 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
feature = 'service.' + service
if v != self.vm.features.get(feature, object()):
self.vm.features[feature] = v
- self.anything_changed = True
for feature in self.vm.features:
if not feature.startswith('service.'):
@@ -852,7 +965,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
service = feature[len('service.'):]
if service not in self.new_srv_dict:
del self.vm.features[feature]
- except Exception as ex:
+ except qubesadmin.exc.QubesException as ex:
msg.append(str(ex))
return msg
@@ -863,116 +976,65 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def set_fw_model(self, model):
self.fw_model = model
self.rulesTreeView.setModel(model)
- self.rulesTreeView.header().setResizeMode(QHeaderView.ResizeToContents)
- self.rulesTreeView.header().setResizeMode(0, QHeaderView.Stretch)
+ self.rulesTreeView.header().setResizeMode(
+ QtGui.QHeaderView.ResizeToContents)
+ self.rulesTreeView.header().setResizeMode(0, QtGui.QHeaderView.Stretch)
self.set_allow(model.allow)
- self.dnsCheckBox.setChecked(model.allowDns)
- self.icmpCheckBox.setChecked(model.allowIcmp)
- self.yumproxyCheckBox.setChecked(model.allowYumProxy)
- if model.tempFullAccessExpireTime:
- self.tempFullAccess.setChecked(True)
- self.tempFullAccessTime.setValue(
- (model.tempFullAccessExpireTime -
- int(datetime.datetime.now().strftime("%s")))/60)
+ if model.temp_full_access_expire_time:
+ self.temp_full_access.setChecked(True)
+ self.temp_full_access_time.setValue(
+ (model.temp_full_access_expire_time -
+ int(firewall.datetime.datetime.now().strftime("%s"))) / 60)
+
+ def disable_all_fw_conf(self):
+ self.firewall_modified_outside_label.setVisible(True)
+ self.policy_allow_radio_button.setEnabled(False)
+ self.policy_deny_radio_button.setEnabled(False)
+ self.rulesTreeView.setEnabled(False)
+ self.new_rule_button.setEnabled(False)
+ self.edit_rule_button.setEnabled(False)
+ self.delete_rule_button.setEnabled(False)
+ self.firewal_rules_label.setEnabled(False)
+ self.tempFullAccessWidget.setEnabled(False)
def set_allow(self, allow):
- self.policyAllowRadioButton.setChecked(allow)
- self.policyDenyRadioButton.setChecked(not allow)
- self.policy_changed(allow)
+ self.policy_allow_radio_button.setChecked(allow)
+ self.policy_deny_radio_button.setChecked(not allow)
+ self.policy_changed()
- def policy_changed(self, checked):
- self.tempFullAccessWidget.setEnabled(self.policyDenyRadioButton.isChecked())
+ def policy_changed(self):
+ self.rulesTreeView.setEnabled(
+ self.policy_deny_radio_button.isChecked())
+ self.new_rule_button.setEnabled(
+ self.policy_deny_radio_button.isChecked())
+ self.edit_rule_button.setEnabled(
+ self.policy_deny_radio_button.isChecked())
+ self.delete_rule_button.setEnabled(
+ self.policy_deny_radio_button.isChecked())
+ self.firewal_rules_label.setEnabled(
+ self.policy_deny_radio_button.isChecked())
+ self.tempFullAccessWidget.setEnabled(
+ self.policy_deny_radio_button.isChecked())
def new_rule_button_pressed(self):
- dialog = NewFwRuleDlg()
- self.run_rule_dialog(dialog)
+ dialog = firewall.NewFwRuleDlg()
+ self.fw_model.run_rule_dialog(dialog)
def edit_rule_button_pressed(self):
- dialog = NewFwRuleDlg()
- dialog.set_ok_enabled(True)
- selected = self.rulesTreeView.selectedIndexes()
- if len(selected) > 0:
- row = self.rulesTreeView.selectedIndexes().pop().row()
- address = self.fw_model.get_column_string(0, row).replace(' ', '')
- dialog.addressComboBox.setItemText(0, address)
- dialog.addressComboBox.setCurrentIndex(0)
- service = self.fw_model.get_column_string(1, row)
- if service == "any":
- service = ""
- dialog.serviceComboBox.setItemText(0, service)
- dialog.serviceComboBox.setCurrentIndex(0)
- protocol = self.fw_model.get_column_string(2, row)
- if protocol == "tcp":
- dialog.tcp_radio.setChecked(True)
- elif protocol == "udp":
- dialog.udp_radio.setChecked(True)
- else:
- dialog.any_radio.setChecked(True)
- self.run_rule_dialog(dialog, row)
+ selected = self.rulesTreeView.selectedIndexes()
+
+ if selected:
+ dialog = firewall.NewFwRuleDlg()
+ dialog.set_ok_state(True)
+ row = self.rulesTreeView.selectedIndexes().pop().row()
+ self.fw_model.populate_edit_dialog(dialog, row)
+ self.fw_model.run_rule_dialog(dialog, row)
def delete_rule_button_pressed(self):
- for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]):
- self.fw_model.removeChild(i)
-
- def run_rule_dialog(self, dialog, row = None):
- if dialog.exec_():
- address = str(dialog.addressComboBox.currentText())
- service = str(dialog.serviceComboBox.currentText())
- port = None
- port2 = None
-
- unmask = address.split("/", 1)
- if len(unmask) == 2:
- address = unmask[0]
- netmask = int(unmask[1])
- else:
- netmask = 32
-
- if address == "*":
- address = "0.0.0.0"
- netmask = 0
-
- if dialog.any_radio.isChecked():
- protocol = "any"
- port = 0
- else:
- if dialog.tcp_radio.isChecked():
- protocol = "tcp"
- elif dialog.udp_radio.isChecked():
- protocol = "udp"
- else:
- protocol = "any"
-
- try:
- range = service.split("-", 1)
- if len(range) == 2:
- port = int(range[0])
- port2 = int(range[1])
- else:
- port = int(service)
- except (TypeError, ValueError) as ex:
- port = self.fw_model.get_service_port(service)
-
- if port is not None:
- if port2 is not None and port2 <= port:
- QMessageBox.warning(None, self.tr("Invalid service ports range"),
- self.tr("Port {0} is lower than port {1}.").format(
- port2, port))
- else:
- item = {"address": address,
- "netmask": netmask,
- "portBegin": port,
- "portEnd": port2,
- "proto": protocol,
- }
- if row is not None:
- self.fw_model.setChild(row, item)
- else:
- self.fw_model.appendChild(item)
- else:
- QMessageBox.warning(None, self.tr("Invalid service name"),
- self.tr("Service '{0}' is unknown.").format(service))
+ for i in set([index.row() for index
+ in self.rulesTreeView.selectedIndexes()]):
+ self.fw_model.remove_child(i)
# Bases on the original code by:
@@ -986,7 +1048,7 @@ def handle_exception(exc_type, exc_value, exc_traceback):
strace = ""
stacktrace = traceback.extract_tb(exc_traceback)
- while len(stacktrace) > 0:
+ while stacktrace:
(filename, line, func, txt) = stacktrace.pop()
strace += "----\n"
strace += "line: %s\n" %txt
@@ -994,20 +1056,20 @@ def handle_exception(exc_type, exc_value, exc_traceback):
strace += "line no.: %d\n" %line
strace += "file: %s\n" %filename
- msg_box = QMessageBox()
+ msg_box = QtGui.QMessageBox()
msg_box.setDetailedText(strace)
- msg_box.setIcon(QMessageBox.Critical)
+ msg_box.setIcon(QtGui.QMessageBox.Critical)
msg_box.setWindowTitle("Houston, we have a problem...")
- msg_box.setText("Whoops. A critical error has occured. This is most likely a bug "
- "in Qubes Manager.
"
+ msg_box.setText("Whoops. A critical error has occured. "
+ "This is most likely a bug in Qubes Manager.
"
"%s" % error +
"
at line %d
of file %s.
"
- % ( line, filename ))
+ % (line, filename))
msg_box.exec_()
-parser = qubesadmin.tools.QubesArgumentParser(vmname_nargs=1)
+parser = QubesArgumentParser(vmname_nargs=1)
parser.add_argument('--tab', metavar='TAB',
action='store',
@@ -1018,12 +1080,10 @@ parser.set_defaults(
)
def main(args=None):
- global settings_window
-
args = parser.parse_args(args)
vm = args.domains.pop()
- qapp = QApplication(sys.argv)
+ qapp = QtGui.QApplication(sys.argv)
qapp.setOrganizationName('Invisible Things Lab')
qapp.setOrganizationDomain("https://www.qubes-os.org/")
qapp.setApplicationName("Qubes VM Settings")
diff --git a/qubesmanager/table_widgets.py b/qubesmanager/table_widgets.py
index b5a662a..06ec486 100644
--- a/qubesmanager/table_widgets.py
+++ b/qubesmanager/table_widgets.py
@@ -1,9 +1,10 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
# -*- coding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
-# Copyright (C) 2014 Marek Marczykowski-Górecki
+# Copyright (C) 2014 Marek Marczykowski-Górecki
+#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -15,61 +16,54 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
-import os
+from PyQt4 import QtGui # pylint: disable=import-error
+from PyQt4 import QtCore # pylint: disable=import-error
+# pylint: disable=too-few-public-methods
-from PyQt4 import QtGui
-
-from PyQt4.QtCore import QSize, Qt
-
-from PyQt4.QtGui import QTableWidgetItem, QHBoxLayout, QIcon, QLabel, QWidget, \
- QSizePolicy, QSpacerItem, QFont, QColor, QProgressBar, QPainter, QPen
-import time
-from qubes.qubes import vm_files
-import main
-
-qubes_dom0_updates_stat_file = '/var/lib/qubes/updates/dom0-updates-available'
-power_order = Qt.DescendingOrder
-update_order = Qt.AscendingOrder
+power_order = QtCore.Qt.DescendingOrder
+update_order = QtCore.Qt.AscendingOrder
row_height = 30
-class VmIconWidget (QWidget):
+class VmIconWidget(QtGui.QWidget):
def __init__(self, icon_path, enabled=True, size_multiplier=0.7,
- tooltip = None, parent=None, icon_sz = (32, 32)):
+ tooltip=None, parent=None, icon_sz=(32, 32)):
super(VmIconWidget, self).__init__(parent)
- self.label_icon = QLabel()
+ self.label_icon = QtGui.QLabel()
if icon_path[0] in ':/':
- icon = QIcon (icon_path)
+ icon = QtGui.QIcon(icon_path)
else:
- icon = QIcon.fromTheme(icon_path)
- icon_sz = QSize (row_height * size_multiplier, row_height * size_multiplier)
- icon_pixmap = icon.pixmap(icon_sz, QIcon.Disabled if not enabled else QIcon.Normal)
- self.label_icon.setPixmap (icon_pixmap)
- self.label_icon.setFixedSize (icon_sz)
- if tooltip != None:
+ icon = QtGui.QIcon.fromTheme(icon_path)
+ icon_sz = QtCore.QSize(row_height * size_multiplier,
+ row_height * size_multiplier)
+ icon_pixmap = icon.pixmap(
+ icon_sz,
+ QtGui.QIcon.Disabled if not enabled else QtGui.QIcon.Normal)
+ self.label_icon.setPixmap(icon_pixmap)
+ self.label_icon.setFixedSize(icon_sz)
+ if tooltip is not None:
self.label_icon.setToolTip(tooltip)
- layout = QHBoxLayout()
+ layout = QtGui.QHBoxLayout()
layout.addWidget(self.label_icon)
- layout.setContentsMargins(0,0,0,0)
+ layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
- def setToolTip(self, tooltip):
+ def setToolTip(self, tooltip): # pylint: disable=invalid-name
if tooltip is not None:
self.label_icon.setToolTip(tooltip)
else:
self.label_icon.setToolTip('')
-class VmTypeWidget(VmIconWidget):
- class VmTypeItem(QTableWidgetItem):
+class VmTypeWidget(VmIconWidget):
+ class VmTypeItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm):
super(VmTypeWidget.VmTypeItem, self).__init__()
self.value = value
@@ -84,40 +78,39 @@ class VmTypeWidget(VmIconWidget):
elif other.vm.qid == 0:
return False
elif self.value == other.value:
- return self.vm.qid < other.vm.qid
- else:
- return self.value < other.value
+ return self.vm.name < other.vm.name
+ return self.value < other.value
def __init__(self, vm, parent=None):
(icon_path, tooltip) = self.get_vm_icon(vm)
- super (VmTypeWidget, self).__init__(icon_path, True, 0.8, tooltip, parent)
+ super(VmTypeWidget, self).__init__(
+ icon_path, True, 0.8, tooltip, parent)
self.vm = vm
- self.tableItem = self.VmTypeItem(self.value, vm)
+ self.table_item = self.VmTypeItem(self.value, vm)
+ self.value = None
+
+ # TODO: add "provides network" column
def get_vm_icon(self, vm):
- if vm.qid == 0:
+ if vm.klass == 'AdminVM':
self.value = 0
- return (":/dom0.png", "Dom0")
- elif vm.is_netvm() and not vm.is_proxyvm():
- self.value = 1
- return (":/netvm.png", "NetVM")
- elif vm.is_proxyvm():
- self.value = 2
- return (":/proxyvm.png", "ProxyVM")
- elif vm.is_appvm() and vm.template is None:
- self.value = 4
- return (":/standalonevm.png", "StandaloneVM")
- elif vm.is_template():
+ icon_name = "dom0"
+ elif vm.klass == 'TemplateVM':
self.value = 3
- return (":/templatevm.png", "TemplateVM")
- elif vm.is_appvm() or vm.is_disposablevm():
+ icon_name = "templatevm"
+ elif vm.klass == 'StandaloneVM':
+ self.value = 4
+ icon_name = "standalonevm"
+ else:
self.value = 5 + vm.label.index
- return (":/appvm.png", "AppVM")
+ icon_name = "appvm"
+
+ return ":/" + icon_name + ".png", vm.klass
class VmLabelWidget(VmIconWidget):
- class VmLabelItem(QTableWidgetItem):
+ class VmLabelItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm):
super(VmLabelWidget.VmLabelItem, self).__init__()
self.value = value
@@ -132,32 +125,27 @@ class VmLabelWidget(VmIconWidget):
elif other.vm.qid == 0:
return False
elif self.value == other.value:
- return self.vm.qid < other.vm.qid
- else:
- return self.value < other.value
+ return self.vm.name < other.vm.name
+ return self.value < other.value
def __init__(self, vm, parent=None):
icon_path = self.get_vm_icon_path(vm)
- super (VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent)
+ super(VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent)
self.vm = vm
- self.tableItem = self.VmLabelItem(self.value, vm)
+ self.table_item = self.VmLabelItem(self.value, vm)
+ self.value = None
def get_vm_icon_path(self, vm):
- if vm.qid == 0:
- self.value = 100
- return ":/off.png"
- else:
- self.value = vm.label.index
- return vm.label.icon
+ self.value = vm.label.index
+ return vm.label.icon
-
-class VmNameItem (QTableWidgetItem):
+class VmNameItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmNameItem, self).__init__()
- self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+ self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.setText(vm.name)
- self.setTextAlignment(Qt.AlignVCenter)
+ self.setTextAlignment(QtCore.Qt.AlignVCenter)
self.qid = vm.qid
def __lt__(self, other):
@@ -168,44 +156,43 @@ class VmNameItem (QTableWidgetItem):
return super(VmNameItem, self).__lt__(other)
-class VmStatusIcon(QLabel):
+class VmStatusIcon(QtGui.QLabel):
def __init__(self, vm, parent=None):
- super (VmStatusIcon, self).__init__(parent)
+ super(VmStatusIcon, self).__init__(parent)
self.vm = vm
self.set_on_icon()
- self.previous_power_state = vm.last_power_state
+ self.previous_power_state = self.vm.get_power_state()
def update(self):
- if self.previous_power_state != self.vm.last_power_state:
+ if self.previous_power_state != self.vm.get_power_state():
self.set_on_icon()
- self.previous_power_state = self.vm.last_power_state
+ self.previous_power_state = self.vm.get_power_state()
def set_on_icon(self):
- if self.vm.last_power_state == "Running":
- icon = QIcon (":/on.png")
- elif self.vm.last_power_state in ["Paused", "Suspended"]:
- icon = QIcon (":/paused.png")
- elif self.vm.last_power_state in ["Transient", "Halting", "Dying"]:
- icon = QIcon (":/transient.png")
+ if self.vm.get_power_state() == "Running":
+ icon = QtGui.QIcon(":/on.png")
+ elif self.vm.get_power_state() in ["Paused", "Suspended"]:
+ icon = QtGui.QIcon(":/paused.png")
+ elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]:
+ icon = QtGui.QIcon(":/transient.png")
else:
- icon = QIcon (":/off.png")
+ icon = QtGui.QIcon(":/off.png")
- icon_sz = QSize (row_height * 0.5, row_height *0.5)
+ icon_sz = QtCore.QSize(row_height * 0.5, row_height * 0.5)
icon_pixmap = icon.pixmap(icon_sz)
- self.setPixmap (icon_pixmap)
- self.setFixedSize (icon_sz)
+ self.setPixmap(icon_pixmap)
+ self.setFixedSize(icon_sz)
-
-class VmInfoWidget (QWidget):
-
- class VmInfoItem (QTableWidgetItem):
+class VmInfoWidget(QtGui.QWidget):
+ class VmInfoItem(QtGui.QTableWidgetItem):
def __init__(self, upd_info_item, vm):
super(VmInfoWidget.VmInfoItem, self).__init__()
self.upd_info_item = upd_info_item
self.vm = vm
def __lt__(self, other):
+ # pylint: disable=too-many-return-statements
if self.vm.qid == 0:
return True
elif other.vm.qid == 0:
@@ -213,30 +200,34 @@ class VmInfoWidget (QWidget):
self_val = self.upd_info_item.value
other_val = other.upd_info_item.value
- if self.tableWidget().horizontalHeader().sortIndicatorOrder() == update_order:
+
+ if self.tableWidget().\
+ horizontalHeader().sortIndicatorOrder() == update_order:
# the result will be sorted by upd, sorting order: Ascending
self_val += 1 if self.vm.is_running() else 0
other_val += 1 if other.vm.is_running() else 0
if self_val == other_val:
- return self.vm.qid < other.vm.qid
- else:
- return self_val > other_val
- elif self.tableWidget().horizontalHeader().sortIndicatorOrder() == power_order:
- #the result will be sorted by power state, sorting order: Descending
- self_val = -(self_val/10 + 10*(1 if self.vm.is_running() else 0))
- other_val = -(other_val/10 + 10*(1 if other.vm.is_running() else 0))
+ return self.vm.name < other.vm.name
+ return self_val > other_val
+ elif self.tableWidget().\
+ horizontalHeader().sortIndicatorOrder() == power_order:
+ # the result will be sorted by power state,
+ # sorting order: Descending
+ self_val = -(self_val/10 +
+ 10*(1 if self.vm.is_running() else 0))
+ other_val = -(other_val/10 +
+ 10*(1 if other.vm.is_running() else 0))
if self_val == other_val:
- return self.vm.qid < other.vm.qid
- else:
- return self_val > other_val
+ return self.vm.name < other.vm.name
+ return self_val > other_val
else:
- #it would be strange if this happened
+ # it would be strange if this happened
return
- def __init__(self, vm, parent = None):
- super (VmInfoWidget, self).__init__(parent)
+ def __init__(self, vm, parent=None):
+ super(VmInfoWidget, self).__init__(parent)
self.vm = vm
- layout = QHBoxLayout ()
+ layout = QtGui.QHBoxLayout()
self.on_icon = VmStatusIcon(vm)
self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
@@ -247,58 +238,43 @@ class VmInfoWidget (QWidget):
layout.addWidget(self.on_icon)
layout.addWidget(self.upd_info)
layout.addWidget(self.error_icon)
- layout.addItem(QSpacerItem(0, 10, QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
+ layout.addItem(QtGui.QSpacerItem(0, 10,
+ QtGui.QSizePolicy.Expanding,
+ QtGui.QSizePolicy.Expanding))
layout.addWidget(self.blk_icon)
layout.addWidget(self.rec_icon)
- layout.setContentsMargins(5,0,5,0)
+ layout.setContentsMargins(5, 0, 5, 0)
self.setLayout(layout)
self.rec_icon.setVisible(False)
self.blk_icon.setVisible(False)
self.error_icon.setVisible(False)
- self.tableItem = self.VmInfoItem(self.upd_info.tableItem, vm)
+ self.table_item = self.VmInfoItem(self.upd_info.table_item, vm)
- def update_vm_state(self, vm, blk_visible, rec_visible=None):
+ def update_vm_state(self, vm):
self.on_icon.update()
self.upd_info.update_outdated(vm)
- if blk_visible != None:
- self.blk_icon.setVisible(blk_visible)
- if rec_visible != None:
- self.rec_icon.setVisible(rec_visible)
- self.error_icon.setToolTip(vm.qubes_manager_state[main.QMVmState
- .ErrorMsg])
- self.error_icon.setVisible(vm.qubes_manager_state[main.QMVmState
- .ErrorMsg] is not None)
-class VmTemplateItem (QTableWidgetItem):
+class VmTemplateItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmTemplateItem, self).__init__()
- self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+ self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
- if vm.template is not None:
+ if getattr(vm, 'template', None) is not None:
self.setText(vm.template.name)
else:
- font = QFont()
- font.setStyle(QFont.StyleItalic)
+ font = QtGui.QFont()
+ font.setStyle(QtGui.QFont.StyleItalic)
self.setFont(font)
- self.setTextColor(QColor("gray"))
+ self.setTextColor(QtGui.QColor("gray"))
- if vm.is_appvm(): # and vm.template is None
- self.setText("StandaloneVM")
- elif vm.is_template():
- self.setText("TemplateVM")
- elif vm.qid == 0:
- self.setText("AdminVM")
- elif vm.is_netvm():
- self.setText("NetVM")
- else:
- self.setText("---")
+ self.setText(vm.klass)
- self.setTextAlignment(Qt.AlignVCenter)
+ self.setTextAlignment(QtCore.Qt.AlignVCenter)
def __lt__(self, other):
if self.vm.qid == 0:
@@ -306,27 +282,22 @@ class VmTemplateItem (QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.text() == other.text():
- return self.vm.qid < other.vm.qid
- else:
- return super(VmTemplateItem, self).__lt__(other)
+ return self.vm.name < other.vm.name
+ return super(VmTemplateItem, self).__lt__(other)
-
-
-class VmNetvmItem (QTableWidgetItem):
+class VmNetvmItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmNetvmItem, self).__init__()
- self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+ self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
- if vm.is_netvm() and not vm.is_proxyvm():
+ if getattr(vm, 'netvm', None) is None:
self.setText("n/a")
- elif vm.netvm is not None:
- self.setText(vm.netvm.name)
else:
- self.setText("---")
+ self.setText(vm.netvm.name)
- self.setTextAlignment(Qt.AlignVCenter)
+ self.setTextAlignment(QtCore.Qt.AlignVCenter)
def __lt__(self, other):
if self.vm.qid == 0:
@@ -334,22 +305,19 @@ class VmNetvmItem (QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.text() == other.text():
- return self.vm.qid < other.vm.qid
- else:
- return super(VmNetvmItem, self).__lt__(other)
+ return self.vm.name < other.vm.name
+ return super(VmNetvmItem, self).__lt__(other)
-class VmInternalItem(QTableWidgetItem):
+
+class VmInternalItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmInternalItem, self).__init__()
- self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+ self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
- self.internal = self.vm.internal
+ self.internal = vm.features.get('internal', False)
- if self.internal:
- self.setText("Yes")
- else:
- self.setText("")
+ self.setText("Yes" if self.internal else "")
def __lt__(self, other):
if self.vm.qid == 0:
@@ -359,144 +327,10 @@ class VmInternalItem(QTableWidgetItem):
return super(VmInternalItem, self).__lt__(other)
-class VmUsageBarWidget (QWidget):
+# features man qvm-features
+class VmUpdateInfoWidget(QtGui.QWidget):
- class VmUsageBarItem (QTableWidgetItem):
- def __init__(self, value, vm):
- super(VmUsageBarWidget.VmUsageBarItem, self).__init__()
- self.value = value
- self.vm = vm
-
- def set_value(self, value):
- self.value = value
-
- def __lt__(self, other):
- if self.vm.qid == 0:
- return True
- elif other.vm.qid == 0:
- return False
- elif self.value == other.value:
- return self.vm.qid < other.vm.qid
- else:
- return int(self.value) < int(other.value)
-
- def __init__(self, min, max, format, update_func, vm, load, hue=210, parent = None):
- super (VmUsageBarWidget, self).__init__(parent)
-
-
- self.min = min
- self.max = max
- self.update_func = update_func
- self.value = min
-
- self.widget = QProgressBar()
- self.widget.setMinimum(min)
- self.widget.setMaximum(max)
- self.widget.setFormat(format)
-
- self.widget.setStyleSheet(
- "QProgressBar:horizontal{" +\
- "border: 1px solid hsv({0}, 100, 250);".format(hue) +\
- "border-radius: 4px;\
- background: transparent;\
- text-align: center;\
- }\
- QProgressBar::chunk:horizontal {\
- background: qlineargradient(x1: 1, y1: 0.5, x2: 1, y2: 0.5, " +\
- "stop: 0 hsv({0}, 170, 207),".format(hue) +
- " stop: 1 white); \
- }"
- )
-
- layout = QHBoxLayout()
- layout.addWidget(self.widget)
-
- self.setLayout(layout)
- self.tableItem = self.VmUsageBarItem(min, vm)
-
- self.update_load(vm, load)
-
-
-
- def update_load(self, vm, load):
- self.value = self.update_func(vm, load)
- self.widget.setValue(self.value)
- self.tableItem.set_value(self.value)
-
-class ChartWidget (QWidget):
-
- class ChartItem (QTableWidgetItem):
- def __init__(self, value, vm):
- super(ChartWidget.ChartItem, self).__init__()
- self.value = value
- self.vm = vm
-
- def set_value(self, value):
- self.value = value
-
- def __lt__(self, other):
- if self.vm.qid == 0:
- return True
- elif other.vm.qid == 0:
- return False
- elif self.value == other.value:
- return self.vm.qid < other.vm.qid
- else:
- return self.value < other.value
-
- def __init__(self, vm, update_func, hue, load = 0, parent = None):
- super (ChartWidget, self).__init__(parent)
- self.update_func = update_func
- self.hue = hue
- if hue < 0 or hue > 255:
- self.hue = 255
- self.load = load
- assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
- self.load_history = [self.load]
- self.tableItem = ChartWidget.ChartItem(self.load, vm)
-
- def update_load (self, vm, load):
- self.load = self.update_func(vm, load)
-
- assert self.load >= 0, "load = {0}".format(self.load)
- # assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
- if self.load > 100:
- # FIXME: This is an ugly workaround for cpu_load:/
- self.load = 100
-
- self.load_history.append (self.load)
- self.tableItem.set_value(self.load)
- self.repaint()
-
- def paintEvent (self, Event = None):
- p = QPainter (self)
- dx = 4
-
- W = self.width()
- H = self.height() - 5
- N = len(self.load_history)
- if N > W/dx:
- tail = N - W/dx
- N = W/dx
- self.load_history = self.load_history[tail:]
-
- assert len(self.load_history) == N
-
- for i in range (0, N-1):
- val = self.load_history[N- i - 1]
- sat = 70 + val*(255-70)/100
- color = QColor.fromHsv (self.hue, sat, 255)
- pen = QPen (color)
- pen.setWidth(dx-1)
- p.setPen(pen)
- if val > 0:
- p.drawLine (W - i*dx - dx, H , W - i*dx - dx, H - (H - 5) * val/100)
-
-
-
-class VmUpdateInfoWidget(QWidget):
-
- class VmUpdateInfoItem (QTableWidgetItem):
+ class VmUpdateInfoItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm):
super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
self.value = 0
@@ -517,91 +351,53 @@ class VmUpdateInfoWidget(QWidget):
elif other.vm.qid == 0:
return False
elif self.value == other.value:
- return self.vm.qid < other.vm.qid
- else:
- return self.value < other.value
+ return self.vm.name < other.vm.name
+ return self.value < other.value
- def __init__(self, vm, show_text=True, parent = None):
- super (VmUpdateInfoWidget, self).__init__(parent)
- layout = QHBoxLayout ()
+ def __init__(self, vm, show_text=True, parent=None):
+ super(VmUpdateInfoWidget, self).__init__(parent)
+ layout = QtGui.QHBoxLayout()
self.show_text = show_text
if self.show_text:
- self.label=QLabel("")
- layout.addWidget(self.label, alignment=Qt.AlignCenter)
+ self.label = QtGui.QLabel("")
+ layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
else:
- self.icon = QLabel("")
- layout.addWidget(self.icon, alignment=Qt.AlignCenter)
+ self.icon = QtGui.QLabel("")
+ layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
self.setLayout(layout)
self.previous_outdated_state = None
self.previous_update_recommended = None
self.value = None
- self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
+ self.table_item = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
def update_outdated(self, vm):
- if vm.type == "HVM":
- return
- if vm.is_outdated():
- outdated_state = "outdated"
- # During TemplateVM shutdown, there's an interval of a few seconds
- # during which vm.template.is_running() returns false but
- # vm.is_outdated() does not yet return true, so the icon disappears.
- # This looks goofy, but we've decided not to fix it at this time
- # (2015-02-09).
- elif vm.template and vm.template.is_running():
+ outdated_state = False
+
+ try:
+ for vol in vm.volumes:
+ if vol.is_outdated():
+ outdated_state = "outdated"
+ break
+ except AttributeError:
+ pass
+
+ if not outdated_state and getattr(vm, 'template', None)\
+ and vm.template.is_running():
outdated_state = "to-be-outdated"
- else:
- outdated_state = None
-
if outdated_state != self.previous_outdated_state:
self.update_status_widget(outdated_state)
-
self.previous_outdated_state = outdated_state
- if not vm.is_updateable():
- return
-
- if vm.qid == 0:
- update_recommended = self.previous_update_recommended
- if os.path.exists(qubes_dom0_updates_stat_file):
- update_recommended = True
- else:
- update_recommended = False
-
- else:
- update_recommended = self.previous_update_recommended
- stat_file_path = vm.dir_path + '/' + vm_files["updates_stat_file"]
- if not os.path.exists(stat_file_path):
- update_recommended = False
- else:
- if (not hasattr(vm, "updates_stat_file_read_time")) or vm.updates_stat_file_read_time <= os.path.getmtime(stat_file_path):
-
- stat_file = open(stat_file_path, "r")
- updates = stat_file.read().strip()
- stat_file.close()
- if updates.isdigit():
- updates = int(updates)
- else:
- updates = 0
-
- if updates == 0:
- update_recommended = False
- else:
- update_recommended = True
- vm.updates_stat_file_read_time = time.time()
-
- if update_recommended and not self.previous_update_recommended:
- self.update_status_widget("update")
- elif self.previous_update_recommended and not update_recommended:
- self.update_status_widget(None)
-
- self.previous_update_recommended = update_recommended
-
+ updates_available = vm.features.get('updates-available', False)
+ if updates_available != self.previous_update_recommended:
+ self.update_status_widget("update" if updates_available else None)
+ self.previous_update_recommended = updates_available
def update_status_widget(self, state):
self.value = state
- self.tableItem.set_value(state)
+ self.table_item.set_value(state)
if state == "update":
label_text = "Check updates"
icon_path = ":/update-recommended.png"
@@ -618,7 +414,7 @@ class VmUpdateInfoWidget(QWidget):
tooltip_text = self.tr(
"The TemplateVM must be stopped before changes from its "
"current session can be picked up by this VM.")
- elif state is None:
+ else:
label_text = ""
icon_path = None
tooltip_text = None
@@ -632,26 +428,27 @@ class VmUpdateInfoWidget(QWidget):
self.icon = VmIconWidget(icon_path, True, 0.7)
self.icon.setToolTip(tooltip_text)
else:
- self.icon = QLabel(label_text)
- self.layout().addWidget(self.icon, alignment=Qt.AlignCenter)
+ self.icon = QtGui.QLabel(label_text)
+ self.layout().addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
-class VmSizeOnDiskItem (QTableWidgetItem):
+class VmSizeOnDiskItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmSizeOnDiskItem, self).__init__()
- self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+ self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.value = 0
self.update()
- self.setTextAlignment(Qt.AlignVCenter)
+ self.setTextAlignment(QtCore.Qt.AlignVCenter)
def update(self):
if self.vm.qid == 0:
self.setText("n/a")
else:
- self.value = self.vm.get_disk_utilization()/(1024*1024)
- self.setText( str(self.value) + " MiB")
+ self.value = 10
+ self.value = round(self.vm.get_disk_utilization()/(1024*1024), 2)
+ self.setText(str(self.value) + " MiB")
def __lt__(self, other):
if self.vm.qid == 0:
@@ -659,21 +456,18 @@ class VmSizeOnDiskItem (QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.value == other.value:
- return self.vm.qid < other.vm.qid
- else:
- return self.value < other.value
+ return self.vm.name < other.vm.name
+ return self.value < other.value
-class VmIPItem(QTableWidgetItem):
+
+class VmIPItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmIPItem, self).__init__()
- self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+ self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
- self.ip = self.vm.ip
- if self.ip:
- self.setText(self.ip)
- else:
- self.setText("n/a")
+ self.ip = getattr(self.vm, 'ip', None)
+ self.setText(self.ip if self.ip is not None else 'n/a')
def __lt__(self, other):
if self.vm.qid == 0:
@@ -682,13 +476,14 @@ class VmIPItem(QTableWidgetItem):
return False
return super(VmIPItem, self).__lt__(other)
-class VmIncludeInBackupsItem(QTableWidgetItem):
+
+class VmIncludeInBackupsItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmIncludeInBackupsItem, self).__init__()
- self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+ self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
- if self.vm.include_in_backups:
+ if getattr(self.vm, 'include_in_backups', None):
self.setText("Yes")
else:
self.setText("")
@@ -699,18 +494,18 @@ class VmIncludeInBackupsItem(QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.vm.include_in_backups == other.vm.include_in_backups:
- return self.vm.qid < other.vm.qid
- else:
- return self.vm.include_in_backups < other.vm.include_in_backups
+ return self.vm.name < other.vm.name
+ return self.vm.include_in_backups < other.vm.include_in_backups
-class VmLastBackupItem(QTableWidgetItem):
+
+class VmLastBackupItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmLastBackupItem, self).__init__()
- self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
+ self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
- if self.vm.backup_timestamp:
- self.setText(str(self.vm.backup_timestamp.date()))
+ if getattr(self.vm, 'backup_timestamp', None):
+ self.setText(self.vm.backup_timestamp)
else:
self.setText("")
@@ -720,10 +515,9 @@ class VmLastBackupItem(QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.vm.backup_timestamp == other.vm.backup_timestamp:
- return self.vm.qid < other.vm.qid
+ return self.vm.name < other.vm.name
elif not self.vm.backup_timestamp:
return False
elif not other.vm.backup_timestamp:
return True
- else:
- return self.vm.backup_timestamp < other.vm.backup_timestamp
+ return self.vm.backup_timestamp < other.vm.backup_timestamp
diff --git a/qubesmanager/thread_monitor.py b/qubesmanager/thread_monitor.py
index 07fdca3..8515aff 100644
--- a/qubesmanager/thread_monitor.py
+++ b/qubesmanager/thread_monitor.py
@@ -14,18 +14,17 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program; if not, see .
#
#
-from PyQt4.QtCore import *
+import PyQt4.QtCore # pylint: disable=import-error
import threading
-class ThreadMonitor(QObject):
+class ThreadMonitor(PyQt4.QtCore.QObject):
def __init__(self):
self.success = True
self.error_msg = None
@@ -41,4 +40,3 @@ class ThreadMonitor(QObject):
def set_finished(self):
self.event_finished.set()
-
diff --git a/qubesmanager/utils.py b/qubesmanager/utils.py
index 25e9f8f..a8b68f9 100644
--- a/qubesmanager/utils.py
+++ b/qubesmanager/utils.py
@@ -20,12 +20,11 @@
# along with this program. If not, see .
#
-import functools
import os
import re
import qubesadmin
-from PyQt4.QtGui import QIcon
+from PyQt4.QtGui import QIcon # pylint: disable=import-error
def _filter_internal(vm):
return (not vm.klass == 'AdminVM'
@@ -153,11 +152,11 @@ def get_path_from_vm(vm, service_name):
if not vm:
return None
- stdout, stderr = vm.run_service_for_stdio(service_name)
+ stdout, _stderr = vm.run_service_for_stdio(service_name)
untrusted_path = stdout.decode(encoding='ascii')[:path_max_len]
- if len(untrusted_path) == 0:
+ if not untrusted_path:
return None
if path_re.match(untrusted_path):
assert '../' not in untrusted_path
diff --git a/rpm_spec/qmgr.spec b/rpm_spec/qmgr.spec
index 7c473fa..877caaa 100644
--- a/rpm_spec/qmgr.spec
+++ b/rpm_spec/qmgr.spec
@@ -43,7 +43,9 @@ cp qubesmanager/qvm_about.sh $RPM_BUILD_ROOT/usr/libexec/qubes-manager/
mkdir -p $RPM_BUILD_ROOT/usr/share/applications
cp qubes-global-settings.desktop $RPM_BUILD_ROOT/usr/share/applications/
cp qubes-vm-create.desktop $RPM_BUILD_ROOT/usr/share/applications/
-
+cp qubes-backup.desktop $RPM_BUILD_ROOT/usr/share/applications/
+cp qubes-backup-restore.desktop $RPM_BUILD_ROOT/usr/share/applications/
+cp qubes-qube-manager.desktop $RPM_BUILD_ROOT/usr/share/applications/
%post
update-desktop-database &> /dev/null || :
@@ -60,6 +62,9 @@ rm -rf $RPM_BUILD_ROOT
/usr/bin/qubes-vm-settings
/usr/bin/qubes-vm-create
/usr/bin/qubes-vm-boot-from-device
+/usr/bin/qubes-backup
+/usr/bin/qubes-backup-restore
+/usr/bin/qubes-qube-manager
/usr/libexec/qubes-manager/mount_for_backup.sh
/usr/libexec/qubes-manager/qvm_about.sh
@@ -83,6 +88,7 @@ rm -rf $RPM_BUILD_ROOT
%{python3_sitelib}/qubesmanager/informationnotes.py
%{python3_sitelib}/qubesmanager/create_new_vm.py
%{python3_sitelib}/qubesmanager/thread_monitor.py
+%{python3_sitelib}/qubesmanager/qube_manager.py
%{python3_sitelib}/qubesmanager/utils.py
%{python3_sitelib}/qubesmanager/bootfromdevice.py
@@ -100,6 +106,7 @@ rm -rf $RPM_BUILD_ROOT
%{python3_sitelib}/qubesmanager/ui_about.py
%{python3_sitelib}/qubesmanager/ui_releasenotes.py
%{python3_sitelib}/qubesmanager/ui_informationnotes.py
+%{python3_sitelib}/qubesmanager/ui_qubemanager.py
%{python3_sitelib}/qubesmanager/i18n/qubesmanager_*.qm
%{python3_sitelib}/qubesmanager/i18n/qubesmanager_*.ts
@@ -108,3 +115,6 @@ rm -rf $RPM_BUILD_ROOT
/usr/share/applications/qubes-global-settings.desktop
/usr/share/applications/qubes-vm-create.desktop
+/usr/share/applications/qubes-backup.desktop
+/usr/share/applications/qubes-backup-restore.desktop
+/usr/share/applications/qubes-qube-manager.desktop
diff --git a/setup.py b/setup.py
index cba28dd..28d2c07 100644
--- a/setup.py
+++ b/setup.py
@@ -21,6 +21,9 @@ if __name__ == '__main__':
'qubes-global-settings = qubesmanager.global_settings:main',
'qubes-vm-settings = qubesmanager.settings:main',
'qubes-vm-create = qubesmanager.create_new_vm:main',
- 'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main'
+ 'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main',
+ 'qubes-backup = qubesmanager.backup:main',
+ 'qubes-backup-restore = qubesmanager.restore:main',
+ 'qubes-qube-manager = qubesmanager.qube_manager:main'
],
})
diff --git a/test-packages/qubes/backup/__init__.py b/test-packages/qubes/backup/__init__.py
new file mode 100644
index 0000000..dc24fbb
--- /dev/null
+++ b/test-packages/qubes/backup/__init__.py
@@ -0,0 +1,5 @@
+class BackupCanceledError(BaseException):
+
+ tmpdir = None
+
+ pass
diff --git a/test-packages/qubes/storage/__init__.py b/test-packages/qubes/storage/__init__.py
new file mode 100644
index 0000000..2ae2839
--- /dev/null
+++ b/test-packages/qubes/storage/__init__.py
@@ -0,0 +1 @@
+pass
diff --git a/test-packages/qubes/storage/file.py b/test-packages/qubes/storage/file.py
new file mode 100644
index 0000000..1503cca
--- /dev/null
+++ b/test-packages/qubes/storage/file.py
@@ -0,0 +1,2 @@
+def get_disk_usage(*args):
+ return 0
diff --git a/test-packages/qubesadmin/__init__.py b/test-packages/qubesadmin/__init__.py
new file mode 100644
index 0000000..1801453
--- /dev/null
+++ b/test-packages/qubesadmin/__init__.py
@@ -0,0 +1,4 @@
+class Qubes(object):
+ pass
+
+DEFAULT = object()
diff --git a/test-packages/qubesadmin/backup/__init__.py b/test-packages/qubesadmin/backup/__init__.py
new file mode 100644
index 0000000..fc80254
--- /dev/null
+++ b/test-packages/qubesadmin/backup/__init__.py
@@ -0,0 +1 @@
+pass
\ No newline at end of file
diff --git a/test-packages/qubesadmin/backup/restore/__init__.py b/test-packages/qubesadmin/backup/restore/__init__.py
new file mode 100644
index 0000000..aa328db
--- /dev/null
+++ b/test-packages/qubesadmin/backup/restore/__init__.py
@@ -0,0 +1,19 @@
+class BackupRestore(object):
+
+ options = object()
+ canceled = None
+
+ def get_restore_info(self, *args):
+ pass
+
+ def restore_do(self, *args):
+ pass
+
+ def get_restore_info(self, *args):
+ pass
+
+ def restore_info_verify(self, *args):
+ pass
+
+ def get_restore_summary(self, *args):
+ pass
diff --git a/test-packages/qubesadmin/devices.py b/test-packages/qubesadmin/devices.py
new file mode 100644
index 0000000..be5de38
--- /dev/null
+++ b/test-packages/qubesadmin/devices.py
@@ -0,0 +1,4 @@
+### mock qubesadmin.devices module
+
+class DeviceAssignment(object):
+ pass
diff --git a/test-packages/qubesadmin/events.py b/test-packages/qubesadmin/events.py
new file mode 100644
index 0000000..68c1b58
--- /dev/null
+++ b/test-packages/qubesadmin/events.py
@@ -0,0 +1,5 @@
+class EventsDispatcher(object):
+
+ def add_handler(self, *args):
+ pass
+ pass
diff --git a/test-packages/qubesadmin/exc.py b/test-packages/qubesadmin/exc.py
new file mode 100644
index 0000000..22a3a27
--- /dev/null
+++ b/test-packages/qubesadmin/exc.py
@@ -0,0 +1,6 @@
+### mock qubesadmin.exc module
+# pylint: disable=unused-variable
+
+
+class QubesException(BaseException):
+ pass
diff --git a/test-packages/qubesadmin/firewall.py b/test-packages/qubesadmin/firewall.py
new file mode 100644
index 0000000..e83c19f
--- /dev/null
+++ b/test-packages/qubesadmin/firewall.py
@@ -0,0 +1,4 @@
+### mock qubesadmin.firewall module
+
+class Rule(object):
+ pass
diff --git a/test-packages/qubesadmin/tools/__init__.py b/test-packages/qubesadmin/tools/__init__.py
new file mode 100644
index 0000000..c448ad0
--- /dev/null
+++ b/test-packages/qubesadmin/tools/__init__.py
@@ -0,0 +1,9 @@
+### mock qubesadmin.tools module
+
+class QubesArgumentParser(object):
+
+ def add_argument(self, *args, **kwargs):
+ pass
+
+ def set_defaults(self, *args, **kwargs):
+ pass
diff --git a/test-packages/qubesadmin/tools/qvm_start.py b/test-packages/qubesadmin/tools/qvm_start.py
new file mode 100644
index 0000000..f04be20
--- /dev/null
+++ b/test-packages/qubesadmin/tools/qvm_start.py
@@ -0,0 +1,4 @@
+### mock qvm_start module
+
+def main(*args, **kwargs):
+ pass
diff --git a/test-packages/qubesadmin/utils.py b/test-packages/qubesadmin/utils.py
new file mode 100644
index 0000000..6915104
--- /dev/null
+++ b/test-packages/qubesadmin/utils.py
@@ -0,0 +1,10 @@
+### mock qubesadmin.utils module
+
+def parse_size(*args, **kwargs):
+ return args[0]
+
+def updates_vms_status(*args, **kwargs):
+ return args[0]
+
+def size_to_human(*args, **kwargs):
+ return args[0]
diff --git a/ui/backupdlg.ui b/ui/backupdlg.ui
index 31760d5..5330fb3 100644
--- a/ui/backupdlg.ui
+++ b/ui/backupdlg.ui
@@ -6,8 +6,8 @@
0
0
- 650
- 399
+ 737
+ 420
@@ -23,73 +23,8 @@
-
-
-
-
-
-
- 0
- 0
-
-
-
- Shutdown all running selected VMs
-
-
-
- :/shutdownvm.png:/shutdownvm.png
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Refresh running states.
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 75
- true
- true
-
-
-
- color:rgb(255, 0, 0)
-
-
- Some of the selected VMs are running (red). Running VMs cannot be backed up!
-
-
- true
-
-
-
-
-
-
- 9
- 50
- false
- false
- false
-
-
Select VMs to backup:
@@ -145,12 +80,79 @@
+ -
+
+
+ Compress the backup
+
+
+ true
+
+
+
+ -
+
+
+
+
+
+
+
+ 255
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 255
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 139
+ 142
+ 142
+
+
+
+
+
+
+
+
+ 75
+ true
+ true
+
+
+
+ Warning: unrecognized data found in configuration files.
+
+
+
-
+
+
+ 0
+ 0
+
+
Backup destination directory
@@ -188,73 +190,106 @@
- -
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Save backup profile
+
+
+
+
+ 10
+ 30
+ 267
+ 25
+
+
+
+ Save settings as default backup profile:
+
+
+
+
+
+ 270
+ 30
+ 539
+ 25
+
+
+
+
+
+
+ true
+
+
+
+
+ -
+
+
+ 0
+ 0
+
+
Backup security
-
-
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
-
<html><head/><body><p>Encryption / Verification<br/>passphrase:</p></body></html>
- -
-
-
- Encrypt backup:
-
-
-
- -
-
-
- Qt::LeftToRight
-
-
-
-
-
- true
-
-
-
- -
-
-
- <html><head/><body><p>Reenter passphrase:</p></body></html>
-
-
-
- -
+
-
QLineEdit::Password
- -
+
-
+
+
+ <html><head/><body><p>Reenter passphrase:</p></body></html>
+
+
+
+ -
QLineEdit::Password
- -
-
-
- <b>Note:</b> Even if you choose not to encrypt your backup, a password is still required in order to provide authenticated verification of the data. See the emergency restore instructions for more info.
-
-
- true
-
-
-
+ -
+
+
+
+ true
+
+
+
+ NOTE: Only running VMs are listed.
+
+
+
@@ -281,8 +316,8 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
-</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
-<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>
+</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;">
+<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html>
@@ -324,6 +359,9 @@ p, li { white-space: pre-wrap; }
-
+
+ 0
+
0
diff --git a/ui/newfwruledlg.ui b/ui/newfwruledlg.ui
index 11da4ed..391e16c 100644
--- a/ui/newfwruledlg.ui
+++ b/ui/newfwruledlg.ui
@@ -31,10 +31,22 @@
6
-
-
-
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
- Protocol
+ UDP
@@ -45,13 +57,6 @@
- -
-
-
- true
-
-
-
-
@@ -59,6 +64,31 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 71
+ 0
+
+
+
+
+
+
+ Any
+
+
+ true
+
+
+
-
@@ -66,6 +96,13 @@
+ -
+
+
+ true
+
+
+
-
@@ -85,44 +122,10 @@
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
+
-
+
- UDP
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 71
- 0
-
-
-
- Any
-
-
- true
+ Protocol
diff --git a/ui/qubemanager.ui b/ui/qubemanager.ui
new file mode 100644
index 0000000..4a2cdc9
--- /dev/null
+++ b/ui/qubemanager.ui
@@ -0,0 +1,841 @@
+
+
+ VmManagerWindow
+
+
+
+ 0
+ 0
+ 769
+ 385
+
+
+
+
+ 0
+ 0
+
+
+
+ Qt::DefaultContextMenu
+
+
+ Qube Manager
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+ true
+
+
+
+
+
+
+ QLayout::SetDefaultConstraint
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+ Search:
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+
+ 200
+ 30
+
+
+
+ Qt::CustomContextMenu
+
+
+ false
+
+
+ 0
+
+
+ Qt::ScrollBarAsNeeded
+
+
+ Qt::ScrollBarAsNeeded
+
+
+ true
+
+
+ QAbstractItemView::SingleSelection
+
+
+ QAbstractItemView::SelectRows
+
+
+ false
+
+
+ Qt::NoPen
+
+
+ true
+
+
+ false
+
+
+ 10
+
+
+ 11
+
+
+ false
+
+
+ 150
+
+
+ 150
+
+
+ false
+
+
+ false
+
+
+
+ Nowy wiersz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+ VM name
+
+
+
+
+ State
+
+
+ Update info
+
+
+
+
+ Template
+
+
+ VM's template
+
+
+
+
+ NetVM
+
+
+ VM's netVM
+
+
+
+
+ Size
+
+
+
+
+ Internal
+
+
+
+
+ IP
+
+
+
+
+ Backups
+
+
+
+
+ Last backup
+
+
+
+
+
+
+
+
+
+ Qt::CustomContextMenu
+
+
+ toolBar
+
+
+ Qt::BottomToolBarArea|Qt::TopToolBarArea
+
+
+ false
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :/createvm.png:/createvm.png
+
+
+ Create &New Qube
+
+
+ Create a new Qube
+
+
+
+
+ false
+
+
+
+ :/removevm.png:/removevm.png
+
+
+ &Delete Qube
+
+
+ Remove an existing Qube (must be stopped first)
+
+
+
+
+ false
+
+
+
+ :/resumevm.png:/resumevm.png
+
+
+ Start/Resu&me Qube
+
+
+ Start/Resume selected Qube
+
+
+
+
+ false
+
+
+
+ :/pausevm.png:/pausevm.png
+
+
+ &Pause Qube
+
+
+ Pause selected Qube
+
+
+
+
+ false
+
+
+
+ :/shutdownvm.png:/shutdownvm.png
+
+
+ &Shutdown Qube
+
+
+ Shutdown selected Qube
+
+
+
+
+ false
+
+
+
+ :/restartvm.png:/restartvm.png
+
+
+ Restar&t Qube
+
+
+ Restart selected Qube
+
+
+
+
+ false
+
+
+
+ :/apps.png:/apps.png
+
+
+ Add/remove app s&hortcuts
+
+
+ Add/remove app shortcuts for this Qube
+
+
+
+
+ false
+
+
+
+ :/updateable.png:/updateable.png
+
+
+ &Update Qube
+
+
+ Update Qube system
+
+
+
+
+
+ :/firewall.png:/firewall.png
+
+
+ Edit Qube &firewall rules
+
+
+ Edit Qube firewall rules
+
+
+
+
+ true
+
+
+
+ :/showcpuload.png:/showcpuload.png
+
+
+ Show graphs
+
+
+ Show Graphs
+
+
+
+
+ Options
+
+
+
+
+ View
+
+
+
+
+ true
+
+
+ true
+
+
+ &Template
+
+
+
+
+ true
+
+
+ true
+
+
+ &NetVM
+
+
+
+
+
+ :/settings.png:/settings.png
+
+
+ Qube s&ettings
+
+
+ Qube Settings
+
+
+
+
+
+ :/restore.png:/restore.png
+
+
+ &Restore Qubes from backup
+
+
+ Restore Qubes from backup
+
+
+
+
+
+ :/backup.png:/backup.png
+
+
+ &Backup Qubes
+
+
+ Backup Qubes
+
+
+
+
+
+ :/global-settings.png:/global-settings.png
+
+
+ &Global settings
+
+
+
+
+
+ :/networking.png:/networking.png
+
+
+ &Qubes Network
+
+
+ false
+
+
+
+
+ true
+
+
+ true
+
+
+ &State
+
+
+
+
+
+ :/killvm.png:/killvm.png
+
+
+ &Kill Qube
+
+
+ Kill selected Qube
+
+
+
+
+
+ :/kbd-layout.png:/kbd-layout.png
+
+
+ Set keyboard la&yout
+
+
+ Set keyboard layout per Qube
+
+
+
+
+ true
+
+
+ true
+
+
+ T&ype
+
+
+ Qube Type
+
+
+
+
+ true
+
+
+ true
+
+
+ &Label
+
+
+
+
+ true
+
+
+ true
+
+
+ N&ame
+
+
+
+
+ true
+
+
+ true
+
+
+ Show tool bar
+
+
+
+
+ true
+
+
+ true
+
+
+ Show menu bar
+
+
+
+
+
+
+
+
+
+ &Qubes OS
+
+
+
+
+ true
+
+
+ true
+
+
+ Si&ze
+
+
+ Size on Disk
+
+
+
+
+
+ :/run-command.png:/run-command.png
+
+
+ &Run command in Qube
+
+
+ Run command in the specified Qube
+
+
+
+
+ false
+
+
+
+ :/templatevm.png:/templatevm.png
+
+
+ &Clone Qube
+
+
+ Clone Qube
+
+
+
+
+ true
+
+
+ true
+
+
+ Inte&rnal
+
+
+ Is an internal Qube
+
+
+
+
+ false
+
+
+
+ :/resumevm.png:/resumevm.png
+
+
+ Start Qube for Window Tools installation
+
+
+ Start Qube for Window Tools installation
+
+
+
+
+ true
+
+
+ true
+
+
+ &IP
+
+
+
+
+ true
+
+
+ true
+
+
+ Include in &backups
+
+
+
+
+ true
+
+
+ true
+
+
+ Last back&up
+
+
+
+
+ Search
+
+
+ Ctrl+F
+
+
+
+
+
+ :/outdated.png:/outdated.png
+
+
+ Refresh Qube List
+
+
+ Refresh Qube List
+
+
+
+
+
+
+
+
diff --git a/ui/restoredlg.ui b/ui/restoredlg.ui
index cc37988..284b867 100644
--- a/ui/restoredlg.ui
+++ b/ui/restoredlg.ui
@@ -52,7 +52,7 @@
Ignore missing templates or netvms, restore VMs anyway.
- ignore missing
+ ignore missing templates and net VMs
@@ -147,6 +147,9 @@
+
+ true
+
-
@@ -211,8 +214,8 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
-</style></head><body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;">
-<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;"><br /></p></body></html>
+</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;">
+<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html>
@@ -241,6 +244,9 @@ p, li { white-space: pre-wrap; }
-
+
+ 0
+
0
diff --git a/ui/settingsdlg.ui b/ui/settingsdlg.ui
index 73257e5..8593eea 100644
--- a/ui/settingsdlg.ui
+++ b/ui/settingsdlg.ui
@@ -29,7 +29,7 @@
- 1
+ 2
@@ -170,7 +170,7 @@
- -
+
-
Qt::Vertical
@@ -373,6 +373,26 @@
+ -
+
+
+ background-color: qlineargradient(spread:pad, x1:1, y1:1, x2:1, y2:0, stop:0 rgba(255, 179, 179, 255), stop:1 rgba(255, 108, 108, 255));
+border-color: rgb(170, 0, 0);
+border-style: solid;
+border-width: 1px;
+
+
+ Delete VM
+
+
+
+ -
+
+
+ Clone VM
+
+
+
@@ -580,7 +600,7 @@
-
-
+
Boot qube from CDROM
@@ -769,47 +789,198 @@
-
-
-
- Allow network access except...
+
+
+
+
+
+
+
+ 255
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 255
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 139
+ 142
+ 142
+
+
+
+
+
-
-
- -
-
-
-
- 323
- 0
-
+
+
+ 75
+ true
+ true
+
- Allow ICMP traffic
+ This qube has no networking - it will not have any network access anyway.
-
+
true
-
-
+
+
+
+
+
+
+
+ 255
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 255
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 139
+ 142
+ 142
+
+
+
+
+
+
+
+
+ 75
+ true
+ true
+
+
- Deny network access except...
+ Networking qube does not support 'qubes-firewall' - firewall restrictions will not be applied.
- -
-
-
- Allow DNS queries
+
-
+
+
+
+
+
+
+
+ 255
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 255
+ 0
+ 0
+
+
+
+
+
+
+
+
+ 139
+ 142
+ 142
+
+
+
+
+
-
- true
+
+
+ 75
+ true
+ true
+
+
+
+ Firewall has been modified manually - please use qvm-firewall for any further configuration.
- -
-
+
-
+
+
-
+
+
+ Allow all outgoing Internet connections
+
+
+
+ -
+
+
+ Changing firewall settings does NOT affect existing connections.
+
+
+ Limit outgoing Internet connections to ...
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ List of allowed (whitelisted) addresses:
+
+
+
+ -
+
QLayout::SetMaximumSize
@@ -851,7 +1022,7 @@
-
-
-
+
@@ -868,7 +1039,7 @@
-
-
+
@@ -885,7 +1056,10 @@
-
-
+
+
+ Changing firewall settings does NOT affect existing connections.
+
@@ -918,14 +1092,7 @@
- -
-
-
- Allow connections to Updates Proxy
-
-
-
- -
+
-
true
@@ -940,15 +1107,15 @@
0
-
-
-
+
-
+
Allow full access for
- -
-
+
-
+
min
@@ -957,9 +1124,35 @@
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ NOTE: To block all network access, set Networking to (none) on the Basic settings tab. This tab provides a very simplified firewall configuration. All DNS requests and ICMP (pings) will be allowed. For more granular control, use the command line tool qvm-firewall.
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ true
+
+
+
@@ -1162,15 +1355,10 @@
vcpus
include_in_balancing
kernel
- policyAllowRadioButton
- policyDenyRadioButton
- icmpCheckBox
- dnsCheckBox
- yumproxyCheckBox
- newRuleButton
+ new_rule_button
rulesTreeView
- editRuleButton
- deleteRuleButton
+ edit_rule_button
+ delete_rule_button
service_line_edit
add_srv_button
services_list
diff --git a/version b/version
index d13e837..2d2d681 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-4.0.6
+4.0.10