Merge branch 'master' of git://git.qubes-os.org/aga/qubes-manager

This commit is contained in:
Marek Marczykowski 2012-02-23 00:03:09 +01:00
commit 6213fab474
34 changed files with 4278 additions and 710 deletions

View File

@ -13,10 +13,16 @@ rpms:
rpm --addsign $(RPMS_DIR)/x86_64/qubes-manager*$(VERSION)*.rpm rpm --addsign $(RPMS_DIR)/x86_64/qubes-manager*$(VERSION)*.rpm
res: res:
pyrcc4 -o qubesmanager/qrc_resources.py resources.qrc pyrcc4 -o qubesmanager/resources_rc.py resources.qrc
pyuic4 -o qubesmanager/ui_mainwindow.py mainwindow.ui
pyuic4 -o qubesmanager/ui_newappvmdlg.py newappvmdlg.ui pyuic4 -o qubesmanager/ui_newappvmdlg.py newappvmdlg.ui
pyuic4 -o qubesmanager/ui_editfwrulesdlg.py editfwrulesdlg.ui pyuic4 -o qubesmanager/ui_editfwrulesdlg.py editfwrulesdlg.ui
pyuic4 -o qubesmanager/ui_newfwruledlg.py newfwruledlg.ui pyuic4 -o qubesmanager/ui_newfwruledlg.py newfwruledlg.ui
pyuic4 -o qubesmanager/ui_multiselectwidget.py multiselectwidget.ui
pyuic4 -o qubesmanager/ui_settingsdlg.py settingsdlg.ui
pyuic4 -o qubesmanager/ui_restoredlg.py restoredlg.ui
pyuic4 -o qubesmanager/ui_backupdlg.py backupdlg.ui
pyuic4 -o qubesmanager/ui_globalsettingsdlg.py globalsettingsdlg.ui
update-repo-current: update-repo-current:
ln -f $(RPMS_DIR)/x86_64/qubes-manager-*$(VERSION)*.rpm ../yum/current-release/current/dom0/rpm/ ln -f $(RPMS_DIR)/x86_64/qubes-manager-*$(VERSION)*.rpm ../yum/current-release/current/dom0/rpm/

196
backupdlg.ui Normal file
View File

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Backup</class>
<widget class="QWizard" name="Backup">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>399</height>
</rect>
</property>
<property name="windowTitle">
<string>Qubes Backup VMs</string>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="options">
<set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set>
</property>
<widget class="QWizardPage" name="select_vms_page">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<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>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="select_dir_page">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Backup destination directory</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Device:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="dev_combobox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>dev1</string>
</property>
</item>
<item>
<property name="text">
<string>longdeviceblablabla</string>
</property>
</item>
<item>
<property name="text">
<string>dev2</string>
</property>
</item>
<item>
<property name="text">
<string>dev3</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Backup directory:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="dir_line_edit"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="select_path_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="confirm_page">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_6">
<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>You're about to perform the following actions:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="textEdit">
<property name="html">
<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; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;br /&gt;A lot of info&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_7">
<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>To continue press Next. </string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="commit_page">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_8">
<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>Backup in progress...</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progress_bar">
<property name="value">
<number>24</number>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

224
globalsettingsdlg.ui Normal file
View File

@ -0,0 +1,224 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GlobalSettings</class>
<widget class="QDialog" name="GlobalSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>678</width>
<height>288</height>
</rect>
</property>
<property name="windowTitle">
<string>Qubes Global Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>System defaults</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>UpdateVM:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="updateVMcombo"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>ClockVM:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="clockVMcombo"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Default netVM:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="defaultNetVMcombo"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Default template:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="defaultTemplateVMCombo"/>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox_3">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Default memory settings</string>
</property>
<widget class="QLabel" name="label_6">
<property name="geometry">
<rect>
<x>11</x>
<y>26</y>
<width>134</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Minimal VM's memory:</string>
</property>
</widget>
<widget class="QLabel" name="label_7">
<property name="geometry">
<rect>
<x>11</x>
<y>57</y>
<width>139</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>dom0 memory margin:</string>
</property>
</widget>
<widget class="QSpinBox" name="minVMmem">
<property name="geometry">
<rect>
<x>156</x>
<y>26</y>
<width>121</width>
<height>25</height>
</rect>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="maximum">
<number>999999999</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
</widget>
<widget class="QSpinBox" name="dom0memMargin">
<property name="geometry">
<rect>
<x>156</x>
<y>57</y>
<width>121</width>
<height>25</height>
</rect>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="maximum">
<number>999999999</number>
</property>
<property name="singleStep">
<number>50</number>
</property>
</widget>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Kernel</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Default kernel:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBox_5"/>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>GlobalSettings</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>GlobalSettings</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

BIN
icons/add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
icons/appsprefs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
icons/edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
icons/flag-blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
icons/flag-green.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
icons/flag-red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
icons/flag-yellow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
icons/mount.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
icons/newfirewall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
icons/on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
icons/pencil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
icons/redfirewall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
icons/redfirewall2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
icons/remove.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

534
mainwindow.ui Normal file
View File

