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
dist: trusty
language: generic
install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
script: ~/qubes-builder/scripts/travis-build
language: python
python:
- '3.5'
install:
- pip install --quiet -r ci/requirements.txt
- git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
script:
- PYTHONPATH=test-packages pylint --rcfile=ci/pylintrc qubesmanager
- ~/qubes-builder/scripts/travis-build
env:
- DIST_DOM0=fc23 USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1
- DIST_DOM0=fc25 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1

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

View File

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

View File

@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
#
# The Qubes OS Project, http://www.qubes-os.org
#
@ -15,84 +15,67 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
#
#
import traceback
import signal
from qubesadmin import Qubes, exc
from qubesadmin import utils as admin_utils
from qubes.storage.file import get_disk_usage
from PyQt4 import QtCore # pylint: disable=import-error
from PyQt4 import QtGui # pylint: disable=import-error
from . import ui_backupdlg # pylint: disable=no-name-in-module
from . import multiselectwidget
from . import backup_utils
from . import utils
import grp
import pwd
import sys
import os
import signal
import shutil
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesException
from qubes.qubes import QubesDaemonPidfile
from qubes.qubes import QubesHost
from qubes import backup
from qubes import qubesutils
import qubesmanager.resources_rc
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
from . import thread_monitor
import threading
import time
from .thread_monitor import *
from operator import itemgetter
from datetime import datetime
from string import replace
from .ui_backupdlg import *
from .multiselectwidget import *
from .backup_utils import *
import main
import grp,pwd
class BackupVMsWindow(Ui_Backup, QWizard):
class BackupVMsWindow(ui_backupdlg.Ui_Backup, multiselectwidget.QtGui.QWizard):
__pyqtSignals__ = ("backup_progress(int)",)
def __init__(self, app, qvm_collection, blk_manager, shutdown_vm_func, parent=None):
def __init__(self, qt_app, qubes_app, parent=None):
super(BackupVMsWindow, self).__init__(parent)
self.app = app
self.qvm_collection = qvm_collection
self.blk_manager = blk_manager
self.shutdown_vm_func = shutdown_vm_func
self.qt_app = qt_app
self.qubes_app = qubes_app
self.backup_settings = QtCore.QSettings()
self.func_output = []
self.selected_vms = []
self.tmpdir_to_remove = None
self.canceled = False
self.vm = self.qvm_collection[0]
self.files_to_backup = None
assert self.vm != None
self.thread_monitor = None
self.setupUi(self)
self.progress_status.text = self.tr("Backup in progress...")
self.show_running_vms_warning(False)
self.dir_line_edit.setReadOnly(False)
self.select_vms_widget = MultiSelectWidget(self)
self.select_vms_widget = multiselectwidget.MultiSelectWidget(self)
self.verticalLayout.insertWidget(1, self.select_vms_widget)
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
self.connect(self.select_vms_widget, SIGNAL("selected_changed()"), self.check_running)
self.connect(self.select_vms_widget, SIGNAL("items_removed(PyQt_PyObject)"), self.vms_removed)
self.connect(self.select_vms_widget, SIGNAL("items_added(PyQt_PyObject)"), self.vms_added)
self.refresh_button.clicked.connect(self.check_running)
self.shutdown_running_vms_button.clicked.connect(self.shutdown_all_running_selected)
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
self.dir_line_edit.connect(self.dir_line_edit, SIGNAL("textChanged(QString)"), self.backup_location_changed)
self.connect(self, QtCore.SIGNAL("currentIdChanged(int)"),
self.current_page_changed)
self.connect(self.select_vms_widget,
QtCore.SIGNAL("items_removed(PyQt_PyObject)"),
self.vms_removed)
self.connect(self.select_vms_widget,
QtCore.SIGNAL("items_added(PyQt_PyObject)"),
self.vms_added)
self.dir_line_edit.connect(self.dir_line_edit,
QtCore.SIGNAL("textChanged(QString)"),
self.backup_location_changed)
self.select_vms_page.isComplete = self.has_selected_vms
self.select_dir_page.isComplete = self.has_selected_dir_and_pass
@ -100,300 +83,256 @@ class BackupVMsWindow(Ui_Backup, QWizard):
# this causes to run isComplete() twice, I don't know why
self.select_vms_page.connect(
self.select_vms_widget,
SIGNAL("selected_changed()"),
SIGNAL("completeChanged()"))
QtCore.SIGNAL("selected_changed()"),
QtCore.SIGNAL("completeChanged()"))
self.passphrase_line_edit.connect(
self.passphrase_line_edit,
SIGNAL("textChanged(QString)"),
QtCore.SIGNAL("textChanged(QString)"),
self.backup_location_changed)
self.passphrase_line_edit_verify.connect(
self.passphrase_line_edit_verify,
SIGNAL("textChanged(QString)"),
QtCore.SIGNAL("textChanged(QString)"),
self.backup_location_changed)
self.total_size = 0
self.__fill_vms_list__()
fill_appvms_list(self)
self.load_settings()
self.target_vm_list, self.target_vm_idx = utils.prepare_vm_choice(
self.appvm_combobox,
self.qubes_app,
None,
self.qubes_app.domains['dom0'],
(lambda vm: vm.klass != 'TemplateVM' and vm.is_running()),
allow_internal=False,
allow_default=False,
allow_none=False
)
selected = self.load_settings()
self.__fill_vms_list__(selected)
def load_settings(self):
dest_vm_name = main.manager_window.manager_settings.value(
'backup/vmname', defaultValue="")
dest_vm_idx = self.appvm_combobox.findText(dest_vm_name.toString())
"""
Helper function that tries to load existing backup profile
(default path: /etc/qubes/backup/qubes-manager-backup.conf )
and then apply its contents to the Backup window.
:return: list of vms to include in backup, if it exists in the profile,
or None if it does not
"""
try:
profile_data = backup_utils.load_backup_profile()
except FileNotFoundError:
return
except exc.QubesException:
QtGui.QMessageBox.information(
None, self.tr("Error loading backup profile"),
self.tr("Unable to load saved backup profile."))
return
if not profile_data:
return
if 'destination_vm' in profile_data:
dest_vm_name = profile_data['destination_vm']
dest_vm_idx = self.appvm_combobox.findText(dest_vm_name)
if dest_vm_idx > -1:
self.appvm_combobox.setCurrentIndex(dest_vm_idx)
if main.manager_window.manager_settings.contains('backup/path'):
dest_path = main.manager_window.manager_settings.value(
'backup/path', defaultValue=None)
self.dir_line_edit.setText(dest_path.toString())
if 'destination_path' in profile_data:
dest_path = profile_data['destination_path']
self.dir_line_edit.setText(dest_path)
if main.manager_window.manager_settings.contains('backup/encrypt'):
encrypt = main.manager_window.manager_settings.value(
'backup/encrypt', defaultValue=None)
self.encryption_checkbox.setChecked(encrypt.toBool())
if 'passphrase_text' in profile_data:
self.passphrase_line_edit.setText(profile_data['passphrase_text'])
self.passphrase_line_edit_verify.setText(
profile_data['passphrase_text'])
def save_settings(self):
main.manager_window.manager_settings.setValue(
'backup/vmname', self.appvm_combobox.currentText())
main.manager_window.manager_settings.setValue(
'backup/path', self.dir_line_edit.text())
main.manager_window.manager_settings.setValue(
'backup/encrypt', self.encryption_checkbox.isChecked())
if 'compression' in profile_data:
self.compress_checkbox.setChecked(profile_data['compression'])
def show_running_vms_warning(self, show):
self.running_vms_warning.setVisible(show)
self.shutdown_running_vms_button.setVisible(show)
self.refresh_button.setVisible(show)
if 'include' in profile_data:
return profile_data['include']
class VmListItem(QListWidgetItem):
return None
def save_settings(self, use_temp):
"""
Helper function that saves backup profile to either
/etc/qubes/backup/qubes-manager-backup.conf or
/etc/qubes/backup/qubes-manager-backup-tmp.conf
:param use_temp: whether to use temporary profile (True) or the default
backup profile (False)
"""
settings = {'destination_vm': self.appvm_combobox.currentText(),
'destination_path': self.dir_line_edit.text(),
'include': [vm.name for vm in self.selected_vms],
'passphrase_text': self.passphrase_line_edit.text(),
'compression': self.compress_checkbox.isChecked()}
backup_utils.write_backup_profile(settings, use_temp)
class VmListItem(QtGui.QListWidgetItem):
# pylint: disable=too-few-public-methods
def __init__(self, vm):
self.vm = vm
if vm.qid == 0:
local_user = grp.getgrnam('qubes').gr_mem[0]
home_dir = pwd.getpwnam(local_user).pw_dir
self.size = qubesutils.get_disk_usage(home_dir)
self.size = get_disk_usage(home_dir)
else:
self.size = self.get_vm_size(vm)
super(BackupVMsWindow.VmListItem, self).__init__(vm.name+ " (" + qubesutils.size_to_human(self.size) + ")")
self.size = vm.get_disk_utilization()
super(BackupVMsWindow.VmListItem, self).__init__(
vm.name + " (" + admin_utils.size_to_human(self.size) + ")")
def get_vm_size(self, vm):
size = 0
if vm.private_img is not None:
size += qubesutils.get_disk_usage (vm.private_img)
if vm.updateable:
size += qubesutils.get_disk_usage(vm.root_img)
return size
def __fill_vms_list__(self):
for vm in self.qvm_collection.values():
if vm.internal:
def __fill_vms_list__(self, selected=None):
for vm in self.qubes_app.domains:
if vm.features.get('internal', False):
continue
item = BackupVMsWindow.VmListItem(vm)
if vm.include_in_backups == True:
if (selected is None and
getattr(vm, 'include_in_backups', True)) \
or (selected and vm.name in selected):
self.select_vms_widget.selected_list.addItem(item)
self.total_size += item.size
else:
self.select_vms_widget.available_list.addItem(item)
self.select_vms_widget.available_list.sortItems()
self.select_vms_widget.selected_list.sortItems()
self.check_running()
self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
self.unrecognized_config_label.setVisible(
selected is not None and
len(selected) != len(self.select_vms_widget.selected_list))
self.total_size_label.setText(
admin_utils.size_to_human(self.total_size))
def vms_added(self, items):
for i in items:
self.total_size += i.size
self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
self.total_size_label.setText(
admin_utils.size_to_human(self.total_size))
def vms_removed(self, items):
for i in items:
self.total_size -= i.size
self.total_size_label.setText(qubesutils.size_to_human(self.total_size))
self.total_size_label.setText(
admin_utils.size_to_human(self.total_size))
def check_running(self):
some_selected_vms_running = False
for i in range(self.select_vms_widget.selected_list.count()):
item = self.select_vms_widget.selected_list.item(i)
if item.vm.is_running() and item.vm.qid != 0:
item.setForeground(QBrush(QColor(255, 0, 0)))
some_selected_vms_running = True
else:
item.setForeground(QBrush(QColor(0, 0, 0)))
self.show_running_vms_warning(some_selected_vms_running)
for i in range(self.select_vms_widget.available_list.count()):
item = self.select_vms_widget.available_list.item(i)
if item.vm.is_running() and item.vm.qid != 0:
item.setForeground(QBrush(QColor(255, 0, 0)))
else:
item.setForeground(QBrush(QColor(0, 0, 0)))
return some_selected_vms_running
def shutdown_all_running_selected(self):
(names, vms) = self.get_running_vms()
if len(vms) == 0:
return
for vm in vms:
self.blk_manager.check_if_serves_as_backend(vm)
reply = QMessageBox.question(None, self.tr("VM Shutdown Confirmation"),
self.tr(
"Are you sure you want to power down the following VMs: "
"<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')
@QtCore.pyqtSlot(name='on_select_path_button_clicked')
def select_path_button_clicked(self):
select_path_button_clicked(self)
backup_utils.select_path_button_clicked(self)
def validateCurrentPage(self):
# pylint: disable=invalid-name
if self.currentPage() is self.select_vms_page:
if self.check_running():
QMessageBox.information(None,
self.tr("Wait!"),
self.tr("Some selected VMs are running. "
"Running VMs can not be backuped. "
"Please shut them down or remove them from the list."))
return False
self.selected_vms = []
for i in range(self.select_vms_widget.selected_list.count()):
self.selected_vms.append(self.select_vms_widget.selected_list.item(i).vm)
self.selected_vms.append(
self.select_vms_widget.selected_list.item(i).vm)
elif self.currentPage() is self.select_dir_page:
backup_location = str(self.dir_line_edit.text())
if not backup_location:
QMessageBox.information(None, self.tr("Wait!"),
QtGui.QMessageBox.information(
None, self.tr("Wait!"),
self.tr("Enter backup target location first."))
return False
if self.appvm_combobox.currentIndex() == 0 and \
not os.path.isdir(backup_location):
QMessageBox.information(None, self.tr("Wait!"),
if self.appvm_combobox.currentIndex() == 0 \
and not os.path.isdir(backup_location):
QtGui.QMessageBox.information(
None, self.tr("Wait!"),
self.tr("Selected directory do not exists or "
"not a directory (%s).") % backup_location)
return False
if not len(self.passphrase_line_edit.text()):
QMessageBox.information(None, self.tr("Wait!"),
self.tr("Enter passphrase for backup encryption/verification first."))
if not self.passphrase_line_edit.text():
QtGui.QMessageBox.information(
None, self.tr("Wait!"),
self.tr("Enter passphrase for backup "
"encryption/verification first."))
return False
if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text():
QMessageBox.information(None,
self.tr("Wait!"),
if self.passphrase_line_edit.text() !=\
self.passphrase_line_edit_verify.text():
QtGui.QMessageBox.information(
None, self.tr("Wait!"),
self.tr("Enter the same passphrase in both fields."))
return False
return True
def gather_output(self, s):
self.func_output.append(s)
def update_progress_bar(self, value):
self.emit(SIGNAL("backup_progress(int)"), value)
def __do_backup__(self, thread_monitor):
def __do_backup__(self, t_monitor):
msg = []
try:
backup.backup_do(self.dir_line_edit.text(),
self.files_to_backup,
self.passphrase_line_edit.text(),
progress_callback=self.update_progress_bar,
encrypted=self.encryption_checkbox.isChecked(),
appvm=self.target_appvm)
#simulate_long_lasting_proces(10, self.update_progress_bar)
except backup.BackupCanceledError as ex:
msg.append(str(ex))
self.canceled = True
if ex.tmpdir:
self.tmpdir_to_remove = ex.tmpdir
except Exception as ex:
print("Exception:", ex)
vm = self.qubes_app.domains[
self.appvm_combobox.currentText()]
if not vm.is_running():
vm.start()
self.qubes_app.qubesd_call(
'dom0', 'admin.backup.Execute',
backup_utils.get_profile_name(True))
except Exception as ex: # pylint: disable=broad-except
msg.append(str(ex))
if len(msg) > 0 :
thread_monitor.set_error_msg('\n'.join(msg))
if msg:
t_monitor.set_error_msg('\n'.join(msg))
thread_monitor.set_finished()
t_monitor.set_finished()
@staticmethod
def cleanup_temporary_files():
try:
os.remove(backup_utils.get_profile_path(use_temp=True))
except FileNotFoundError:
pass
def current_page_changed(self, id):
def current_page_changed(self, page_id): # pylint: disable=unused-argument
old_sigchld_handler = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if self.currentPage() is self.confirm_page:
self.target_appvm = None
if self.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
self.target_appvm = self.qvm_collection.get_vm_by_name(
self.appvm_combobox.currentText())
del self.func_output[:]
try:
self.files_to_backup = backup.backup_prepare(
self.selected_vms,
print_callback = self.gather_output,
hide_vm_names=self.encryption_checkbox.isChecked())
except Exception as ex:
print("Exception:", ex)
QMessageBox.critical(None,
self.tr("Error while preparing backup."),
self.tr("ERROR: {0}").format(ex))
self.save_settings(use_temp=True)
backup_summary = self.qubes_app.qubesd_call(
'dom0', 'admin.backup.Info',
backup_utils.get_profile_name(True))
self.textEdit.setReadOnly(True)
self.textEdit.setFontFamily("Monospace")
self.textEdit.setText("\n".join(self.func_output))
self.save_settings()
self.textEdit.setText(backup_summary.decode())
elif self.currentPage() is self.commit_page:
if self.save_profile_checkbox.isChecked():
self.save_settings(use_temp=False)
self.button(self.FinishButton).setDisabled(True)
self.showFileDialog.setEnabled(
self.appvm_combobox.currentIndex() != 0)
self.showFileDialog.setChecked(self.showFileDialog.isEnabled()
and str(self.dir_line_edit.text())
.count("media/") > 0)
self.thread_monitor = ThreadMonitor()
thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,))
self.thread_monitor = thread_monitor.ThreadMonitor()
thread = threading.Thread(
target=self.__do_backup__,
args=(self.thread_monitor,))
thread.daemon = True
thread.start()
counter = 0
while not self.thread_monitor.is_finished():
self.app.processEvents()
self.qt_app.processEvents()
time.sleep(0.1)
if not self.thread_monitor.success:
if self.canceled:
self.progress_status.setText(self.tr("Backup aborted."))
if self.tmpdir_to_remove:
if QMessageBox.warning(None, self.tr("Backup aborted"),
self.tr("Do you want to remove temporary files from "
"%s?") % self.tmpdir_to_remove,
QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes:
shutil.rmtree(self.tmpdir_to_remove)
self.progress_status.setText(
self.tr(
"Backup aborted. "
"Temporary file may be left at backup location."))
else:
self.progress_status.setText(self.tr("Backup error."))
QMessageBox.warning(self, self.tr("Backup error!"),
QtGui.QMessageBox.warning(
self, self.tr("Backup error!"),
self.tr("ERROR: {}").format(
self.thread_monitor.error_msg))
else:
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(100)
self.progress_status.setText(self.tr("Backup finished."))
if self.showFileDialog.isChecked():
@ -402,84 +341,78 @@ class BackupVMsWindow(Ui_Backup, QWizard):
orig_text + self.tr(
" Please unmount your backup volume and cancel "
"the file selection dialog."))
if self.target_appvm:
self.target_appvm.run("QUBESRPC %s dom0" % "qubes"
".SelectDirectory")
backup_utils.select_path_button_clicked(self, False, True)
self.button(self.CancelButton).setEnabled(False)
self.button(self.FinishButton).setEnabled(True)
self.showFileDialog.setEnabled(False)
self.cleanup_temporary_files()
signal.signal(signal.SIGCHLD, old_sigchld_handler)
def reject(self):
#cancell clicked while the backup is in progress.
#calling kill on tar.
if self.currentPage() is self.commit_page:
if backup.backup_cancel():
self.canceled = True
self.qubes_app.qubesd_call(
'dom0', 'admin.backup.Cancel',
backup_utils.get_profile_name(True))
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(0)
self.button(self.CancelButton).setDisabled(True)
self.cleanup_temporary_files()
else:
self.cleanup_temporary_files()
self.done(0)
def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0
def has_selected_dir_and_pass(self):
if not len(self.passphrase_line_edit.text()):
if not self.passphrase_line_edit.text():
return False
if self.passphrase_line_edit.text() != self.passphrase_line_edit_verify.text():
if self.passphrase_line_edit.text() != \
self.passphrase_line_edit_verify.text():
return False
return len(self.dir_line_edit.text()) > 0
def backup_location_changed(self, new_dir=None):
self.select_dir_page.emit(SIGNAL("completeChanged()"))
# pylint: disable=unused-argument
self.select_dir_page.emit(QtCore.SIGNAL("completeChanged()"))
# Bases on the original code by:
# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
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 = os.path.basename(filename)
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 "
"in Qubes Restore VMs application.<br><br>"
"<b><i>%s</i></b>" % error +
"at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
"in Qubes Global Settings application.<br><br><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
qubes_host = QubesHost()
global app
app = QApplication(sys.argv)
app.setOrganizationName("The Qubes Project")
app.setOrganizationDomain("http://qubes-os.org")
app.setApplicationName("Qubes Backup VMs")
qt_app = QtGui.QApplication(sys.argv)
qt_app.setOrganizationName("The Qubes Project")
qt_app.setOrganizationDomain("http://qubes-os.org")
qt_app.setApplicationName("Qubes Backup VMs")
sys.excepthook = handle_exception
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
app = Qubes()
global backup_window
backup_window = BackupVMsWindow()
backup_window = BackupVMsWindow(qt_app, app)
backup_window.show()
app.exec_()
app.exit()
qt_app.exec_()
qt_app.exit()
if __name__ == "__main__":
app_main()
main()

View File

@ -14,96 +14,117 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
#
#
import re
import sys
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtGui # pylint: disable=import-error
from PyQt4 import QtCore # pylint: disable=import-error
import subprocess
import time
from .thread_monitor import *
from . import utils
import yaml
path_re = re.compile(r"[a-zA-Z0-9/:.,_+=() -]*")
path_max_len = 512
def fill_appvms_list(dialog):
"""
Helper function, designed to fill the destination vm combobox in both backup
and restore GUI tools.
:param dialog: QtGui.QWizard with a combobox called appvm_combobox
"""
dialog.appvm_combobox.clear()
dialog.appvm_combobox.addItem("dom0")
dialog.appvm_combobox.setCurrentIndex(0) # current selected is null ""
for vm in dialog.qvm_collection.values():
if vm.is_appvm() and vm.internal:
continue
if vm.is_template() and vm.installed_by_rpm:
for vm in dialog.qubes_app.domains:
if vm.features.get('internal', False) or vm.klass == 'TemplateVM':
continue
if vm.is_running() and vm.qid != 0:
dialog.appvm_combobox.addItem(vm.name)
def enable_dir_line_edit(dialog, boolean):
dialog.dir_line_edit.setEnabled(boolean)
dialog.select_path_button.setEnabled(boolean)
def get_path_for_vm(vm, service_name):
if not vm:
return None
proc = vm.run("QUBESRPC %s dom0" % service_name, passio_popen=True)
proc.stdin.close()
untrusted_path = proc.stdout.readline(path_max_len)
if len(untrusted_path) == 0:
return None
if path_re.match(untrusted_path):
assert '../' not in untrusted_path
assert '\0' not in untrusted_path
return untrusted_path.strip()
else:
return None
def select_path_button_clicked(dialog, select_file = False):
def select_path_button_clicked(dialog, select_file=False, read_only=False):
"""
Helper function that displays a file/directory selection wizard. Used by
backup and restore GUI tools.
:param dialog: QtGui.QWizard with a dir_line_edit text box that wants to
receive a file/directory path and appvm_combobox with VM to use
:param select_file: True: select file dialog; False: select directory
dialog
:param read_only: should the dir_line_edit be changed after selecting a file
or directory
:return:
"""
backup_location = str(dialog.dir_line_edit.text())
file_dialog = QFileDialog()
file_dialog = QtGui.QFileDialog()
file_dialog.setReadOnly(True)
if select_file:
file_dialog_function = file_dialog.getOpenFileName
else:
file_dialog_function = file_dialog.getExistingDirectory
new_appvm = None
new_path = None
if dialog.appvm_combobox.currentIndex() != 0: #An existing appvm chosen
new_appvm = str(dialog.appvm_combobox.currentText())
vm = dialog.qvm_collection.get_vm_by_name(new_appvm)
if vm:
new_path = get_path_for_vm(vm, "qubes.SelectFile" if select_file
vm = dialog.qubes_app.domains[new_appvm]
try:
new_path = utils.get_path_from_vm(
vm,
"qubes.SelectFile" if select_file
else "qubes.SelectDirectory")
except subprocess.CalledProcessError:
if not read_only:
QtGui.QMessageBox.warning(
None,
dialog.tr("Nothing selected!"),
dialog.tr("No file or directory selected."))
else:
new_path = file_dialog_function(dialog,
dialog.tr("Select backup location."),
backup_location if backup_location else '/')
return
if new_path != None:
if os.path.basename(new_path) == 'qubes.xml':
backup_location = os.path.dirname(new_path)
else:
backup_location = new_path
dialog.dir_line_edit.setText(backup_location)
if new_path and not read_only:
dialog.dir_line_edit.setText(new_path)
if (new_path or new_appvm) and len(backup_location) > 0:
dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
if new_path and backup_location and not read_only:
dialog.select_dir_page.emit(QtCore.SIGNAL("completeChanged()"))
def simulate_long_lasting_proces(period, progress_callback):
for i in range(period):
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
# -*- coding: utf8 -*-
# pylint: skip-file
#
# The Qubes OS Project, http://www.qubes-os.org
#
@ -15,9 +16,8 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
import threading
import time

View File

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

View File

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

View File

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

View File

@ -13,91 +13,90 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
#
#
import datetime
import ipaddress
import os
import re
import sys
import xml.etree.ElementTree
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore, QtGui # pylint: disable=import-error
import qubesadmin.firewall
from . import ui_newfwruledlg
from . import ui_newfwruledlg # pylint: disable=no-name-in-module
class FirewallModifiedOutsideError(ValueError):
pass
class QIPAddressValidator(QValidator):
class QIPAddressValidator(QtGui.QValidator):
# pylint: disable=too-few-public-methods
def __init__(self, parent=None):
super(QIPAddressValidator, self).__init__(parent)
def validate(self, input, pos):
hostname = str(input)
def validate(self, input_string, pos):
# pylint: disable=too-many-return-statements,no-self-use
hostname = str(input_string)
if len(hostname) > 255 or len(hostname) == 0:
return (QValidator.Intermediate, input, pos)
if len(hostname) > 255 or not hostname:
return (QtGui.QValidator.Intermediate, input_string, pos)
if hostname == "*":
return (QValidator.Acceptable, input, pos)
return (QtGui.QValidator.Acceptable, input_string, pos)
unmask = hostname.split("/", 1)
if len(unmask) == 2:
hostname = unmask[0]
mask = unmask[1]
if mask.isdigit() or mask == "":
if re.match("^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None:
return (QValidator.Invalid, input, pos)
if re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", hostname) is None:
return (QtGui.QValidator.Invalid, input_string, pos)
if mask != "":
mask = int(unmask[1])
if mask < 0 or mask > 32:
return (QValidator.Invalid, input, pos)
return (QtGui.QValidator.Invalid, input_string, pos)
else:
return (QValidator.Invalid, input, pos)
return (QtGui.QValidator.Invalid, input_string, pos)
if hostname[-1:] == ".":
hostname = hostname[:-1]
if hostname[-1:] == "-":
return (QValidator.Intermediate, input, pos)
return (QtGui.QValidator.Intermediate, input_string, pos)
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
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):
super(NewFwRuleDlg, self).__init__(parent)
self.setupUi(self)
self.set_ok_enabled(False)
self.set_ok_state(False)
self.addressComboBox.setValidator(QIPAddressValidator())
self.addressComboBox.editTextChanged.connect(self.address_editing_finished)
self.serviceComboBox.setValidator(QRegExpValidator(QRegExp("[a-z][a-z0-9-]+|[0-9]+(-[0-9]+)?", Qt.CaseInsensitive), None))
self.addressComboBox.editTextChanged.connect(
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.setInsertPolicy(QComboBox.InsertAtBottom)
self.serviceComboBox.setInsertPolicy(QtGui.QComboBox.InsertAtBottom)
self.populate_combos()
self.serviceComboBox.setInsertPolicy(QComboBox.InsertAtTop)
self.serviceComboBox.setInsertPolicy(QtGui.QComboBox.InsertAtTop)
def accept(self):
if self.tcp_radio.isChecked() or self.udp_radio.isChecked():
if len(self.serviceComboBox.currentText()) == 0:
msg = QMessageBox()
if not self.serviceComboBox.currentText():
msg = QtGui.QMessageBox()
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
QDialog.accept(self)
QtGui.QDialog.accept(self)
def populate_combos(self):
example_addresses = [
@ -120,12 +119,12 @@ class NewFwRuleDlg (QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg):
self.serviceComboBox.addItem(service)
def address_editing_finished(self):
self.set_ok_enabled(True)
self.set_ok_state(True)
def set_ok_enabled(self, on):
ok_button = self.buttonBox.button(QDialogButtonBox.Ok)
def set_ok_state(self, ok_state):
ok_button = self.buttonBox.button(QtGui.QDialogButtonBox.Ok)
if ok_button is not None:
ok_button.setEnabled(on)
ok_button.setEnabled(ok_state)
def on_tcp_radio_toggled(self, checked):
if checked:
@ -139,55 +138,43 @@ class NewFwRuleDlg (QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg):
if checked:
self.serviceComboBox.setEnabled(False)
class QubesFirewallRulesModel(QAbstractItemModel):
class QubesFirewallRulesModel(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
QAbstractItemModel.__init__(self, parent)
QtCore.QAbstractItemModel.__init__(self, parent)
self.__columnValues = {
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.__column_names = {0: "Address", 1: "Service", 2: "Protocol", }
self.__services = list()
pattern = re.compile("(?P<name>[a-z][a-z0-9-]+)\s+(?P<port>[0-9]+)/(?P<protocol>[a-z]+)", re.IGNORECASE)
f = open('/etc/services', 'r')
for line in f:
pattern = re.compile(
r"(?P<name>[a-z][a-z0-9-]+)\s+(?P<port>[0-9]+)/"
r"(?P<protocol>[a-z]+)",
re.IGNORECASE)
with open('/etc/services', 'r') as file:
for line in file:
match = pattern.match(line)
if match is not None:
service = match.groupdict()
self.__services.append( (service["name"], int(service["port"]),) )
f.close()
self.__services.append(
(service["name"], int(service["port"]),))
self.fw_changed = False
self.allow = None # is the default policy allow or deny
self.temp_full_access_expire_time = None # temporary full access time
self.__vm = None # VM that the model applies to
self.__children = None # list of rules in the FW
def sort(self, idx, order):
from operator import attrgetter
rev = (order == 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"],
rev = (order == QtCore.Qt.AscendingOrder)
self.children.sort(key=lambda x: self.get_column_string(idx, x),
reverse=rev)
if idx==2:
self.children.sort(key=lambda x: x['proto'], reverse
= rev)
index1 = self.createIndex(0, 0)
index2 = self.createIndex(len(self)-1, len(self.__columnValues)-1)
index2 = self.createIndex(len(self) - 1, len(self.__column_names) - 1)
self.dataChanged.emit(index1, index2)
def get_service_name(self, port):
for service in self.__services:
if service[1] == port:
if str(service[1]) == str(port):
return service[0]
return str(port)
@ -197,256 +184,292 @@ class QubesFirewallRulesModel(QAbstractItemModel):
return service[1]
return None
def get_column_string(self, col, row):
return self.__columnValues[col](row)
def rule_to_dict(self, rule):
def get_column_string(self, col, rule):
# pylint: disable=too-many-return-statements
# Address
if col == 0:
if rule.dsthost is None:
raise FirewallModifiedOutsideError('no dsthost')
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)
d = {}
if not rule.proto:
d['proto'] = 'any'
d['portBegin'] = 'any'
d['portEnd'] = None
else:
d['proto'] = rule.proto
# Service
if col == 1:
if rule.dstports is None:
raise FirewallModifiedOutsideError('no dstport')
d['portBegin'] = rule.dstports.range[0]
d['portEnd'] = rule.dstports.range[1] \
if rule.dstports.range[0] != rule.dstports.range[1] \
else None
return "any"
if rule.dstports.range[0] != rule.dstports.range[1]:
return str(rule.dstports)
return self.get_service_name(rule.dstports)
if rule.dsthost.type == 'dsthost':
d['address'] = str(rule.dsthost)
d['netmask'] = 32
elif rule.dsthost.type == 'dst4':
network = ipaddress.IPv4Network(rule.dsthost)
d['address'] = str(network.network_address)
d['netmask'] = int(network.prefixlen)
else:
raise FirewallModifiedOutsideError(
'cannot map dsthost.type={!s}'.format(rule.dsthost))
# Protocol
if col == 2:
if rule.proto is None:
return "any"
return str(rule.proto)
return "unknown"
if rule.expire is not None:
d['expire'] = int(rule.expire)
return d
def get_firewall_conf(self, vm):
@staticmethod
def get_firewall_conf(vm):
conf = {
'allow': None,
'allowDns': False,
'allowIcmp': False,
'allowYumProxy': False,
'expire': 0,
'rules': [],
}
allow_dns = False
allow_icmp = False
common_action = None
tentative_action = None
reversed_rules = list(reversed(vm.firewall.rules))
last_rule = reversed_rules.pop(0)
if last_rule == qubesadmin.firewall.Rule('action=accept') \
or last_rule == qubesadmin.firewall.Rule('action=drop'):
common_action = last_rule.action
else:
FirewallModifiedOutsideError('Last rule must be either '
'drop all or accept all.')
dns_rule = qubesadmin.firewall.Rule(None,
action='accept', specialtarget='dns')
icmp_rule = qubesadmin.firewall.Rule(None,
action='accept', proto='icmp')
while reversed_rules:
rule = reversed_rules[0]
if rule.dsthost is not None or rule.proto is not None:
break
tentative_action = reversed_rules.pop(0).action
rule = reversed_rules.pop(0)
if not reversed_rules:
conf['allow'] = tentative_action == 'accept'
return conf
for rule in reversed_rules:
if rule.specialtarget == 'dns':
conf['allowDns'] = (rule.action == 'accept')
if rule == dns_rule:
allow_dns = True
continue
if rule.proto == 'icmp':
if rule.icmptype is not None:
raise FirewallModifiedOutsideError(
'cannot map icmptype != None')
conf['allowIcmp'] = (rule.action == 'accept')
if rule == icmp_rule:
allow_icmp = True
continue
if common_action is None:
common_action = rule.action
elif common_action != rule.action:
raise FirewallModifiedOutsideError('incoherent action')
if rule.specialtarget is not None or rule.icmptype is not None:
raise FirewallModifiedOutsideError("Rule type unknown!")
conf['rules'].insert(0, self.rule_to_dict(rule))
if (rule.dsthost is not None or rule.proto is not None) \
and rule.expire is None:
if rule.action == 'accept':
conf['rules'].insert(0, rule)
continue
else:
raise FirewallModifiedOutsideError('No blacklist support.')
if rule.expire is not None and rule.dsthost is None \
and rule.proto is None:
conf['expire'] = int(str(rule.expire))
continue
raise FirewallModifiedOutsideError('it does not add up.')
conf['allow'] = (common_action == 'accept')
if not allow_icmp and not conf['allow']:
raise FirewallModifiedOutsideError('ICMP must be allowed.')
if not allow_dns and not conf['allow']:
raise FirewallModifiedOutsideError('DNS must be allowed')
if common_action is None or common_action != tentative_action:
# we've got only specialtarget and/or icmp
conf['allow'] = tentative_action == 'accept'
return conf
raise FirewallModifiedOutsideError('it does not add up')
def write_firewall_conf(self, vm, conf):
common_action = qubesadmin.firewall.Action(
'drop' if conf['allow'] else 'accept')
@staticmethod
def write_firewall_conf(vm, conf):
rules = []
for rule in conf['rules']:
kwargs = {}
if rule['proto'] != 'any':
kwargs['proto'] = rule['proto']
if rule['portBegin'] != 'any':
kwargs['dstports'] = '-'.join(map(str, filter((lambda x: x),
(rule['portBegin'], rule['portEnd']))))
rules.append(rule)
netmask = str(rule['netmask']) if rule['netmask'] != 32 else None
rules.append(qubesadmin.firewall.Rule(None,
action=common_action,
dsthost='/'.join(map(str, filter((lambda x: x),
(rule['address'], netmask)))),
**kwargs))
if conf['allowDns']:
if not conf['allow']:
rules.append(qubesadmin.firewall.Rule(None,
action='accept', specialtarget='dns'))
if conf['allowIcmp']:
if not conf['allow']:
rules.append(qubesadmin.firewall.Rule(None,
action='accept', proto='icmp'))
if common_action == 'drop':
if conf['allow']:
rules.append(qubesadmin.firewall.Rule(None,
action='accept'))
else:
rules.append(qubesadmin.firewall.Rule(None,
action='drop'))
vm.firewall.rules = rules
def set_vm(self, vm):
self.__vm = vm
self.clearChildren()
self.clear_children()
conf = self.get_firewall_conf(vm)
self.allow = conf["allow"]
self.allowDns = conf["allowDns"]
self.allowIcmp = conf["allowIcmp"]
self.allowYumProxy = conf["allowYumProxy"]
self.tempFullAccessExpireTime = 0
self.temp_full_access_expire_time = conf['expire']
for rule in conf["rules"]:
self.appendChild(rule)
if "expire" in rule and rule["address"] == "0.0.0.0":
self.tempFullAccessExpireTime = rule["expire"]
self.append_child(rule)
def get_vm_name(self):
return self.__vm.name
def apply_rules(self, allow, dns, icmp, yumproxy, tempFullAccess=False,
tempFullAccessTime=None):
def apply_rules(self, allow, temp_full_access=False,
temp_full_access_time=None):
assert self.__vm is not None
if self.allow != allow or self.allowDns != dns or \
self.allowIcmp != icmp or self.allowYumProxy != yumproxy or \
(self.tempFullAccessExpireTime != 0) != tempFullAccess:
if self.allow != allow or \
(self.temp_full_access_expire_time != 0) != temp_full_access:
self.fw_changed = True
conf = {"allow": allow,
"allowDns": dns,
"allowIcmp": icmp,
"allowYumProxy": yumproxy,
"rules": list()
}
for rule in self.children:
if "expire" in rule and rule["address"] == "0.0.0.0" and \
rule["netmask"] == 0 and rule["proto"] == "any":
# rule already present, update its time
if tempFullAccess:
rule["expire"] = \
int(datetime.datetime.now().strftime("%s")) + \
tempFullAccessTime*60
tempFullAccess = False
conf["rules"].append(rule)
conf['rules'].extend(self.children)
if tempFullAccess and not allow:
conf["rules"].append({"address": "0.0.0.0",
"netmask": 0,
"proto": "any",
"expire": int(
datetime.datetime.now().strftime("%s"))+\
tempFullAccessTime*60
})
if temp_full_access and not allow:
conf["rules"].append(qubesadmin.firewall.Rule(
None,
action='accept',
expire=int(datetime.datetime.now().strftime("%s")) +
temp_full_access_time * 60))
if self.fw_changed:
self.write_firewall_conf(self.__vm, conf)
def index(self, row, column, parent=QModelIndex()):
def populate_edit_dialog(self, dialog, row):
address = self.get_column_string(0, self.children[row])
dialog.addressComboBox.setItemText(0, address)
dialog.addressComboBox.setCurrentIndex(0)
service = self.get_column_string(1, self.children[row])
if service == "any":
service = ""
dialog.serviceComboBox.setItemText(0, service)
dialog.serviceComboBox.setCurrentIndex(0)
protocol = self.get_column_string(2, self.children[row])
if protocol == "tcp":
dialog.tcp_radio.setChecked(True)
elif protocol == "udp":
dialog.udp_radio.setChecked(True)
else:
dialog.any_radio.setChecked(True)
def run_rule_dialog(self, dialog, row=None):
if dialog.exec_():
address = str(dialog.addressComboBox.currentText())
service = str(dialog.serviceComboBox.currentText())
rule = qubesadmin.firewall.Rule(None, action='accept')
if address is not None and address != "*":
try:
rule.dsthost = address
except ValueError:
QtGui.QMessageBox.warning(None, self.tr("Invalid address"),
self.tr("Address '{0}' is invalid.").format(address))
if dialog.tcp_radio.isChecked():
rule.proto = 'tcp'
elif dialog.udp_radio.isChecked():
rule.proto = 'udp'
if '-' in service:
try:
rule.dstports = service
except ValueError:
QtGui.QMessageBox.warning(
None,
self.tr("Invalid port or service"),
self.tr("Port number or service '{0}' is invalid.")
.format(service))
elif service:
try:
rule.dstports = service
except (TypeError, ValueError):
if self.get_service_port(service) is not None:
rule.dstports = self.get_service_port(service)
else:
QtGui.QMessageBox.warning(None,
self.tr("Invalid port or service"),
self.tr("Port number or service '{0}' is invalid.")
.format(service))
if row is not None:
self.set_child(row, rule)
else:
self.append_child(rule)
def index(self, row, column, parent=QtCore.QModelIndex()):
if not self.hasIndex(row, column, parent):
return QModelIndex()
return QtCore.QModelIndex()
return self.createIndex(row, column, self.children[row])
def parent(self, child):
return QModelIndex()
def parent(self, child): # pylint: disable=unused-argument,no-self-use
return QtCore.QModelIndex()
def rowCount(self, parent=QModelIndex()):
# pylint: disable=invalid-name,unused-argument
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self)
def columnCount(self, parent=QModelIndex()):
return len(self.__columnValues)
# pylint: disable=invalid-name,unused-argument
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.__column_names)
def hasChildren(self, index=QModelIndex()):
parentItem = index.internalPointer()
if parentItem is not None:
return False
else:
return True
# pylint: disable=invalid-name,no-self-use
def hasChildren(self, index=QtCore.QModelIndex()):
parent_item = index.internalPointer()
return parent_item is None
def data(self, index, role=Qt.DisplayRole):
if index.isValid() and role == Qt.DisplayRole:
return self.__columnValues[index.column()](index.row())
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid() and role == QtCore.Qt.DisplayRole:
return self.get_column_string(index.column(),
self.children[index.row()])
def headerData(self, section, orientation, role=Qt.DisplayRole):
if section < len(self.__columnNames) \
and orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.__columnNames[section]
# pylint: disable=invalid-name
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if section < len(self.__column_names) \
and orientation == QtCore.Qt.Horizontal \
and role == QtCore.Qt.DisplayRole:
return self.__column_names[section]
@property
def children(self):
return self.__children
def appendChild(self, child):
def append_child(self, child):
row = len(self)
self.beginInsertRows(QModelIndex(), row, row)
self.beginInsertRows(QtCore.QModelIndex(), row, row)
self.children.append(child)
self.endInsertRows()
index = self.createIndex(row, 0, child)
self.dataChanged.emit(index, index)
self.fw_changed = True
def removeChild(self, i):
def remove_child(self, i):
if i >= len(self):
return
self.beginRemoveRows(QModelIndex(), i, i)
self.beginRemoveRows(QtCore.QModelIndex(), i, i)
del self.children[i]
self.endRemoveRows()
index = self.createIndex(i, 0)
self.dataChanged.emit(index, index)
self.fw_changed = True
def setChild(self, i, child):
def set_child(self, i, child):
self.children[i] = child
index = self.createIndex(i, 0, child)
self.dataChanged.emit(index, index)
self.fw_changed = True
def clearChildren(self):
def clear_children(self):
self.__children = list()
def __len__(self):
return len(self.children)

View File

@ -15,28 +15,29 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
#
#
import sys
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import os.path
import traceback
from PyQt4 import QtCore, QtGui # pylint: disable=import-error
from qubesadmin import Qubes
from qubesadmin.utils import parse_size, updates_vms_status
from .ui_globalsettingsdlg import *
from . import ui_globalsettingsdlg # pylint: disable=no-name-in-module
from configparser import ConfigParser
from qubesadmin.utils import parse_size, updates_vms_status
qmemman_config_path = '/etc/qubes/qmemman.conf'
class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings,
QtGui.QDialog):
def __init__(self, app, qvm_collection, parent=None):
super(GlobalSettingsWindow, self).__init__(parent)
@ -46,8 +47,11 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.setupUi(self)
self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply)
self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
self.connect(
self.buttonBox,
QtCore.SIGNAL("accepted()"),
self.save_and_apply)
self.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject)
self.__init_system_defaults__()
self.__init_kernel_defaults__()
@ -89,7 +93,8 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.clock_vm_combo.setCurrentIndex(self.clockvm_idx)
# default netvm
netvms = [vm for vm in all_vms if getattr(vm, 'provides_network', False)]
netvms = [vm for vm in all_vms
if getattr(vm, 'provides_network', False)]
self.netvm_idx = -1
current_netvm = self.qvm_collection.default_netvm
@ -152,7 +157,8 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
def __init_kernel_defaults__(self):
kernel_list = []
# TODO system_path["qubes_kernels_base_dir"] idea: qubes.pulls['linux-kernel'].volumes
# TODO system_path["qubes_kernels_base_dir"]
# idea: qubes.pools['linux-kernel'].volumes
for k in os.listdir('/var/lib/qubes/vm-kernels'):
kernel_list.append(k)
@ -175,7 +181,6 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
def __init_mem_defaults__(self):
#qmemman settings
self.qmemman_config = ConfigParser()
self.vm_min_mem_val = '200MiB' #str(qmemman_algo.MIN_PREFMEM)
@ -183,8 +188,10 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.qmemman_config.read(qmemman_config_path)
if self.qmemman_config.has_section('global'):
self.vm_min_mem_val = self.qmemman_config.get('global', 'vm-min-mem')
self.dom0_mem_boost_val = self.qmemman_config.get('global', 'dom0-mem-boost')
self.vm_min_mem_val = \
self.qmemman_config.get('global', 'vm-min-mem')
self.dom0_mem_boost_val = \
self.qmemman_config.get('global', 'dom0-mem-boost')
self.vm_min_mem_val = parse_size(self.vm_min_mem_val)
self.dom0_mem_boost_val = parse_size(self.dom0_mem_boost_val)
@ -199,7 +206,8 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
current_min_vm_mem = self.min_vm_mem.value()
current_dom0_mem_boost = self.dom0_mem_boost.value()
if current_min_vm_mem*1024*1024 != self.vm_min_mem_val or current_dom0_mem_boost*1024*1024 != self.dom0_mem_boost_val:
if current_min_vm_mem*1024*1024 != self.vm_min_mem_val \
or current_dom0_mem_boost*1024*1024 != self.dom0_mem_boost_val:
current_min_vm_mem = str(current_min_vm_mem)+'M'
current_dom0_mem_boost = str(current_dom0_mem_boost)+'M'
@ -207,38 +215,46 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
if not self.qmemman_config.has_section('global'):
#add the whole section
self.qmemman_config.add_section('global')
self.qmemman_config.set('global', 'vm-min-mem', current_min_vm_mem)
self.qmemman_config.set('global', 'dom0-mem-boost', current_dom0_mem_boost)
self.qmemman_config.set('global', 'cache-margin-factor', str(1.3)) # removed qmemman_algo.CACHE_FACTOR
self.qmemman_config.set(
'global', 'vm-min-mem', current_min_vm_mem)
self.qmemman_config.set(
'global', 'dom0-mem-boost', current_dom0_mem_boost)
self.qmemman_config.set(
'global', 'cache-margin-factor', str(1.3))
# removed qmemman_algo.CACHE_FACTOR
qmemman_config_file = open(qmemman_config_path, 'a')
self.qmemman_config.write(qmemman_config_file)
qmemman_config_file.close()
else:
#If there already is a 'global' section, we don't use SafeConfigParser.write() - it would get rid of all the comments...
#If there already is a 'global' section, we don't use
# SafeConfigParser.write() - it would get rid of
# all the comments...
lines_to_add = {}
lines_to_add['vm-min-mem'] = "vm-min-mem = " + current_min_vm_mem + "\n"
lines_to_add['dom0-mem-boost'] = "dom0-mem-boost = " + current_dom0_mem_boost +"\n"
lines_to_add['vm-min-mem'] = \
"vm-min-mem = " + current_min_vm_mem + "\n"
lines_to_add['dom0-mem-boost'] = \
"dom0-mem-boost = " + current_dom0_mem_boost +"\n"
config_lines = []
qmemman_config_file = open(qmemman_config_path, 'r')
for l in qmemman_config_file:
if l.strip().startswith('vm-min-mem'):
for line in qmemman_config_file:
if line.strip().startswith('vm-min-mem'):
config_lines.append(lines_to_add['vm-min-mem'])
del lines_to_add['vm-min-mem']
elif l.strip().startswith('dom0-mem-boost'):
elif line.strip().startswith('dom0-mem-boost'):
config_lines.append(lines_to_add['dom0-mem-boost'])
del lines_to_add['dom0-mem-boost']
else:
config_lines.append(l)
config_lines.append(line)
qmemman_config_file.close()
for l in lines_to_add:
config_lines.append(l)
for line in lines_to_add:
config_lines.append(line)
qmemman_config_file = open(qmemman_config_path, 'w')
qmemman_config_file.writelines(config_lines)
@ -252,17 +268,19 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
self.updates_dom0.setChecked(self.updates_dom0_val)
updates_vms = updates_vms_status(self.qvm_collection)
if updates_vms is None:
self.updates_vm.setCheckState(Qt.PartiallyChecked)
self.updates_vm.setCheckState(QtCore.Qt.PartiallyChecked)
else:
self.updates_vm.setCheckState(updates_vms)
def __apply_updates__(self):
if self.updates_dom0.isChecked() != self.updates_dom0_val:
# TODO updates_dom0_toggle(self.qvm_collection, self.updates_dom0.isChecked())
# TODO updates_dom0_toggle(
# self.qvm_collection, self.updates_dom0.isChecked())
raise NotImplementedError('Toggle dom0 updates not implemented')
if self.updates_vm.checkState() != Qt.PartiallyChecked:
if self.updates_vm.checkState() != QtCore.Qt.PartiallyChecked:
for vm in self.qvm_collection.domains:
vm.features['check-updates'] = bool(self.updates_vm.checkState())
vm.features['check-updates'] = \
bool(self.updates_vm.checkState())
def reject(self):
self.done(0)
@ -280,25 +298,21 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
# Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
def handle_exception(exc_type, exc_value, exc_traceback):
import os.path
import traceback
filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
filename = os.path.basename(filename)
error = "%s: %s" % (exc_type.__name__, exc_value)
QMessageBox.critical(None, "Houston, we have a problem...",
QtGui.QMessageBox.critical(
None,
"Houston, we have a problem...",
"Whoops. A critical error has occured. This is most likely a bug "
"in Qubes Global Settings application.<br><br>"
"<b><i>%s</i></b>" % error +
"at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
"in Qubes Global Settings application.<br><br><b><i>%s</i></b>" %
error + "at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
% (line, filename))
def main():
global qtapp
qtapp = QApplication(sys.argv)
qtapp = QtGui.QApplication(sys.argv)
qtapp.setOrganizationName("The Qubes Project")
qtapp.setOrganizationDomain("http://qubes-os.org")
qtapp.setApplicationName("Qubes Global Settings")
@ -307,7 +321,6 @@ def main():
app = Qubes()
global global_window
global_window = GlobalSettingsWindow(qtapp, app)
global_window.show()

View File

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

View File

@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
#
# The Qubes OS Project, http://www.qubes-os.org
#
@ -15,28 +15,24 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
#
#
import sys
from PyQt4 import QtCore # pylint: disable=import-error
from PyQt4 import QtGui # pylint: disable=import-error
from . import ui_logdlg # pylint: disable=no-name-in-module
from . import clipboard
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qubes.qubes import QubesException
import qubesmanager.resources_rc
from .ui_logdlg import *
from .clipboard import *
# Display only this size of log
LOG_DISPLAY_SIZE = 1024*1024
class LogDialog(Ui_LogDialog, QDialog):
class LogDialog(ui_logdlg.Ui_LogDialog, QtGui.QDialog):
# pylint: disable=too-few-public-methods
def __init__(self, app, log_path, parent=None):
super(LogDialog, self).__init__(parent)
@ -47,7 +43,9 @@ class LogDialog(Ui_LogDialog, QDialog):
self.setupUi(self)
self.setWindowTitle(log_path)
self.connect(self.copy_to_qubes_clipboard, SIGNAL("clicked()"), self.copy_to_qubes_clipboard_triggered)
self.connect(self.copy_to_qubes_clipboard,
QtCore.SIGNAL("clicked()"),
self.copy_to_clipboard_triggered)
self.__init_log_text__()
@ -56,7 +54,8 @@ class LogDialog(Ui_LogDialog, QDialog):
log = open(self.log_path)
log.seek(0, os.SEEK_END)
if log.tell() > LOG_DISPLAY_SIZE:
self.displayed_text = self.tr("(Showing only last %d bytes of file)\n") % LOG_DISPLAY_SIZE
self.displayed_text = self.tr(
"(Showing only last %d bytes of file)\n") % LOG_DISPLAY_SIZE
log.seek(-LOG_DISPLAY_SIZE, os.SEEK_END)
else:
log.seek(0, os.SEEK_SET)
@ -64,6 +63,5 @@ class LogDialog(Ui_LogDialog, QDialog):
log.close()
self.log_text.setPlainText(self.displayed_text)
def copy_to_qubes_clipboard_triggered(self):
copy_text_to_qubes_clipboard(self.displayed_text)
def copy_to_clipboard_triggered(self):
clipboard.copy_text_to_qubes_clipboard(self.displayed_text)

View File

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

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

View File

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

View File

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

View File

@ -1,9 +1,10 @@
#!/usr/bin/python2
#!/usr/bin/python3
# -*- coding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2014 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
# Copyright (C) 2014 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -15,61 +16,54 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
import os
from PyQt4 import QtGui # pylint: disable=import-error
from PyQt4 import QtCore # pylint: disable=import-error
# pylint: disable=too-few-public-methods
from PyQt4 import QtGui
from PyQt4.QtCore import QSize, Qt
from PyQt4.QtGui import QTableWidgetItem, QHBoxLayout, QIcon, QLabel, QWidget, \
QSizePolicy, QSpacerItem, QFont, QColor, QProgressBar, QPainter, QPen
import time
from qubes.qubes import vm_files
import main
qubes_dom0_updates_stat_file = '/var/lib/qubes/updates/dom0-updates-available'
power_order = Qt.DescendingOrder
update_order = Qt.AscendingOrder
power_order = QtCore.Qt.DescendingOrder
update_order = QtCore.Qt.AscendingOrder
row_height = 30
class VmIconWidget (QWidget):
class VmIconWidget(QtGui.QWidget):
def __init__(self, icon_path, enabled=True, size_multiplier=0.7,
tooltip=None, parent=None, icon_sz=(32, 32)):
super(VmIconWidget, self).__init__(parent)
self.label_icon = QLabel()
self.label_icon = QtGui.QLabel()
if icon_path[0] in ':/':
icon = QIcon (icon_path)
icon = QtGui.QIcon(icon_path)
else:
icon = QIcon.fromTheme(icon_path)
icon_sz = QSize (row_height * size_multiplier, row_height * size_multiplier)
icon_pixmap = icon.pixmap(icon_sz, QIcon.Disabled if not enabled else QIcon.Normal)
icon = QtGui.QIcon.fromTheme(icon_path)
icon_sz = QtCore.QSize(row_height * size_multiplier,
row_height * size_multiplier)
icon_pixmap = icon.pixmap(
icon_sz,
QtGui.QIcon.Disabled if not enabled else QtGui.QIcon.Normal)
self.label_icon.setPixmap(icon_pixmap)
self.label_icon.setFixedSize(icon_sz)
if tooltip != None:
if tooltip is not None:
self.label_icon.setToolTip(tooltip)
layout = QHBoxLayout()
layout = QtGui.QHBoxLayout()
layout.addWidget(self.label_icon)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
def setToolTip(self, tooltip):
def setToolTip(self, tooltip): # pylint: disable=invalid-name
if tooltip is not None:
self.label_icon.setToolTip(tooltip)
else:
self.label_icon.setToolTip('')
class VmTypeWidget(VmIconWidget):
class VmTypeItem(QTableWidgetItem):
class VmTypeWidget(VmIconWidget):
class VmTypeItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm):
super(VmTypeWidget.VmTypeItem, self).__init__()
self.value = value
@ -84,40 +78,39 @@ class VmTypeWidget(VmIconWidget):
elif other.vm.qid == 0:
return False
elif self.value == other.value:
return self.vm.qid < other.vm.qid
else:
return self.vm.name < other.vm.name
return self.value < other.value
def __init__(self, vm, parent=None):
(icon_path, tooltip) = self.get_vm_icon(vm)
super (VmTypeWidget, self).__init__(icon_path, True, 0.8, tooltip, parent)
super(VmTypeWidget, self).__init__(
icon_path, True, 0.8, tooltip, parent)
self.vm = vm
self.tableItem = self.VmTypeItem(self.value, vm)
self.table_item = self.VmTypeItem(self.value, vm)
self.value = None
# TODO: add "provides network" column
def get_vm_icon(self, vm):
if vm.qid == 0:
if vm.klass == 'AdminVM':
self.value = 0
return (":/dom0.png", "Dom0")
elif vm.is_netvm() and not vm.is_proxyvm():
self.value = 1
return (":/netvm.png", "NetVM")
elif vm.is_proxyvm():
self.value = 2
return (":/proxyvm.png", "ProxyVM")
elif vm.is_appvm() and vm.template is None:
self.value = 4
return (":/standalonevm.png", "StandaloneVM")
elif vm.is_template():
icon_name = "dom0"
elif vm.klass == 'TemplateVM':
self.value = 3
return (":/templatevm.png", "TemplateVM")
elif vm.is_appvm() or vm.is_disposablevm():
icon_name = "templatevm"
elif vm.klass == 'StandaloneVM':
self.value = 4
icon_name = "standalonevm"
else:
self.value = 5 + vm.label.index
return (":/appvm.png", "AppVM")
icon_name = "appvm"
return ":/" + icon_name + ".png", vm.klass
class VmLabelWidget(VmIconWidget):
class VmLabelItem(QTableWidgetItem):
class VmLabelItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm):
super(VmLabelWidget.VmLabelItem, self).__init__()
self.value = value
@ -132,32 +125,27 @@ class VmLabelWidget(VmIconWidget):
elif other.vm.qid == 0:
return False
elif self.value == other.value:
return self.vm.qid < other.vm.qid
else:
return self.vm.name < other.vm.name
return self.value < other.value
def __init__(self, vm, parent=None):
icon_path = self.get_vm_icon_path(vm)
super(VmLabelWidget, self).__init__(icon_path, True, 0.8, None, parent)
self.vm = vm
self.tableItem = self.VmLabelItem(self.value, vm)
self.table_item = self.VmLabelItem(self.value, vm)
self.value = None
def get_vm_icon_path(self, vm):
if vm.qid == 0:
self.value = 100
return ":/off.png"
else:
self.value = vm.label.index
return vm.label.icon
class VmNameItem (QTableWidgetItem):
class VmNameItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmNameItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.setText(vm.name)
self.setTextAlignment(Qt.AlignVCenter)
self.setTextAlignment(QtCore.Qt.AlignVCenter)
self.qid = vm.qid
def __lt__(self, other):
@ -168,44 +156,43 @@ class VmNameItem (QTableWidgetItem):
return super(VmNameItem, self).__lt__(other)
class VmStatusIcon(QLabel):
class VmStatusIcon(QtGui.QLabel):
def __init__(self, vm, parent=None):
super(VmStatusIcon, self).__init__(parent)
self.vm = vm
self.set_on_icon()
self.previous_power_state = vm.last_power_state
self.previous_power_state = self.vm.get_power_state()
def update(self):
if self.previous_power_state != self.vm.last_power_state:
if self.previous_power_state != self.vm.get_power_state():
self.set_on_icon()
self.previous_power_state = self.vm.last_power_state
self.previous_power_state = self.vm.get_power_state()
def set_on_icon(self):
if self.vm.last_power_state == "Running":
icon = QIcon (":/on.png")
elif self.vm.last_power_state in ["Paused", "Suspended"]:
icon = QIcon (":/paused.png")
elif self.vm.last_power_state in ["Transient", "Halting", "Dying"]:
icon = QIcon (":/transient.png")
if self.vm.get_power_state() == "Running":
icon = QtGui.QIcon(":/on.png")
elif self.vm.get_power_state() in ["Paused", "Suspended"]:
icon = QtGui.QIcon(":/paused.png")
elif self.vm.get_power_state() in ["Transient", "Halting", "Dying"]:
icon = QtGui.QIcon(":/transient.png")
else:
icon = QIcon (":/off.png")
icon = QtGui.QIcon(":/off.png")
icon_sz = QSize (row_height * 0.5, row_height *0.5)
icon_sz = QtCore.QSize(row_height * 0.5, row_height * 0.5)
icon_pixmap = icon.pixmap(icon_sz)
self.setPixmap(icon_pixmap)
self.setFixedSize(icon_sz)
class VmInfoWidget (QWidget):
class VmInfoItem (QTableWidgetItem):
class VmInfoWidget(QtGui.QWidget):
class VmInfoItem(QtGui.QTableWidgetItem):
def __init__(self, upd_info_item, vm):
super(VmInfoWidget.VmInfoItem, self).__init__()
self.upd_info_item = upd_info_item
self.vm = vm
def __lt__(self, other):
# pylint: disable=too-many-return-statements
if self.vm.qid == 0:
return True
elif other.vm.qid == 0:
@ -213,21 +200,25 @@ class VmInfoWidget (QWidget):
self_val = self.upd_info_item.value
other_val = other.upd_info_item.value
if self.tableWidget().horizontalHeader().sortIndicatorOrder() == update_order:
if self.tableWidget().\
horizontalHeader().sortIndicatorOrder() == update_order:
# the result will be sorted by upd, sorting order: Ascending
self_val += 1 if self.vm.is_running() else 0
other_val += 1 if other.vm.is_running() else 0
if self_val == other_val:
return self.vm.qid < other.vm.qid
else:
return self.vm.name < other.vm.name
return self_val > other_val
elif self.tableWidget().horizontalHeader().sortIndicatorOrder() == power_order:
#the result will be sorted by power state, sorting order: Descending
self_val = -(self_val/10 + 10*(1 if self.vm.is_running() else 0))
other_val = -(other_val/10 + 10*(1 if other.vm.is_running() else 0))
elif self.tableWidget().\
horizontalHeader().sortIndicatorOrder() == power_order:
# the result will be sorted by power state,
# sorting order: Descending
self_val = -(self_val/10 +
10*(1 if self.vm.is_running() else 0))
other_val = -(other_val/10 +
10*(1 if other.vm.is_running() else 0))
if self_val == other_val:
return self.vm.qid < other.vm.qid
else:
return self.vm.name < other.vm.name
return self_val > other_val
else:
# it would be strange if this happened
@ -236,7 +227,7 @@ class VmInfoWidget (QWidget):
def __init__(self, vm, parent=None):
super(VmInfoWidget, self).__init__(parent)
self.vm = vm
layout = QHBoxLayout ()
layout = QtGui.QHBoxLayout()
self.on_icon = VmStatusIcon(vm)
self.upd_info = VmUpdateInfoWidget(vm, show_text=False)
@ -247,7 +238,9 @@ class VmInfoWidget (QWidget):
layout.addWidget(self.on_icon)
layout.addWidget(self.upd_info)
layout.addWidget(self.error_icon)
layout.addItem(QSpacerItem(0, 10, QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
layout.addItem(QtGui.QSpacerItem(0, 10,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding))
layout.addWidget(self.blk_icon)
layout.addWidget(self.rec_icon)
@ -258,47 +251,30 @@ class VmInfoWidget (QWidget):
self.blk_icon.setVisible(False)
self.error_icon.setVisible(False)
self.tableItem = self.VmInfoItem(self.upd_info.tableItem, vm)
self.table_item = self.VmInfoItem(self.upd_info.table_item, vm)
def update_vm_state(self, vm, blk_visible, rec_visible=None):
def update_vm_state(self, vm):
self.on_icon.update()
self.upd_info.update_outdated(vm)
if blk_visible != None:
self.blk_icon.setVisible(blk_visible)
if rec_visible != None:
self.rec_icon.setVisible(rec_visible)
self.error_icon.setToolTip(vm.qubes_manager_state[main.QMVmState
.ErrorMsg])
self.error_icon.setVisible(vm.qubes_manager_state[main.QMVmState
.ErrorMsg] is not None)
class VmTemplateItem (QTableWidgetItem):
class VmTemplateItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmTemplateItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
if vm.template is not None:
if getattr(vm, 'template', None) is not None:
self.setText(vm.template.name)
else:
font = QFont()
font.setStyle(QFont.StyleItalic)
font = QtGui.QFont()
font.setStyle(QtGui.QFont.StyleItalic)
self.setFont(font)
self.setTextColor(QColor("gray"))
self.setTextColor(QtGui.QColor("gray"))
if vm.is_appvm(): # and vm.template is None
self.setText("StandaloneVM")
elif vm.is_template():
self.setText("TemplateVM")
elif vm.qid == 0:
self.setText("AdminVM")
elif vm.is_netvm():
self.setText("NetVM")
else:
self.setText("---")
self.setText(vm.klass)
self.setTextAlignment(Qt.AlignVCenter)
self.setTextAlignment(QtCore.Qt.AlignVCenter)
def __lt__(self, other):
if self.vm.qid == 0:
@ -306,27 +282,22 @@ class VmTemplateItem (QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.text() == other.text():
return self.vm.qid < other.vm.qid
else:
return self.vm.name < other.vm.name
return super(VmTemplateItem, self).__lt__(other)
class VmNetvmItem (QTableWidgetItem):
class VmNetvmItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmNetvmItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
if vm.is_netvm() and not vm.is_proxyvm():
if getattr(vm, 'netvm', None) is None:
self.setText("n/a")
elif vm.netvm is not None:
self.setText(vm.netvm.name)
else:
self.setText("---")
self.setText(vm.netvm.name)
self.setTextAlignment(Qt.AlignVCenter)
self.setTextAlignment(QtCore.Qt.AlignVCenter)
def __lt__(self, other):
if self.vm.qid == 0:
@ -334,22 +305,19 @@ class VmNetvmItem (QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.text() == other.text():
return self.vm.qid < other.vm.qid
else:
return self.vm.name < other.vm.name
return super(VmNetvmItem, self).__lt__(other)
class VmInternalItem(QTableWidgetItem):
class VmInternalItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmInternalItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.internal = self.vm.internal
self.internal = vm.features.get('internal', False)
if self.internal:
self.setText("Yes")
else:
self.setText("")
self.setText("Yes" if self.internal else "")
def __lt__(self, other):
if self.vm.qid == 0:
@ -359,144 +327,10 @@ class VmInternalItem(QTableWidgetItem):
return super(VmInternalItem, self).__lt__(other)
class VmUsageBarWidget (QWidget):
# features man qvm-features
class VmUpdateInfoWidget(QtGui.QWidget):
class VmUsageBarItem (QTableWidgetItem):
def __init__(self, value, vm):
super(VmUsageBarWidget.VmUsageBarItem, self).__init__()
self.value = value
self.vm = vm
def set_value(self, value):
self.value = value
def __lt__(self, other):
if self.vm.qid == 0:
return True
elif other.vm.qid == 0:
return False
elif self.value == other.value:
return self.vm.qid < other.vm.qid
else:
return int(self.value) < int(other.value)
def __init__(self, min, max, format, update_func, vm, load, hue=210, parent = None):
super (VmUsageBarWidget, self).__init__(parent)
self.min = min
self.max = max
self.update_func = update_func
self.value = min
self.widget = QProgressBar()
self.widget.setMinimum(min)
self.widget.setMaximum(max)
self.widget.setFormat(format)
self.widget.setStyleSheet(
"QProgressBar:horizontal{" +\
"border: 1px solid hsv({0}, 100, 250);".format(hue) +\
"border-radius: 4px;\
background: transparent;\
text-align: center;\
}\
QProgressBar::chunk:horizontal {\
background: qlineargradient(x1: 1, y1: 0.5, x2: 1, y2: 0.5, " +\
"stop: 0 hsv({0}, 170, 207),".format(hue) +
" stop: 1 white); \
}"
)
layout = QHBoxLayout()
layout.addWidget(self.widget)
self.setLayout(layout)
self.tableItem = self.VmUsageBarItem(min, vm)
self.update_load(vm, load)
def update_load(self, vm, load):
self.value = self.update_func(vm, load)
self.widget.setValue(self.value)
self.tableItem.set_value(self.value)
class ChartWidget (QWidget):
class ChartItem (QTableWidgetItem):
def __init__(self, value, vm):
super(ChartWidget.ChartItem, self).__init__()
self.value = value
self.vm = vm
def set_value(self, value):
self.value = value
def __lt__(self, other):
if self.vm.qid == 0:
return True
elif other.vm.qid == 0:
return False
elif self.value == other.value:
return self.vm.qid < other.vm.qid
else:
return self.value < other.value
def __init__(self, vm, update_func, hue, load = 0, parent = None):
super (ChartWidget, self).__init__(parent)
self.update_func = update_func
self.hue = hue
if hue < 0 or hue > 255:
self.hue = 255
self.load = load
assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
self.load_history = [self.load]
self.tableItem = ChartWidget.ChartItem(self.load, vm)
def update_load (self, vm, load):
self.load = self.update_func(vm, load)
assert self.load >= 0, "load = {0}".format(self.load)
# assert self.load >= 0 and self.load <= 100, "load = {0}".format(self.load)
if self.load > 100:
# FIXME: This is an ugly workaround for cpu_load:/
self.load = 100
self.load_history.append (self.load)
self.tableItem.set_value(self.load)
self.repaint()
def paintEvent (self, Event = None):
p = QPainter (self)
dx = 4
W = self.width()
H = self.height() - 5
N = len(self.load_history)
if N > W/dx:
tail = N - W/dx
N = W/dx
self.load_history = self.load_history[tail:]
assert len(self.load_history) == N
for i in range (0, N-1):
val = self.load_history[N- i - 1]
sat = 70 + val*(255-70)/100
color = QColor.fromHsv (self.hue, sat, 255)
pen = QPen (color)
pen.setWidth(dx-1)
p.setPen(pen)
if val > 0:
p.drawLine (W - i*dx - dx, H , W - i*dx - dx, H - (H - 5) * val/100)
class VmUpdateInfoWidget(QWidget):
class VmUpdateInfoItem (QTableWidgetItem):
class VmUpdateInfoItem(QtGui.QTableWidgetItem):
def __init__(self, value, vm):
super(VmUpdateInfoWidget.VmUpdateInfoItem, self).__init__()
self.value = 0
@ -517,91 +351,53 @@ class VmUpdateInfoWidget(QWidget):
elif other.vm.qid == 0:
return False
elif self.value == other.value:
return self.vm.qid < other.vm.qid
else:
return self.vm.name < other.vm.name
return self.value < other.value
def __init__(self, vm, show_text=True, parent=None):
super(VmUpdateInfoWidget, self).__init__(parent)
layout = QHBoxLayout ()
layout = QtGui.QHBoxLayout()
self.show_text = show_text
if self.show_text:
self.label=QLabel("")
layout.addWidget(self.label, alignment=Qt.AlignCenter)
self.label = QtGui.QLabel("")
layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
else:
self.icon = QLabel("")
layout.addWidget(self.icon, alignment=Qt.AlignCenter)
self.icon = QtGui.QLabel("")
layout.addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
self.setLayout(layout)
self.previous_outdated_state = None
self.previous_update_recommended = None
self.value = None
self.tableItem = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
self.table_item = VmUpdateInfoWidget.VmUpdateInfoItem(self.value, vm)
def update_outdated(self, vm):
if vm.type == "HVM":
return
if vm.is_outdated():
outdated_state = False
try:
for vol in vm.volumes:
if vol.is_outdated():
outdated_state = "outdated"
# During TemplateVM shutdown, there's an interval of a few seconds
# during which vm.template.is_running() returns false but
# vm.is_outdated() does not yet return true, so the icon disappears.
# This looks goofy, but we've decided not to fix it at this time
# (2015-02-09).
elif vm.template and vm.template.is_running():
outdated_state = "to-be-outdated"
else:
outdated_state = None
break
except AttributeError:
pass
if not outdated_state and getattr(vm, 'template', None)\
and vm.template.is_running():
outdated_state = "to-be-outdated"
if outdated_state != self.previous_outdated_state:
self.update_status_widget(outdated_state)
self.previous_outdated_state = outdated_state
if not vm.is_updateable():
return
if vm.qid == 0:
update_recommended = self.previous_update_recommended
if os.path.exists(qubes_dom0_updates_stat_file):
update_recommended = True
else:
update_recommended = False
else:
update_recommended = self.previous_update_recommended
stat_file_path = vm.dir_path + '/' + vm_files["updates_stat_file"]
if not os.path.exists(stat_file_path):
update_recommended = False
else:
if (not hasattr(vm, "updates_stat_file_read_time")) or vm.updates_stat_file_read_time <= os.path.getmtime(stat_file_path):
stat_file = open(stat_file_path, "r")
updates = stat_file.read().strip()
stat_file.close()
if updates.isdigit():
updates = int(updates)
else:
updates = 0
if updates == 0:
update_recommended = False
else:
update_recommended = True
vm.updates_stat_file_read_time = time.time()
if update_recommended and not self.previous_update_recommended:
self.update_status_widget("update")
elif self.previous_update_recommended and not update_recommended:
self.update_status_widget(None)
self.previous_update_recommended = update_recommended
updates_available = vm.features.get('updates-available', False)
if updates_available != self.previous_update_recommended:
self.update_status_widget("update" if updates_available else None)
self.previous_update_recommended = updates_available
def update_status_widget(self, state):
self.value = state
self.tableItem.set_value(state)
self.table_item.set_value(state)
if state == "update":
label_text = "<font color=\"#CCCC00\">Check updates</font>"
icon_path = ":/update-recommended.png"
@ -618,7 +414,7 @@ class VmUpdateInfoWidget(QWidget):
tooltip_text = self.tr(
"The TemplateVM must be stopped before changes from its "
"current session can be picked up by this VM.")
elif state is None:
else:
label_text = ""
icon_path = None
tooltip_text = None
@ -632,25 +428,26 @@ class VmUpdateInfoWidget(QWidget):
self.icon = VmIconWidget(icon_path, True, 0.7)
self.icon.setToolTip(tooltip_text)
else:
self.icon = QLabel(label_text)
self.layout().addWidget(self.icon, alignment=Qt.AlignCenter)
self.icon = QtGui.QLabel(label_text)
self.layout().addWidget(self.icon, alignment=QtCore.Qt.AlignCenter)
class VmSizeOnDiskItem (QTableWidgetItem):
class VmSizeOnDiskItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmSizeOnDiskItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.value = 0
self.update()
self.setTextAlignment(Qt.AlignVCenter)
self.setTextAlignment(QtCore.Qt.AlignVCenter)
def update(self):
if self.vm.qid == 0:
self.setText("n/a")
else:
self.value = self.vm.get_disk_utilization()/(1024*1024)
self.value = 10
self.value = round(self.vm.get_disk_utilization()/(1024*1024), 2)
self.setText(str(self.value) + " MiB")
def __lt__(self, other):
@ -659,21 +456,18 @@ class VmSizeOnDiskItem (QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.value == other.value:
return self.vm.qid < other.vm.qid
else:
return self.vm.name < other.vm.name
return self.value < other.value
class VmIPItem(QTableWidgetItem):
class VmIPItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmIPItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
self.ip = self.vm.ip
if self.ip:
self.setText(self.ip)
else:
self.setText("n/a")
self.ip = getattr(self.vm, 'ip', None)
self.setText(self.ip if self.ip is not None else 'n/a')
def __lt__(self, other):
if self.vm.qid == 0:
@ -682,13 +476,14 @@ class VmIPItem(QTableWidgetItem):
return False
return super(VmIPItem, self).__lt__(other)
class VmIncludeInBackupsItem(QTableWidgetItem):
class VmIncludeInBackupsItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmIncludeInBackupsItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
if self.vm.include_in_backups:
if getattr(self.vm, 'include_in_backups', None):
self.setText("Yes")
else:
self.setText("")
@ -699,18 +494,18 @@ class VmIncludeInBackupsItem(QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.vm.include_in_backups == other.vm.include_in_backups:
return self.vm.qid < other.vm.qid
else:
return self.vm.name < other.vm.name
return self.vm.include_in_backups < other.vm.include_in_backups
class VmLastBackupItem(QTableWidgetItem):
class VmLastBackupItem(QtGui.QTableWidgetItem):
def __init__(self, vm):
super(VmLastBackupItem, self).__init__()
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.vm = vm
if self.vm.backup_timestamp:
self.setText(str(self.vm.backup_timestamp.date()))
if getattr(self.vm, 'backup_timestamp', None):
self.setText(self.vm.backup_timestamp)
else:
self.setText("")
@ -720,10 +515,9 @@ class VmLastBackupItem(QTableWidgetItem):
elif other.vm.qid == 0:
return False
elif self.vm.backup_timestamp == other.vm.backup_timestamp:
return self.vm.qid < other.vm.qid
return self.vm.name < other.vm.name
elif not self.vm.backup_timestamp:
return False
elif not other.vm.backup_timestamp:
return True
else:
return self.vm.backup_timestamp < other.vm.backup_timestamp

View File

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

View File

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

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

View File

@ -21,6 +21,9 @@ if __name__ == '__main__':
'qubes-global-settings = qubesmanager.global_settings:main',
'qubes-vm-settings = qubesmanager.settings:main',
'qubes-vm-create = qubesmanager.create_new_vm:main',
'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main'
'qubes-vm-boot-from-device = qubesmanager.bootfromdevice:main',
'qubes-backup = qubesmanager.backup:main',
'qubes-backup-restore = qubesmanager.restore:main',
'qubes-qube-manager = qubesmanager.qube_manager:main'
],
})

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>
<x>0</x>
<y>0</y>
<width>650</width>
<height>399</height>
<width>737</width>
<height>420</height>
</rect>
</property>
<property name="windowTitle">
@ -23,73 +23,8 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<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">
<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">
<string>Select VMs to backup:</string>
</property>
@ -145,12 +80,79 @@
</item>
</layout>
</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>
</widget>
<widget class="QWizardPage" name="select_dir_page">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Backup destination directory</string>
</property>
@ -188,30 +190,38 @@
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_2">
<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>Backup security</string>
<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>
<layout class="QFormLayout" name="formLayout">
<item row="2" column="0">
<widget class="QLabel" name="label_12">
<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>Save settings as default backup profile:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<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>
<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/>
@ -220,39 +230,64 @@
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_11">
<item row="2" column="0">
<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">
<string>Backup security</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_12">
<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>
<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>
</widget>
</item>
<item row="2" column="1">
<item row="1" column="1">
<widget class="QLineEdit" name="passphrase_line_edit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</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">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</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>
</layout>
</widget>
</item>
</layout>
<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>
@ -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;
&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; }
&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;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;/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-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@ -324,6 +359,9 @@ p, li { white-space: pre-wrap; }
</item>
<item>
<widget class="QProgressBar" name="progress_bar">
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>0</number>
</property>

View File

@ -31,10 +31,22 @@
<property name="verticalSpacing">
<number>6</number>
</property>
<item row="2" column="0">
<widget class="QLabel" name="label">
<item row="2" column="2">
<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">
<string>Protocol</string>
<string>UDP</string>
</property>
</widget>
</item>
@ -45,13 +57,6 @@
</property>
</widget>
</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">
<widget class="QLabel" name="label_2">
<property name="text">
@ -59,6 +64,31 @@
</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="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">
<widget class="QComboBox" name="addressComboBox">
<property name="editable">
@ -66,6 +96,13 @@
</property>
</widget>
</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">
<widget class="QRadioButton" name="tcp_radio">
<property name="sizePolicy">
@ -85,44 +122,10 @@
</property>
</widget>
</item>
<item row="2" column="2">
<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>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>UDP</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>
<string>Protocol</string>
</property>
</widget>
</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>
</property>
<property name="text">
<string>ignore missing</string>
<string>ignore missing templates and net VMs</string>
</property>
</widget>
</item>
@ -147,6 +147,9 @@
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<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;
&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; }
&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;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;/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-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@ -241,6 +244,9 @@ p, li { white-space: pre-wrap; }
</item>
<item>
<widget class="QProgressBar" name="progress_bar">
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>0</number>
</property>

View File

@ -29,7 +29,7 @@
<locale language="English" country="UnitedStates"/>
</property>
<property name="currentIndex">
<number>1</number>
<number>2</number>
</property>
<widget class="QWidget" name="basic_tab">
<property name="locale">
@ -170,7 +170,7 @@
</layout>
</widget>
</item>
<item row="5" column="0">
<item row="6" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -373,6 +373,26 @@
</layout>
</widget>
</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>
</widget>
<widget class="QWidget" name="advanced_tab">
@ -580,7 +600,7 @@
<widget class="QComboBox" name="default_dispvm"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QPushButton" name="bootFromDeviceButton">
<widget class="QPushButton" name="boot_from_device_button">
<property name="text">
<string>Boot qube from CDROM</string>
</property>
@ -769,47 +789,198 @@
</attribute>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<widget class="QRadioButton" name="policyAllowRadioButton">
<property name="text">
<string>Allow network access except...</string>
<widget class="QLabel" name="no_netvm_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>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="icmpCheckBox">
<property name="minimumSize">
<size>
<width>323</width>
<height>0</height>
</size>
<property name="font">
<font>
<weight>75</weight>
<italic>true</italic>
<bold>true</bold>
</font>
</property>
<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 name="checked">
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<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">
<string>Deny network access except...</string>
<string>Networking qube does not support 'qubes-firewall' - firewall restrictions will not be applied.</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="dnsCheckBox">
<property name="text">
<string>Allow DNS queries</string>
<item row="2" column="0">
<widget class="QLabel" name="firewall_modified_outside_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="checked">
<bool>true</bool>
<property name="font">
<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>
</widget>
</item>
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item row="3" column="0">
<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">
<enum>QLayout::SetMaximumSize</enum>
</property>
@ -851,7 +1022,7 @@
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QPushButton" name="newRuleButton">
<widget class="QPushButton" name="new_rule_button">
<property name="text">
<string/>
</property>
@ -868,7 +1039,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="editRuleButton">
<widget class="QPushButton" name="edit_rule_button">
<property name="text">
<string/>
</property>
@ -885,7 +1056,10 @@
</widget>
</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">
<string/>
</property>
@ -918,14 +1092,7 @@
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="yumproxyCheckBox">
<property name="text">
<string>Allow connections to Updates Proxy</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="8" column="0">
<widget class="QWidget" name="tempFullAccessWidget" native="true">
<property name="enabled">
<bool>true</bool>
@ -940,15 +1107,15 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="tempFullAccess">
<item row="1" column="0">
<widget class="QCheckBox" name="temp_full_access">
<property name="text">
<string>Allow full access for </string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="tempFullAccessTime">
<item row="1" column="1">
<widget class="QSpinBox" name="temp_full_access_time">
<property name="suffix">
<string> min</string>
</property>
@ -957,9 +1124,35 @@
</property>
</widget>
</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>
</widget>
</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>
</widget>
<widget class="QWidget" name="devices_tab">
@ -1162,15 +1355,10 @@
<tabstop>vcpus</tabstop>
<tabstop>include_in_balancing</tabstop>
<tabstop>kernel</tabstop>
<tabstop>policyAllowRadioButton</tabstop>
<tabstop>policyDenyRadioButton</tabstop>
<tabstop>icmpCheckBox</tabstop>
<tabstop>dnsCheckBox</tabstop>
<tabstop>yumproxyCheckBox</tabstop>
<tabstop>newRuleButton</tabstop>
<tabstop>new_rule_button</tabstop>
<tabstop>rulesTreeView</tabstop>
<tabstop>editRuleButton</tabstop>
<tabstop>deleteRuleButton</tabstop>
<tabstop>edit_rule_button</tabstop>
<tabstop>delete_rule_button</tabstop>
<tabstop>service_line_edit</tabstop>
<tabstop>add_srv_button</tabstop>
<tabstop>services_list</tabstop>

View File

@ -1 +1 @@
4.0.6
4.0.10