Merge branch 'master' into virt-mode-select-fixes

This commit is contained in:
Marta Marczykowska-Górecka 2018-01-16 20:04:16 +01:00
commit 48c3bfa798
No known key found for this signature in database
GPG Key ID: 9A752C30B26FD04B
48 changed files with 4293 additions and 1892 deletions

View File

@ -1,7 +1,13 @@
sudo: required sudo: required
dist: trusty dist: trusty
language: generic language: python
install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder python:
script: ~/qubes-builder/scripts/travis-build - '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: 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

211
ci/pylintrc Normal file
View File

@ -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

11
ci/requirements.txt Normal file
View File

@ -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

View File

@ -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;

9
qubes-backup.desktop Normal file
View File

@ -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;

View File

@ -1,7 +1,6 @@
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application
Exec=qubes-global-settings Exec=qubes-global-settings
Path=/user/bin
Icon=qubes-manager Icon=qubes-manager
Terminal=false Terminal=false
Name=Qubes Global Settings Name=Qubes Global Settings

View File

@ -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;

View File

@ -1,4 +1,4 @@
#!/usr/bin/python2 #!/usr/bin/python3
# coding=utf-8 # coding=utf-8
# #
# The Qubes OS Project, http://www.qubes-os.org # The Qubes OS Project, http://www.qubes-os.org
@ -16,21 +16,19 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
from PyQt4.QtCore import SIGNAL, SLOT from PyQt4.QtCore import SIGNAL, SLOT # pylint: disable=import-error
from PyQt4.QtGui import QDialog, QIcon from PyQt4.QtGui import QDialog, QIcon # pylint: disable=import-error
from qubesmanager.releasenotes import ReleaseNotesDialog from qubesmanager.releasenotes import ReleaseNotesDialog
from qubesmanager.informationnotes import InformationNotesDialog from qubesmanager.informationnotes import InformationNotesDialog
from .ui_about import * from . import ui_about # pylint: disable=no-name-in-module
class AboutDialog(ui_about.Ui_AboutDialog, QDialog):
class AboutDialog(Ui_AboutDialog, QDialog):
def __init__(self): def __init__(self):
super(AboutDialog, self).__init__() super(AboutDialog, self).__init__()

View File

@ -14,28 +14,19 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
import os
import subprocess import subprocess
import sys
import time
from operator import itemgetter import PyQt4.QtGui # pylint: disable=import-error
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import qubesmanager.resources_rc
# TODO description in tooltip # TODO description in tooltip
# TODO icon # TODO icon
class AppListWidgetItem(QListWidgetItem): # pylint: disable=too-few-public-methods
class AppListWidgetItem(PyQt4.QtGui.QListWidgetItem):
def __init__(self, name, ident, parent=None): def __init__(self, name, ident, parent=None):
super(AppListWidgetItem, self).__init__(name, parent) super(AppListWidgetItem, self).__init__(name, parent)
# self.setToolTip(command) # self.setToolTip(command)
@ -43,12 +34,12 @@ class AppListWidgetItem(QListWidgetItem):
@classmethod @classmethod
def from_line(cls, line): 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) return cls(name=name, ident=ident)
class AppmenuSelectManager: class AppmenuSelectManager:
def __init__(self, vm, apps_multiselect, parent=None): def __init__(self, vm, apps_multiselect):
self.vm = vm self.vm = vm
self.app_list = apps_multiselect # this is a multiselect wiget self.app_list = apps_multiselect # this is a multiselect wiget
self.whitelisted = None self.whitelisted = None
@ -60,7 +51,9 @@ class AppmenuSelectManager:
).decode().strip().split('\n') if line] ).decode().strip().split('\n') if line]
# Check if appmenu entry is really installed # 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() self.app_list.clear()
@ -69,11 +62,11 @@ class AppmenuSelectManager:
'--get-available', '--i-understand-format-is-unstable', '--get-available', '--i-understand-format-is-unstable',
self.vm.name]).decode().splitlines()] self.vm.name]).decode().splitlines()]
for a in available_appmenus: for app in available_appmenus:
if a.ident in self.whitelisted: if app.ident in self.whitelisted:
self.app_list.selected_list.addItem(a) self.app_list.selected_list.addItem(app)
else: else:
self.app_list.available_list.addItem(a) self.app_list.available_list.addItem(app)
self.app_list.available_list.sortItems() self.app_list.available_list.sortItems()
self.app_list.selected_list.sortItems() self.app_list.selected_list.sortItems()

View File