@ -0,0 +1,534 @@
<?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>821</width>
<height>600</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="windowTitle">
<string>Qubes VM Manager</string>
</property>
<property name="windowIcon">
<iconset resource="resources.qrc">
<normaloff>:/qubes.png</normaloff>:/qubes.png</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="Maximum" vsizetype="Maximum">
<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="margin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QTableWidget" name="table">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<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::ScrollBarAlwaysOn</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>4</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/>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="toolTip">
<string>VM name</string>
</property>
</column>
<column>
<property name="text">
<string>Upd</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>CPU</string>
</property>
</column>
<column>
<property name="text">
<string>CPU Graph</string>
</property>
<property name="toolTip">
<string>CPU usage graph</string>
</property>
</column>
<column>
<property name="text">
<string>MEM</string>
</property>
</column>
<column>
<property name="text">
<string>MEM Graph</string>
</property>
<property name="toolTip">
<string>Memory usage graph</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>821</width>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="menuOptions">
<property name="title">
<string>Options</string>
</property>
<addaction name="action_global_settings"/>
<addaction name="action_backup"/>
<addaction name="action_restore"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
<addaction name="actionUpd"/>
<addaction name="actionTemplate"/>
<addaction name="actionNetVM"/>
<addaction name="actionCPU"/>
<addaction name="actionCPU_Graph"/>
<addaction name="actionMEM"/>
<addaction name="actionMEM_Graph"/>
</widget>
<addaction name="menuOptions"/>
<addaction name="menuView"/>
</widget>
<widget class="QToolBar" name="toolBar">
<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="separator"/>
<addaction name="action_settings"/>
<addaction name="action_editfwrules"/>
<addaction name="action_appmenus"/>
<addaction name="action_updatevm"/>
<addaction name="separator"/>
<addaction name="action_showallvms"/>
</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 AppVM</string>
</property>
<property name="toolTip">
<string>Create a new AppVM</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>remove AppVM</string>
</property>
<property name="toolTip">
<string>Remove an existing AppVM (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/Resume VM</string>
</property>
<property name="toolTip">
<string>Start/Resume a VM</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>Pause VM</string>
</property>
<property name="toolTip">
<string>Pause a running VM</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>Shutdown VM</string>
</property>
<property name="toolTip">
<string>Shutdown a running VM</string>
</property>
</action>
<action name="action_appmenus">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/appsprefs.png</normaloff>:/appsprefs.png</iconset>
</property>
<property name="text">
<string>Select VM applications</string>
</property>
<property name="toolTip">
<string>Select applications present in menu for this VM</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>Update VM</string>
</property>
<property name="toolTip">
<string>Update VM system</string>
</property>
</action>
<action name="action_showallvms">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/showallvms.png</normaloff>
<selectedoff>:/showallvms.png</selectedoff>:/showallvms.png</iconset>
</property>
<property name="text">
<string>Show/Hide inactive VMs</string>
</property>
<property name="toolTip">
<string>Show/Hide inactive VMs</string>
</property>
</action>
<action name="action_editfwrules">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/redfirewall.png</normaloff>:/redfirewall.png</iconset>
</property>
<property name="text">
<string>Edit VM Firewall rules</string>
</property>
<property name="toolTip">
<string>Edit VM 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="actionOptions">
<property name="text">
<string>Options</string>
</property>
</action>
<action name="actionView">
<property name="text">
<string>View</string>
</property>
</action>
<action name="actionCPU">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>CPU</string>
</property>
</action>
<action name="actionCPU_Graph">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>CPU Graph</string>
</property>
</action>
<action name="actionMEM">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>MEM</string>
</property>
</action>
<action name="actionMEM_Graph">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>MEM Graph</string>
</property>
</action>
<action name="actionTemplate">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Template</string>
</property>
</action>
<action name="actionNetVM">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>NetVM</string>
</property>
</action>
<action name="action_settings">
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/root.png</normaloff>:/root.png</iconset>
</property>
<property name="text">
<string>Settings</string>
</property>
<property name="toolTip">
<string>VM Settings</string>
</property>
</action>
<action name="action_restore">
<property name="text">
<string>Restore VMs from backup</string>
</property>
</action>
<action name="action_backup">
<property name="text">
<string>Backup VMs</string>
</property>
</action>
<action name="action_global_settings">
<property name="text">
<string>Global settings</string>
</property>
</action>
<action name="actionUpd">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Upd</string>
</property>
</action>
</widget>
<resources>
<include location="resources.qrc"/>
</resources>
<connections/>
</ui>

193
multiselectwidget.ui Normal file
View File

@ -0,0 +1,193 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MultiSelectWidget</class>
<widget class="QWidget" name="MultiSelectWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>602</width>
<height>459</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Available</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<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>
</item>
<item>
<widget class="QListWidget" name="available_list"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="add_all_button">
<property name="text">
<string>&gt;&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="add_selected_button">
<property name="text">
<string>&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove_selected_button">
<property name="text">
<string>&lt;</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove_all_button">
<property name="text">
<string>&lt;&lt;</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Selected</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<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>
</item>
<item>
<widget class="QListWidget" name="selected_list"/>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -33,235 +33,87 @@ from qubes.qubes import QubesDaemonPidfile
from qubes.qubes import QubesHost from qubes.qubes import QubesHost
from qubes.qubes import qrexec_client_path from qubes.qubes import qrexec_client_path
import qubesmanager.qrc_resources import qubesmanager.resources_rc
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import subprocess import subprocess
import time import time
import threading
from operator import itemgetter from operator import itemgetter
from thread_monitor import *
from multiselectwidget import *
whitelisted_filename = 'whitelisted-appmenus.list' whitelisted_filename = 'whitelisted-appmenus.list'
class AppRowInTable(object): class AppListWidgetItem(QListWidgetItem):
def __init__(self, filename, name, row_no, table): def __init__(self, name, filename, parent = None):
super(AppListWidgetItem, self).__init__(name, parent)
self.filename = filename self.filename = filename
self.row_no = row_no
table.setRowHeight (row_no, AppmenuSelectWindow.row_height)
self.name_widget = QTableWidgetItem(name)
self.name_widget.setFlags (Qt.ItemIsSelectable | Qt.ItemIsEnabled )
table.setItem(row_no, 0, self.name_widget)
self.appvm_widget = QCheckBox()
table.setCellWidget(row_no, 1, self.appvm_widget)
class ThreadMonitor(QObject):
def __init__(self):
self.success = True
self.error_msg = None
self.event_finished = threading.Event()
def set_error_msg(self, error_msg):
self.success = False
self.error_msg = error_msg
self.set_finished()
def is_finished(self):
return self.event_finished.is_set()
def set_finished(self):
self.event_finished.set()
class AppmenuSelectWindow(QDialog):
row_height = 20
def __init__(self, vm, parent=None): class AppmenuSelectManager:
super(AppmenuSelectWindow, self).__init__(parent) def __init__(self, vm, apps_multiselect, parent=None):
self.gridLayout = QGridLayout(self)
self.buttonBox = QDialogButtonBox(self)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply)
self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
self.table = QTableWidget(self)
self.table.clear()
self.table.setColumnCount(2)
self.table.setColumnWidth (0, 200)
self.table.setColumnWidth (1, 40)
self.table.horizontalHeader().setResizeMode(QHeaderView.Stretch)
self.table.horizontalHeader().setResizeMode(1, QHeaderView.Fixed)
self.table.setAlternatingRowColors(True)
self.table.verticalHeader().hide()
self.table.horizontalHeader().show()
self.table.setGridStyle(Qt.NoPen)
self.table.setSortingEnabled(True)
self.table.setSelectionBehavior(QTableWidget.SelectRows)
self.table.setSelectionMode(QTableWidget.SingleSelection)
self.gridLayout.addWidget(self.table, 0, 0, 1, 1)
self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1)
self.app_list = apps_multiselect # this is a multiselect wiget
self.vm = vm self.vm = vm
if self.vm.template_vm: if self.vm.template_vm:
self.source_vm = self.vm.template_vm self.source_vm = self.vm.template_vm
else: else:
self.source_vm = self.vm self.source_vm = self.vm
self.setWindowTitle("Qubes Appmenus for %s" % vm.name)
self.resize(250,500)
self.fill_table() self.fill_apps_list()
self.load_list_of_selected()
def reject(self): def fill_apps_list(self):
self.done(0)
def addActions(self, target, actions):
for action in actions:
if action is None:
target.addSeparator()
else:
target.addAction(action)
def createAction(self, text, slot=None, shortcut=None, icon=None,
tip=None, checkable=False, signal="triggered()"):
action = QAction(text, self)
if icon is not None:
action.setIcon(QIcon(":/%s.png" % icon))
if shortcut is not None:
action.setShortcut(shortcut)
if tip is not None:
action.setToolTip(tip)
action.setStatusTip(tip)
if slot is not None:
self.connect(action, SIGNAL(signal), slot)
if checkable:
action.setCheckable(True)
return action
def fill_table(self):
template_dir = self.source_vm.appmenus_templates_dir template_dir = self.source_vm.appmenus_templates_dir
template_file_list = os.listdir(template_dir) template_file_list = os.listdir(template_dir)
self.table.clear() whitelisted = []
self.table.setHorizontalHeaderLabels(['Name', 'VM']) if os.path.exists(self.vm.dir_path + '/' + whitelisted_filename):
self.table.setRowCount(len(template_file_list)) f = open(self.vm.dir_path + '/' + whitelisted_filename, 'r')
whitelisted = [item.strip() for item in f]
f.close()
row_no = 0 self.app_list.clear()
appmenus = []
available_appmenus = []
for template_file in template_file_list: for template_file in template_file_list:
desktop_template = open(template_dir + '/' + template_file, 'r') desktop_template = open(template_dir + '/' + template_file, 'r')
for line in desktop_template: for line in desktop_template:
if line.startswith("Name=%VMNAME%: "): if line.startswith("Name=%VMNAME%: "):
desktop_name = line.partition('Name=%VMNAME%: ')[2].strip() desktop_name = line.partition('Name=%VMNAME%: ')[2].strip()
row = AppRowInTable (template_file, desktop_name, row_no, self.table) available_appmenus.append( (template_file, desktop_name) )
appmenus.append(row)
row_no += 1
break break
desktop_template.close() desktop_template.close()
self.table.setRowCount(row_no) whitelisted_appmenus = [a for a in available_appmenus if a[0] in whitelisted]
self.appmenus = appmenus available_appmenus = [a for a in available_appmenus if a[0] not in whitelisted]
self.table.sortItems(0)
for a in available_appmenus:
self.app_list.available_list.addItem( AppListWidgetItem(a[1], a[0]))
def load_list_of_selected(self): for a in whitelisted_appmenus:
if not os.path.exists(self.vm.dir_path + '/' + whitelisted_filename): self.app_list.selected_list.addItem( AppListWidgetItem(a[1], a[0]))
# select none
for row in self.appmenus: self.app_list.available_list.sortItems()
row.appvm_widget.setCheckState(Qt.Unchecked) self.app_list.selected_list.sortItems()
return
f = open(self.vm.dir_path + '/' + whitelisted_filename, 'r')
whitelisted = [item.strip() for item in f]
f.close()
for row in self.appmenus:
if row.filename in whitelisted:
row.appvm_widget.setCheckState(Qt.Checked)
else:
row.appvm_widget.setCheckState(Qt.Unchecked)
def save_list_of_selected(self): def save_list_of_selected(self):
whitelisted = open(self.vm.dir_path + '/' + whitelisted_filename, 'w') whitelisted = open(self.vm.dir_path + '/' + whitelisted_filename, 'w')
for row in self.appmenus: for i in range(self.app_list.selected_list.count()):
if row.appvm_widget.checkState() == Qt.Checked: item = self.app_list.selected_list.item(i)
whitelisted.write(row.filename + '\n') whitelisted.write(item.filename + '\n')
whitelisted.close() whitelisted.close()
def save_and_apply(self): def save_appmenu_select_changes(self):
self.save_list_of_selected() self.save_list_of_selected()
subprocess.check_call([qubes_appmenu_remove_cmd, self.vm.name]) subprocess.check_call([qubes_appmenu_remove_cmd, self.vm.name])
subprocess.check_call([qubes_appmenu_create_cmd, self.source_vm.appmenus_templates_dir, self.vm.name]) subprocess.check_call([qubes_appmenu_create_cmd, self.source_vm.appmenus_templates_dir, self.vm.name])
self.done(0)
# 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 "
"in Qubes Appmenu Select application.<br><br>"
"<b><i>%s</i></b>" % error +
"at <b>line %d</b> of file <b>%s</b>.<br/><br/>"
% ( line, filename ))
#sys.exit(1)
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 Appmenu Select")
app.setWindowIcon(QIcon(":/qubes.png"))
sys.excepthook = handle_exception
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
vm = None
if len(sys.argv) > 1:
vm = qvm_collection.get_vm_by_name(sys.argv[1])
if vm is None or vm.qid not in qvm_collection:
QMessageBox.critical(None, "Qubes Appmenu Select Error",
"A VM with the name '{0}' does not exist in the system.".format(sys.argv[1]))
sys.exit(1)
else:
vms_list = [vm.name for vm in qvm_collection.values() if (vm.is_appvm() or vm.is_template())]
vmname = QInputDialog.getItem(None, "Select VM", "Select VM:", vms_list, editable = False)
if not vmname[1]:
sys.exit(1)
vm = qvm_collection.get_vm_by_name(vmname[0])
global manager_window
select_window = AppmenuSelectWindow(vm)
select_window.show()
app.exec_()
app.exit()

239
qubesmanager/backup.py Normal file
View File

@ -0,0 +1,239 @@
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2012 Agnieszka Kostrzewa <agnieszka.kostrzewa@gmail.com>
# Copyright (C) 2012 Marek Marczykowski <marmarek@mimuw.edu.pl>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU 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.
#
#
import sys
import os
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 qubesutils
import qubesmanager.resources_rc
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import subprocess
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 *
class BackupVMsWindow(Ui_Backup, QWizard):
__pyqtSignals__ = ("backup_progress(int)",)
excluded = []
to_backup = []
def __init__(self, app, qvm_collection, blk_manager, parent=None):
super(BackupVMsWindow, self).__init__(parent)
self.app = app
self.qvm_collection = qvm_collection
self.blk_manager = blk_manager
self.dev_mount_path = None
self.backup_dir = None
self.func_output = []
for vm in self.qvm_collection.values():
if vm.qid == 0:
self.vm = vm
break;
assert self.vm != None
self.setupUi(self)
self.dir_line_edit.setReadOnly(True)
self.select_vms_widget = MultiSelectWidget(self)
self.verticalLayout.insertWidget(1, self.select_vms_widget)
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated)
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
self.select_vms_page.isComplete = self.has_selected_vms
self.select_dir_page.isComplete = self.has_selected_dir
#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.__fill_vms_list__()
fill_devs_list(self)
def __fill_vms_list__(self):
for vm in self.qvm_collection.values():
if vm.is_running() and vm.qid != 0:
self.excluded.append(vm.name)
continue
if vm.is_appvm() and vm.internal:
self.excluded.append(vm.name)
continue
if vm.is_template() and vm.installed_by_rpm:
self.excluded.append(vm.name)
continue
self.to_backup.append(vm.name)
self.select_vms_widget.available_list.addItem(vm.name)
def dev_combobox_activated(self, idx):
dev_combobox_activated(self, idx)
@pyqtSlot(name='on_select_path_button_clicked')
def select_path_button_clicked(self):
select_path_button_clicked(self)
def validateCurrentPage(self):
if self.currentPage() is self.select_vms_page:
for i in range(self.select_vms_widget.available_list.count()):
vmname = self.select_vms_widget.available_list.item(i).text()
self.excluded.append(vmname)
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):
msg = []
try:
qubesutils.backup_do(str(self.backup_dir), self.files_to_backup, self.update_progress_bar)
#simulate_long_lasting_proces(10, self.update_progress_bar)
except Exception as ex:
msg.append(str(ex))
if len(msg) > 0 :
thread_monitor.set_error_msg('\n'.join(msg))
thread_monitor.set_finished()
def current_page_changed(self, id):
if self.currentPage() is self.confirm_page:
del self.func_output[:]
self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output)
self.textEdit.setReadOnly(True)
self.textEdit.setFontFamily("Monospace")
self.textEdit.setText("\n".join(self.func_output))
elif self.currentPage() is self.commit_page:
self.button(self.CancelButton).setDisabled(True)
self.button(self.FinishButton).setDisabled(True)
self.thread_monitor = ThreadMonitor()
thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,))
thread.daemon = True
thread.start()
while not self.thread_monitor.is_finished():
self.app.processEvents()
time.sleep (0.1)
if not self.thread_monitor.success:
QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg))
umount_device(self.dev_mount_path)
self.button(self.FinishButton).setEnabled(True)
def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0
def has_selected_dir(self):
return self.backup_dir != None
# 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 "
"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 Backup VMs")
sys.excepthook = handle_exception
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
global backup_window
backup_window = BackupVMsWindow()
backup_window.show()
app.exec_()
app.exit()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,159 @@
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2012 Agnieszka Kostrzewa <agnieszka.kostrzewa@gmail.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU 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.
#
#
import sys
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import subprocess
import time
from thread_monitor import *
from datetime import datetime
from string import replace
def check_if_mounted(dev_path):
mounts_file = open("/proc/mounts")
for m in list(mounts_file):
if m.startswith(dev_path):
return m.split(" ")[1]
return None
def mount_device(dev_path):
try:
mount_dir_name = "backup" + replace(str(datetime.now()),' ', '-').split(".")[0]
pmount_cmd = ["pmount", dev_path, mount_dir_name]
res = subprocess.check_call(pmount_cmd)
except Exception as ex:
QMessageBox.warning (None, "Error mounting selected device!", "ERROR: {0}".format(ex))
return None
if res == 0:
dev_mount_path = "/media/"+mount_dir_name
return dev_mount_path
return None
def umount_device(dev_mount_path):
try:
pumount_cmd = ["pumount", dev_mount_path]
res = subprocess.check_call(pumount_cmd)
if res == 0:
dev_mount_path = None
except Exception as ex:
QMessageBox.warning (None, "Could not unmount backup device!", "ERROR: {0}".format(ex))
return dev_mount_path
def fill_devs_list(dialog):
dialog.dev_combobox.clear()
dialog.dev_combobox.addItem("None")
for a in dialog.blk_manager.attached_devs:
if dialog.blk_manager.attached_devs[a]['attached_to']['vm'] == dialog.vm.name :
att = a + " " + unicode(dialog.blk_manager.attached_devs[a]['size']) + " " + dialog.blk_manager.attached_devs[a]['desc']
dialog.dev_combobox.addItem(att, QVariant(a))
for a in dialog.blk_manager.free_devs:
att = a + " " + unicode(dialog.blk_manager.free_devs[a]['size']) + " " + dialog.blk_manager.free_devs[a]['desc']
dialog.dev_combobox.addItem(att, QVariant(a))
dialog.dev_combobox.setCurrentIndex(0) #current selected is null ""
dialog.prev_dev_idx = 0
dialog.dir_line_edit.clear()
dialog.dir_line_edit.setEnabled(False)
dialog.select_path_button.setEnabled(False)
def enable_dir_line_edit(dialog, boolean):
dialog.dir_line_edit.setEnabled(boolean)
dialog.select_path_button.setEnabled(boolean)
def dev_combobox_activated(dialog, idx):
if idx == dialog.prev_dev_idx: #nothing has changed
return
#there was a change
dialog.dir_line_edit.setText("")
dialog.backup_dir = None
if dialog.dev_mount_path != None:
dialog.dev_mount_path = umount_device(dialog.dev_mount_path)
if dialog_dev_mount_path != None:
dialog.dev_combobox.setCurrentIndex(dialog.prev_dev_idx)
return
enable_dir_line_edit(dialog, False)
if dialog.dev_combobox.currentText() != "None": #An existing device chosen
dev_name = str(dialog.dev_combobox.itemData(idx).toString())
if dev_name in dialog.blk_manager.free_devs:
if dev_name.startswith(dialog.vm.name): # originally attached to dom0
dev_path = "/dev/"+dev_name.split(":")[1]
else: # originally attached to another domain, eg. usbvm
#attach it to dom0, then treat it as an attached device
dialog.blk_manager.attach_device(dialog.vm, dev_name)
if dev_name in dialog.blk_manager.attached_devs: #is attached to dom0
assert dialog.blk_manager.attached_devs[dev_name]['attached_to']['vm'] == dialog.vm.name
dev_path = "/dev/" + dialog.blk_manager.attached_devs[dev_name]['attached_to']['frontend']
#check if device mounted
dialog.dev_mount_path = check_if_mounted(dev_path)
if dialog.dev_mount_path != None:
enable_dir_line_edit(dialog, True)
else:
dialog.dev_mount_path = mount_device(dev_path)
if dialog.dev_mount_path != None:
enable_dir_line_edit(dialog, True)
else:
dialog.dev_combobox.setCurrentIndex(0) #if couldn't mount - set current device to "None"
dialog.prev_dev_idx = idx
dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
def select_path_button_clicked(dialog):
dialog.backup_dir = dialog.dir_line_edit.text()
file_dialog = QFileDialog()
file_dialog.setReadOnly(True)
new_path = file_dialog.getExistingDirectory(dialog, "Select backup directory.", dialog.dev_mount_path)
if new_path:
dialog.dir_line_edit.setText(new_path)
dialog.backup_dir = new_path
dialog.select_dir_page.emit(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

View File

@ -31,106 +31,8 @@ from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesException from qubes.qubes import QubesException
from qubes.qubes import dry_run from qubes.qubes import dry_run
import ui_editfwrulesdlg
import ui_newfwruledlg import ui_newfwruledlg
class EditFwRulesDlg (QDialog, ui_editfwrulesdlg.Ui_EditFwRulesDlg):
def __init__(self, parent = None):
super (EditFwRulesDlg, self).__init__(parent)
self.setupUi(self)
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.policyAllowRadioButton.toggled.connect(self.policy_radio_toggled)
self.dnsCheckBox.toggled.connect(self.dns_checkbox_toggled)
self.icmpCheckBox.toggled.connect(self.icmp_checkbox_toggled)
def set_model(self, model):
self.__model = model
self.rulesTreeView.setModel(model)
self.rulesTreeView.header().setResizeMode(QHeaderView.ResizeToContents)
self.rulesTreeView.header().setResizeMode(0, QHeaderView.Stretch)
self.set_allow(model.allow)
self.dnsCheckBox.setChecked(model.allowDns)
self.icmpCheckBox.setChecked(model.allowIcmp)
self.setWindowTitle(model.get_vm_name() + " firewall")
def set_allow(self, allow):
self.policyAllowRadioButton.setChecked(allow)
self.policyDenyRadioButton.setChecked(not allow)
def policy_radio_toggled(self, on):
self.__model.allow = self.policyAllowRadioButton.isChecked()
def dns_checkbox_toggled(self, on):
self.__model.allowDns = on
def icmp_checkbox_toggled(self, on):
self.__model.allowIcmp = on
def new_rule_button_pressed(self):
dialog = NewFwRuleDlg()
self.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.__model.get_column_string(0, row).replace(' ', '')
dialog.addressComboBox.setItemText(0, address)
dialog.addressComboBox.setCurrentIndex(0)
service = self.__model.get_column_string(1, row)
dialog.serviceComboBox.setItemText(0, service)
dialog.serviceComboBox.setCurrentIndex(0)
self.run_rule_dialog(dialog, row)
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 service == "*":
service = "0"
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.__model.get_service_port(service)
if port is not None:
if port2 is not None and port2 <= port:
QMessageBox.warning(None, "Invalid service ports range", "Port {0} is lower than port {1}.".format(port2, port))
else:
item = QubesFirewallRuleItem(address, netmask, port, port2)
if row is not None:
self.__model.setChild(row, item)
else:
self.__model.appendChild(item)
else:
QMessageBox.warning(None, "Invalid service name", "Service '{0} is unknown.".format(service))
def delete_rule_button_pressed(self):
for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]):
self.__model.removeChild(i)
class QIPAddressValidator(QValidator): class QIPAddressValidator(QValidator):
def __init__(self, parent = None): def __init__(self, parent = None):
@ -397,3 +299,4 @@ class QubesFirewallRulesModel(QAbstractItemModel):
def __len__(self): def __len__(self):
return len(self.children) return len(self.children)