@ -1,4 +1,4 @@
#!/usr/bin/python2 #!/usr/bin/python3
# #
# The Qubes OS Project, http://www.qubes-os.org # The Qubes OS Project, http://www.qubes-os.org
# #
@ -15,385 +15,324 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
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 sys
import os import os
import signal from . import thread_monitor
import shutil import threading
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
import time 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, qt_app, qubes_app, parent=None):
def __init__(self, app, qvm_collection, blk_manager, shutdown_vm_func, parent=None):
super(BackupVMsWindow, self).__init__(parent) super(BackupVMsWindow, self).__init__(parent)
self.app = app self.qt_app = qt_app
self.qvm_collection = qvm_collection self.qubes_app = qubes_app
self.blk_manager = blk_manager self.backup_settings = QtCore.QSettings()
self.shutdown_vm_func = shutdown_vm_func
self.func_output = []
self.selected_vms = [] self.selected_vms = []
self.tmpdir_to_remove = None
self.canceled = False self.canceled = False
self.thread_monitor = None
self.vm = self.qvm_collection[0]
self.files_to_backup = None
assert self.vm != None
self.setupUi(self) self.setupUi(self)
self.progress_status.text = self.tr("Backup in progress...") self.progress_status.text = self.tr("Backup in progress...")
self.show_running_vms_warning(False)
self.dir_line_edit.setReadOnly(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.verticalLayout.insertWidget(1, self.select_vms_widget)
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed) self.connect(self, QtCore.SIGNAL("currentIdChanged(int)"),
self.connect(self.select_vms_widget, SIGNAL("selected_changed()"), self.check_running) self.current_page_changed)
self.connect(self.select_vms_widget, SIGNAL("items_removed(PyQt_PyObject)"), self.vms_removed) self.connect(self.select_vms_widget,
self.connect(self.select_vms_widget, SIGNAL("items_added(PyQt_PyObject)"), self.vms_added) QtCore.SIGNAL("items_removed(PyQt_PyObject)"),
self.refresh_button.clicked.connect(self.check_running) self.vms_removed)
self.shutdown_running_vms_button.clicked.connect(self.shutdown_all_running_selected) self.connect(self.select_vms_widget,
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue) QtCore.SIGNAL("items_added(PyQt_PyObject)"),
self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed) 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_vms_page.isComplete = self.has_selected_vms
self.select_dir_page.isComplete = self.has_selected_dir_and_pass self.select_dir_page.isComplete = self.has_selected_dir_and_pass
#FIXME # FIXME
#this causes to run isComplete() twice, I don't know why # this causes to run isComplete() twice, I don't know why
self.select_vms_page.connect( self.select_vms_page.connect(
self.select_vms_widget, self.select_vms_widget,
SIGNAL("selected_changed()"), QtCore.SIGNAL("selected_changed()"),
SIGNAL("completeChanged()")) QtCore.SIGNAL("completeChanged()"))
self.passphrase_line_edit.connect( self.passphrase_line_edit.connect(
self.passphrase_line_edit, self.passphrase_line_edit,
SIGNAL("textChanged(QString)"), QtCore.SIGNAL("textChanged(QString)"),
self.backup_location_changed) self.backup_location_changed)
self.passphrase_line_edit_verify.connect( self.passphrase_line_edit_verify.connect(
self.passphrase_line_edit_verify, self.passphrase_line_edit_verify,
SIGNAL("textChanged(QString)"), QtCore.SIGNAL("textChanged(QString)"),
self.backup_location_changed) self.backup_location_changed)
self.total_size = 0 self.total_size = 0
self.__fill_vms_list__()
fill_appvms_list(self) self.target_vm_list, self.target_vm_idx = utils.prepare_vm_choice(
self.load_settings() 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): def load_settings(self):
dest_vm_name = main.manager_window.manager_settings.value( """
'backup/vmname', defaultValue="") Helper function that tries to load existing backup profile
dest_vm_idx = self.appvm_combobox.findText(dest_vm_name.toString()) (default path: /etc/qubes/backup/qubes-manager-backup.conf )
if dest_vm_idx > -1: and then apply its contents to the Backup window.
self.appvm_combobox.setCurrentIndex(dest_vm_idx) :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'): if 'destination_vm' in profile_data:
dest_path = main.manager_window.manager_settings.value( dest_vm_name = profile_data['destination_vm']
'backup/path', defaultValue=None) dest_vm_idx = self.appvm_combobox.findText(dest_vm_name)
self.dir_line_edit.setText(dest_path.toString()) if dest_vm_idx > -1:
self.appvm_combobox.setCurrentIndex(dest_vm_idx)
if main.manager_window.manager_settings.contains('backup/encrypt'): if 'destination_path' in profile_data:
encrypt = main.manager_window.manager_settings.value( dest_path = profile_data['destination_path']
'backup/encrypt', defaultValue=None) self.dir_line_edit.setText(dest_path)
self.encryption_checkbox.setChecked(encrypt.toBool())
def save_settings(self): if 'passphrase_text' in profile_data:
main.manager_window.manager_settings.setValue( self.passphrase_line_edit.setText(profile_data['passphrase_text'])
'backup/vmname', self.appvm_combobox.currentText()) self.passphrase_line_edit_verify.setText(
main.manager_window.manager_settings.setValue( profile_data['passphrase_text'])
'backup/path', self.dir_line_edit.text())
main.manager_window.manager_settings.setValue(
'backup/encrypt', self.encryption_checkbox.isChecked())
def show_running_vms_warning(self, show): if 'compression' in profile_data:
self.running_vms_warning.setVisible(show) self.compress_checkbox.setChecked(profile_data['compression'])
self.shutdown_running_vms_button.setVisible(show)
self.refresh_button.setVisible(show)
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): def __init__(self, vm):
self.vm = vm self.vm = vm
if vm.qid == 0: if vm.qid == 0:
local_user = grp.getgrnam('qubes').gr_mem[0] local_user = grp.getgrnam('qubes').gr_mem[0]
home_dir = pwd.getpwnam(local_user).pw_dir home_dir = pwd.getpwnam(local_user).pw_dir
self.size = qubesutils.get_disk_usage(home_dir) self.size = get_disk_usage(home_dir)
else: else:
self.size = self.get_vm_size(vm) self.size = vm.get_disk_utilization()
super(BackupVMsWindow.VmListItem, self).__init__(vm.name+ " (" + qubesutils.size_to_human(self.size) + ")") super(BackupVMsWindow.VmListItem, self).__init__(
vm.name + " (" + admin_utils.size_to_human(self.size) + ")")
def get_vm_size(self, vm): def __fill_vms_list__(self, selected=None):
size = 0 for vm in self.qubes_app.domains:
if vm.private_img is not None: if vm.features.get('internal', False):
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:
continue continue
item = BackupVMsWindow.VmListItem(vm) 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.select_vms_widget.selected_list.addItem(item)
self.total_size += item.size self.total_size += item.size
else: else:
self.select_vms_widget.available_list.addItem(item) self.select_vms_widget.available_list.addItem(item)
self.select_vms_widget.available_list.sortItems() self.select_vms_widget.available_list.sortItems()
self.select_vms_widget.selected_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): def vms_added(self, items):
for i in items: for i in items:
self.total_size += i.size 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): def vms_removed(self, items):
for i in items: for i in items:
self.total_size -= i.size 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): @QtCore.pyqtSlot(name='on_select_path_button_clicked')
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: "
"<b>{0}</b>?<br/>"
"<small>This will shutdown all the running applications "
"within them.</small>").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 <b>{0}</b>...".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')
def select_path_button_clicked(self): def select_path_button_clicked(self):
select_path_button_clicked(self) backup_utils.select_path_button_clicked(self)
def validateCurrentPage(self): def validateCurrentPage(self):
# pylint: disable=invalid-name
if self.currentPage() is self.select_vms_page: 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 = [] self.selected_vms = []
for i in range(self.select_vms_widget.selected_list.count()): 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: elif self.currentPage() is self.select_dir_page:
backup_location = str(self.dir_line_edit.text()) backup_location = str(self.dir_line_edit.text())
if not backup_location: if not backup_location:
QMessageBox.information(None, self.tr("Wait!"), QtGui.QMessageBox.information(
None, self.tr("Wait!"),
self.tr("Enter backup target location first.")) self.tr("Enter backup target location first."))
return False return False
if self.appvm_combobox.currentIndex() == 0 and \ if self.appvm_combobox.currentIndex() == 0 \
not os.path.isdir(backup_location): and not os.path.isdir(backup_location):
QMessageBox.information(None, self.tr("Wait!"), QtGui.QMessageBox.information(
None, self.tr("Wait!"),
self.tr("Selected directory do not exists or " self.tr("Selected directory do not exists or "
"not a directory (%s).") % backup_location) "not a directory (%s).") % backup_location)
return False return False
if not len(self.passphrase_line_edit.text()): if not self.passphrase_line_edit.text():
QMessageBox.information(None, self.tr("Wait!"), QtGui.QMessageBox.information(
self.tr("Enter passphrase for backup encryption/verification first.")) None, self.tr("Wait!"),
self.tr("Enter passphrase for backup "
"encryption/verification first."))
return False return False
if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text(): if self.passphrase_line_edit.text() !=\
QMessageBox.information(None, self.passphrase_line_edit_verify.text():
self.tr("Wait!"), QtGui.QMessageBox.information(
None, self.tr("Wait!"),
self.tr("Enter the same passphrase in both fields.")) self.tr("Enter the same passphrase in both fields."))
return False return False
return True return True
def gather_output(self, s): def __do_backup__(self, t_monitor):
self.func_output.append(s)
def update_progress_bar(self, value):
self.emit(SIGNAL("backup_progress(int)"), value)
def __do_backup__(self, thread_monitor):
msg = [] msg = []
try: try:
backup.backup_do(self.dir_line_edit.text(), vm = self.qubes_app.domains[
self.files_to_backup, self.appvm_combobox.currentText()]
self.passphrase_line_edit.text(), if not vm.is_running():
progress_callback=self.update_progress_bar, vm.start()
encrypted=self.encryption_checkbox.isChecked(), self.qubes_app.qubesd_call(
appvm=self.target_appvm) 'dom0', 'admin.backup.Execute',
#simulate_long_lasting_proces(10, self.update_progress_bar) backup_utils.get_profile_name(True))
except backup.BackupCanceledError as ex: except Exception as ex: # pylint: disable=broad-except
msg.append(str(ex))
self.canceled = True
if ex.tmpdir:
self.tmpdir_to_remove = ex.tmpdir
except Exception as ex:
print("Exception:", ex)
msg.append(str(ex)) msg.append(str(ex))
if len(msg) > 0 : if msg:
thread_monitor.set_error_msg('\n'.join(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) old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if self.currentPage() is self.confirm_page: if self.currentPage() is self.confirm_page:
self.target_appvm = None self.save_settings(use_temp=True)
if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen backup_summary = self.qubes_app.qubesd_call(
self.target_appvm = self.qvm_collection.get_vm_by_name( 'dom0', 'admin.backup.Info',
self.appvm_combobox.currentText()) backup_utils.get_profile_name(True))
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.textEdit.setReadOnly(True) self.textEdit.setReadOnly(True)
self.textEdit.setFontFamily("Monospace") self.textEdit.setFontFamily("Monospace")
self.textEdit.setText("\n".join(self.func_output)) self.textEdit.setText(backup_summary.decode())
self.save_settings()
elif self.currentPage() is self.commit_page: 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.button(self.FinishButton).setDisabled(True)
self.showFileDialog.setEnabled( self.showFileDialog.setEnabled(
self.appvm_combobox.currentIndex() != 0) self.appvm_combobox.currentIndex() != 0)
self.showFileDialog.setChecked(self.showFileDialog.isEnabled() self.showFileDialog.setChecked(self.showFileDialog.isEnabled()
and str(self.dir_line_edit.text()) and str(self.dir_line_edit.text())
.count("media/") > 0) .count("media/") > 0)
self.thread_monitor = ThreadMonitor() self.thread_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,)) thread = threading.Thread(
target=self.__do_backup__,
args=(self.thread_monitor,))
thread.daemon = True thread.daemon = True
thread.start() thread.start()
counter = 0
while not self.thread_monitor.is_finished(): while not self.thread_monitor.is_finished():
self.app.processEvents() self.qt_app.processEvents()
time.sleep (0.1) time.sleep(0.1)
if not self.thread_monitor.success: if not self.thread_monitor.success:
if self.canceled: if self.canceled:
self.progress_status.setText(self.tr("Backup aborted.")) self.progress_status.setText(
if self.tmpdir_to_remove: self.tr(
if QMessageBox.warning(None, self.tr("Backup aborted"), "Backup aborted. "
self.tr("Do you want to remove temporary files from " "Temporary file may be left at backup location."))
"%s?") % self.tmpdir_to_remove,
QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes:
shutil.rmtree(self.tmpdir_to_remove)
else: else:
self.progress_status.setText(self.tr("Backup error.")) 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.tr("ERROR: {}").format(
self.thread_monitor.error_msg)) self.thread_monitor.error_msg))
else: else:
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(100) self.progress_bar.setValue(100)
self.progress_status.setText(self.tr("Backup finished.")) self.progress_status.setText(self.tr("Backup finished."))
if self.showFileDialog.isChecked(): if self.showFileDialog.isChecked():
@ -402,84 +341,78 @@ class BackupVMsWindow(Ui_Backup, QWizard):
orig_text + self.tr( orig_text + self.tr(
" Please unmount your backup volume and cancel " " Please unmount your backup volume and cancel "
"the file selection dialog.")) "the file selection dialog."))
if self.target_appvm: backup_utils.select_path_button_clicked(self, False, True)
self.target_appvm.run("QUBESRPC %s dom0" % "qubes"
".SelectDirectory")
self.button(self.CancelButton).setEnabled(False) self.button(self.CancelButton).setEnabled(False)
self.button(self.FinishButton).setEnabled(True) self.button(self.FinishButton).setEnabled(True)
self.showFileDialog.setEnabled(False) self.showFileDialog.setEnabled(False)
self.cleanup_temporary_files()
signal.signal(signal.SIGCHLD, old_sigchld_handler) signal.signal(signal.SIGCHLD, old_sigchld_handler)
def reject(self): def reject(self):
#cancell clicked while the backup is in progress.
#calling kill on tar.
if self.currentPage() is self.commit_page: if self.currentPage() is self.commit_page:
if backup.backup_cancel(): self.canceled = True
self.button(self.CancelButton).setDisabled(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: else:
self.cleanup_temporary_files()
self.done(0) self.done(0)
def has_selected_vms(self): def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0 return self.select_vms_widget.selected_list.count() > 0
def has_selected_dir_and_pass(self): def has_selected_dir_and_pass(self):
if not len(self.passphrase_line_edit.text()): if not self.passphrase_line_edit.text():
return False 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 False
return len(self.dir_line_edit.text()) > 0 return len(self.dir_line_edit.text()) > 0
def backup_location_changed(self, new_dir = None): def backup_location_changed(self, new_dir=None):
self.select_dir_page.emit(SIGNAL("completeChanged()")) # pylint: disable=unused-argument
self.select_dir_page.emit(QtCore.SIGNAL("completeChanged()"))
# Bases on the original code by: # Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com> # Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
def handle_exception(exc_type, exc_value, exc_traceback ): def handle_exception(exc_type, exc_value, exc_traceback):
import sys filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
import os.path filename = os.path.basename(filename)
import traceback error = "%s: %s" % (exc_type.__name__, exc_value)
filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop() QtGui.QMessageBox.critical(
filename = os.path.basename( filename ) None,
error = "%s: %s" % ( exc_type.__name__, exc_value ) "Houston, we have a problem...",
"Whoops. A critical error has occured. This is most likely a bug "
QMessageBox.critical(None, "Houston, we have a problem...", "in Qubes Global Settings application.<br><br><b><i>%s</i></b>" %
"Whoops. A critical error has occured. This is most likely a bug " error + "at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
"in Qubes Restore VMs application.<br><br>" % (line, filename))
"<b><i>%s</i></b>" % error +
"at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
% ( line, filename ))
def app_main(): def main():
global qubes_host qt_app = QtGui.QApplication(sys.argv)
qubes_host = QubesHost() qt_app.setOrganizationName("The Qubes Project")
qt_app.setOrganizationDomain("http://qubes-os.org")
global app qt_app.setApplicationName("Qubes Backup VMs")
app = QApplication(sys.argv)
app.setOrganizationName("The Qubes Project")
app.setOrganizationDomain("http://qubes-os.org")
app.setApplicationName("Qubes Backup VMs")
sys.excepthook = handle_exception sys.excepthook = handle_exception
qvm_collection = QubesVmCollection() app = Qubes()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
global backup_window backup_window = BackupVMsWindow(qt_app, app)
backup_window = BackupVMsWindow()
backup_window.show() backup_window.show()
app.exec_() qt_app.exec_()
app.exit() qt_app.exit()
if __name__ == "__main__": if __name__ == "__main__":
app_main() main()

View File

@ -14,96 +14,117 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
import re import re
import sys from PyQt4 import QtGui # pylint: disable=import-error
import os from PyQt4 import QtCore # pylint: disable=import-error
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import subprocess import subprocess
import time from . import utils
import yaml
from .thread_monitor import *
path_re = re.compile(r"[a-zA-Z0-9/:.,_+=() -]*") path_re = re.compile(r"[a-zA-Z0-9/:.,_+=() -]*")
path_max_len = 512 path_max_len = 512
def fill_appvms_list(dialog): 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.clear()
dialog.appvm_combobox.addItem("dom0") 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(): for vm in dialog.qubes_app.domains:
if vm.is_appvm() and vm.internal: if vm.features.get('internal', False) or vm.klass == 'TemplateVM':
continue
if vm.is_template() and vm.installed_by_rpm:
continue continue
if vm.is_running() and vm.qid != 0: if vm.is_running() and vm.qid != 0:
dialog.appvm_combobox.addItem(vm.name) dialog.appvm_combobox.addItem(vm.name)
def enable_dir_line_edit(dialog, boolean): def enable_dir_line_edit(dialog, boolean):
dialog.dir_line_edit.setEnabled(boolean) dialog.dir_line_edit.setEnabled(boolean)
dialog.select_path_button.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()) backup_location = str(dialog.dir_line_edit.text())
file_dialog = QFileDialog() file_dialog = QtGui.QFileDialog()
file_dialog.setReadOnly(True) 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 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: new_appvm = str(dialog.appvm_combobox.currentText())
if os.path.basename(new_path) == 'qubes.xml': vm = dialog.qubes_app.domains[new_appvm]
backup_location = os.path.dirname(new_path) 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: else:
backup_location = new_path return
dialog.dir_line_edit.setText(backup_location)
if (new_path or new_appvm) and len(backup_location) > 0: if new_path and not read_only:
dialog.select_dir_page.emit(SIGNAL("completeChanged()")) dialog.dir_line_edit.setText(new_path)
def simulate_long_lasting_proces(period, progress_callback): if new_path and backup_location and not read_only:
for i in range(period): dialog.select_dir_page.emit(QtCore.SIGNAL("completeChanged()"))
progress_callback((i*100)/period)
time.sleep(1)
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)

View File

@ -1,5 +1,6 @@
#!/usr/bin/python2 #!/usr/bin/python2
# -*- coding: utf8 -*- # -*- coding: utf8 -*-
# pylint: skip-file
# #
# The Qubes OS Project, http://www.qubes-os.org # The Qubes OS Project, http://www.qubes-os.org
# #
@ -15,9 +16,8 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import threading import threading
import time import time

View File

@ -12,20 +12,21 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
import sys
import subprocess import subprocess
from . import utils from . import utils
from .firewall import * from . import ui_bootfromdevice # pylint: disable=no-name-in-module
from .ui_bootfromdevice import * from PyQt4 import QtGui, QtCore # pylint: disable=import-error
import qubesadmin.tools.qvm_start as qvm_start 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): def __init__(self, vm, qapp, parent=None):
super(VMBootFromDeviceWindow, self).__init__(parent) super(VMBootFromDeviceWindow, self).__init__(parent)
@ -33,14 +34,19 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
self.qapp = qapp self.qapp = qapp
self.setupUi(self) 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.connect(self.buttonBox, SIGNAL("rejected()"), self.reject) self.buttonBox,
QtCore.SIGNAL("accepted()"),
self.save_and_apply)
self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject)
# populate buttons and such # populate buttons and such
self.__init_buttons__() self.__init_buttons__()
# warn user if the VM is currently running
self.__warn_if_running__()
def reject(self): def reject(self):
self.done(0) self.done(0)
@ -49,15 +55,30 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
if self.blockDeviceRadioButton.isChecked(): if self.blockDeviceRadioButton.isChecked():
cdrom_location = self.blockDeviceComboBox.currentText() cdrom_location = self.blockDeviceComboBox.currentText()
elif self.fileRadioButton.isChecked(): 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: else:
QMessageBox.warning(None, QtGui.QMessageBox.warning(
self.tr( None,
"ERROR!"), self.tr("ERROR!"),
self.tr("No file or block device selected; please select one.")) self.tr("No file or block device selected; please select one."))
return return
# warn user if the VM is currently running
self.__warn_if_running__()
qvm_start.main(['--cdrom', cdrom_location, self.vm.name]) 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): def __init_buttons__(self):
self.fileVM.setEnabled(False) self.fileVM.setEnabled(False)
self.selectFileButton.setEnabled(False) self.selectFileButton.setEnabled(False)
@ -86,7 +107,8 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
) )
def radio_button_clicked(self): def radio_button_clicked(self):
self.blockDeviceComboBox.setEnabled(self.blockDeviceRadioButton.isChecked()) self.blockDeviceComboBox.setEnabled(
self.blockDeviceRadioButton.isChecked())
self.fileVM.setEnabled(self.fileRadioButton.isChecked()) self.fileVM.setEnabled(self.fileRadioButton.isChecked())
self.selectFileButton.setEnabled(self.fileRadioButton.isChecked()) self.selectFileButton.setEnabled(self.fileRadioButton.isChecked())
self.pathText.setEnabled(self.fileRadioButton.isChecked()) self.pathText.setEnabled(self.fileRadioButton.isChecked())
@ -103,18 +125,17 @@ class VMBootFromDeviceWindow(Ui_BootDialog, QDialog):
self.pathText.setText(new_path) self.pathText.setText(new_path)
parser = qubesadmin.tools.QubesArgumentParser(vmname_nargs=1) parser = tools.QubesArgumentParser(vmname_nargs=1)
def main(args=None): def main(args=None):
global bootfromdevice_window
args = parser.parse_args(args) args = parser.parse_args(args)
vm = args.domains.pop() vm = args.domains.pop()
qapp = QApplication(sys.argv) qapp = QtGui.QApplication(sys.argv)
qapp.setOrganizationName('Invisible Things Lab') qapp.setOrganizationName('Invisible Things Lab')
qapp.setOrganizationDomain("https://www.qubes-os.org/") qapp.setOrganizationDomain("https://www.qubes-os.org/")
qapp.setApplicationName("Qubes VM Settings") qapp.setApplicationName("Boot Qube From Device")
# if not utils.is_debug(): #FIXME # if not utils.is_debug(): #FIXME
# sys.excepthook = handle_exception # sys.excepthook = handle_exception

View File

@ -1,4 +1,5 @@
#!/usr/bin/python2 #!/usr/bin/python2
# pylint: skip-file
# #
# The Qubes OS Project, http://www.qubes-os.org # The Qubes OS Project, http://www.qubes-os.org
# #
@ -16,9 +17,8 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #

View File

@ -16,34 +16,30 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
import os
import sys import sys
import threading import threading
import time import time
import subprocess import subprocess
from PyQt4.QtCore import * from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from PyQt4.QtGui import *
import qubesadmin import qubesadmin
import qubesadmin.tools import qubesadmin.tools
import qubesadmin.exc
import qubesmanager.resources_rc
from . import utils from . import utils
from .ui_newappvmdlg import Ui_NewVMDlg from .ui_newappvmdlg import Ui_NewVMDlg # pylint: disable=import-error
from .thread_monitor import ThreadMonitor from .thread_monitor import ThreadMonitor
class NewVmDlg(QDialog, Ui_NewVMDlg): class NewVmDlg(QtGui.QDialog, Ui_NewVMDlg):
def __init__(self, qtapp, app, parent = None): def __init__(self, qtapp, app, parent=None):
super(NewVmDlg, self).__init__(parent) super(NewVmDlg, self).__init__(parent)
self.setupUi(self) self.setupUi(self)
@ -51,7 +47,8 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
self.app = app self.app = app
# Theoretically we should be locking for writing here and unlock # 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 # Instead we lock for writing in the actual worker thread
self.label_list, self.label_idx = utils.prepare_label_choice( self.label_list, self.label_idx = utils.prepare_label_choice(
self.label, self.label,
@ -73,13 +70,13 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
(lambda vm: vm.provides_network), (lambda vm: vm.provides_network),
allow_internal=False, allow_default=True, allow_none=True) allow_internal=False, allow_default=True, allow_none=True)
self.name.setValidator(QRegExpValidator( self.name.setValidator(QtGui.QRegExpValidator(
QRegExp("[a-zA-Z0-9-]*", Qt.CaseInsensitive), None)) QtCore.QRegExp("[a-zA-Z0-9-]*", QtCore.Qt.CaseInsensitive), None))
self.name.selectAll() self.name.selectAll()
self.name.setFocus() self.name.setFocus()
if len(self.template_list) == 0: if not self.template_list:
QMessageBox.warning(None, QtGui.QMessageBox.warning(None,
self.tr('No template available!'), self.tr('No template available!'),
self.tr('Cannot create a qube when no template exists.')) self.tr('Cannot create a qube when no template exists.'))
@ -99,7 +96,8 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
self.done(0) self.done(0)
def accept(self): 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()) name = str(self.name.text())
try: try:
@ -107,7 +105,7 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
except LookupError: except LookupError:
pass pass
else: else:
QMessageBox.warning(None, QtGui.QMessageBox.warning(None,
self.tr('Incorrect qube name!'), self.tr('Incorrect qube name!'),
self.tr('A qube with the name <b>{}</b> already exists in the ' self.tr('A qube with the name <b>{}</b> already exists in the '
'system!').format(name)) 'system!').format(name))
@ -132,7 +130,7 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
thread.daemon = True thread.daemon = True
thread.start() thread.start()
progress = QProgressDialog( progress = QtGui.QProgressDialog(
self.tr("Creating new qube <b>{}</b>...").format(name), "", 0, 0) self.tr("Creating new qube <b>{}</b>...").format(name), "", 0, 0)
progress.setCancelButton(None) progress.setCancelButton(None)
progress.setModal(True) progress.setModal(True)
@ -140,12 +138,12 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
while not thread_monitor.is_finished(): while not thread_monitor.is_finished():
self.qtapp.processEvents() self.qtapp.processEvents()
time.sleep (0.1) time.sleep(0.1)
progress.hide() progress.hide()
if not thread_monitor.success: if not thread_monitor.success:
QMessageBox.warning(None, QtGui.QMessageBox.warning(None,
self.tr("Error creating the qube!"), self.tr("Error creating the qube!"),
self.tr("ERROR: {}").format(thread_monitor.error_msg)) self.tr("ERROR: {}").format(thread_monitor.error_msg))
@ -177,8 +175,10 @@ class NewVmDlg(QDialog, Ui_NewVMDlg):
for k, v in properties.items(): for k, v in properties.items():
setattr(vm, k, v) setattr(vm, k, v)
except Exception as ex: except qubesadmin.exc.QubesException as qex:
thread_monitor.set_error_msg(str(ex)) 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() thread_monitor.set_finished()
@ -218,7 +218,7 @@ parser = qubesadmin.tools.QubesArgumentParser()
def main(args=None): def main(args=None):
args = parser.parse_args(args) args = parser.parse_args(args)
qtapp = QApplication(sys.argv) qtapp = QtGui.QApplication(sys.argv)
qtapp.setOrganizationName('Invisible Things Lab') qtapp.setOrganizationName('Invisible Things Lab')
qtapp.setOrganizationDomain('https://www.qubes-os.org/') qtapp.setOrganizationDomain('https://www.qubes-os.org/')
qtapp.setApplicationName('Create qube') qtapp.setApplicationName('Create qube')

View File

@ -13,91 +13,90 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
import datetime import datetime
import ipaddress
import os
import re 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 import qubesadmin.firewall
from . import ui_newfwruledlg from . import ui_newfwruledlg # pylint: disable=no-name-in-module
class FirewallModifiedOutsideError(ValueError): class FirewallModifiedOutsideError(ValueError):
pass pass
class QIPAddressValidator(QValidator): class QIPAddressValidator(QtGui.QValidator):
def __init__(self, parent = None): # pylint: disable=too-few-public-methods
super (QIPAddressValidator, self).__init__(parent) def __init__(self, parent=None):
super(QIPAddressValidator, self).__init__(parent)
def validate(self, input, pos): def validate(self, input_string, pos):
hostname = str(input) # pylint: disable=too-many-return-statements,no-self-use
hostname = str(input_string)
if len(hostname) > 255 or len(hostname) == 0: if len(hostname) > 255 or not hostname:
return (QValidator.Intermediate, input, pos) return (QtGui.QValidator.Intermediate, input_string, pos)
if hostname == "*": if hostname == "*":
return (QValidator.Acceptable, input, pos) return (QtGui.QValidator.Acceptable, input_string, pos)
unmask = hostname.split("/", 1) unmask = hostname.split("/", 1)
if len(unmask) == 2: if len(unmask) == 2:
hostname = unmask[0] hostname = unmask[0]
mask = unmask[1] mask = unmask[1]
if mask.isdigit() or mask == "": if mask.isdigit() or mask == "":
if re.match("^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None: if re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None:
return (QValidator.Invalid, input, pos) return (QtGui.QValidator.Invalid, input_string, pos)
if mask != "": if mask != "":
mask = int(unmask[1]) mask = int(unmask[1])
if mask < 0 or mask > 32: if mask < 0 or mask > 32:
return (QValidator.Invalid, input, pos) return (QtGui.QValidator.Invalid, input_string, pos)
else: else:
return (QValidator.Invalid, input, pos) return (QtGui.QValidator.Invalid, input_string, pos)
if hostname[-1:] == ".": if hostname[-1:] == ".":
hostname = hostname[:-1] hostname = hostname[:-1]
if 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}(?<!-)$", re.IGNORECASE) allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
if all(allowed.match(x) for x in hostname.split(".")): if all(allowed.match(x) for x in hostname.split(".")):
return (QValidator.Acceptable, input, pos) return (QtGui.QValidator.Acceptable, input_string, pos)
return (QValidator.Invalid, input, pos) return (QtGui.QValidator.Invalid, input_string, pos)
class NewFwRuleDlg (QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): class NewFwRuleDlg(QtGui.QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg):
def __init__(self, parent = None): def __init__(self, parent=None):
super (NewFwRuleDlg, self).__init__(parent) super(NewFwRuleDlg, self).__init__(parent)
self.setupUi(self) self.setupUi(self)
self.set_ok_enabled(False) self.set_ok_state(False)
self.addressComboBox.setValidator(QIPAddressValidator()) self.addressComboBox.setValidator(QIPAddressValidator())
self.addressComboBox.editTextChanged.connect(self.address_editing_finished) self.addressComboBox.editTextChanged.connect(
self.serviceComboBox.setValidator(QRegExpValidator(QRegExp("[a-z][a-z0-9-]+|[0-9]+(-[0-9]+)?", Qt.CaseInsensitive), None)) self.address_editing_finished)
self.serviceComboBox.setValidator(QtGui.QRegExpValidator(
QtCore.QRegExp("[a-z][a-z0-9-]+|[0-9]+(-[0-9]+)?",
QtCore.Qt.CaseInsensitive), None))
self.serviceComboBox.setEnabled(False) self.serviceComboBox.setEnabled(False)
self.serviceComboBox.setInsertPolicy(QComboBox.InsertAtBottom) self.serviceComboBox.setInsertPolicy(QtGui.QComboBox.InsertAtBottom)
self.populate_combos() self.populate_combos()
self.serviceComboBox.setInsertPolicy(QComboBox.InsertAtTop) self.serviceComboBox.setInsertPolicy(QtGui.QComboBox.InsertAtTop)
def accept(self): def accept(self):
if self.tcp_radio.isChecked() or self.udp_radio.isChecked(): if self.tcp_radio.isChecked() or self.udp_radio.isChecked():
if len(self.serviceComboBox.currentText()) == 0: if not self.serviceComboBox.currentText():
msg = QMessageBox() msg = QtGui.QMessageBox()
msg.warning(self, self.tr("Firewall rule"), msg.warning(self, self.tr("Firewall rule"),
self.tr("You need to fill service name/port for TCP/UDP rule")) self.tr("You need to fill service "
"name/port for TCP/UDP rule"))
return return
QDialog.accept(self) QtGui.QDialog.accept(self)
def populate_combos(self): def populate_combos(self):
example_addresses = [ example_addresses = [
@ -108,8 +107,8 @@ class NewFwRuleDlg (QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg):
displayed_services = [ displayed_services = [
'', '',
'http', 'https', 'ftp', 'ftps', 'smtp', 'http', 'https', 'ftp', 'ftps', 'smtp',
'smtps', 'pop3', 'pop3s', 'imap', 'imaps', 'odmr', 'smtps', 'pop3', 'pop3s', 'imap', 'imaps', 'odmr',
'nntp', 'nntps', 'ssh', 'telnet', 'telnets', 'ntp', 'nntp', 'nntps', 'ssh', 'telnet', 'telnets', 'ntp',
'snmp', 'ldap', 'ldaps', 'irc', 'ircs', 'xmpp-client', 'snmp', 'ldap', 'ldaps', 'irc', 'ircs', 'xmpp-client',
'syslog', 'printer', 'nfs', 'x11', 'syslog', 'printer', 'nfs', 'x11',
'1024-1234' '1024-1234'
@ -120,12 +119,12 @@ class NewFwRuleDlg (QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg):
self.serviceComboBox.addItem(service) self.serviceComboBox.addItem(service)
def address_editing_finished(self): def address_editing_finished(self):
self.set_ok_enabled(True) self.set_ok_state(True)
def set_ok_enabled(self, on): def set_ok_state(self, ok_state):
ok_button = self.buttonBox.button(QDialogButtonBox.Ok) ok_button = self.buttonBox.button(QtGui.QDialogButtonBox.Ok)
if ok_button is not None: if ok_button is not None:
ok_button.setEnabled(on) ok_button.setEnabled(ok_state)
def on_tcp_radio_toggled(self, checked): def on_tcp_radio_toggled(self, checked):
if checked: if checked:
@ -139,55 +138,43 @@ class NewFwRuleDlg (QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg):
if checked: if checked:
self.serviceComboBox.setEnabled(False) self.serviceComboBox.setEnabled(False)
class QubesFirewallRulesModel(QAbstractItemModel): class QubesFirewallRulesModel(QtCore.QAbstractItemModel):
def __init__(self, parent=None): def __init__(self, parent=None):
QAbstractItemModel.__init__(self, parent) QtCore.QAbstractItemModel.__init__(self, parent)
self.__columnValues = { self.__column_names = {0: "Address", 1: "Service", 2: "Protocol", }
0: lambda x: "*" if self.children[x]["address"] == "0.0.0.0" and
self.children[x]["netmask"] == 0 else
self.children[x]["address"] + ("" if self.children[x][ "netmask"] == 32 else
" /{0}".format(self.children[x][
"netmask"])),
1: lambda x: "any" if self.children[x]["portBegin"] == 0 else
"{0}-{1}".format(self.children[x]["portBegin"], self.children[x][
"portEnd"]) if self.children[x]["portEnd"] is not None else \
self.get_service_name(self.children[x]["portBegin"]),
2: lambda x: self.children[x]["proto"], }
self.__columnNames = {0: "Address", 1: "Service", 2: "Protocol", }
self.__services = list() self.__services = list()
pattern = re.compile("(?P<name>[a-z][a-z0-9-]+)\s+(?P<port>[0-9]+)/(?P<protocol>[a-z]+)", re.IGNORECASE) pattern = re.compile(
f = open('/etc/services', 'r') r"(?P<name>[a-z][a-z0-9-]+)\s+(?P<port>[0-9]+)/"
for line in f: r"(?P<protocol>[a-z]+)",
match = pattern.match(line) re.IGNORECASE)
if match is not None: with open('/etc/services', 'r') as file:
service = match.groupdict() for line in file:
self.__services.append( (service["name"], int(service["port"]),) ) match = pattern.match(line)
f.close() if match is not None:
service = match.groupdict()
self.__services.append(
(service["name"], int(service["port"]),))
self.fw_changed = False 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): 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) 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) self.dataChanged.emit(index1, index2)
def get_service_name(self, port): def get_service_name(self, port):
for service in self.__services: for service in self.__services:
if service[1] == port: if str(service[1]) == str(port):
return service[0] return service[0]
return str(port) return str(port)
@ -197,256 +184,292 @@ class QubesFirewallRulesModel(QAbstractItemModel):
return service[1] return service[1]
return None return None
def get_column_string(self, col, row): def get_column_string(self, col, rule):
return self.__columnValues[col](row) # 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)
# Service
def rule_to_dict(self, rule): if col == 1:
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
if rule.dstports is None: if rule.dstports is None:
raise FirewallModifiedOutsideError('no dstport') return "any"
d['portBegin'] = rule.dstports.range[0] if rule.dstports.range[0] != rule.dstports.range[1]:
d['portEnd'] = rule.dstports.range[1] \ return str(rule.dstports)
if rule.dstports.range[0] != rule.dstports.range[1] \ return self.get_service_name(rule.dstports)
else None
if rule.dsthost.type == 'dsthost': # Protocol
d['address'] = str(rule.dsthost) if col == 2:
d['netmask'] = 32 if rule.proto is None:
elif rule.dsthost.type == 'dst4': return "any"
network = ipaddress.IPv4Network(rule.dsthost) return str(rule.proto)
d['address'] = str(network.network_address) return "unknown"
d['netmask'] = int(network.prefixlen)
else:
raise FirewallModifiedOutsideError(
'cannot map dsthost.type={!s}'.format(rule.dsthost))
if rule.expire is not None: @staticmethod
d['expire'] = int(rule.expire) def get_firewall_conf(vm):
return d
def get_firewall_conf(self, vm):
conf = { conf = {
'allow': None, 'allow': None,
'allowDns': False, 'expire': 0,
'allowIcmp': False,
'allowYumProxy': False,
'rules': [], 'rules': [],
} }
allow_dns = False
allow_icmp = False
common_action = None common_action = None
tentative_action = None
reversed_rules = list(reversed(vm.firewall.rules)) 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: while reversed_rules:
rule = reversed_rules[0] rule = reversed_rules.pop(0)
if rule.dsthost is not None or rule.proto is not None:
break
tentative_action = reversed_rules.pop(0).action
if not reversed_rules: if rule == dns_rule:
conf['allow'] = tentative_action == 'accept' allow_dns = True
return conf
for rule in reversed_rules:
if rule.specialtarget == 'dns':
conf['allowDns'] = (rule.action == 'accept')
continue continue
if rule.proto == 'icmp': if rule == icmp_rule:
if rule.icmptype is not None: allow_icmp = True
raise FirewallModifiedOutsideError(
'cannot map icmptype != None')
conf['allowIcmp'] = (rule.action == 'accept')
continue continue
if common_action is None: if rule.specialtarget is not None or rule.icmptype is not None:
common_action = rule.action raise FirewallModifiedOutsideError("Rule type unknown!")
elif common_action != rule.action:
raise FirewallModifiedOutsideError('incoherent action')
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: if rule.expire is not None and rule.dsthost is None \
# we've got only specialtarget and/or icmp and rule.proto is None:
conf['allow'] = tentative_action == 'accept' conf['expire'] = int(str(rule.expire))
return conf continue
raise FirewallModifiedOutsideError('it does not add up') raise FirewallModifiedOutsideError('it does not add up.')
def write_firewall_conf(self, vm, conf): conf['allow'] = (common_action == 'accept')
common_action = qubesadmin.firewall.Action(
'drop' if conf['allow'] else '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 = [] rules = []
for rule in conf['rules']: for rule in conf['rules']:
kwargs = {} rules.append(rule)
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']))))
netmask = str(rule['netmask']) if rule['netmask'] != 32 else None if not conf['allow']:
rules.append(qubesadmin.firewall.Rule(None,
action=common_action,
dsthost='/'.join(map(str, filter((lambda x: x),
(rule['address'], netmask)))),
**kwargs))
if conf['allowDns']:
rules.append(qubesadmin.firewall.Rule(None, rules.append(qubesadmin.firewall.Rule(None,
action='accept', specialtarget='dns')) action='accept', specialtarget='dns'))
if conf['allowIcmp']: if not conf['allow']:
rules.append(qubesadmin.firewall.Rule(None, rules.append(qubesadmin.firewall.Rule(None,
action='accept', proto='icmp')) action='accept', proto='icmp'))
if common_action == 'drop': if conf['allow']:
rules.append(qubesadmin.firewall.Rule(None, rules.append(qubesadmin.firewall.Rule(None,
action='accept')) action='accept'))
else:
rules.append(qubesadmin.firewall.Rule(None,
action='drop'))
vm.firewall.rules = rules vm.firewall.rules = rules
def set_vm(self, vm): def set_vm(self, vm):
self.__vm = vm self.__vm = vm
self.clearChildren() self.clear_children()
conf = self.get_firewall_conf(vm) conf = self.get_firewall_conf(vm)
self.allow = conf["allow"] self.allow = conf["allow"]
self.allowDns = conf["allowDns"]
self.allowIcmp = conf["allowIcmp"] self.temp_full_access_expire_time = conf['expire']
self.allowYumProxy = conf["allowYumProxy"]
self.tempFullAccessExpireTime = 0
for rule in conf["rules"]: for rule in conf["rules"]:
self.appendChild(rule) self.append_child(rule)
if "expire" in rule and rule["address"] == "0.0.0.0":
self.tempFullAccessExpireTime = rule["expire"]
def get_vm_name(self): def get_vm_name(self):
return self.__vm.name return self.__vm.name
def apply_rules(self, allow, dns, icmp, yumproxy, tempFullAccess=False, def apply_rules(self, allow, temp_full_access=False,
tempFullAccessTime=None): temp_full_access_time=None):
assert self.__vm is not None assert self.__vm is not None
if self.allow != allow or self.allowDns != dns or \ if self.allow != allow or \
self.allowIcmp != icmp or self.allowYumProxy != yumproxy or \ (self.temp_full_access_expire_time != 0) != temp_full_access:
(self.tempFullAccessExpireTime != 0) != tempFullAccess:
self.fw_changed = True self.fw_changed = True
conf = { "allow": allow, conf = {"allow": allow,
"allowDns": dns,
"allowIcmp": icmp,
"allowYumProxy": yumproxy,
"rules": list() "rules": list()
} }
for rule in self.children: conf['rules'].extend(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)
if tempFullAccess and not allow: if temp_full_access and not allow:
conf["rules"].append({"address": "0.0.0.0", conf["rules"].append(qubesadmin.firewall.Rule(
"netmask": 0, None,
"proto": "any", action='accept',
"expire": int( expire=int(datetime.datetime.now().strftime("%s")) +
datetime.datetime.now().strftime("%s"))+\ temp_full_access_time * 60))
tempFullAccessTime*60
})
if self.fw_changed: if self.fw_changed:
self.write_firewall_conf(self.__vm, conf) 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): if not self.hasIndex(row, column, parent):
return QModelIndex() return QtCore.QModelIndex()
return self.createIndex(row, column, self.children[row]) return self.createIndex(row, column, self.children[row])
def parent(self, child): def parent(self, child): # pylint: disable=unused-argument,no-self-use
return QModelIndex() return QtCore.QModelIndex()
def rowCount(self, parent=QModelIndex()): # pylint: disable=invalid-name,unused-argument
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self) return len(self)
def columnCount(self, parent=QModelIndex()): # pylint: disable=invalid-name,unused-argument
return len(self.__columnValues) def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.__column_names)
def hasChildren(self, index=QModelIndex()): # pylint: disable=invalid-name,no-self-use
parentItem = index.internalPointer() def hasChildren(self, index=QtCore.QModelIndex()):
if parentItem is not None: parent_item = index.internalPointer()
return False return parent_item is None
else:
return True
def data(self, index, role=Qt.DisplayRole): def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid() and role == Qt.DisplayRole: if index.isValid() and role == QtCore.Qt.DisplayRole:
return self.__columnValues[index.column()](index.row()) return self.get_column_string(index.column(),
self.children[index.row()])
def headerData(self, section, orientation, role=Qt.DisplayRole): # pylint: disable=invalid-name
if section < len(self.__columnNames) \ def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
and orientation == Qt.Horizontal and role == Qt.DisplayRole: if section < len(self.__column_names) \
return self.__columnNames[section] and orientation == QtCore.Qt.Horizontal \
and role == QtCore.Qt.DisplayRole:
return self.__column_names[section]
@property @property
def children(self): def children(self):
return self.__children return self.__children
def appendChild(self, child): def append_child(self, child):
row = len(self) row = len(self)
self.beginInsertRows(QModelIndex(), row, row) self.beginInsertRows(QtCore.QModelIndex(), row, row)
self.children.append(child) self.children.append(child)
self.endInsertRows() self.endInsertRows()
index = self.createIndex(row, 0, child) index = self.createIndex(row, 0, child)
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
self.fw_changed = True self.fw_changed = True
def removeChild(self, i): def remove_child(self, i):
if i >= len(self): if i >= len(self):
return return
self.beginRemoveRows(QModelIndex(), i, i) self.beginRemoveRows(QtCore.QModelIndex(), i, i)
del self.children[i] del self.children[i]
self.endRemoveRows() self.endRemoveRows()
index = self.createIndex(i, 0) index = self.createIndex(i, 0)
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
self.fw_changed = True self.fw_changed = True
def setChild(self, i, child): def set_child(self, i, child):
self.children[i] = child self.children[i] = child
index = self.createIndex(i, 0, child) index = self.createIndex(i, 0, child)
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
self.fw_changed = True self.fw_changed = True
def clearChildren(self): def clear_children(self):
self.__children = list() self.__children = list()
def __len__(self): def __len__(self):
return len(self.children) return len(self.children)

View File

@ -15,28 +15,29 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
import sys import sys
import os import os
from PyQt4.QtCore import * import os.path
from PyQt4.QtGui import * import traceback
from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from qubesadmin import Qubes 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 configparser import ConfigParser
from qubesadmin.utils import parse_size, updates_vms_status
qmemman_config_path = '/etc/qubes/qmemman.conf' 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): def __init__(self, app, qvm_collection, parent=None):
super(GlobalSettingsWindow, self).__init__(parent) super(GlobalSettingsWindow, self).__init__(parent)
@ -45,10 +46,13 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.qvm_collection = qvm_collection self.qvm_collection = qvm_collection
self.setupUi(self) self.setupUi(self)
self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply) self.connect(
self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject) self.buttonBox,
QtCore.SIGNAL("accepted()"),
self.save_and_apply)
self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject)
self.__init_system_defaults__() self.__init_system_defaults__()
self.__init_kernel_defaults__() self.__init_kernel_defaults__()
self.__init_mem_defaults__() self.__init_mem_defaults__()
@ -89,7 +93,8 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.clock_vm_combo.setCurrentIndex(self.clockvm_idx) self.clock_vm_combo.setCurrentIndex(self.clockvm_idx)
# default netvm # 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 self.netvm_idx = -1
current_netvm = self.qvm_collection.default_netvm 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 = str(self.update_vm_combo.currentText())
updatevm_name = updatevm_name.split(' ')[0] updatevm_name = updatevm_name.split(' ')[0]
updatevm = self.qvm_collection.domains[updatevm_name] updatevm = self.qvm_collection.domains[updatevm_name]
self.qvm_collection.updatevm = updatevm self.qvm_collection.updatevm = updatevm
#clockvm #clockvm
@ -130,7 +135,7 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
clockvm_name = str(self.clock_vm_combo.currentText()) clockvm_name = str(self.clock_vm_combo.currentText())
clockvm_name = clockvm_name.split(' ')[0] clockvm_name = clockvm_name.split(' ')[0]
clockvm = self.qvm_collection.domains[clockvm_name] clockvm = self.qvm_collection.domains[clockvm_name]
self.qvm_collection.clockvm = clockvm self.qvm_collection.clockvm = clockvm
#default netvm #default netvm
@ -138,7 +143,7 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
name = str(self.default_netvm_combo.currentText()) name = str(self.default_netvm_combo.currentText())
name = name.split(' ')[0] name = name.split(' ')[0]
vm = self.qvm_collection.domains[name] vm = self.qvm_collection.domains[name]
self.qvm_collection.default_netvm = vm self.qvm_collection.default_netvm = vm
#default template #default template
@ -146,13 +151,14 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
name = str(self.default_template_combo.currentText()) name = str(self.default_template_combo.currentText())
name = name.split(' ')[0] name = name.split(' ')[0]
vm = self.qvm_collection.domains[name] vm = self.qvm_collection.domains[name]
self.qvm_collection.default_template = vm self.qvm_collection.default_template = vm
def __init_kernel_defaults__(self): def __init_kernel_defaults__(self):
kernel_list = [] 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'): for k in os.listdir('/var/lib/qubes/vm-kernels'):
kernel_list.append(k) kernel_list.append(k)
@ -170,21 +176,22 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
if self.default_kernel_combo.currentIndex() != self.kernel_idx: if self.default_kernel_combo.currentIndex() != self.kernel_idx:
kernel = str(self.default_kernel_combo.currentText()) kernel = str(self.default_kernel_combo.currentText())
kernel = kernel.split(' ')[0] kernel = kernel.split(' ')[0]
self.qvm_collection.default_kernel = kernel self.qvm_collection.default_kernel = kernel
def __init_mem_defaults__(self):
def __init_mem_defaults__(self):
#qmemman settings #qmemman settings
self.qmemman_config = ConfigParser() 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.dom0_mem_boost_val = '350MiB' #str(qmemman_algo.DOM0_MEM_BOOST)
self.qmemman_config.read(qmemman_config_path) self.qmemman_config.read(qmemman_config_path)
if self.qmemman_config.has_section('global'): if self.qmemman_config.has_section('global'):
self.vm_min_mem_val = self.qmemman_config.get('global', 'vm-min-mem') self.vm_min_mem_val = \
self.dom0_mem_boost_val = self.qmemman_config.get('global', 'dom0-mem-boost') 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.vm_min_mem_val = parse_size(self.vm_min_mem_val)
self.dom0_mem_boost_val = parse_size(self.dom0_mem_boost_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_min_vm_mem = self.min_vm_mem.value()
current_dom0_mem_boost = self.dom0_mem_boost.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_min_vm_mem = str(current_min_vm_mem)+'M'
current_dom0_mem_boost = str(current_dom0_mem_boost)+'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'): if not self.qmemman_config.has_section('global'):
#add the whole section #add the whole section
self.qmemman_config.add_section('global') self.qmemman_config.add_section('global')
self.qmemman_config.set('global', 'vm-min-mem', current_min_vm_mem) self.qmemman_config.set(
self.qmemman_config.set('global', 'dom0-mem-boost', current_dom0_mem_boost) 'global', 'vm-min-mem', current_min_vm_mem)
self.qmemman_config.set('global', 'cache-margin-factor', str(1.3)) # removed qmemman_algo.CACHE_FACTOR 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') qmemman_config_file = open(qmemman_config_path, 'a')
self.qmemman_config.write(qmemman_config_file) self.qmemman_config.write(qmemman_config_file)
qmemman_config_file.close() qmemman_config_file.close()
else: 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 = {}
lines_to_add['vm-min-mem'] = "vm-min-mem = " + current_min_vm_mem + "\n" lines_to_add['vm-min-mem'] = \
lines_to_add['dom0-mem-boost'] = "dom0-mem-boost = " + current_dom0_mem_boost +"\n" "vm-min-mem = " + current_min_vm_mem + "\n"
lines_to_add['dom0-mem-boost'] = \
"dom0-mem-boost = " + current_dom0_mem_boost +"\n"
config_lines = [] config_lines = []
qmemman_config_file = open(qmemman_config_path, 'r') qmemman_config_file = open(qmemman_config_path, 'r')
for l in qmemman_config_file: for line in qmemman_config_file:
if l.strip().startswith('vm-min-mem'): if line.strip().startswith('vm-min-mem'):
config_lines.append(lines_to_add['vm-min-mem']) config_lines.append(lines_to_add['vm-min-mem'])
del 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']) config_lines.append(lines_to_add['dom0-mem-boost'])
del lines_to_add['dom0-mem-boost'] del lines_to_add['dom0-mem-boost']
else: else:
config_lines.append(l) config_lines.append(line)
qmemman_config_file.close() qmemman_config_file.close()
for l in lines_to_add: for line in lines_to_add:
config_lines.append(l) config_lines.append(line)
qmemman_config_file = open(qmemman_config_path, 'w') qmemman_config_file = open(qmemman_config_path, 'w')
qmemman_config_file.writelines(config_lines) qmemman_config_file.writelines(config_lines)
@ -252,24 +268,26 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.updates_dom0.setChecked(self.updates_dom0_val) self.updates_dom0.setChecked(self.updates_dom0_val)
updates_vms = updates_vms_status(self.qvm_collection) updates_vms = updates_vms_status(self.qvm_collection)
if updates_vms is None: if updates_vms is None:
self.updates_vm.setCheckState(Qt.PartiallyChecked) self.updates_vm.setCheckState(QtCore.Qt.PartiallyChecked)
else: else:
self.updates_vm.setCheckState(updates_vms) self.updates_vm.setCheckState(updates_vms)
def __apply_updates__(self): def __apply_updates__(self):
if self.updates_dom0.isChecked() != self.updates_dom0_val: 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') 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: 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): def reject(self):
self.done(0) self.done(0)
def save_and_apply(self): def save_and_apply(self):
self.__apply_system_defaults__() self.__apply_system_defaults__()
self.__apply_kernel_defaults__() self.__apply_kernel_defaults__()
self.__apply_mem_defaults__() self.__apply_mem_defaults__()
self.__apply_updates__() self.__apply_updates__()
@ -279,26 +297,22 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
# Bases on the original code by: # Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com> # Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
def handle_exception( exc_type, exc_value, exc_traceback ): def handle_exception(exc_type, exc_value, exc_traceback):
import os.path filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
import traceback filename = os.path.basename(filename)
error = "%s: %s" % (exc_type.__name__, exc_value)
filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop() QtGui.QMessageBox.critical(
filename = os.path.basename( filename ) None,
error = "%s: %s" % ( exc_type.__name__, exc_value ) "Houston, we have a problem...",
"Whoops. A critical error has occured. This is most likely a bug "
QMessageBox.critical(None, "Houston, we have a problem...", "in Qubes Global Settings application.<br><br><b><i>%s</i></b>" %
"Whoops. A critical error has occured. This is most likely a bug " error + "at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
"in Qubes Global Settings application.<br><br>" % (line, filename))
"<b><i>%s</i></b>" % error +
"at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
% ( line, filename ))
def main(): def main():
qtapp = QtGui.QApplication(sys.argv)
global qtapp
qtapp = QApplication(sys.argv)
qtapp.setOrganizationName("The Qubes Project") qtapp.setOrganizationName("The Qubes Project")
qtapp.setOrganizationDomain("http://qubes-os.org") qtapp.setOrganizationDomain("http://qubes-os.org")
qtapp.setApplicationName("Qubes Global Settings") qtapp.setApplicationName("Qubes Global Settings")
@ -307,7 +321,6 @@ def main():
app = Qubes() app = Qubes()
global global_window
global_window = GlobalSettingsWindow(qtapp, app) global_window = GlobalSettingsWindow(qtapp, app)
global_window.show() global_window.show()

View File

@ -1,4 +1,4 @@
#!/usr/bin/python2 #!/usr/bin/python3
# coding=utf-8 # coding=utf-8
# #
# The Qubes OS Project, http://www.qubes-os.org # The Qubes OS Project, http://www.qubes-os.org
@ -16,22 +16,23 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
from PyQt4.QtCore import SIGNAL from PyQt4.QtGui import QDialog # pylint: disable=import-error
from PyQt4.QtGui import QDialog, QIcon
from .ui_informationnotes import * from . import ui_informationnotes # pylint: disable=no-name-in-module
import subprocess import subprocess
class InformationNotesDialog(Ui_InformationNotesDialog, QDialog): class InformationNotesDialog(ui_informationnotes.Ui_InformationNotesDialog,
QDialog):
# pylint: disable=too-few-public-methods
def __init__(self): def __init__(self):
super(InformationNotesDialog, self).__init__() super(InformationNotesDialog, self).__init__()
self.setupUi(self) self.setupUi(self)
details = subprocess.check_output(['/usr/libexec/qubes-manager/qvm_about.sh']) details = subprocess.check_output(
self.informationNotes.setText(details) ['/usr/libexec/qubes-manager/qvm_about.sh'])
self.informationNotes.setText(details.decode())

View File

@ -1,4 +1,4 @@
#!/usr/bin/python2 #!/usr/bin/python3
# #
# The Qubes OS Project, http://www.qubes-os.org # The Qubes OS Project, http://www.qubes-os.org
# #
@ -15,28 +15,24 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
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 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 # Display only this size of log
LOG_DISPLAY_SIZE = 1024*1024 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): def __init__(self, app, log_path, parent=None):
super(LogDialog, self).__init__(parent) super(LogDialog, self).__init__(parent)
@ -46,9 +42,11 @@ class LogDialog(Ui_LogDialog, QDialog):
self.setupUi(self) self.setupUi(self)
self.setWindowTitle(log_path) 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__() self.__init_log_text__()
def __init_log_text__(self): def __init_log_text__(self):
@ -56,7 +54,8 @@ class LogDialog(Ui_LogDialog, QDialog):
log = open(self.log_path) log = open(self.log_path)
log.seek(0, os.SEEK_END) log.seek(0, os.SEEK_END)
if log.tell() > LOG_DISPLAY_SIZE: 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) log.seek(-LOG_DISPLAY_SIZE, os.SEEK_END)
else: else:
log.seek(0, os.SEEK_SET) log.seek(0, os.SEEK_SET)
@ -64,6 +63,5 @@ class LogDialog(Ui_LogDialog, QDialog):
log.close() log.close()
self.log_text.setPlainText(self.displayed_text) self.log_text.setPlainText(self.displayed_text)
def copy_to_clipboard_triggered(self):
def copy_to_qubes_clipboard_triggered(self): clipboard.copy_text_to_qubes_clipboard(self.displayed_text)
copy_text_to_qubes_clipboard(self.displayed_text)

View File

@ -1,46 +1,47 @@
import sys from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from PyQt4.QtCore import * from . import ui_multiselectwidget # pylint: disable=no-name-in-module
from PyQt4.QtGui import *
from .ui_multiselectwidget import *
class MultiSelectWidget(Ui_MultiSelectWidget, QWidget): class MultiSelectWidget(
ui_multiselectwidget.Ui_MultiSelectWidget, QtGui.QWidget):
__pyqtSignals__ = ("selected_changed()",) __pyqtSignals__ = ("selected_changed()",)
__pyqtSignals__ = ("items_added(PyQt_PyObject)",) __pyqtSignals__ = ("items_added(PyQt_PyObject)",)
__pyqtSignals__ = ("items_removed(PyQt_PyObject)",) __pyqtSignals__ = ("items_removed(PyQt_PyObject)",)
def __init__(self, parent=None): def __init__(self, parent=None):
super(MultiSelectWidget, self).__init__() super(MultiSelectWidget, self).__init__(parent)
self.setupUi(self) self.setupUi(self)
self.add_selected_button.clicked.connect(self.add_selected) self.add_selected_button.clicked.connect(self.add_selected)
self.add_all_button.clicked.connect(self.add_all) self.add_all_button.clicked.connect(self.add_all)
self.remove_selected_button.clicked.connect(self.remove_selected) self.remove_selected_button.clicked.connect(self.remove_selected)
self.remove_all_button.clicked.connect(self.remove_all) self.remove_all_button.clicked.connect(self.remove_all)
self.available_list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.available_list.setSelectionMode(
self.selected_list.setSelectionMode(QAbstractItemView.ExtendedSelection) QtGui.QAbstractItemView.ExtendedSelection)
self.selected_list.setSelectionMode(
QtGui.QAbstractItemView.ExtendedSelection)
def switch_selected(self, src, dst): def switch_selected(self, src, dst):
selected = src.selectedItems() selected = src.selectedItems()
items = [] items = []
for s in selected: for selected_item in selected:
row = src.indexFromItem(s).row() row = src.indexFromItem(selected_item).row()
item = src.takeItem(row) item = src.takeItem(row)
dst.addItem(item) dst.addItem(item)
items.append(item) items.append(item)
dst.sortItems() dst.sortItems()
self.emit(SIGNAL("selected_changed()")) self.emit(QtCore.SIGNAL("selected_changed()"))
if src is self.selected_list: if src is self.selected_list:
self.emit(SIGNAL("items_removed(PyQt_PyObject)"), items) self.emit(QtCore.SIGNAL("items_removed(PyQt_PyObject)"), items)
else: else:
self.emit(SIGNAL("items_added(PyQt_PyObject)"), items) self.emit(QtCore.SIGNAL("items_added(PyQt_PyObject)"), items)
def add_selected(self): def add_selected(self):
self.switch_selected(self.available_list, self.selected_list) self.switch_selected(self.available_list, self.selected_list)
def remove_selected(self): 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): def move_all(self, src, dst):
items = [] items = []
while src.count() > 0: while src.count() > 0:
@ -48,11 +49,11 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
dst.addItem(item) dst.addItem(item)
items.append(item) items.append(item)
dst.sortItems() dst.sortItems()
self.emit(SIGNAL("selected_changed()")) self.emit(QtCore.SIGNAL("selected_changed()"))
if src is self.selected_list: if src is self.selected_list:
self.emit(SIGNAL("items_removed(PyQt_PyObject)"), items) self.emit(QtCore.SIGNAL("items_removed(PyQt_PyObject)"), items)
else: else:
self.emit(SIGNAL("items_added(PyQt_PyObject)"), items) self.emit(QtCore.SIGNAL("items_added(PyQt_PyObject)"), items)
def add_all(self): def add_all(self):
@ -64,4 +65,3 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
def clear(self): def clear(self):
self.available_list.clear() self.available_list.clear()
self.selected_list.clear() self.selected_list.clear()

1188
qubesmanager/qube_manager.py Executable file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#!/usr/bin/python2 #!/usr/bin/python3
# coding=utf-8 # coding=utf-8
# #
# The Qubes OS Project, http://www.qubes-os.org # The Qubes OS Project, http://www.qubes-os.org
@ -16,18 +16,17 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
from PyQt4.QtCore import SIGNAL from PyQt4.QtGui import QDialog # pylint: disable=import-error
from PyQt4.QtGui import QDialog, QIcon
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): def __init__(self):
super(ReleaseNotesDialog, self).__init__() super(ReleaseNotesDialog, self).__init__()

View File

@ -15,104 +15,90 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
import sys 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 os
import shutil import os.path
from PyQt4.QtCore import * import traceback
from PyQt4.QtGui import * 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 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 backup
from qubes import qubesutils
from .ui_restoredlg import * from . import ui_restoredlg # pylint: disable=no-name-in-module
from .multiselectwidget import * from . import multiselectwidget
from . import backup_utils
from . import thread_monitor
from .backup_utils import *
from multiprocessing import Queue, Event from multiprocessing import Queue, Event
from multiprocessing.queues import Empty 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) super(RestoreVMsWindow, self).__init__(parent)
self.app = app self.qt_app = qt_app
self.qvm_collection = qvm_collection self.qubes_app = qubes_app
self.blk_manager = blk_manager
self.restore_options = None
self.vms_to_restore = None self.vms_to_restore = None
self.func_output = [] self.func_output = []
# Set up logging
self.feedback_queue = Queue() 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.canceled = False
self.tmpdir_to_remove = None
self.error_detected = Event() self.error_detected = Event()
self.thread_monitor = None
self.excluded = {} self.backup_restore = None
self.target_appvm = None
self.vm = self.qvm_collection[0]
assert self.vm != None
self.setupUi(self) 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.select_vms_layout.insertWidget(1, self.select_vms_widget)
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed) self.connect(self,
self.connect(self, SIGNAL("restore_progress(QString)"), self.commit_text_edit.append) QtCore.SIGNAL("currentIdChanged(int)"),
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue) self.current_page_changed)
self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed) self.dir_line_edit.connect(self.dir_line_edit,
self.connect(self.verify_only, SIGNAL("stateChanged(int)"), QtCore.SIGNAL("textChanged(QString)"),
self.on_verify_only_toogled) self.backup_location_changed)
self.select_dir_page.isComplete = self.has_selected_dir self.select_dir_page.isComplete = self.has_selected_dir
self.select_vms_page.isComplete = self.has_selected_vms self.select_vms_page.isComplete = self.has_selected_vms
self.confirm_page.isComplete = self.all_vms_good self.confirm_page.isComplete = self.all_vms_good
#FIXME # FIXME
#this causes to run isComplete() twice, I don't know why # 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()")) self.select_vms_page.connect(
self.select_vms_widget,
QtCore.SIGNAL("selected_changed()"),
QtCore.SIGNAL("completeChanged()"))
fill_appvms_list(self) backup_utils.fill_appvms_list(self)
self.__init_restore_options__()
@pyqtSlot(name='on_select_path_button_clicked') @QtCore.pyqtSlot(name='on_select_path_button_clicked')
def select_path_button_clicked(self): 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): def cleanupPage(self, p_int): # pylint: disable=invalid-name
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):
if self.page(p_int) is self.select_vms_page: if self.page(p_int) is self.select_vms_page:
self.vms_to_restore = None self.vms_to_restore = None
else: else:
@ -126,119 +112,95 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
self.select_vms_widget.available_list.clear() self.select_vms_widget.available_list.clear()
self.target_appvm = None self.target_appvm = None
if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen if self.appvm_combobox.currentIndex() != 0: # An existing appvm chosen
self.target_appvm = self.qvm_collection.get_vm_by_name( self.target_appvm = self.qubes_app.domains[
str(self.appvm_combobox.currentText())) str(self.appvm_combobox.currentText())]
try: try:
self.vms_to_restore = backup.backup_restore_prepare( self.backup_restore = restore.BackupRestore(
self.dir_line_edit.text(), self.qubes_app,
self.passphrase_line_edit.text(), self.dir_line_edit.text(),
options=self.restore_options, self.target_appvm,
host_collection=self.qvm_collection, self.passphrase_line_edit.text()
encrypted=self.encryption_checkbox.isChecked(), )
appvm=self.target_appvm)
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: for vmname in self.vms_to_restore:
if vmname.startswith('$'): if vmname.startswith('$'):
# Internal info # Internal info
continue continue
self.select_vms_widget.available_list.addItem(vmname) self.select_vms_widget.available_list.addItem(vmname)
except QubesException as ex: except exc.QubesException as ex:
QMessageBox.warning (None, self.tr("Restore error!"), str(ex)) QtGui.QMessageBox.warning(None, self.tr("Restore error!"), str(ex))
def __init_restore_options__(self): def append_output(self, text):
if not self.restore_options: self.commit_text_edit.append(text)
self.restore_options = {}
backup.backup_restore_set_defaults(self.restore_options)
if 'use-default-template' in self.restore_options and 'use-default-netvm' in self.restore_options: def __do_restore__(self, t_monitor):
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'<font color="red">{0}</font>'.format(s)))
def restore_output(self, s):
self.feedback_queue.put((SIGNAL("restore_progress(QString)"),
u'<font color="black">{0}</font>'.format(s)))
def update_progress_bar(self, value):
self.feedback_queue.put((SIGNAL("backup_progress(int)"), value))
def __do_restore__(self, thread_monitor):
err_msg = [] err_msg = []
self.qvm_collection.lock_db_for_writing()
try: try:
backup.backup_restore_do(self.vms_to_restore, self.backup_restore.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)
except backup.BackupCanceledError as ex: except backup.BackupCanceledError as ex:
self.canceled = True self.canceled = True
self.tmpdir_to_remove = ex.tmpdir
err_msg.append(str(ex)) err_msg.append(str(ex))
except Exception as ex: except Exception as ex: # pylint: disable=broad-except
print ("Exception:", ex)
err_msg.append(str(ex)) err_msg.append(str(ex))
err_msg.append( err_msg.append(
self.tr("Partially restored files left in " self.tr("Partially restored files left in /var/tmp/restore_*, "
"/var/tmp/restore_*, investigate them and/or clean them up")) "investigate them and/or clean them up"))
self.qvm_collection.unlock_db()
if self.canceled: if self.canceled:
self.emit(SIGNAL("restore_progress(QString)"), self.append_output('<b><font color="red">{0}</font></b>'.format(
'<b><font color="red">{0}</font></b>' self.tr("Restore aborted!")))
.format(self.tr("Restore aborted!"))) elif err_msg or self.error_detected.is_set():
elif len(err_msg) > 0 or self.error_detected.is_set(): if err_msg:
if len(err_msg) > 0: t_monitor.set_error_msg('\n'.join(err_msg))
thread_monitor.set_error_msg('\n'.join(err_msg)) self.append_output('<b><font color="red">{0}</font></b>'.format(
self.emit(SIGNAL("restore_progress(QString)"), self.tr("Finished with errors!")))
'<b><font color="red">{0}</font></b>'
.format(self.tr("Finished with errors!")))
else: else:
self.emit(SIGNAL("restore_progress(QString)"), self.append_output('<font color="green">{0}</font>'.format(
'<font color="green">{0}</font>' self.tr("Finished successfully!")))
.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) old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if self.currentPage() is self.select_vms_page: if self.currentPage() is self.select_vms_page:
self.__fill_vms_list__() self.__fill_vms_list__()
elif self.currentPage() is self.confirm_page: elif self.currentPage() is self.confirm_page:
for v in self.excluded:
self.vms_to_restore[v] = self.excluded[v] self.vms_to_restore = self.backup_restore.get_restore_info()
self.excluded = {}
for i in range(self.select_vms_widget.available_list.count()): for i in range(self.select_vms_widget.available_list.count()):
vmname = self.select_vms_widget.available_list.item(i).text() vmname = self.select_vms_widget.available_list.item(i).text()
self.excluded[str(vmname)] = self.vms_to_restore[str(vmname)]
del self.vms_to_restore[str(vmname)] del self.vms_to_restore[str(vmname)]
del self.func_output[:] self.vms_to_restore = self.backup_restore.restore_info_verify(
self.vms_to_restore = backup.restore_info_verify(self.vms_to_restore, self.vms_to_restore)
self.qvm_collection)
backup.backup_restore_print_summary( self.func_output = self.backup_restore.get_restore_summary(
self.vms_to_restore, print_callback = self.gather_output) self.vms_to_restore
)
self.confirm_text_edit.setReadOnly(True) self.confirm_text_edit.setReadOnly(True)
self.confirm_text_edit.setFontFamily("Monospace") 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: elif self.currentPage() is self.commit_page:
self.button(self.FinishButton).setDisabled(True) self.button(self.FinishButton).setDisabled(True)
@ -247,50 +209,46 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
and str(self.dir_line_edit.text()) and str(self.dir_line_edit.text())
.count("media/") > 0) .count("media/") > 0)
self.thread_monitor = ThreadMonitor() self.thread_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread (target= self.__do_restore__ , args=(self.thread_monitor,)) thread = threading.Thread(target=self.__do_restore__,
args=(self.thread_monitor,))
thread.daemon = True thread.daemon = True
thread.start() thread.start()
while not self.thread_monitor.is_finished(): while not self.thread_monitor.is_finished():
self.app.processEvents() self.qt_app.processEvents()
time.sleep (0.1) time.sleep(0.1)
try: try:
for (signal_to_emit,data) in iter(self.feedback_queue.get_nowait,None): log_record = self.feedback_queue.get_nowait()
self.emit(signal_to_emit,data) while log_record:
if log_record.levelno == logging.ERROR or\
log_record.levelno == logging.CRITICAL:
output = '<font color="red">{0}</font>'.format(
log_record.getMessage())
else:
output = log_record.getMessage()
self.append_output(output)
log_record = self.feedback_queue.get_nowait()
except Empty: except Empty:
pass pass
if not self.thread_monitor.success: if not self.thread_monitor.success:
if self.canceled: if not self.canceled:
if self.tmpdir_to_remove and \ QtGui.QMessageBox.warning(
QMessageBox.warning(None, self.tr("Restore aborted"), None,
self.tr("Do you want to remove temporary files " self.tr("Backup error!"),
"from %s?") % self.tmpdir_to_remove, self.tr("ERROR: {0}").format(
QMessageBox.Yes, QMessageBox.No) == \ self.thread_monitor.error_msg))
QMessageBox.Yes: self.progress_bar.setMaximum(100)
shutil.rmtree(self.tmpdir_to_remove) self.progress_bar.setValue(100)
else:
QMessageBox.warning(None,
self.tr("Backup error!"), self.tr("ERROR: {0}")
.format(self.thread_monitor.error_msg))
if self.showFileDialog.isChecked(): if self.showFileDialog.isChecked():
self.emit(SIGNAL("restore_progress(QString)"), self.append_output(
'<b><font color="black">{0}</font></b>'.format( '<b><font color="black">{0}</font></b>'.format(
self.tr( self.tr("Please unmount your backup volume and cancel "
"Please unmount your backup volume and cancel" "the file selection dialog.")))
" the file selection dialog."))) self.qt_app.processEvents()
if self.target_appvm: backup_utils.select_path_button_clicked(self, False, True)
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.button(self.FinishButton).setEnabled(True) self.button(self.FinishButton).setEnabled(True)
self.button(self.CancelButton).setEnabled(False) self.button(self.CancelButton).setEnabled(False)
self.showFileDialog.setEnabled(False) self.showFileDialog.setEnabled(False)
@ -298,20 +256,19 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
signal.signal(signal.SIGCHLD, old_sigchld_handler) signal.signal(signal.SIGCHLD, old_sigchld_handler)
def all_vms_good(self): def all_vms_good(self):
for vminfo in self.vms_to_restore.values(): for vm_info in self.vms_to_restore.values():
if not vminfo.has_key('vm'): if not vm_info.vm:
continue continue
if not vminfo['good-to-go']: if not vm_info.good_to_go:
return False return False
return True return True
def reject(self): def reject(self):
if self.currentPage() is self.commit_page: if self.currentPage() is self.commit_page:
if backup.backup_cancel(): self.backup_restore.canceled = True
self.emit(SIGNAL("restore_progress(QString)"), self.append_output('<font color="red">{0}</font>'.format(
'<font color="red">{0}</font>' self.tr("Aborting the operation...")))
.format(self.tr("Aborting the operation..."))) self.button(self.CancelButton).setDisabled(True)
self.button(self.CancelButton).setDisabled(True)
else: else:
self.done(0) self.done(0)
@ -331,58 +288,46 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
def has_selected_vms(self): def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0 return self.select_vms_widget.selected_list.count() > 0
def backup_location_changed(self, new_dir = None): def backup_location_changed(self, new_dir=None):
self.select_dir_page.emit(SIGNAL("completeChanged()")) # pylint: disable=unused-argument
self.select_dir_page.emit(QtCore.SIGNAL("completeChanged()"))
# Bases on the original code by: # Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com> # Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
def handle_exception( exc_type, exc_value, exc_traceback ): def handle_exception(exc_type, exc_value, exc_traceback):
import sys
import os.path
import traceback
filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop() filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
filename = os.path.basename( filename ) filename = os.path.basename(filename)
error = "%s: %s" % ( exc_type.__name__, exc_value ) error = "%s: %s" % (exc_type.__name__, exc_value)
QMessageBox.critical(None, "Houston, we have a problem...", QtGui.QMessageBox.critical(None, "Houston, we have a problem...",
"Whoops. A critical error has occured. This is most likely a bug " "Whoops. A critical error has occured. "
"This is most likely a bug "
"in Qubes Restore VMs application.<br><br>" "in Qubes Restore VMs application.<br><br>"
"<b><i>%s</i></b>" % error + "<b><i>%s</i></b>" % error +
"at <b>line %d</b> of file <b>%s</b>.<br/><br/>" "at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
% ( line, filename )) % (line, filename))
def main(): def main():
global qubes_host qt_app = QtGui.QApplication(sys.argv)
qubes_host = QubesHost() qt_app.setOrganizationName("The Qubes Project")
qt_app.setOrganizationDomain("http://qubes-os.org")
global app qt_app.setApplicationName("Qubes Restore VMs")
app = QApplication(sys.argv)
app.setOrganizationName("The Qubes Project")
app.setOrganizationDomain("http://qubes-os.org")
app.setApplicationName("Qubes Restore VMs")
sys.excepthook = handle_exception sys.excepthook = handle_exception
qvm_collection = QubesVmCollection() qubes_app = Qubes()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
global restore_window restore_window = RestoreVMsWindow(qt_app, qubes_app)
restore_window = RestoreVMsWindow()
restore_window.show() restore_window.show()
app.exec_() qt_app.exec_()
app.exit() qt_app.exit()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -17,39 +17,37 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
import collections import collections
import copy
import os
import os.path import os.path
import re import re
import subprocess import subprocess
import sys
import threading import threading
import time import time
import traceback import traceback
import os
import qubesadmin import sys
import qubesadmin.tools from qubesadmin.tools import QubesArgumentParser
from qubesadmin import devices
import qubesadmin.exc
from . import utils from . import utils
from . import multiselectwidget from . import multiselectwidget
from . import thread_monitor from . import thread_monitor
from .appmenu_select import AppmenuSelectManager from .appmenu_select import AppmenuSelectManager
from .backup_utils import get_path_for_vm from . import firewall
from .firewall import * from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from .ui_settingsdlg import * from . import ui_settingsdlg #pylint: disable=no-name-in-module
from .bootfromdevice import main as bootfromdevice
class VMSettingsWindow(Ui_SettingsDialog, QDialog): # pylint: disable=too-many-instance-attributes
class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
tabs_indices = collections.OrderedDict(( tabs_indices = collections.OrderedDict((
('basic', 0), ('basic', 0),
('advanced', 1), ('advanced', 1),
@ -73,42 +71,59 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.setWindowTitle(self.tr("Settings: {vm}").format(vm=self.vm.name)) self.setWindowTitle(self.tr("Settings: {vm}").format(vm=self.vm.name))
if init_page in self.tabs_indices: if init_page in self.tabs_indices:
idx = self.tabs_indices[init_page] 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.tabWidget.setCurrentIndex(idx)
self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply) self.connect(self.buttonBox,
self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject) 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.currentChanged.connect(self.current_tab_changed)
# self.tabWidget.setTabEnabled(self.tabs_indices["firewall"], vm.is_networked() and not vm.provides_network)
###### basic tab ###### basic tab
self.__init_basic_tab__() self.__init_basic_tab__()
self.rename_vm_button.clicked.connect(self.rename_vm) 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 ###### advanced tab
self.__init_advanced_tab__() self.__init_advanced_tab__()
self.include_in_balancing.stateChanged.connect(self.include_in_balancing_state_changed) self.include_in_balancing.stateChanged.connect(
self.connect(self.init_mem, SIGNAL("editingFinished()"), self.check_mem_changes) self.include_in_balancing_changed)
self.connect(self.max_mem_size, SIGNAL("editingFinished()"), self.check_mem_changes) self.connect(self.init_mem,
self.bootFromDeviceButton.clicked.connect(self.boot_from_cdrom_button_pressed) 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 ###### firewall tab
if self.tabWidget.isTabEnabled(self.tabs_indices['firewall']): if self.tabWidget.isTabEnabled(self.tabs_indices['firewall']):
model = QubesFirewallRulesModel() model = firewall.QubesFirewallRulesModel()
model.set_vm(vm) try:
self.set_fw_model(model) 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.new_rule_button.clicked.connect(self.new_rule_button_pressed)
self.editRuleButton.clicked.connect(self.edit_rule_button_pressed) self.edit_rule_button.clicked.connect(self.edit_rule_button_pressed)
self.deleteRuleButton.clicked.connect(self.delete_rule_button_pressed) self.delete_rule_button.clicked.connect(
self.policyDenyRadioButton.clicked.connect(self.policy_changed) self.delete_rule_button_pressed)
self.policyAllowRadioButton.clicked.connect(self.policy_changed) 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 ####### devices tab
self.__init_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 ####### services tab
self.__init_services_tab__() self.__init_services_tab__()
@ -119,8 +134,9 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]): if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]):
self.app_list = multiselectwidget.MultiSelectWidget(self) self.app_list = multiselectwidget.MultiSelectWidget(self)
self.apps_layout.addWidget(self.app_list) self.apps_layout.addWidget(self.app_list)
self.AppListManager = AppmenuSelectManager(self.vm, self.app_list) self.app_list_manager = AppmenuSelectManager(self.vm, self.app_list)
self.refresh_apps_button.clicked.connect(self.refresh_apps_button_pressed) self.refresh_apps_button.clicked.connect(
self.refresh_apps_button_pressed)
def reject(self): def reject(self):
self.done(0) self.done(0)
@ -131,91 +147,112 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def save_and_apply(self): def save_and_apply(self):
t_monitor = thread_monitor.ThreadMonitor() 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.daemon = True
thread.start() thread.start()
progress = QProgressDialog( progress = QtGui.QProgressDialog(
self.tr("Applying settings to <b>{0}</b>...").format(self.vm.name), "", 0, 0) self.tr("Applying settings to <b>{0}</b>...").format(self.vm.name),
"", 0, 0)
progress.setCancelButton(None) progress.setCancelButton(None)
progress.setModal(True) progress.setModal(True)
progress.show() progress.show()
while not t_monitor.is_finished(): while not t_monitor.is_finished():
self.qapp.processEvents() self.qapp.processEvents()
time.sleep (0.1) time.sleep(0.1)
progress.hide() progress.hide()
if not t_monitor.success: if not t_monitor.success:
QMessageBox.warning(None, QtGui.QMessageBox.warning(
self.tr("Error while changing settings for {0}!").format(self.vm.name), None,
self.tr("ERROR: {0}").format(t_monitor.error_msg)) self.tr("Error while changing settings for {0}!"
).format(self.vm.name),
self.tr("ERROR: {0}").format(t_monitor.error_msg))
self.done(0) self.done(0)
def __save_changes__(self, t_monitor): def __save_changes__(self, t_monitor):
self.anything_changed = False
ret = [] ret = []
try: try:
ret_tmp = self.__apply_basic_tab__() ret_tmp = self.__apply_basic_tab__()
if len(ret_tmp) > 0: if ret_tmp:
ret += ["Basic tab:"] + ret_tmp ret += ["Basic tab:"] + ret_tmp
ret_tmp = self.__apply_advanced_tab__() ret_tmp = self.__apply_advanced_tab__()
if len(ret_tmp) > 0: if ret_tmp:
ret += ["Advanced tab:"] + ret_tmp ret += ["Advanced tab:"] + ret_tmp
ret_tmp = self.__apply_devices_tab__() ret_tmp = self.__apply_devices_tab__()
if len(ret_tmp) > 0: if ret_tmp:
ret += ["Devices tab:"] + ret_tmp ret += ["Devices tab:"] + ret_tmp
ret_tmp = self.__apply_services_tab__() ret_tmp = self.__apply_services_tab__()
if len(ret_tmp) > 0: if ret_tmp:
ret += ["Sevices tab:"] + ret_tmp ret += ["Sevices tab:"] + ret_tmp
except Exception as ex: except qubesadmin.exc.QubesException as qex:
ret.append(self.tr('Error while saving changes: ') + str(ex)) ret.append(self.tr('Error while saving changes: ') + str(qex))
except Exception as ex: # pylint: disable=broad-except
ret.append(repr(ex))
try: try:
if self.tabWidget.isTabEnabled(self.tabs_indices["firewall"]): if self.policy_allow_radio_button.isEnabled():
self.fw_model.apply_rules(self.policyAllowRadioButton.isChecked(), self.fw_model.apply_rules(
self.dnsCheckBox.isChecked(), self.policy_allow_radio_button.isChecked(),
self.icmpCheckBox.isChecked(), self.temp_full_access.isChecked(),
self.yumproxyCheckBox.isChecked(), self.temp_full_access_time.value())
self.tempFullAccess.isChecked(), except qubesadmin.exc.QubesException as qex:
self.tempFullAccessTime.value()) ret += [self.tr("Firewall tab:"), str(qex)]
if self.fw_model.fw_changed: except Exception as ex: # pylint: disable=broad-except
# might modified vm.services ret += [self.tr("Firewall tab:"), repr(ex)]
self.anything_changed = True
except Exception as ex:
ret += [self.tr("Firewall tab:"), str(ex)]
try: try:
if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]): if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]):
self.AppListManager.save_appmenu_select_changes() self.app_list_manager.save_appmenu_select_changes()
except Exception as ex: except qubesadmin.exc.QubesException as qex:
ret += [self.tr("Applications tab:"), str(ex)] 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)) t_monitor.set_error_msg('\n'.join(ret))
utils.debug('\n'.join(ret)) utils.debug('\n'.join(ret))
t_monitor.set_finished() t_monitor.set_finished()
def current_tab_changed(self, idx): def check_network_availability(self):
if idx == self.tabs_indices["firewall"]: netvm = self.vm.netvm
netvm = self.vm.netvm self.no_netvm_label.setVisible(netvm is None)
if netvm is not None and \ self.netvm_no_firewall_label.setVisible(
not netvm.features.check_with_template('qubes-firewall', False): netvm is not None and
QMessageBox.warning(None, not netvm.features.check_with_template('qubes-firewall', False))
self.tr("VM configuration problem!"), if netvm is None:
self.tr("The '{vm}' AppVM is network connected to " 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!<br/>" "'{netvm}', which does not support firewall!<br/>"
"You may edit the '{vm}' VM firewall rules, but these " "You may edit the '{vm}' VM firewall rules, but these "
"will not take any effect until you connect it to " "will not take any effect until you connect it to "
"a working Firewall VM.").format( "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 ######### basic tab
@ -233,9 +270,17 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def __init_basic_tab__(self): def __init_basic_tab__(self):
self.vmname.setText(self.vm.name) 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.vmname.setEnabled(False)
self.rename_vm_button.setEnabled(not self.vm.is_running()) 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: if self.vm.qid == 0:
self.vmlabel.setVisible(False) self.vmlabel.setVisible(False)
@ -282,7 +327,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.autostart_vm.setVisible(False) self.autostart_vm.setVisible(False)
#type #type
self.type_label.setText(type(self.vm).__name__) self.type_label.setText(self.vm.klass)
#installed by rpm #installed by rpm
self.rpm_label.setText('Yes' if self.vm.installed_by_rpm else 'No') 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: if self.vmlabel.currentIndex() != self.label_idx:
label = self.label_list[self.vmlabel.currentIndex()] label = self.label_list[self.vmlabel.currentIndex()]
self.vm.label = label self.vm.label = label
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
#vm template changed #vm template changed
@ -325,24 +369,22 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.template_name.currentIndex() != self.template_idx: if self.template_name.currentIndex() != self.template_idx:
self.vm.template = \ self.vm.template = \
self.template_list[self.template_name.currentIndex()] self.template_list[self.template_name.currentIndex()]
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
#vm netvm changed #vm netvm changed
try: try:
if self.netVM.currentIndex() != self.netvm_idx: if self.netVM.currentIndex() != self.netvm_idx:
self.vm.netvm = self.netvm_list[self.netVM.currentIndex()] self.vm.netvm = self.netvm_list[self.netVM.currentIndex()]
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
#include in backups #include in backups
try: 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.vm.include_in_backups = self.include_in_backups.isChecked()
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
#run_in_debug_mode #run_in_debug_mode
@ -350,8 +392,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.run_in_debug_mode.isVisible(): if self.run_in_debug_mode.isVisible():
if self.vm.debug != self.run_in_debug_mode.isChecked(): if self.vm.debug != self.run_in_debug_mode.isChecked():
self.vm.debug = self.run_in_debug_mode.isChecked() self.vm.debug = self.run_in_debug_mode.isChecked()
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
#autostart_vm #autostart_vm
@ -359,8 +400,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.autostart_vm.isVisible(): if self.autostart_vm.isVisible():
if self.vm.autostart != self.autostart_vm.isChecked(): if self.vm.autostart != self.autostart_vm.isChecked():
self.vm.autostart = self.autostart_vm.isChecked() self.vm.autostart = self.autostart_vm.isChecked()
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
#max priv storage #max priv storage
@ -368,8 +408,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.priv_img_size != priv_size: if self.priv_img_size != priv_size:
try: try:
self.vm.volumes['private'].resize(priv_size * 1024**2) self.vm.volumes['private'].resize(priv_size * 1024**2)
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
#max sys storage #max sys storage
@ -377,8 +416,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.root_img_size != sys_size: if self.root_img_size != sys_size:
try: try:
self.vm.volumes['root'].resize(sys_size * 1024**2) self.vm.volumes['root'].resize(sys_size * 1024**2)
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
return msg return msg
@ -386,54 +424,122 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def check_mem_changes(self): def check_mem_changes(self):
if self.max_mem_size.value() < self.init_mem.value(): if self.max_mem_size.value() < self.init_mem.value():
QMessageBox.warning(None, QtGui.QMessageBox.warning(
None,
self.tr("Warning!"), self.tr("Warning!"),
self.tr("Max memory can not be less than initial memory.<br>" self.tr("Max memory can not be less than initial memory.<br>"
"Setting max memory to equal initial memory.")) "Setting max memory to equal initial memory."))
self.max_mem_size.setValue(self.init_mem.value()) 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(): if self.init_mem.value() * 10 < self.max_mem_size.value():
QMessageBox.warning(None, QtGui.QMessageBox.warning(
None,
self.tr("Warning!"), self.tr("Warning!"),
self.tr("Initial memory can not be less than one tenth " self.tr("Initial memory can not be less than one tenth "
"Max memory.<br>Setting initial memory to the minimum " "Max memory.<br>Setting initial memory to the minimum "
"allowed value.")) "allowed value."))
self.init_mem.setValue(self.max_mem_size.value() / 10) 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): def _rename_vm(self, t_monitor, name):
try: try:
self.vm.app.clone_vm(self.vm, name) self.vm.app.clone_vm(self.vm, name)
del self.vm.app.domains[self.vm.name] del self.vm.app.domains[self.vm.name]
except Exception as ex: except qubesadmin.exc.QubesException as qex:
t_monitor.set_error_msg(str(ex)) 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() t_monitor.set_finished()
def rename_vm(self): 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: if ok:
self._run_in_thread(self._rename_vm, new_vm_name)
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.done(0) 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? '
'<br/> All VM settings and data will be irrevocably'
' deleted. <br/> 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 ######### advanced tab
def __init_advanced_tab__(self): def __init_advanced_tab__(self):
@ -453,7 +559,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.include_in_balancing.setEnabled(True) self.include_in_balancing.setEnabled(True)
self.include_in_balancing.setChecked( 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()) self.max_mem_size.setEnabled(self.include_in_balancing.isChecked())
#in case VM is HVM #in case VM is HVM
@ -490,16 +596,13 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
try: try:
if self.init_mem.value() != int(self.vm.memory): if self.init_mem.value() != int(self.vm.memory):
self.vm.memory = self.init_mem.value() self.vm.memory = self.init_mem.value()
self.anything_changed = True
if self.max_mem_size.value() != int(self.vm.maxmem): if self.max_mem_size.value() != int(self.vm.maxmem):
self.vm.maxmem = self.max_mem_size.value() self.vm.maxmem = self.max_mem_size.value()
self.anything_changed = True
if self.vcpus.value() != int(self.vm.vcpus): if self.vcpus.value() != int(self.vm.vcpus):
self.vm.vcpus = self.vcpus.value() self.vm.vcpus = self.vcpus.value()
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
#include_in_memory_balancing applied in services tab #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(): if hasattr(self.vm, "kernel") and self.kernel_groupbox.isVisible():
try: try:
if self.kernel.currentIndex() != self.kernel_idx: if self.kernel.currentIndex() != self.kernel_idx:
self.vm.kernel = self.kernel_list[self.kernel.currentIndex()] self.vm.kernel = self.kernel_list[
self.anything_changed = True self.kernel.currentIndex()]
except Exception as ex: except qubesadmin.exc.QubesException as ex:
msg.append(str(ex)) msg.append(str(ex))
#vm default_dispvm changed #vm default_dispvm changed
@ -518,8 +621,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if self.default_dispvm.currentIndex() != self.default_dispvm_idx: if self.default_dispvm.currentIndex() != self.default_dispvm_idx:
self.vm.default_dispvm = \ self.vm.default_dispvm = \
self.default_dispvm_list[self.default_dispvm.currentIndex()] self.default_dispvm_list[self.default_dispvm.currentIndex()]
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
msg.append(str(ex)) msg.append(str(ex))
try: try:
@ -632,11 +734,11 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
for dev in lspci.splitlines(): for dev in lspci.splitlines():
devs.append((dev.rstrip(), dev.split(' ')[0])) devs.append((dev.rstrip(), dev.split(' ')[0]))
class DevListWidgetItem(QListWidgetItem): # pylint: disable=too-few-public-methods
def __init__(self, name, ident, parent = None): class DevListWidgetItem(QtGui.QListWidgetItem):
def __init__(self, name, ident, parent=None):
super(DevListWidgetItem, self).__init__(name, parent) super(DevListWidgetItem, self).__init__(name, parent)
self.ident = ident self.ident = ident
self.Type
persistent = [ass.ident.replace('_', ':') persistent = [ass.ident.replace('_', ':')
for ass in self.vm.devices['pci'].persistent()] for ass in self.vm.devices['pci'].persistent()]
@ -649,7 +751,8 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.dev_list.available_list.addItem( self.dev_list.available_list.addItem(
DevListWidgetItem(name, ident)) 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_adv.show()
self.dmm_warning_dev.show() self.dmm_warning_dev.show()
else: else:
@ -677,7 +780,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
for i in range(self.dev_list.selected_list.count())] for i in range(self.dev_list.selected_list.count())]
for ident in new: for ident in new:
if ident not in old: if ident not in old:
ass = qubesadmin.devices.DeviceAssignment( ass = devices.DeviceAssignment(
self.vm.app.domains['dom0'], self.vm.app.domains['dom0'],
ident.replace(':', '_'), ident.replace(':', '_'),
persistent=True) persistent=True)
@ -686,24 +789,22 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if ass.ident.replace('_', ':') not in new: if ass.ident.replace('_', ':') not in new:
self.vm.devices['pci'].detach(ass) self.vm.devices['pci'].detach(ass)
self.anything_changed = True except qubesadmin.exc.QubesException as ex:
except Exception as ex:
if utils.is_debug(): if utils.is_debug():
traceback.print_exc() traceback.print_exc()
msg.append(str(ex)) msg.append(str(ex))
return msg return msg
def include_in_balancing_state_changed(self, state): def include_in_balancing_changed(self, state):
for r in range (self.services_list.count()): for i in range(self.services_list.count()):
item = self.services_list.item(r) item = self.services_list.item(i)
if str(item.text()) == 'meminfo-writer': if str(item.text()) == 'meminfo-writer':
item.setCheckState(state) item.setCheckState(state)
break break
if self.dev_list.selected_list.count() > 0: 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_adv.show()
self.dmm_warning_dev.show() self.dmm_warning_dev.show()
else: else:
@ -713,7 +814,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def devices_selection_changed(self): def devices_selection_changed(self):
if self.include_in_balancing.isChecked(): 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_adv.show()
self.dmm_warning_dev.show() self.dmm_warning_dev.show()
else: else:
@ -757,15 +858,17 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
self.refresh_apps_button.setText(self.tr('Refresh in progress...')) self.refresh_apps_button.setText(self.tr('Refresh in progress...'))
t_monitor = thread_monitor.ThreadMonitor() 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.daemon = True
thread.start() thread.start()
while not t_monitor.is_finished(): while not t_monitor.is_finished():
self.qapp.processEvents() 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.setEnabled(True)
self.refresh_apps_button.setText(self.tr('Refresh Applications')) self.refresh_apps_button.setText(self.tr('Refresh Applications'))
@ -778,23 +881,29 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if not feature.startswith('service.'): if not feature.startswith('service.'):
continue continue
service = feature[len('service.'):] service = feature[len('service.'):]
item = QListWidgetItem(service) item = QtGui.QListWidgetItem(service)
item.setCheckState(QtCore.Qt.Checked item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked
if self.vm.features[feature] else QtCore.Qt.Unchecked) if self.vm.features[feature]
else ui_settingsdlg.QtCore.Qt.Unchecked)
self.services_list.addItem(item) self.services_list.addItem(item)
self.new_srv_dict[service] = self.vm.features[feature] 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): def __add_service__(self):
srv = str(self.service_line_edit.text()).strip() srv = str(self.service_line_edit.text()).strip()
if srv != "": if srv != "":
if srv in self.new_srv_dict: if srv in self.new_srv_dict:
QMessageBox.information(None, '', QtGui.QMessageBox.information(
None,
'',
self.tr('Service already on the list!')) self.tr('Service already on the list!'))
else: else:
item = QListWidgetItem(srv) item = QtGui.QListWidgetItem(srv)
item.setCheckState(QtCore.Qt.Checked) item.setCheckState(ui_settingsdlg.QtCore.Qt.Checked)
self.services_list.addItem(item) self.services_list.addItem(item)
self.new_srv_dict[srv] = True self.new_srv_dict[srv] = True
@ -804,9 +913,11 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
if not item: if not item:
return return
if str(item.text()) == 'meminfo-writer': if str(item.text()) == 'meminfo-writer':
QMessageBox.information(None, QtGui.QMessageBox.information(
None,
self.tr('Service can not be removed'), 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 return
row = self.services_list.currentRow() row = self.services_list.currentRow()
@ -816,10 +927,10 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def services_item_clicked(self, item): def services_item_clicked(self, item):
if str(item.text()) == 'meminfo-writer': 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(): if not self.include_in_balancing.isChecked():
self.include_in_balancing.setChecked(True) 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(): if self.include_in_balancing.isChecked():
self.include_in_balancing.setChecked(False) self.include_in_balancing.setChecked(False)
@ -828,13 +939,16 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
msg = [] msg = []
try: try:
for r in range(self.services_list.count()): for i in range(self.services_list.count()):
item = self.services_list.item(r) item = self.services_list.item(i)
self.new_srv_dict[str(item.text())] = (item.checkState() == QtCore.Qt.Checked) 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() 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 != meminfo_writer_checked:
if balancing_is_checked != balancing_was_checked: if balancing_is_checked != balancing_was_checked:
@ -844,7 +958,6 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
feature = 'service.' + service feature = 'service.' + service
if v != self.vm.features.get(feature, object()): if v != self.vm.features.get(feature, object()):
self.vm.features[feature] = v self.vm.features[feature] = v
self.anything_changed = True
for feature in self.vm.features: for feature in self.vm.features:
if not feature.startswith('service.'): if not feature.startswith('service.'):
@ -852,7 +965,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
service = feature[len('service.'):] service = feature[len('service.'):]
if service not in self.new_srv_dict: if service not in self.new_srv_dict:
del self.vm.features[feature] del self.vm.features[feature]
except Exception as ex: except qubesadmin.exc.QubesException as ex:
msg.append(str(ex)) msg.append(str(ex))
return msg return msg
@ -863,116 +976,65 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog):
def set_fw_model(self, model): def set_fw_model(self, model):
self.fw_model = model self.fw_model = model
self.rulesTreeView.setModel(model) self.rulesTreeView.setModel(model)
self.rulesTreeView.header().setResizeMode(QHeaderView.ResizeToContents) self.rulesTreeView.header().setResizeMode(
self.rulesTreeView.header().setResizeMode(0, QHeaderView.Stretch) QtGui.QHeaderView.ResizeToContents)
self.rulesTreeView.header().setResizeMode(0, QtGui.QHeaderView.Stretch)
self.set_allow(model.allow) self.set_allow(model.allow)
self.dnsCheckBox.setChecked(model.allowDns) if model.temp_full_access_expire_time:
self.icmpCheckBox.setChecked(model.allowIcmp) self.temp_full_access.setChecked(True)
self.yumproxyCheckBox.setChecked(model.allowYumProxy) self.temp_full_access_time.setValue(
if model.tempFullAccessExpireTime: (model.temp_full_access_expire_time -
self.tempFullAccess.setChecked(True) int(firewall.datetime.datetime.now().strftime("%s"))) / 60)
self.tempFullAccessTime.setValue(
(model.tempFullAccessExpireTime - def disable_all_fw_conf(self):
int(datetime.datetime.now().strftime("%s")))/60) 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): def set_allow(self, allow):
self.policyAllowRadioButton.setChecked(allow) self.policy_allow_radio_button.setChecked(allow)
self.policyDenyRadioButton.setChecked(not allow) self.policy_deny_radio_button.setChecked(not allow)
self.policy_changed(allow) self.policy_changed()
def policy_changed(self, checked): def policy_changed(self):
self.tempFullAccessWidget.setEnabled(self.policyDenyRadioButton.isChecked()) 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): def new_rule_button_pressed(self):
dialog = NewFwRuleDlg() dialog = firewall.NewFwRuleDlg()
self.run_rule_dialog(dialog) self.fw_model.run_rule_dialog(dialog)
def edit_rule_button_pressed(self): 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): def delete_rule_button_pressed(self):
for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]): for i in set([index.row() for index
self.fw_model.removeChild(i) in self.rulesTreeView.selectedIndexes()]):
self.fw_model.remove_child(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))
# Bases on the original code by: # Bases on the original code by:
@ -986,7 +1048,7 @@ def handle_exception(exc_type, exc_value, exc_traceback):
strace = "" strace = ""
stacktrace = traceback.extract_tb(exc_traceback) stacktrace = traceback.extract_tb(exc_traceback)
while len(stacktrace) > 0: while stacktrace:
(filename, line, func, txt) = stacktrace.pop() (filename, line, func, txt) = stacktrace.pop()
strace += "----\n" strace += "----\n"
strace += "line: %s\n" %txt 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 += "line no.: %d\n" %line
strace += "file: %s\n" %filename strace += "file: %s\n" %filename
msg_box = QMessageBox() msg_box = QtGui.QMessageBox()
msg_box.setDetailedText(strace) 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.setWindowTitle("Houston, we have a problem...")
msg_box.setText("Whoops. A critical error has occured. This is most likely a bug " msg_box.setText("Whoops. A critical error has occured. "
"in Qubes Manager.<br><br>" "This is most likely a bug in Qubes Manager.<br><br>"
"<b><i>%s</i></b>" % error + "<b><i>%s</i></b>" % error +
"<br/>at line <b>%d</b><br/>of file %s.<br/><br/>" "<br/>at line <b>%d</b><br/>of file %s.<br/><br/>"
% ( line, filename )) % (line, filename))
msg_box.exec_() msg_box.exec_()
parser = qubesadmin.tools.QubesArgumentParser(vmname_nargs=1) parser = QubesArgumentParser(vmname_nargs=1)
parser.add_argument('--tab', metavar='TAB', parser.add_argument('--tab', metavar='TAB',
action='store', action='store',
@ -1018,12 +1080,10 @@ parser.set_defaults(
) )
def main(args=None): def main(args=None):
global settings_window
args = parser.parse_args(args) args = parser.parse_args(args)
vm = args.domains.pop() vm = args.domains.pop()
qapp = QApplication(sys.argv) qapp = QtGui.QApplication(sys.argv)
qapp.setOrganizationName('Invisible Things Lab') qapp.setOrganizationName('Invisible Things Lab')
qapp.setOrganizationDomain("https://www.qubes-os.org/") qapp.setOrganizationDomain("https://www.qubes-os.org/")
qapp.setApplicationName("Qubes VM Settings") qapp.setApplicationName("Qubes VM Settings")

View File

@ -1,9 +1,10 @@
#!/usr/bin/python2 #!/usr/bin/python3
# -*- coding: utf8 -*- # -*- coding: utf8 -*-
# #
# The Qubes OS Project, http://www.qubes-os.org # The Qubes OS Project, http://www.qubes-os.org
# #
# Copyright (C) 2014 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com> # Copyright (C) 2014 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.com>
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -15,61 +16,54 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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 power_order = QtCore.Qt.DescendingOrder
update_order = QtCore.Qt.AscendingOrder
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
row_height = 30 row_height = 30
class VmIconWidget (QWidget): class VmIconWidget(QtGui.QWidget):
def __init__(self, icon_path, enabled=True, size_multiplier=0.7, 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) super(VmIconWidget, self).__init__(parent)
self.label_icon = QLabel() self.label_icon = QtGui.QLabel()
if icon_path[0] in ':/': if icon_path[0] in ':/':
icon = QIcon (icon_path) icon = QtGui.QIcon(icon_path)
else: else:
icon = QIcon.fromTheme(icon_path) icon = QtGui.QIcon.fromTheme(icon_path)
icon_sz = QSize (row_height * size_multiplier, row_height * size_multiplier) icon_sz = QtCore.QSize(row_height * size_multiplier,
icon_pixmap = icon.pixmap(icon_sz, QIcon.Disabled if not enabled else QIcon.Normal) row_height * size_multiplier)
self.label_icon.setPixmap (icon_pixmap) icon_pixmap = icon.pixmap(
self.label_icon.setFixedSize (icon_sz) icon_sz,
if tooltip != None: 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) self.label_icon.setToolTip(tooltip)
layout = QHBoxLayout() layout = QtGui.QHBoxLayout()
layout.addWidget(self.label_icon) layout.addWidget(self.label_icon)
layout.setContentsMargins(0,0,0,0) layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout) self.setLayout(layout)
def setToolTip(self, tooltip): def setToolTip(self, tooltip): # pylint: disable=invalid-name
if tooltip is not None: if tooltip is not None:
self.label_icon.setToolTip(tooltip) self.label_icon.setToolTip(tooltip)
else: else:
self.label_icon.setToolTip('') self.label_icon.setToolTip('')
class VmTypeWidget(VmIconWidget):
class VmTypeItem(QTableWidgetItem): class VmTypeWidget(VmIconWidget):
class VmTypeItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm): def __init__(self, value, vm):
super(VmTypeWidget.VmTypeItem, self).__init__() super(VmTypeWidget.VmTypeItem, self).__init__()
self.value = value self.value = value
@ -84,40 +78,39 @@ class VmTypeWidget(VmIconWidget):
elif other.vm.qid == 0: elif other.vm.qid == 0:
return False return False
elif self.value == other.value: elif self.value == other.value:
return self.vm.qid < other.vm.qid return self.vm.name < other.vm.name
else: return self.value < other.value
return self.value < other.value
def __init__(self, vm, parent=None): def __init__(self, vm, parent=None):
(icon_path, tooltip) = self.get_vm_icon(vm) (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.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): def get_vm_icon(self, vm):
if vm.qid == 0: if vm.klass == 'AdminVM':
self.value = 0 self.value = 0
return (":/dom0.png", "Dom0") icon_name = "dom0"
elif vm.is_netvm() and not vm.is_proxyvm(): elif vm.klass == 'TemplateVM':
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():
self.value = 3 self.value = 3
return (":/templatevm.png", "TemplateVM") icon_name = "templatevm"
elif vm.is_appvm() or vm.is_disposablevm(): elif vm.klass == 'StandaloneVM':
self.value = 4
icon_name = "standalonevm"
else:
self.value = 5 + vm.label.index self.value = 5 + vm.label.index
return (":/appvm.png", "AppVM") icon_name = "appvm"
return ":/" + icon_name + ".png", vm.klass
class VmLabelWidget(VmIconWidget): class VmLabelWidget(VmIconWidget):
class VmLabelItem(QTableWidgetItem): class VmLabelItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm): def __init__(self, value, vm):
super(VmLabelWidget.VmLabelItem, self).__init__() super(VmLabelWidget.VmLabelItem, self).__init__()
self.value = value self.value = value
@ -132,32 +125,27 @@ class VmLabelWidget(VmIconWidget):
elif other.vm.qid == 0: elif other.vm.qid == 0:
return False return False
elif self.value == other.value: elif self.value == other.value:
return self.vm.qid < other.vm.qid return self.vm.name < other.vm.name
else: return self.value < other.value
return self.value < other.value
def __init__(self, vm, parent=None): def __init__(self, vm, parent=None):
icon_path = self.get_vm_icon_path(vm) 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.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): def get_vm_icon_path(self, vm):
if vm.qid == 0: self.value = vm.label.index
self.value = 100 return vm.label.icon
return ":/off.png"
else:
self.value = vm.label.index
return vm.label.icon
class VmNameItem(QtGui.QTableWidgetItem):
class VmNameItem (QTableWidgetItem):
def __init__(self, vm): def __init__(self, vm):
super(VmNameItem, self).__init__() super(VmNameItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.setText(vm.name) self.setText(vm.name)
self.setTextAlignment(Qt.AlignVCenter) self.setTextAlignment(QtCore.Qt.AlignVCenter)
self.qid = vm.qid self.qid = vm.qid
def __lt__(self, other): def __lt__(self, other):
@ -168,44 +156,43 @@ class VmNameItem (QTableWidgetItem):
return super(VmNameItem, self).__lt__(other) return super(VmNameItem, self).__lt__(other)
class VmStatusIcon(QLabel): class VmStatusIcon(QtGui.QLabel):
def __init__(self, vm, parent=None): def __init__(self, vm, parent=None):
super (VmStatusIcon, self).__init__(parent) super(VmStatusIcon, self).__init__(parent)
self.vm = vm self.vm = vm
self.set_on_icon() self.set_on_icon()
self.previous_power_state = vm.last_power_state self.previous_power_state = self.vm.get_power_state()
def update(self): 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.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): def set_on_icon(self):
if self.vm.last_power_state == "Running": if self.vm.get_power_state() == "Running":
icon = QIcon (":/on.png") icon = QtGui.QIcon(":/on.png")
elif self.vm.last_power_state in ["Paused", "Suspended"]: elif self.vm.get_power_state() in ["Paused", "Suspended"]:
icon = QIcon (":/paused.png") icon = QtGui.QIcon(":/paused.png")
elif self.vm.last_power_state in ["Transient", "Halting", "Dying"]: elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]:
icon = QIcon (":/transient.png") icon = QtGui.QIcon(":/transient.png")
else: 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) icon_pixmap = icon.pixmap(icon_sz)
self.setPixmap (icon_pixmap) self.setPixmap(icon_pixmap)
self.setFixedSize (icon_sz) self.setFixedSize(icon_sz)
class VmInfoWidget(QtGui.QWidget):
class VmInfoWidget (QWidget): class VmInfoItem(QtGui.QTableWidgetItem):
class VmInfoItem (QTableWidgetItem):
def __init__(self, upd_info_item, vm): def __init__(self, upd_info_item, vm):
super(VmInfoWidget.VmInfoItem, self).__init__() super(VmInfoWidget.VmInfoItem, self).__init__()
self.upd_info_item = upd_info_item self.upd_info_item = upd_info_item
self.vm = vm self.vm = vm
def __lt__(self, other): def __lt__(self, other):
# pylint: disable=too-many-return-statements
if self.vm.qid == 0: if self.vm.qid == 0:
return True return True
elif other.vm.qid == 0: elif other.vm.qid == 0:
@ -213,30 +200,34 @@ class VmInfoWidget (QWidget):
self_val = self.upd_info_item.value self_val = self.upd_info_item.value
other_val = other.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 # the result will be sorted by upd, sorting order: Ascending
self_val += 1 if self.vm.is_running() else 0 self_val += 1 if self.vm.is_running() else 0
other_val += 1 if other.vm.is_running() else 0 other_val += 1 if other.vm.is_running() else 0
if self_val == other_val: if self_val == other_val:
return self.vm.qid < other.vm.qid return self.vm.name < other.vm.name
else: return self_val > other_val
return self_val > other_val elif self.tableWidget().\
elif self.tableWidget().horizontalHeader().sortIndicatorOrder() == power_order: horizontalHeader().sortIndicatorOrder() == power_order:
#the result will be sorted by power state, sorting order: Descending # the result will be sorted by power state,
self_val = -(self_val/10 + 10*(1 if self.vm.is_running() else 0)) # sorting order: Descending
other_val = -(other_val/10 + 10*(1 if other.vm.is_running() else 0)) 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: if self_val == other_val:
return self.vm.qid < other.vm.qid return self.vm.name < other.vm.name
else: return self_val > other_val
return self_val > other_val
else: else:
#it would be strange if this happened # it would be strange if this happened
return return
def __init__(self, vm, parent = None): def __init__(self, vm, parent=None):
super (VmInfoWidget, self).__init__(parent) super(VmInfoWidget, self).__init__(parent)
self.vm = vm self.vm = vm
layout = QHBoxLayout () layout = QtGui.QHBoxLayout()
self.on_icon = VmStatusIcon(vm) self.on_icon = VmStatusIcon(vm)
self.upd_info = VmUpdateInfoWidget(vm, show_text=False) self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
@ -247,58 +238,43 @@ class VmInfoWidget (QWidget):
layout.addWidget(self.on_icon) layout.addWidget(self.on_icon)
layout.addWidget(self.upd_info) layout.addWidget(self.upd_info)
layout.addWidget(self.error_icon) 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.blk_icon)
layout.addWidget(self.rec_icon) layout.addWidget(self.rec_icon)
layout.setContentsMargins(5,0,5,0) layout.setContentsMargins(5, 0, 5, 0)
self.setLayout(layout) self.setLayout(layout)
self.rec_icon.setVisible(False) self.rec_icon.setVisible(False)
self.blk_icon.setVisible(False) self.blk_icon.setVisible(False)
self.error_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.on_icon.update()
self.upd_info.update_outdated(vm) 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): def __init__(self, vm):
super(VmTemplateItem, self).__init__() super(VmTemplateItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm self.vm = vm
if vm.template is not None: if getattr(vm, 'template', None) is not None:
self.setText(vm.template.name) self.setText(vm.template.name)
else: else:
font = QFont() font = QtGui.QFont()
font.setStyle(QFont.StyleItalic) font.setStyle(QtGui.QFont.StyleItalic)
self.setFont(font) self.setFont(font)
self.setTextColor(QColor("gray")) self.setTextColor(QtGui.QColor("gray"))
if vm.is_appvm(): # and vm.template is None self.setText(vm.klass)
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.setTextAlignment(Qt.AlignVCenter) self.setTextAlignment(QtCore.Qt.AlignVCenter)
def __lt__(self, other): def __lt__(self, other):
if self.vm.qid == 0: if self.vm.qid == 0:
@ -306,27 +282,22 @@ class VmTemplateItem (QTableWidgetItem):
elif other.vm.qid == 0: elif other.vm.qid == 0:
return False return False
elif self.text() == other.text(): elif self.text() == other.text():
return self.vm.qid < other.vm.qid return self.vm.name < other.vm.name
else: return super(VmTemplateItem, self).__lt__(other)
return super(VmTemplateItem, self).__lt__(other)
class VmNetvmItem(QtGui.QTableWidgetItem):
class VmNetvmItem (QTableWidgetItem):
def __init__(self, vm): def __init__(self, vm):
super(VmNetvmItem, self).__init__() super(VmNetvmItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm self.vm = vm
if vm.is_netvm() and not vm.is_proxyvm(): if getattr(vm, 'netvm', None) is None:
self.setText("n/a") self.setText("n/a")
elif vm.netvm is not None:
self.setText(vm.netvm.name)
else: else:
self.setText("---") self.setText(vm.netvm.name)
self.setTextAlignment(Qt.AlignVCenter) self.setTextAlignment(QtCore.Qt.AlignVCenter)
def __lt__(self, other): def __lt__(self, other):
if self.vm.qid == 0: if self.vm.qid == 0:
@ -334,22 +305,19 @@ class VmNetvmItem (QTableWidgetItem):
elif other.vm.qid == 0: elif other.vm.qid == 0:
return False return False
elif self.text() == other.text(): elif self.text() == other.text():
return self.vm.qid < other.vm.qid return self.vm.name < other.vm.name
else: return super(VmNetvmItem, self).__lt__(other)
return super(VmNetvmItem, self).__lt__(other)
class VmInternalItem(QTableWidgetItem):
class VmInternalItem(QtGui.QTableWidgetItem):
def __init__(self, vm): def __init__(self, vm):
super(VmInternalItem, self).__init__() super(VmInternalItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm self.vm = vm
self.internal = self.vm.internal self.internal = vm.features.get('internal', False)
if self.internal: self.setText("Yes" if self.internal else "")
self.setText("Yes")
else:
self.setText("")
def __lt__(self, other): def __lt__(self, other):
if self.vm.qid == 0: if self.vm.qid == 0:
@ -359,144 +327,10 @@ class VmInternalItem(QTableWidgetItem):
return super(VmInternalItem, self).__lt__(other) return super(VmInternalItem, self).__lt__(other)
class VmUsageBarWidget (QWidget): # features man qvm-features
class VmUpdateInfoWidget(QtGui.QWidget):
class VmUsageBarItem (QTableWidgetItem): class VmUpdateInfoItem(QtGui.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):
def __init__(self, value, vm): def __init__(self, value, vm):
super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__() super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
self.value = 0 self.value = 0
@ -517,91 +351,53 @@ class VmUpdateInfoWidget(QWidget):
elif other.vm.qid == 0: elif other.vm.qid == 0:
return False return False
elif self.value == other.value: elif self.value == other.value:
return self.vm.qid < other.vm.qid return self.vm.name < other.vm.name
else: return self.value < other.value
return self.value < other.value
def __init__(self, vm, show_text=True, parent = None): def __init__(self, vm, show_text=True, parent=None):
super (VmUpdateInfoWidget, self).__init__(parent) super(VmUpdateInfoWidget, self).__init__(parent)
layout = QHBoxLayout () layout = QtGui.QHBoxLayout()
self.show_text = show_text self.show_text = show_text
if self.show_text: if self.show_text:
self.label=QLabel("") self.label = QtGui.QLabel("")
layout.addWidget(self.label, alignment=Qt.AlignCenter) layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
else: else:
self.icon = QLabel("") self.icon = QtGui.QLabel("")
layout.addWidget(self.icon, alignment=Qt.AlignCenter) layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
self.setLayout(layout) self.setLayout(layout)
self.previous_outdated_state = None self.previous_outdated_state = None
self.previous_update_recommended = None self.previous_update_recommended = None
self.value = None self.value = None
self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm) self.table_item = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
def update_outdated(self, vm): def update_outdated(self, vm):
if vm.type == "HVM":
return
if vm.is_outdated(): outdated_state = False
outdated_state = "outdated"
# During TemplateVM shutdown, there's an interval of a few seconds try:
# during which vm.template.is_running() returns false but for vol in vm.volumes:
# vm.is_outdated() does not yet return true, so the icon disappears. if vol.is_outdated():
# This looks goofy, but we've decided not to fix it at this time outdated_state = "outdated"
# (2015-02-09). break
elif vm.template and vm.template.is_running(): except AttributeError:
pass
if not outdated_state and getattr(vm, 'template', None)\
and vm.template.is_running():
outdated_state = "to-be-outdated" outdated_state = "to-be-outdated"
else:
outdated_state = None
if outdated_state != self.previous_outdated_state: if outdated_state != self.previous_outdated_state:
self.update_status_widget(outdated_state) self.update_status_widget(outdated_state)
self.previous_outdated_state = outdated_state self.previous_outdated_state = outdated_state
if not vm.is_updateable(): updates_available = vm.features.get('updates-available', False)
return if updates_available != self.previous_update_recommended:
self.update_status_widget("update" if updates_available else None)
if vm.qid == 0: self.previous_update_recommended = updates_available
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
def update_status_widget(self, state): def update_status_widget(self, state):
self.value = state self.value = state
self.tableItem.set_value(state) self.table_item.set_value(state)
if state == "update": if state == "update":
label_text = "<font color=\"#CCCC00\">Check updates</font>" label_text = "<font color=\"#CCCC00\">Check updates</font>"
icon_path = ":/update-recommended.png" icon_path = ":/update-recommended.png"
@ -618,7 +414,7 @@ class VmUpdateInfoWidget(QWidget):
tooltip_text = self.tr( tooltip_text = self.tr(
"The TemplateVM must be stopped before changes from its " "The TemplateVM must be stopped before changes from its "
"current session can be picked up by this VM.") "current session can be picked up by this VM.")
elif state is None: else:
label_text = "" label_text = ""
icon_path = None icon_path = None
tooltip_text = None tooltip_text = None
@ -632,26 +428,27 @@ class VmUpdateInfoWidget(QWidget):
self.icon = VmIconWidget(icon_path, True, 0.7) self.icon = VmIconWidget(icon_path, True, 0.7)
self.icon.setToolTip(tooltip_text) self.icon.setToolTip(tooltip_text)
else: else:
self.icon = QLabel(label_text) self.icon = QtGui.QLabel(label_text)
self.layout().addWidget(self.icon, alignment=Qt.AlignCenter) self.layout().addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
class VmSizeOnDiskItem (QTableWidgetItem): class VmSizeOnDiskItem(QtGui.QTableWidgetItem):
def __init__(self, vm): def __init__(self, vm):
super(VmSizeOnDiskItem, self).__init__() super(VmSizeOnDiskItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm self.vm = vm
self.value = 0 self.value = 0
self.update() self.update()
self.setTextAlignment(Qt.AlignVCenter) self.setTextAlignment(QtCore.Qt.AlignVCenter)
def update(self): def update(self):
if self.vm.qid == 0: if self.vm.qid == 0:
self.setText("n/a") self.setText("n/a")
else: else:
self.value = self.vm.get_disk_utilization()/(1024*1024) self.value = 10
self.setText( str(self.value) + " MiB") self.value = round(self.vm.get_disk_utilization()/(1024*1024), 2)
self.setText(str(self.value) + " MiB")
def __lt__(self, other): def __lt__(self, other):
if self.vm.qid == 0: if self.vm.qid == 0:
@ -659,21 +456,18 @@ class VmSizeOnDiskItem (QTableWidgetItem):
elif other.vm.qid == 0: elif other.vm.qid == 0:
return False return False
elif self.value == other.value: elif self.value == other.value:
return self.vm.qid < other.vm.qid return self.vm.name < other.vm.name
else: return self.value < other.value
return self.value < other.value
class VmIPItem(QTableWidgetItem):
class VmIPItem(QtGui.QTableWidgetItem):
def __init__(self, vm): def __init__(self, vm):
super(VmIPItem, self).__init__() super(VmIPItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm self.vm = vm
self.ip = self.vm.ip self.ip = getattr(self.vm, 'ip', None)
if self.ip: self.setText(self.ip if self.ip is not None else 'n/a')
self.setText(self.ip)
else:
self.setText("n/a")
def __lt__(self, other): def __lt__(self, other):
if self.vm.qid == 0: if self.vm.qid == 0:
@ -682,13 +476,14 @@ class VmIPItem(QTableWidgetItem):
return False return False
return super(VmIPItem, self).__lt__(other) return super(VmIPItem, self).__lt__(other)
class VmIncludeInBackupsItem(QTableWidgetItem):
class VmIncludeInBackupsItem(QtGui.QTableWidgetItem):
def __init__(self, vm): def __init__(self, vm):
super(VmIncludeInBackupsItem, self).__init__() super(VmIncludeInBackupsItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm self.vm = vm
if self.vm.include_in_backups: if getattr(self.vm, 'include_in_backups', None):
self.setText("Yes") self.setText("Yes")
else: else:
self.setText("") self.setText("")
@ -699,18 +494,18 @@ class VmIncludeInBackupsItem(QTableWidgetItem):
elif other.vm.qid == 0: elif other.vm.qid == 0:
return False return False
elif self.vm.include_in_backups == other.vm.include_in_backups: elif self.vm.include_in_backups == other.vm.include_in_backups:
return self.vm.qid < other.vm.qid return self.vm.name < other.vm.name
else: return self.vm.include_in_backups < other.vm.include_in_backups
return self.vm.include_in_backups < other.vm.include_in_backups
class VmLastBackupItem(QTableWidgetItem):
class VmLastBackupItem(QtGui.QTableWidgetItem):
def __init__(self, vm): def __init__(self, vm):
super(VmLastBackupItem, self).__init__() super(VmLastBackupItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm self.vm = vm
if self.vm.backup_timestamp: if getattr(self.vm, 'backup_timestamp', None):
self.setText(str(self.vm.backup_timestamp.date())) self.setText(self.vm.backup_timestamp)
else: else:
self.setText("") self.setText("")
@ -720,10 +515,9 @@ class VmLastBackupItem(QTableWidgetItem):
elif other.vm.qid == 0: elif other.vm.qid == 0:
return False return False
elif self.vm.backup_timestamp == other.vm.backup_timestamp: 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: elif not self.vm.backup_timestamp:
return False return False
elif not other.vm.backup_timestamp: elif not other.vm.backup_timestamp:
return True return True
else: return self.vm.backup_timestamp < other.vm.backup_timestamp
return self.vm.backup_timestamp < other.vm.backup_timestamp

View File

@ -14,18 +14,17 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU Lesser General Public License along
# along with this program; if not, write to the Free Software # with this program; if not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# #
# #
from PyQt4.QtCore import * import PyQt4.QtCore # pylint: disable=import-error
import threading import threading
class ThreadMonitor(QObject): class ThreadMonitor(PyQt4.QtCore.QObject):
def __init__(self): def __init__(self):
self.success = True self.success = True
self.error_msg = None self.error_msg = None
@ -41,4 +40,3 @@ class ThreadMonitor(QObject):
def set_finished(self): def set_finished(self):
self.event_finished.set() self.event_finished.set()

View File

@ -20,12 +20,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import functools
import os import os
import re import re
import qubesadmin import qubesadmin
from PyQt4.QtGui import QIcon from PyQt4.QtGui import QIcon # pylint: disable=import-error
def _filter_internal(vm): def _filter_internal(vm):
return (not vm.klass == 'AdminVM' return (not vm.klass == 'AdminVM'
@ -153,11 +152,11 @@ def get_path_from_vm(vm, service_name):
if not vm: if not vm:
return None 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] untrusted_path = stdout.decode(encoding='ascii')[:path_max_len]
if len(untrusted_path) == 0: if not untrusted_path:
return None return None
if path_re.match(untrusted_path): if path_re.match(untrusted_path):
assert '../' not in untrusted_path assert '../' not in untrusted_path

View File

@ -43,7 +43,9 @@ cp qubesmanager/qvm_about.sh $RPM_BUILD_ROOT/usr/libexec/qubes-manager/
mkdir -p $RPM_BUILD_ROOT/usr/share/applications mkdir -p $RPM_BUILD_ROOT/usr/share/applications
cp qubes-global-settings.desktop $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-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 %post
update-desktop-database &> /dev/null || : update-desktop-database &> /dev/null || :
@ -60,6 +62,9 @@ rm -rf $RPM_BUILD_ROOT
/usr/bin/qubes-vm-settings /usr/bin/qubes-vm-settings
/usr/bin/qubes-vm-create /usr/bin/qubes-vm-create
/usr/bin/qubes-vm-boot-from-device /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/mount_for_backup.sh
/usr/libexec/qubes-manager/qvm_about.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/informationnotes.py
%{python3_sitelib}/qubesmanager/create_new_vm.py %{python3_sitelib}/qubesmanager/create_new_vm.py
%{python3_sitelib}/qubesmanager/thread_monitor.py %{python3_sitelib}/qubesmanager/thread_monitor.py
%{python3_sitelib}/qubesmanager/qube_manager.py
%{python3_sitelib}/qubesmanager/utils.py %{python3_sitelib}/qubesmanager/utils.py
%{python3_sitelib}/qubesmanager/bootfromdevice.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_about.py
%{python3_sitelib}/qubesmanager/ui_releasenotes.py %{python3_sitelib}/qubesmanager/ui_releasenotes.py
%{python3_sitelib}/qubesmanager/ui_informationnotes.py %{python3_sitelib}/qubesmanager/ui_informationnotes.py
%{python3_sitelib}/qubesmanager/ui_qubemanager.py
%{python3_sitelib}/qubesmanager/i18n/qubesmanager_*.qm %{python3_sitelib}/qubesmanager/i18n/qubesmanager_*.qm
%{python3_sitelib}/qubesmanager/i18n/qubesmanager_*.ts %{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-global-settings.desktop
/usr/share/applications/qubes-vm-create.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

View File

@ -21,6 +21,9 @@ if __name__ == '__main__':
'qubes-global-settings = qubesmanager.global_settings:main', 'qubes-global-settings = qubesmanager.global_settings:main',
'qubes-vm-settings = qubesmanager.settings:main', 'qubes-vm-settings = qubesmanager.settings:main',
'qubes-vm-create = qubesmanager.create_new_vm: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'
], ],
}) })

View File

@ -0,0 +1,5 @@
class BackupCanceledError(BaseException):
tmpdir = None
pass

View File

@ -0,0 +1 @@
pass

View File

@ -0,0 +1,2 @@
def get_disk_usage(*args):
return 0

View File

@ -0,0 +1,4 @@
class Qubes(object):
pass
DEFAULT = object()

View File

@ -0,0 +1 @@
pass

View File

@ -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

View File

@ -0,0 +1,4 @@
### mock qubesadmin.devices module
class DeviceAssignment(object):
pass

View File

@ -0,0 +1,5 @@
class EventsDispatcher(object):
def add_handler(self, *args):
pass
pass

View File

@ -0,0 +1,6 @@
### mock qubesadmin.exc module
# pylint: disable=unused-variable
class QubesException(BaseException):
pass

View File

@ -0,0 +1,4 @@
### mock qubesadmin.firewall module
class Rule(object):
pass

View File

@ -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

View File

@ -0,0 +1,4 @@
### mock qvm_start module
def main(*args, **kwargs):
pass

View File

@ -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]

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>650</width> <width>737</width>
<height>399</height> <height>420</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -23,73 +23,8 @@
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="1">
<widget class="QPushButton" name="shutdown_running_vms_button">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Shutdown all running selected VMs</string>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/shutdownvm.png</normaloff>:/shutdownvm.png</iconset>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="refresh_button">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Refresh running states.</string>
</property>
</widget>
</item>
<item row="1" column="0" rowspan="2">
<widget class="QLabel" name="running_vms_warning">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<italic>true</italic>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">color:rgb(255, 0, 0)</string>
</property>
<property name="text">
<string>Some of the selected VMs are running (red). Running VMs cannot be backed up!</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4">
<property name="font">
<font>
<pointsize>9</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
<underline>false</underline>
</font>
</property>
<property name="text"> <property name="text">
<string>Select VMs to backup:</string> <string>Select VMs to backup:</string>
</property> </property>
@ -145,12 +80,79 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QCheckBox" name="compress_checkbox">
<property name="text">
<string>Compress the backup</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="unrecognized_config_label">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>139</red>
<green>142</green>
<blue>142</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<weight>75</weight>
<italic>true</italic>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Warning: unrecognized data found in configuration files. </string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="select_dir_page"> <widget class="QWizardPage" name="select_dir_page">
<layout class="QGridLayout" name="gridLayout_5"> <layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title"> <property name="title">
<string>Backup destination directory</string> <string>Backup destination directory</string>
</property> </property>
@ -188,73 +190,106 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="3" column="0">
<widget class="QGroupBox" name="groupBox_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Save backup profile</string>
</property>
<widget class="QLabel" name="label_8">
<property name="geometry">
<rect>
<x>10</x>
<y>30</y>
<width>267</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>Save settings as default backup profile:</string>
</property>
</widget>
<widget class="QCheckBox" name="save_profile_checkbox">
<property name="geometry">
<rect>
<x>270</x>
<y>30</y>
<width>539</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title"> <property name="title">
<string>Backup security</string> <string>Backup security</string>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QFormLayout" name="formLayout">
<item row="2" column="0"> <property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_12"> <widget class="QLabel" name="label_12">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Encryption / Verification&lt;br/&gt;passphrase:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Encryption / Verification&lt;br/&gt;passphrase:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="1" column="1">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Encrypt backup:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="encryption_checkbox">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reenter passphrase:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="passphrase_line_edit"> <widget class="QLineEdit" name="passphrase_line_edit">
<property name="echoMode"> <property name="echoMode">
<enum>QLineEdit::Password</enum> <enum>QLineEdit::Password</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="4" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reenter passphrase:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="passphrase_line_edit_verify"> <widget class="QLineEdit" name="passphrase_line_edit_verify">
<property name="echoMode"> <property name="echoMode">
<enum>QLineEdit::Password</enum> <enum>QLineEdit::Password</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;b&gt;Note:&lt;/b&gt; 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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>NOTE: Only running VMs are listed.</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="confirm_page"> <widget class="QWizardPage" name="confirm_page">
@ -281,8 +316,8 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -324,6 +359,9 @@ p, li { white-space: pre-wrap; }
</item> </item>
<item> <item>
<widget class="QProgressBar" name="progress_bar"> <widget class="QProgressBar" name="progress_bar">
<property name="maximum">
<number>0</number>
</property>
<property name="value"> <property name="value">
<number>0</number> <number>0</number>
</property> </property>

View File

@ -31,10 +31,22 @@
<property name="verticalSpacing"> <property name="verticalSpacing">
<number>6</number> <number>6</number>
</property> </property>
<item row="2" column="0"> <item row="2" column="2">
<widget class="QLabel" name="label"> <widget class="QRadioButton" name="udp_radio">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text"> <property name="text">
<string>Protocol</string> <string>UDP</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -45,13 +57,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" colspan="3">
<widget class="QComboBox" name="serviceComboBox">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
@ -59,6 +64,31 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="3">
<widget class="QRadioButton" name="any_radio">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>71</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string/>
</property>
<property name="text">
<string> Any </string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3"> <item row="0" column="1" colspan="3">
<widget class="QComboBox" name="addressComboBox"> <widget class="QComboBox" name="addressComboBox">
<property name="editable"> <property name="editable">
@ -66,6 +96,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" colspan="3">
<widget class="QComboBox" name="serviceComboBox">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QRadioButton" name="tcp_radio"> <widget class="QRadioButton" name="tcp_radio">
<property name="sizePolicy"> <property name="sizePolicy">
@ -85,44 +122,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="2"> <item row="2" column="0">
<widget class="QRadioButton" name="udp_radio"> <widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text"> <property name="text">
<string>UDP</string> <string>Protocol</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QRadioButton" name="any_radio">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>71</width>
<height>0</height>
</size>
</property>
<property name="text">
<string> Any </string>
</property>
<property name="checked">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>

841
ui/qubemanager.ui Normal file
View File

@ -0,0 +1,841 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VmManagerWindow</class>
<widget class="QMainWindow" name="VmManagerWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>769</width>
<height>385</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="windowTitle">
<string>Qube Manager</string>
</property>
<property name="windowIcon">
<iconset theme="qubes-manager">
<normaloff/>
</iconset>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<widget class="QWidget" name="centralwidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<layout class="QHBoxLayout" name="searchContainer">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Search:</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QTableWidget" name="table">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>200</width>
<height>30</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="gridStyle">
<enum>Qt::NoPen</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<property name="rowCount">
<number>10</number>
</property>
<property name="columnCount">
<number>11</number>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>150</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>150</number>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<row>
<property name="text">
<string>Nowy wiersz</string>
</property>
</row>
<row/>
<row/>
<row/>
<row/>
<row/>
<row/>
<row/>
<row/>
<row/>
<column>
<property name="text">
<string/>
</property>
</column>
<column>
<property name="text">
<string/>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="toolTip">
<string>VM name</string>
</property>
</column>
<column>
<property name="text">
<string>State</string>
</property>
<property name="toolTip">
<string>Update info</string>
</property>
</column>
<column>
<property name="text">
<string>Template</string>
</property>
<property name="toolTip">
<string>VM's template</string>
</property>
</column>
<column>
<property name="text">
<string>NetVM</string>
</property>
<property name="toolTip">
<string>VM's netVM</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
</column>
<column>
<property name="text">
<string>Internal</string>
</property>
</column>
<column>
<property name="text">
<string>IP</string>
</property>
</column>
<column>
<property name="text">
<string>Backups</string>
</property>
</column>
<column>
<property name="text">
<string>Last backup</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>769</width>
<height>28</height>
</rect>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<widget class="QMenu" name="menu_system">
<property name="title">
<string>&amp;System</string>
</property>
<addaction name="action_global_settings"/>
<addaction name="action_show_network"/>
<addaction name="action_backup"/>
<addaction name="action_restore"/>
</widget>
<widget class="QMenu" name="menu_view">
<property name="title">
<string>&amp;View</string>
</property>
<addaction name="action_vm_type"/>
<addaction name="action_label"/>
<addaction name="action_name"/>
<addaction name="action_state"/>
<addaction name="action_template"/>
<addaction name="action_netvm"/>
<addaction name="action_size_on_disk"/>
<addaction name="action_internal"/>
<addaction name="action_ip"/>
<addaction name="action_backups"/>
<addaction name="action_last_backup"/>
<addaction name="separator"/>
<addaction name="action_toolbar"/>
<addaction name="action_menubar"/>
<addaction name="separator"/>
<addaction name="separator"/>
<addaction name="action_search"/>
<addaction name="action_refresh_list"/>
</widget>
<widget class="QMenu" name="menu_vm">
<property name="title">
<string>&amp;Qube</string>
</property>
<widget class="QMenu" name="logs_menu">
<property name="title">
<string>&amp;Logs</string>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/log.png</normaloff>:/log.png</iconset>
</property>
</widget>
<widget class="QMenu" name="blk_menu">
<property name="title">
<string>Attach/detach &amp;block devices</string>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/mount.png</normaloff>:/mount.png</iconset>
</property>
</widget>
<addaction name="action_createvm"/>
<addaction name="action_removevm"/>
<addaction name="action_clonevm"/>
<addaction name="separator"/>
<addaction name="action_resumevm"/>
<addaction name="action_pausevm"/>
<addaction name="action_shutdownvm"/>
<addaction name="action_restartvm"/>
<addaction name="action_killvm"/>
<addaction name="separator"/>
<addaction name="action_settings"/>
<addaction name="action_editfwrules"/>
<addaction name="action_appmenus"/>
<addaction name="action_updatevm"/>
<addaction name="action_run_command_in_vm"/>
<addaction name="action_set_keyboard_layout"/>
<addaction name="separator"/>
<addaction name="logs_menu"/>
<addaction name="blk_menu"/>
</widget>
<widget class="QMenu" name="menu_about">
<property name="title">
<string>&amp;About</string>
</property>
<addaction name="action_about_qubes"/>
</widget>
<addaction name="menu_system"/>
<addaction name="menu_vm"/>
<addaction name="menu_view"/>
<addaction name="menu_about"/>
</widget>
<widget class="QToolBar" name="toolbar">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="windowTitle">
<string>toolBar</string>
</property>
<property name="allowedAreas">
<set>Qt::BottomToolBarArea|Qt::TopToolBarArea</set>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_createvm"/>
<addaction name="action_removevm"/>
<addaction name="separator"/>
<addaction name="action_resumevm"/>
<addaction name="action_pausevm"/>
<addaction name="action_shutdownvm"/>
<addaction name="action_restartvm"/>
<addaction name="separator"/>
<addaction name="action_settings"/>
<addaction name="action_editfwrules"/>
<addaction name="action_appmenus"/>
<addaction name="action_updatevm"/>
<addaction name="action_set_keyboard_layout"/>
<addaction name="separator"/>
<addaction name="action_global_settings"/>
<addaction name="action_backup"/>
<addaction name="action_restore"/>
<addaction name="separator"/>
<addaction name="action_refresh_list"/>
</widget>
<action name="action_createvm">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/createvm.png</normaloff>:/createvm.png</iconset>
</property>
<property name="text">
<string>Create &amp;New Qube</string>
</property>
<property name="toolTip">
<string>Create a new Qube</string>
</property>
</action>
<action name="action_removevm">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/removevm.png</normaloff>:/removevm.png</iconset>
</property>
<property name="text">
<string>&amp;Delete Qube</string>
</property>
<property name="toolTip">
<string>Remove an existing Qube (must be stopped first)</string>
</property>
</action>
<action name="action_resumevm">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/resumevm.png</normaloff>:/resumevm.png</iconset>
</property>
<property name="text">
<string>Start/Resu&amp;me Qube</string>
</property>
<property name="toolTip">
<string>Start/Resume selected Qube</string>
</property>
</action>
<action name="action_pausevm">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/pausevm.png</normaloff>:/pausevm.png</iconset>
</property>
<property name="text">
<string>&amp;Pause Qube</string>
</property>
<property name="toolTip">
<string>Pause selected Qube</string>
</property>
</action>
<action name="action_shutdownvm">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/shutdownvm.png</normaloff>:/shutdownvm.png</iconset>
</property>
<property name="text">
<string>&amp;Shutdown Qube</string>
</property>
<property name="toolTip">
<string>Shutdown selected Qube</string>
</property>
</action>
<action name="action_restartvm">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/restartvm.png</normaloff>:/restartvm.png</iconset>
</property>
<property name="text">
<string>Restar&amp;t Qube</string>
</property>
<property name="toolTip">
<string>Restart selected Qube</string>
</property>
</action>
<action name="action_appmenus">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/apps.png</normaloff>:/apps.png</iconset>
</property>
<property name="text">
<string>Add/remove app s&amp;hortcuts</string>
</property>
<property name="toolTip">
<string>Add/remove app shortcuts for this Qube</string>
</property>
</action>
<action name="action_updatevm">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/updateable.png</normaloff>:/updateable.png</iconset>
</property>
<property name="text">
<string>&amp;Update Qube</string>
</property>
<property name="toolTip">
<string>Update Qube system</string>
</property>
</action>
<action name="action_editfwrules">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/firewall.png</normaloff>:/firewall.png</iconset>
</property>
<property name="text">
<string>Edit Qube &amp;firewall rules</string>
</property>
<property name="toolTip">
<string>Edit Qube firewall rules</string>
</property>
</action>
<action name="action_showgraphs">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/showcpuload.png</normaloff>:/showcpuload.png</iconset>
</property>
<property name="text">
<string>Show graphs</string>
</property>
<property name="toolTip">
<string>Show Graphs</string>
</property>
</action>
<action name="action_options">
<property name="text">
<string>Options</string>
</property>
</action>
<action name="action_view">
<property name="text">
<string>View</string>
</property>
</action>
<action name="action_template">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Template</string>
</property>
</action>
<action name="action_netvm">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;NetVM</string>
</property>
</action>
<action name="action_settings">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/settings.png</normaloff>:/settings.png</iconset>
</property>
<property name="text">
<string>Qube s&amp;ettings</string>
</property>
<property name="toolTip">
<string>Qube Settings</string>
</property>
</action>
<action name="action_restore">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/restore.png</normaloff>:/restore.png</iconset>
</property>
<property name="text">
<string>&amp;Restore Qubes from backup</string>
</property>
<property name="toolTip">
<string>Restore Qubes from backup</string>
</property>
</action>
<action name="action_backup">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/backup.png</normaloff>:/backup.png</iconset>
</property>
<property name="text">
<string>&amp;Backup Qubes</string>
</property>
<property name="toolTip">
<string>Backup Qubes</string>
</property>
</action>
<action name="action_global_settings">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/global-settings.png</normaloff>:/global-settings.png</iconset>
</property>
<property name="text">
<string>&amp;Global settings</string>
</property>
</action>
<action name="action_show_network">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/networking.png</normaloff>:/networking.png</iconset>
</property>
<property name="text">
<string>&amp;Qubes Network</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="action_state">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;State</string>
</property>
</action>
<action name="action_killvm">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/killvm.png</normaloff>:/killvm.png</iconset>
</property>
<property name="text">
<string>&amp;Kill Qube</string>
</property>
<property name="toolTip">
<string>Kill selected Qube</string>
</property>
</action>
<action name="action_set_keyboard_layout">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/kbd-layout.png</normaloff>:/kbd-layout.png</iconset>
</property>
<property name="text">
<string>Set keyboard la&amp;yout</string>
</property>
<property name="toolTip">
<string>Set keyboard layout per Qube</string>
</property>
</action>
<action name="action_vm_type">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>T&amp;ype</string>
</property>
<property name="toolTip">
<string>Qube Type</string>
</property>
</action>
<action name="action_label">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Label</string>
</property>
</action>
<action name="action_name">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>N&amp;ame</string>
</property>
</action>
<action name="action_toolbar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Show tool bar</string>
</property>
</action>
<action name="action_menubar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Show menu bar</string>
</property>
</action>
<action name="action_about_qubes">
<property name="icon">
<iconset theme="qubes-manager">
<normaloff/>
</iconset>
</property>
<property name="text">
<string>&amp;Qubes OS</string>
</property>
</action>
<action name="action_size_on_disk">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Si&amp;ze</string>
</property>
<property name="toolTip">
<string>Size on Disk</string>
</property>
</action>
<action name="action_run_command_in_vm">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/run-command.png</normaloff>:/run-command.png</iconset>
</property>
<property name="text">
<string>&amp;Run command in Qube</string>
</property>
<property name="toolTip">
<string>Run command in the specified Qube</string>
</property>
</action>
<action name="action_clonevm">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/templatevm.png</normaloff>:/templatevm.png</iconset>
</property>
<property name="text">
<string>&amp;Clone Qube</string>
</property>
<property name="toolTip">
<string>Clone Qube</string>
</property>
</action>
<action name="action_internal">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Inte&amp;rnal</string>
</property>
<property name="toolTip">
<string>Is an internal Qube</string>
</property>
</action>
<action name="action_startvm_tools_install">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/resumevm.png</normaloff>:/resumevm.png</iconset>
</property>
<property name="text">
<string>Start Qube for Window Tools installation</string>
</property>
<property name="toolTip">
<string>Start Qube for Window Tools installation</string>
</property>
</action>
<action name="action_ip">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;IP</string>
</property>
</action>
<action name="action_backups">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Include in &amp;backups</string>
</property>
</action>
<action name="action_last_backup">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Last back&amp;up</string>
</property>
</action>
<action name="action_search">
<property name="text">
<string>Search</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
<action name="action_refresh_list">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/outdated.png</normaloff>:/outdated.png</iconset>
</property>
<property name="text">
<string>Refresh Qube List</string>
</property>
<property name="toolTip">
<string>Refresh Qube List</string>
</property>
</action>
</widget>
<resources>
<include location="../resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -52,7 +52,7 @@
<string>Ignore missing templates or netvms, restore VMs anyway.</string> <string>Ignore missing templates or netvms, restore VMs anyway.</string>
</property> </property>
<property name="text"> <property name="text">
<string>ignore missing</string> <string>ignore missing templates and net VMs</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -147,6 +147,9 @@
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
<property name="checked">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
@ -211,8 +214,8 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -241,6 +244,9 @@ p, li { white-space: pre-wrap; }
</item> </item>
<item> <item>
<widget class="QProgressBar" name="progress_bar"> <widget class="QProgressBar" name="progress_bar">
<property name="maximum">
<number>0</number>
</property>
<property name="value"> <property name="value">
<number>0</number> <number>0</number>
</property> </property>

View File

@ -29,7 +29,7 @@
<locale language="English" country="UnitedStates"/> <locale language="English" country="UnitedStates"/>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>2</number>
</property> </property>
<widget class="QWidget" name="basic_tab"> <widget class="QWidget" name="basic_tab">
<property name="locale"> <property name="locale">
@ -170,7 +170,7 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="6" column="0">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -373,6 +373,26 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="4" column="1">
<widget class="QPushButton" name="delete_vm_button">
<property name="styleSheet">
<string notr="true">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;</string>
</property>
<property name="text">
<string>Delete VM</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QPushButton" name="clone_vm_button">
<property name="text">
<string>Clone VM</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="advanced_tab"> <widget class="QWidget" name="advanced_tab">
@ -580,7 +600,7 @@
<widget class="QComboBox" name="default_dispvm"/> <widget class="QComboBox" name="default_dispvm"/>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="2" column="0" colspan="2">
<widget class="QPushButton" name="bootFromDeviceButton"> <widget class="QPushButton" name="boot_from_device_button">
<property name="text"> <property name="text">
<string>Boot qube from CDROM</string> <string>Boot qube from CDROM</string>
</property> </property>
@ -769,47 +789,198 @@
</attribute> </attribute>
<layout class="QGridLayout" name="gridLayout_8"> <layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QRadioButton" name="policyAllowRadioButton"> <widget class="QLabel" name="no_netvm_label">
<property name="text"> <property name="palette">
<string>Allow network access except...</string> <palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>139</red>
<green>142</green>
<blue>142</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property> </property>
</widget> <property name="font">
</item> <font>
<item row="0" column="1"> <weight>75</weight>
<widget class="QCheckBox" name="icmpCheckBox"> <italic>true</italic>
<property name="minimumSize"> <bold>true</bold>
<size> </font>
<width>323</width>
<height>0</height>
</size>
</property> </property>
<property name="text"> <property name="text">
<string>Allow ICMP traffic</string> <string>This qube has no networking - it will not have any network access anyway.</string>
</property> </property>
<property name="checked"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QRadioButton" name="policyDenyRadioButton"> <widget class="QLabel" name="netvm_no_firewall_label">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>139</red>
<green>142</green>
<blue>142</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<weight>75</weight>
<italic>true</italic>
<bold>true</bold>
</font>
</property>
<property name="text"> <property name="text">
<string>Deny network access except...</string> <string>Networking qube does not support 'qubes-firewall' - firewall restrictions will not be applied.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="2" column="0">
<widget class="QCheckBox" name="dnsCheckBox"> <widget class="QLabel" name="firewall_modified_outside_label">
<property name="text"> <property name="palette">
<string>Allow DNS queries</string> <palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>139</red>
<green>142</green>
<blue>142</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property> </property>
<property name="checked"> <property name="font">
<bool>true</bool> <font>
<weight>75</weight>
<italic>true</italic>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Firewall has been modified manually - please use qvm-firewall for any further configuration.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QRadioButton" name="policy_allow_radio_button">
<property name="text">
<string>Allow all outgoing Internet connections</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="policy_deny_radio_button">
<property name="toolTip">
<string>Changing firewall settings does NOT affect existing connections.</string>
</property>
<property name="text">
<string>Limit outgoing Internet connections to ...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0" colspan="2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="firewal_rules_label">
<property name="text">
<string>List of allowed (whitelisted) addresses:</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<layout class="QHBoxLayout" name="firewallRulesLayout">
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum> <enum>QLayout::SetMaximumSize</enum>
</property> </property>
@ -851,7 +1022,7 @@
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_6"> <layout class="QVBoxLayout" name="verticalLayout_6">
<item> <item>
<widget class="QPushButton" name="newRuleButton"> <widget class="QPushButton" name="new_rule_button">
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
@ -868,7 +1039,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="editRuleButton"> <widget class="QPushButton" name="edit_rule_button">
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
@ -885,7 +1056,10 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="deleteRuleButton"> <widget class="QPushButton" name="delete_rule_button">
<property name="toolTip">
<string>Changing firewall settings does NOT affect existing connections.</string>
</property>
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
@ -918,14 +1092,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="1"> <item row="8" column="0">
<widget class="QCheckBox" name="yumproxyCheckBox">
<property name="text">
<string>Allow connections to Updates Proxy</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QWidget" name="tempFullAccessWidget" native="true"> <widget class="QWidget" name="tempFullAccessWidget" native="true">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
@ -940,15 +1107,15 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="0"> <item row="1" column="0">
<widget class="QCheckBox" name="tempFullAccess"> <widget class="QCheckBox" name="temp_full_access">
<property name="text"> <property name="text">
<string>Allow full access for </string> <string>Allow full access for </string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="tempFullAccessTime"> <widget class="QSpinBox" name="temp_full_access_time">
<property name="suffix"> <property name="suffix">
<string> min</string> <string> min</string>
</property> </property>
@ -957,9 +1124,35 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="9" column="0" colspan="2">
<widget class="QLabel" name="label_22">
<property name="text">
<string>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.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="devices_tab"> <widget class="QWidget" name="devices_tab">
@ -1162,15 +1355,10 @@
<tabstop>vcpus</tabstop> <tabstop>vcpus</tabstop>
<tabstop>include_in_balancing</tabstop> <tabstop>include_in_balancing</tabstop>
<tabstop>kernel</tabstop> <tabstop>kernel</tabstop>
<tabstop>policyAllowRadioButton</tabstop> <tabstop>new_rule_button</tabstop>
<tabstop>policyDenyRadioButton</tabstop>
<tabstop>icmpCheckBox</tabstop>
<tabstop>dnsCheckBox</tabstop>
<tabstop>yumproxyCheckBox</tabstop>
<tabstop>newRuleButton</tabstop>
<tabstop>rulesTreeView</tabstop> <tabstop>rulesTreeView</tabstop>
<tabstop>editRuleButton</tabstop> <tabstop>edit_rule_button</tabstop>
<tabstop>deleteRuleButton</tabstop> <tabstop>delete_rule_button</tabstop>
<tabstop>service_line_edit</tabstop> <tabstop>service_line_edit</tabstop>
<tabstop>add_srv_button</tabstop> <tabstop>add_srv_button</tabstop>
<tabstop>services_list</tabstop> <tabstop>services_list</tabstop>

View File

@ -1 +1 @@
4.0.6 4.0.10