View File

@ -0,0 +1,107 @@
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2012 Agnieszka Kostrzewa <agnieszka.kostrzewa@gmail.com>
# Copyright (C) 2012 Marek Marczykowski <marmarek@mimuw.edu.pl>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU 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.
#
#
import sys
import os
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
import qubesmanager.resources_rc
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import subprocess
import time
import threading
from operator import itemgetter
from ui_globalsettingsdlg import *
class GlobalSettingsWindow(Ui_GlobalSettings, QDialog):
def __init__(self, parent=None):
super(GlobalSettingsWindow, self).__init__(parent)
self.setupUi(self)
def reject(self):
self.done(0)
def save_and_apply(self):
pass
# 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 "
"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 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 Global Settings")
sys.excepthook = handle_exception
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
global global_window
global_window = GlobalSettingsWindow()
global_window.show()
app.exec_()
app.exit()
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from ui_multiselectwidget import *
class MultiSelectWidget(Ui_MultiSelectWidget, QWidget):
__pyqtSignals__ = ("selected_changed()",)
def __init__(self, parent=None):
super(MultiSelectWidget, self).__init__()
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)
def switch_selected(self, src, dst):
selected = src.selectedItems()
for s in selected:
row = src.indexFromItem(s).row()
item = src.takeItem(row)
dst.addItem(item)
dst.sortItems()
self.emit(SIGNAL("selected_changed()"))
def add_selected(self):
self.switch_selected(self.available_list, self.selected_list)
def remove_selected(self):
self.switch_selected(self.selected_list, self.available_list)
def move_all(self, src, dst):
while src.count() > 0:
item = src.takeItem(0)
dst.addItem(item)
dst.sortItems()
self.emit(SIGNAL("selected_changed()"))
def add_all(self):
self.move_all(self.available_list, self.selected_list)
def remove_all(self):
self.move_all(self.selected_list, self.available_list)
def clear(self):
self.available_list.clear()
self.selected_list.clear()

276
qubesmanager/restore.py Normal file
View File

@ -0,0 +1,276 @@
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2012 Agnieszka Kostrzewa <agnieszka.kostrzewa@gmail.com>
# Copyright (C) 2012 Marek Marczykowski <marmarek@mimuw.edu.pl>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU 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.
#
#
import sys
import os
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.qubes import qubes_base_dir
import qubesmanager.resources_rc
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import subprocess
import time
from operator import itemgetter
from thread_monitor import *
from qubes import qubesutils
from ui_restoredlg import *
from multiselectwidget import *
from backup_utils import *
class RestoreVMsWindow(Ui_Restore, QWizard):
__pyqtSignals__ = ("restore_progress(int)",)
def __init__(self, app, qvm_collection, blk_manager, parent=None):
super(RestoreVMsWindow, self).__init__(parent)
self.app = app
self.qvm_collection = qvm_collection
self.blk_manager = blk_manager
self.dev_mount_path = None
self.backup_dir = None
self.restore_options = None
self.backup_vms_list = None
self.func_output = []
self.excluded = {}
for vm in self.qvm_collection.values():
if vm.qid == 0:
self.vm = vm
break;
assert self.vm != None
self.setupUi(self)
self.select_vms_widget = 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.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated)
self.connect(self, SIGNAL("restore_progress(QString)"), self.commit_text_edit.append)
self.select_dir_page.isComplete = self.has_selected_dir
self.select_vms_page.isComplete = self.has_selected_vms
#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()"))
fill_devs_list(self)
self.__init_restore_options__()
def dev_combobox_activated(self, idx):
dev_combobox_activated(self, idx)
@pyqtSlot(name='on_select_path_button_clicked')
def select_path_button_clicked(self):
select_path_button_clicked(self)
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_skip_dom0_toggled(self, checked):
self.restore_options['dom0-home'] = checked
def __fill_vms_list__(self):
if self.backup_vms_list != None:
return
self.select_vms_widget.selected_list.clear()
self.select_vms_widget.available_list.clear()
self.vms_to_restore = qubesutils.backup_restore_prepare(str(self.backup_dir), self.restore_options, self.qvm_collection)
for vmname in self.vms_to_restore:
self.select_vms_widget.available_list.addItem(vmname)
def __init_restore_options__(self):
if not self.restore_options:
self.restore_options = {}
qubesutils.backup_restore_set_defaults(self.restore_options)
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'])
if 'dom0-home' in self.restore_options:
self.skip_dom0.setChecked(self.restore_options['dom0-home'])
def gather_output(self, s):
self.func_output.append(s)
def restore_error_output(self, s):
self.emit(SIGNAL("restore_progress(QString)"), '<font color="red">{0}</font>'.format(s))
def restore_output(self, s):
self.emit(SIGNAL("restore_progress(QString)"),'<font color="black">{0}</font>'.format(s))
def __do_restore__(self, thread_monitor):
err_msg = []
self.qvm_collection.lock_db_for_writing()
try:
qubesutils.backup_restore_do(str(self.backup_dir), self.vms_to_restore, self.qvm_collection, self.restore_output, self.restore_error_output)
except Exception as ex:
err_msg.append(str(ex))
self.qvm_collection.unlock_db()
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("Finished with errors!"))
else:
self.emit(SIGNAL("restore_progress(QString)"),'<font color="green">{0}</font>'.format("Finished successfully!"))
thread_monitor.set_finished()
def current_page_changed(self, id):
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 = {}
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[:]
qubesutils.backup_restore_print_summary(self.vms_to_restore, print_callback = self.gather_output)
self.confirm_text_edit.setReadOnly(True)
self.confirm_text_edit.setFontFamily("Monospace")
self.confirm_text_edit.setText("\n".join(self.func_output))
elif self.currentPage() is self.commit_page:
self.button(self.CancelButton).setDisabled(True)
self.button(self.FinishButton).setDisabled(True)
self.thread_monitor = ThreadMonitor()
thread = threading.Thread (target= self.__do_restore__ , args=(self.thread_monitor,))
thread.daemon = True
thread.start()
while not self.thread_monitor.is_finished():
self.app.processEvents()
time.sleep (0.1)
#if not self.thread_monitor.success:
#QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg))
umount_device(self.dev_mount_path)
self.button(self.FinishButton).setEnabled(True)
def has_selected_dir(self):
return self.backup_dir != None
def has_selected_vms(self):
return self.select_vms_widget.selected_list.count() > 0
# 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 "
"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")
sys.excepthook = handle_exception
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
global restore_window
restore_window = RestoreVMsWindow()
restore_window.show()
app.exec_()
app.exit()
if __name__ == "__main__":
main()

403
qubesmanager/settings.py Normal file
View File

@ -0,0 +1,403 @@
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2012 Agnieszka Kostrzewa <agnieszka.kostrzewa@gmail.com>
# Copyright (C) 2012 Marek Marczykowski <marmarek@mimuw.edu.pl>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU 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.
#
#
import sys
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesVmLabels
from qubes.qubes import QubesException
from qubes.qubes import qubes_appmenu_create_cmd
from qubes.qubes import qubes_appmenu_remove_cmd
from qubes.qubes import QubesDaemonPidfile
from qubes.qubes import QubesHost
from qubes.qubes import qrexec_client_path
import qubesmanager.resources_rc
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
import subprocess
import time
import threading
from operator import itemgetter
from ui_settingsdlg import *
from multiselectwidget import *
from appmenu_select import *
from firewall import *
class VMSettingsWindow(Ui_SettingsDialog, QDialog):
tabs_indices = {"basic": 0,
"advanced": 1,
"firewall": 2,
"devices": 3,
"applications": 4,
"services": 5,}
def __init__(self, vm, app, qvm_collection, init_page="basic", parent=None):
super(VMSettingsWindow, self).__init__(parent)
self.app = app
self.qvm_collection = qvm_collection
self.vm = vm
if self.vm.template_vm:
self.source_vm = self.vm.template_vm
else:
self.source_vm = self.vm
self.setupUi(self)
if init_page in self.tabs_indices:
idx = self.tabs_indices[init_page]
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.tabWidget.currentChanged.connect(self.current_tab_changed)
###### basic tab
self.__init_basic_tab__()
###### firewall tab
model = QubesFirewallRulesModel()
model.set_vm(vm)
self.set_fw_model(model)
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.policyAllowRadioButton.toggled.connect(self.policy_radio_toggled)
self.dnsCheckBox.toggled.connect(self.dns_checkbox_toggled)
self.icmpCheckBox.toggled.connect(self.icmp_checkbox_toggled)
####### devices tab
self.dev_list = MultiSelectWidget(self)
self.devices_layout.addWidget(self.dev_list)
####### apps tab
if not vm.is_netvm():
self.app_list = MultiSelectWidget(self)
self.apps_layout.addWidget(self.app_list)
self.AppListManager = AppmenuSelectManager(self.vm, self.app_list)
else:
self.tabWidget.setTabEnabled(self.tabs_indices["applications"], False)
def reject(self):
self.done(0)
#needed not to close the dialog before applying changes
def accept(self):
pass
def save_and_apply(self):
thread_monitor = ThreadMonitor()
thread = threading.Thread (target=self.__save_changes__, args=(thread_monitor,))
thread.daemon = True
thread.start()
progress = QProgressDialog ("Applying settings to <b>{0}</b>...".format(self.vm.name), "", 0, 0)
progress.setCancelButton(None)
progress.setModal(True)
progress.show()
while not thread_monitor.is_finished():
self.app.processEvents()
time.sleep (0.1)
progress.hide()
if not thread_monitor.success:
QMessageBox.warning (None, "Error while changing settings for {0}!", "ERROR: {1}".format(self.vm.name, thread_monitor.error_msg))
self.done(0)
def __save_changes__(self, thread_monitor):
ret = self.__apply_basic_tab__()
if len(ret) > 0 :
thread_monitor.set_error_msg('\n'.join(ret))
thread_monitor.set_finished()
return
#self.fw_model.apply_rules()
self.AppListManager.save_appmenu_select_changes()
thread_monitor.set_finished()
def current_tab_changed(self, idx):
if idx == self.tabs_indices["firewall"]:
if self.vm.netvm_vm is not None and not self.vm.netvm_vm.is_proxyvm():
QMessageBox.warning (None, "VM configuration problem!", "The '{0}' AppVM is not network connected to a FirewallVM!<p>".format(self.vm.name) +\
"You may edit the '{0}' VM firewall rules, but these will not take any effect until you connect it to a working Firewall VM.".format(self.vm.name))
######### basic tab
def __init_basic_tab__(self):
self.vmname.setText(self.vm.name)
#self.qvm_collection.lock_db_for_reading()
#self.qvm_collection.load()
#self.qvm_collection.unlock_db()
self.label_list = QubesVmLabels.values()
self.label_list.sort(key=lambda l: l.index)
self.label_idx = 0
for (i, label) in enumerate(self.label_list):
if label == self.vm.label:
self.label_idx = i
self.vmlabel.insertItem(i, label.name)
self.vmlabel.setItemIcon (i, QIcon(label.icon_path))
self.vmlabel.setCurrentIndex(self.label_idx)
if not self.vm.is_template() and self.vm.template_vm is not None:
template_vm_list = [vm for vm in self.qvm_collection.values() if not vm.internal and vm.is_template()]
self.template_idx = 0
for (i, vm) in enumerate(template_vm_list):
text = vm.name
if vm is self.qvm_collection.get_default_template_vm():
text += " (default)"
if vm.qid == self.vm.template_vm.qid:
self.template_idx = i
text += " (current)"
self.template_name.insertItem(i, text)
self.template_name.setCurrentIndex(self.template_idx)
else:
self.template_name.setEnabled(False)
if not self.vm.is_netvm():
netvm_list = [vm for vm in self.qvm_collection.values() if not vm.internal and vm.is_netvm()]
self.netvm_idx = 0
for (i, vm) in enumerate(netvm_list):
text = vm.name
if vm is self.qvm_collection.get_default_netvm_vm():
text += " (default)"
if vm.qid == self.vm.netvm_vm.qid:
self.netvm_idx = i
text += " (current)"
self.netVM.insertItem(i, text)
self.netVM.setCurrentIndex(self.netvm_idx)
else:
self.netVM.setEnabled(False)
#self.vmname.selectAll()
#self.vmname.setFocus()
def __apply_basic_tab__(self):
msg = []
if self.vm.is_running():
msg.append("Can't change settings of a running VM.")
msg.append("telemele")
return msg
# vmname changed
vmname = str(self.vmname.text())
if self.vm.name != vmname:
if self.qvm_collection.get_vm_by_name(vmname) is not None:
msg.append("A VM named <b>{0}</b> already exists in the system!".format(vmname))
else:
oldname = self.vm.name
try:
self.qvm_collection.lock_db_for_writing()
self.vm.pre_rename(vmname)
self.vm.set_name(vmname)
self.vm.post_rename(oldname)
self.qvm_collection.save()
except Exception as ex:
msg.append(str(ex))
finally:
self.qvm_collection.unlock_db()
#vm label changed
if self.vmlabel.currentIndex() != self.label_idx:
label = self.label_list[self.vmlabel.currentIndex()]
self.qvm_collection.lock_db_for_writing()
self.vm.label = label
self.qvm_collection.save()
self.qvm_collection.unlock_db()
return msg
# template_vm = template_vm_list[dialog.template_name.currentIndex()]
# allow_networking = dialog.allow_networking.isChecked()
######### firewall tab related
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.set_allow(model.allow)
self.dnsCheckBox.setChecked(model.allowDns)
self.icmpCheckBox.setChecked(model.allowIcmp)
def set_allow(self, allow):
self.policyAllowRadioButton.setChecked(allow)
self.policyDenyRadioButton.setChecked(not allow)
def policy_radio_toggled(self, on):
self.fw_model.allow = self.policyAllowRadioButton.isChecked()
def dns_checkbox_toggled(self, on):
self.fw_model.allowDns = on
def icmp_checkbox_toggled(self, on):
self.fw_model.allowIcmp = on
def new_rule_button_pressed(self):
dialog = NewFwRuleDlg()
self.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)
dialog.serviceComboBox.setItemText(0, service)
dialog.serviceComboBox.setCurrentIndex(0)
self.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 service == "*":
service = "0"
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, "Invalid service ports range", "Port {0} is lower than port {1}.".format(port2, port))
else:
item = QubesFirewallRuleItem(address, netmask, port, port2)
if row is not None:
self.fw_model.setChild(row, item)
else:
self.fw_model.appendChild(item)
else:
QMessageBox.warning(None, "Invalid service name", "Service '{0} is unknown.".format(service))
# 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 "
"in Qubes VM 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 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 VM Settings")
sys.excepthook = handle_exception
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
vm = None
if len(sys.argv) > 1:
vm = qvm_collection.get_vm_by_name(sys.argv[1])
if vm is None or vm.qid not in qvm_collection:
QMessageBox.critical(None, "Qubes VM Settings Error",
"A VM with the name '{0}' does not exist in the system.".format(sys.argv[1]))
sys.exit(1)
else:
vms_list = [vm.name for vm in qvm_collection.values() if (vm.is_appvm() or vm.is_template())]
vmname = QInputDialog.getItem(None, "Select VM", "Select VM:", vms_list, editable = False)
if not vmname[1]:
sys.exit(1)
vm = qvm_collection.get_vm_by_name(vmname[0])
global settings_window
settings_window = VMSettingsWindow(vm, app, qvm_collection, "basic")
settings_window.show()
app.exec_()
app.exit()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,44 @@
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU 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.
#
#
from PyQt4.QtCore import *
import threading
class ThreadMonitor(QObject):
def __init__(self):
self.success = True
self.error_msg = None
self.event_finished = threading.Event()
def set_error_msg(self, error_msg):
self.success = False
self.error_msg = error_msg
self.set_finished()
def is_finished(self):
return self.event_finished.is_set()
def set_finished(self):
self.event_finished.set()

View File

@ -1,22 +1,35 @@
<!DOCTYPE RCC><RCC version="1.0"> <RCC>
<qresource> <qresource>
<file alias="qubes.png">icons/qubes.png</file> <file alias="mount.png">icons/mount.png</file>
<file alias="appvm.png">icons/appvm.png</file> <file alias="pencil.png">icons/pencil.png</file>
<file alias="netvm.png">icons/netvm.png</file> <file alias="redfirewall.png">icons/redfirewall.png</file>
<file alias="networking.png">icons/networking.png</file> <file alias="edit.png">icons/edit.png</file>
<file alias="dom0.png">icons/dom0.png</file> <file alias="add.png">icons/add.png</file>
<file alias="storagevm.png">icons/storagevm.png</file> <file alias="flag-blue.png">icons/flag-blue.png</file>
<file alias="templatevm.png">icons/templatevm.png</file> <file alias="flag-green.png">icons/flag-green.png</file>
<file alias="updateable.png">icons/updateable.png</file> <file alias="flag-red.png">icons/flag-red.png</file>
<file alias="home.png">icons/home.png</file> <file alias="flag-yellow.png">icons/flag-yellow.png</file>
<file alias="root.png">icons/root.png</file> <file alias="remove.png">icons/remove.png</file>
<file alias="createvm.png">icons/createvm.png</file> <file alias="on.png">icons/on.png</file>
<file alias="removevm.png">icons/removevm.png</file> <file alias="appsprefs.png">icons/appsprefs.png</file>
<file alias="shutdownvm.png">icons/shutdownvm.png</file> <file alias="newfirewall.png">icons/newfirewall.png</file>
<file alias="resumevm.png">icons/resumevm.png</file> <file alias="qubes.png">icons/qubes.png</file>
<file alias="pausevm.png">icons/pausevm.png</file> <file alias="appvm.png">icons/appvm.png</file>
<file alias="showallvms.png">icons/showallvms.png</file> <file alias="netvm.png">icons/netvm.png</file>
<file alias="showcpuload.png">icons/showcpuload.png</file> <file alias="networking.png">icons/networking.png</file>
<file alias="firewall.png">icons/firewall.png</file> <file alias="dom0.png">icons/dom0.png</file>
</qresource> <file alias="storagevm.png">icons/storagevm.png</file>
<file alias="templatevm.png">icons/templatevm.png</file>
<file alias="updateable.png">icons/updateable.png</file>
<file alias="home.png">icons/home.png</file>
<file alias="root.png">icons/root.png</file>
<file alias="createvm.png">icons/createvm.png</file>
<file alias="removevm.png">icons/removevm.png</file>
<file alias="shutdownvm.png">icons/shutdownvm.png</file>
<file alias="resumevm.png">icons/resumevm.png</file>
<file alias="pausevm.png">icons/pausevm.png</file>
<file alias="showallvms.png">icons/showallvms.png</file>
<file alias="showcpuload.png">icons/showcpuload.png</file>
<file alias="firewall.png">icons/firewall.png</file>
</qresource>
</RCC> </RCC>

229
restoredlg.ui Normal file
View File

@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Restore</class>
<widget class="QWizard" name="Restore">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>399</height>
</rect>
</property>
<property name="windowTitle">
<string>Qubes Restore VMs</string>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="options">
<set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set>
</property>
<widget class="QWizardPage" name="select_dir_page">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="title">
<string>Backup source location</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Device</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="dev_combobox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>dev1</string>
</property>
</item>
<item>
<property name="text">
<string>longdeviceblablabla</string>
</property>
</item>
<item>
<property name="text">
<string>dev2</string>
</property>
</item>
<item>
<property name="text">
<string>dev3</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Backup directory:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="dir_line_edit"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="select_path_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="options_groupbox">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="title">
<string>Restore options</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QCheckBox" name="ignore_missing">
<property name="toolTip">
<string>Ignore missing templates or netvms, restore VMs anyway.</string>
</property>
<property name="text">
<string>ignore missing</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="skip_dom0">
<property name="text">
<string>skip dom0</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="ignore_uname_mismatch">
<property name="toolTip">
<string>Ignore dom0 username mismatch while restoring homedir.</string>
</property>
<property name="text">
<string>ignore username mismatch</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>215</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="select_vms_page">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QGroupBox" name="select_vms_groupbox">
<property name="title">
<string>VMs to restore</string>
</property>
<layout class="QVBoxLayout" name="select_vms_layout"/>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="confirm_page">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_6">
<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>You're about to perform the following actions:</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="confirm_text_edit">
<property name="html">
<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; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info&lt;br /&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A lot of info A lot of info A lot of info A lot of info A lot of info A lot of info&lt;br /&gt;A lot of info&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_7">
<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>To continue press Next. </string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="commit_page">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTextEdit" name="commit_text_edit"/>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -33,12 +33,25 @@ cp qubes-appmenu-select $RPM_BUILD_ROOT/usr/bin
mkdir -p $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager/ mkdir -p $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager/
cp qubesmanager/main.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager cp qubesmanager/main.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/appmenu_select.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager cp qubesmanager/appmenu_select.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/backup.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/backup_utils.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/firewall.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager cp qubesmanager/firewall.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/qrc_resources.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager cp qubesmanager/global_settings.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/multiselectwidget.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/restore.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/settings.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/thread_monitor.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/resources_rc.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/__init__.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager cp qubesmanager/__init__.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_backupdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_editfwrulesdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_globalsettingsdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_mainwindow.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_multiselectwidget.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_newappvmdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager cp qubesmanager/ui_newappvmdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_newfwruledlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager cp qubesmanager/ui_newfwruledlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_editfwrulesdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager cp qubesmanager/ui_restoredlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
cp qubesmanager/ui_settingsdlg.py{,c,o} $RPM_BUILD_ROOT%{python_sitearch}/qubesmanager
mkdir -p $RPM_BUILD_ROOT/usr/share/applications mkdir -p $RPM_BUILD_ROOT/usr/share/applications
cp qubes-manager.desktop $RPM_BUILD_ROOT/usr/share/applications cp qubes-manager.desktop $RPM_BUILD_ROOT/usr/share/applications
@ -67,21 +80,60 @@ rm -rf $RPM_BUILD_ROOT
%{python_sitearch}/qubesmanager/appmenu_select.py %{python_sitearch}/qubesmanager/appmenu_select.py
%{python_sitearch}/qubesmanager/appmenu_select.pyc %{python_sitearch}/qubesmanager/appmenu_select.pyc
%{python_sitearch}/qubesmanager/appmenu_select.pyo %{python_sitearch}/qubesmanager/appmenu_select.pyo
%{python_sitearch}/qubesmanager/backup.py
%{python_sitearch}/qubesmanager/backup.pyc
%{python_sitearch}/qubesmanager/backup.pyo
%{python_sitearch}/qubesmanager/backup_utils.py
%{python_sitearch}/qubesmanager/backup_utils.pyc
%{python_sitearch}/qubesmanager/backup_utils.pyo
%{python_sitearch}/qubesmanager/firewall.py %{python_sitearch}/qubesmanager/firewall.py
%{python_sitearch}/qubesmanager/firewall.pyc %{python_sitearch}/qubesmanager/firewall.pyc
%{python_sitearch}/qubesmanager/firewall.pyo %{python_sitearch}/qubesmanager/firewall.pyo
%{python_sitearch}/qubesmanager/qrc_resources.py %{python_sitearch}/qubesmanager/global_settings.py
%{python_sitearch}/qubesmanager/qrc_resources.pyc %{python_sitearch}/qubesmanager/global_settings.pyc
%{python_sitearch}/qubesmanager/qrc_resources.pyo %{python_sitearch}/qubesmanager/global_settings.pyo
%{python_sitearch}/qubesmanager/multiselectwidget.py
%{python_sitearch}/qubesmanager/multiselectwidget.pyc
%{python_sitearch}/qubesmanager/multiselectwidget.pyo
%{python_sitearch}/qubesmanager/restore.py
%{python_sitearch}/qubesmanager/restore.pyc
%{python_sitearch}/qubesmanager/restore.pyo
%{python_sitearch}/qubesmanager/settings.py
%{python_sitearch}/qubesmanager/settings.pyc
%{python_sitearch}/qubesmanager/settings.pyo
%{python_sitearch}/qubesmanager/thread_monitor.py
%{python_sitearch}/qubesmanager/thread_monitor.pyc
%{python_sitearch}/qubesmanager/thread_monitor.pyo
%{python_sitearch}/qubesmanager/resources_rc.py
%{python_sitearch}/qubesmanager/resources_rc.pyc
%{python_sitearch}/qubesmanager/resources_rc.pyo
%{python_sitearch}/qubesmanager/ui_backupdlg.py
%{python_sitearch}/qubesmanager/ui_backupdlg.pyc
%{python_sitearch}/qubesmanager/ui_backupdlg.pyo
%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.py
%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.pyc
%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.pyo
%{python_sitearch}/qubesmanager/ui_globalsettingsdlg.py
%{python_sitearch}/qubesmanager/ui_globalsettingsdlg.pyc
%{python_sitearch}/qubesmanager/ui_globalsettingsdlg.pyo
%{python_sitearch}/qubesmanager/ui_mainwindow.py
%{python_sitearch}/qubesmanager/ui_mainwindow.pyc
%{python_sitearch}/qubesmanager/ui_mainwindow.pyo
%{python_sitearch}/qubesmanager/ui_multiselectwidget.py
%{python_sitearch}/qubesmanager/ui_multiselectwidget.pyc
%{python_sitearch}/qubesmanager/ui_multiselectwidget.pyo
%{python_sitearch}/qubesmanager/ui_newappvmdlg.py %{python_sitearch}/qubesmanager/ui_newappvmdlg.py
%{python_sitearch}/qubesmanager/ui_newappvmdlg.pyc %{python_sitearch}/qubesmanager/ui_newappvmdlg.pyc
%{python_sitearch}/qubesmanager/ui_newappvmdlg.pyo %{python_sitearch}/qubesmanager/ui_newappvmdlg.pyo
%{python_sitearch}/qubesmanager/ui_newfwruledlg.py %{python_sitearch}/qubesmanager/ui_newfwruledlg.py
%{python_sitearch}/qubesmanager/ui_newfwruledlg.pyc %{python_sitearch}/qubesmanager/ui_newfwruledlg.pyc
%{python_sitearch}/qubesmanager/ui_newfwruledlg.pyo %{python_sitearch}/qubesmanager/ui_newfwruledlg.pyo
%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.py %{python_sitearch}/qubesmanager/ui_restoredlg.py
%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.pyc %{python_sitearch}/qubesmanager/ui_restoredlg.pyc
%{python_sitearch}/qubesmanager/ui_editfwrulesdlg.pyo %{python_sitearch}/qubesmanager/ui_restoredlg.pyo
%{python_sitearch}/qubesmanager/ui_settingsdlg.py
%{python_sitearch}/qubesmanager/ui_settingsdlg.pyc
%{python_sitearch}/qubesmanager/ui_settingsdlg.pyo
/usr/share/applications/qubes-manager.desktop /usr/share/applications/qubes-manager.desktop

807
settingsdlg.ui Normal file
View File

@ -0,0 +1,807 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDialog</class>
<widget class="QDialog" name="SettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>694</width>
<height>483</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<property name="windowIcon">
<iconset resource="resources.qrc">
<normaloff>:/root.png</normaloff>:/root.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="currentIndex">
<number>5</number>
</property>
<widget class="QWidget" name="basic_tab">
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<attribute name="title">
<string>Basic</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name &amp; label:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="vmname">
<property name="text">
<string>myappvm</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="vmlabel">
<property name="frame">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Template:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="template_name"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>NetVM:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="netVM"/>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="checkBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Include in backups by default</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Info</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="type_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>AppVM</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Installed by RPM:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="rpm_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>No</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Disk storage</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
<widget class="QSpinBox" name="priv_size">
<property name="enabled">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>GB</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Private storage max. size:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>73</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="advanced_tab">
<property name="enabled">
<bool>false</bool>
</property>
<attribute name="title">
<string>Advanced</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_9">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Memory/CPU</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Initial memory:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="mem_size">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>xx</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_16">
<property name="text">
<string>MB</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Max memory:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="max_mem_size">
<property name="enabled">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>400</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>MB</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>VCPUs no.:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="vcpus">
<property name="enabled">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="include_in_balancing">
<property name="text">
<string>Include in memory balancing</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
<string>Kernel</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Kernel:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="kernel"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Kernel opts:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="kernel_opts">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>[]</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_11">
<property name="title">
<string>Paths</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>dir:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="dir_path">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>dir_path</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>config:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="config_path">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>config_path</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>root img:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="root_img_path">
<property name="text">
<string>root_img_path</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>root volatile img:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="volatile_img_path">
<property name="text">
<string>volatile_path</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>private img:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="private_img_path">
<property name="text">
<string>private_path</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>88</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="firewall_tab">
<attribute name="title">
<string>Firewall rules</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QRadioButton" name="policyAllowRadioButton">
<property name="text">
<string>Allow network access except...</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="policyDenyRadioButton">
<property name="text">
<string>Deny network access except...</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QTreeView" name="rulesTreeView">
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>false</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>true</bool>
</property>
<attribute name="headerDefaultSectionSize">
<number>40</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QCheckBox" name="icmpCheckBox">
<property name="text">
<string>Allow ICMP traffic</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="dnsCheckBox">
<property name="text">
<string>Allow DNS queries</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QPushButton" name="newRuleButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/add.png</normaloff>:/add.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="editRuleButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/pencil.png</normaloff>:/pencil.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteRuleButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/remove.png</normaloff>:/remove.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="devices_tab">
<property name="enabled">
<bool>false</bool>
</property>
<attribute name="title">
<string>Devices</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<layout class="QVBoxLayout" name="devices_layout"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="apps_tab">
<attribute name="icon">
<iconset resource="resources.qrc">
<normaloff>:/appsprefs.png</normaloff>:/appsprefs.png</iconset>
</attribute>
<attribute name="title">
<string>Applications</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0">
<layout class="QVBoxLayout" name="apps_layout"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="services_tab">
<property name="enabled">
<bool>false</bool>
</property>
<attribute name="title">
<string>Services</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLineEdit" name="service_lineEdit"/>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="add_button">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/add.png</normaloff>:/add.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="4" column="0" rowspan="2">
<widget class="QListWidget" name="services_list">
<item>
<property name="text">
<string>ntpd</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>cupsd</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>meminfo</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Checked services will be turned on.</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Unchecked services will be turned off.</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Unlisted services will follow default VM's settings.</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="remove_button">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/remove.png</normaloff>:/remove.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="resources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1 +1 @@
1.1.6 1.2.0