--- pyanaconda/ui/gui/spokes/filter.glade | 31 +++++++++++++++++++++++++++++++ pyanaconda/ui/gui/spokes/filter.py | 5 +++++ 2 files changed, 36 insertions(+)
diff --git a/pyanaconda/ui/gui/spokes/filter.glade b/pyanaconda/ui/gui/spokes/filter.glade index 836a76d..cde4c80 100644 --- a/pyanaconda/ui/gui/spokes/filter.glade +++ b/pyanaconda/ui/gui/spokes/filter.glade @@ -90,6 +90,7 @@ <object class="GtkNotebook" id="advancedNotebook"> <property name="visible">True</property> <property name="can_focus">True</property> + <property name="vexpand">True</property> <signal name="switch-page" handler="on_page_switched" swapped="no"/> <child> <object class="GtkGrid" id="searchGrid"> @@ -1216,11 +1217,41 @@ </object> <packing> <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="addAdditionalCombo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="hexpand">True</property> + <property name="active">0</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <items> + <item translatable="yes">Add Additional Targets</item> + <item translatable="yes">Add iSCSI Target...</item> + <item translatable="yes">Add FCoE SAN...</item> + <item translatable="yes">Add ZFCP LUN...</item> + </items> + <signal name="changed" handler="on_add_additional_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> <property name="top_attach">0</property> <property name="width">1</property> <property name="height">1</property> </packing> </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> </object> <packing> <property name="expand">False</property> diff --git a/pyanaconda/ui/gui/spokes/filter.py b/pyanaconda/ui/gui/spokes/filter.py index f093704..1c1be28 100644 --- a/pyanaconda/ui/gui/spokes/filter.py +++ b/pyanaconda/ui/gui/spokes/filter.py @@ -310,10 +310,12 @@ class FilterSpoke(NormalSpoke): RaidPage(self.storage, self.builder), ZPage(self.storage, self.builder)]
+ self._addAdditionalCombo = self.builder.get_object("addAdditionalCombo") self._notebook = self.builder.get_object("advancedNotebook")
if not arch.isS390(): self._notebook.remove_page(-1) + self._addAdditionalCombo.remove(3)
self._store = self.builder.get_object("diskStore") self._addDisksButton = self.builder.get_object("addDisksButton") @@ -426,6 +428,9 @@ class FilterSpoke(NormalSpoke):
self._update_summary()
+ def on_add_additional_changed(self, *args): + pass + ## ## SEARCH TAB SIGNAL HANDLERS ##
--- pyanaconda/ui/gui/spokes/filter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/filter.py b/pyanaconda/ui/gui/spokes/filter.py index 1c1be28..f7c3de4 100644 --- a/pyanaconda/ui/gui/spokes/filter.py +++ b/pyanaconda/ui/gui/spokes/filter.py @@ -349,11 +349,11 @@ class FilterSpoke(NormalSpoke): for disk in itertools.ifilterfalse(isLocalDisk, self.disks): if self.pages[1].ismember(disk): multipathDisks.append(disk) - elif self.disks[2].ismember(disk): + elif self.pages[2].ismember(disk): otherDisks.append(disk) - elif self.disks[3].ismember(disk): + elif self.pages[3].ismember(disk): raidDisks.append(disk) - elif self.disks[4].ismember(disk): + elif self.pages[4].ismember(disk): zDisks.append(disk)
self.pages[0].setup(self._store, self.selected_disks, [])
--- pyanaconda/ui/gui/spokes/filter.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/filter.py b/pyanaconda/ui/gui/spokes/filter.py index f7c3de4..80ea03e 100644 --- a/pyanaconda/ui/gui/spokes/filter.py +++ b/pyanaconda/ui/gui/spokes/filter.py @@ -115,6 +115,18 @@ class FilterPage(object): """ return True
+ def setupCombo(self, combo, items): + """Populate a given GtkComboBoxText instance with a list of items. The + combo will first be cleared, so this method is suitable for calling + repeatedly. The first item in the list will be selected by default. + """ + combo.remove_all() + for i in sorted(items): + combo.append_text(i) + + if items: + combo.set_active(0) + class SearchPage(FilterPage): def __init__(self, storage, builder): FilterPage.__init__(self, storage, builder) @@ -169,7 +181,7 @@ class MultipathPage(FilterPage): selected = disk.name in selectedNames
store.append([True, selected, not disk.protected, - disk.name, "", disk.model,size_str(disk.size), + disk.name, "", disk.model, size_str(disk.size), disk.vendor, disk.bus, disk.serial, disk.wwid, "\n".join(paths), "", "", "", ""]) @@ -183,22 +195,8 @@ class MultipathPage(FilterPage): self._combo.set_active(0) self._combo.emit("changed")
- self._vendorCombo = self.builder.get_object("multipathVendorCombo") - self._icCombo = self.builder.get_object("multipathInterconnectCombo") - - self._vendorCombo.remove_all() - for v in sorted(vendors): - self._vendorCombo.append_text(v) - - self._icCombo.remove_all() - for i in sorted(interconnects): - self._icCombo.append_text(i) - - if vendors: - self._vendorCombo.set_active(0) - - if interconnects: - self._icCombo.set_active(0) + self.setupCombo(self.builder.get_object("multipathVendorCombo"), vendors) + self.setupCombo(self.builder.get_object("multipathInterconnectCombo"), interconnects)
def _filter_func(self, device): if not self.filterActive:
This does not yet include support for CHAP/rCHAP (well, it might but I have not tested it at all), complete error handling, or correct filtering. However if you are careful and type everything in as you should, you can add iSCSI disks to the installer. --- configure.ac | 1 + po/POTFILES.in | 1 + pyanaconda/constants.py | 2 + pyanaconda/ui/gui/spokes/Makefile.am | 2 +- pyanaconda/ui/gui/spokes/advstorage/Makefile.am | 25 + pyanaconda/ui/gui/spokes/advstorage/__init__.py | 0 pyanaconda/ui/gui/spokes/advstorage/iscsi.glade | 1144 +++++++++++++++++++++++ pyanaconda/ui/gui/spokes/advstorage/iscsi.py | 340 +++++++ pyanaconda/ui/gui/spokes/filter.glade | 327 ++++++- pyanaconda/ui/gui/spokes/filter.py | 87 +- pyanaconda/ui/lib/disks.py | 4 +- 11 files changed, 1925 insertions(+), 8 deletions(-) create mode 100644 pyanaconda/ui/gui/spokes/advstorage/Makefile.am create mode 100644 pyanaconda/ui/gui/spokes/advstorage/__init__.py create mode 100644 pyanaconda/ui/gui/spokes/advstorage/iscsi.glade create mode 100644 pyanaconda/ui/gui/spokes/advstorage/iscsi.py
diff --git a/configure.ac b/configure.ac index a928365..d90d83f 100644 --- a/configure.ac +++ b/configure.ac @@ -233,6 +233,7 @@ AC_CONFIG_FILES([Makefile pyanaconda/ui/gui/categories/Makefile pyanaconda/ui/gui/hubs/Makefile pyanaconda/ui/gui/spokes/Makefile + pyanaconda/ui/gui/spokes/advstorage/Makefile pyanaconda/ui/gui/spokes/lib/Makefile pyanaconda/ui/gui/tools/Makefile pyanaconda/ui/gui/Makefile diff --git a/po/POTFILES.in b/po/POTFILES.in index f5a1d8c..695be87 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -89,6 +89,7 @@ pyanaconda/ui/gui/spokes/source.glade pyanaconda/ui/gui/spokes/welcome.glade pyanaconda/ui/gui/spokes/user.glade pyanaconda/ui/gui/spokes/custom.glade +pyanaconda/ui/gui/spokes/advstorage/iscsi.glade pyanaconda/ui/gui/spokes/lib/cart.glade pyanaconda/ui/gui/spokes/lib/detailederror.glade pyanaconda/ui/gui/spokes/lib/passphrase.glade diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py index 4310bf1..f050a8d 100644 --- a/pyanaconda/constants.py +++ b/pyanaconda/constants.py @@ -122,3 +122,5 @@ THREAD_CHECK_SOFTWARE = "AnaCheckSoftwareThread" THREAD_SOURCE_WATCHER = "AnaSourceWatcher" THREAD_INSTALL = "AnaInstallThread" THREAD_CONFIGURATION = "AnaConfigurationThread" +THREAD_ISCSI_DISCOVER = "AnaIscsiDiscoverThread" +THREAD_ISCSI_LOGIN = "AnaIscsiLoginThread" diff --git a/pyanaconda/ui/gui/spokes/Makefile.am b/pyanaconda/ui/gui/spokes/Makefile.am index 352f4e4..a052dab 100644 --- a/pyanaconda/ui/gui/spokes/Makefile.am +++ b/pyanaconda/ui/gui/spokes/Makefile.am @@ -15,7 +15,7 @@ # # Author: Chris Lumens clumens@redhat.com
-SUBDIRS = lib +SUBDIRS = advstorage lib
MAINTAINERCLEANFILES = Makefile.in
diff --git a/pyanaconda/ui/gui/spokes/advstorage/Makefile.am b/pyanaconda/ui/gui/spokes/advstorage/Makefile.am new file mode 100644 index 0000000..bcad43c --- /dev/null +++ b/pyanaconda/ui/gui/spokes/advstorage/Makefile.am @@ -0,0 +1,25 @@ +# Copyright (C) 2013 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +# Author: Chris Lumens clumens@redhat.com + +MAINTAINERCLEANFILES = Makefile.in + +pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME) +spokesdir = $(pkgpyexecdir)/ui/gui/spokes/advstorage +spokes_PYTHON = *.py + +uidir = $(datadir)/$(PACKAGE_NAME)/ui/spokes/advstorage +dist_ui_DATA = *.glade diff --git a/pyanaconda/ui/gui/spokes/advstorage/__init__.py b/pyanaconda/ui/gui/spokes/advstorage/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade b/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade new file mode 100644 index 0000000..1e5ac2c --- /dev/null +++ b/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade @@ -0,0 +1,1144 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.0 --> + <object class="GtkDialog" id="iscsiDialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="modal">True</property> + <property name="type_hint">dialog</property> + <property name="decorated">False</property> + <child internal-child="vbox"> + <object class="GtkBox" id="dialog-vbox1"> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkButtonBox" id="dialog-action_area1"> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="cancelButton"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="okButton"> + <property name="label">gtk-ok</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="box1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">ADD iSCSI STORAGE TARGET</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="iscsiNotebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> + <child> + <object class="GtkGrid" id="configureGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_bottom">6</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">To use iSCSI disks, you must provide the address of your iSCSI target and the iSCSI initiator name you've configured for your host.</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">3</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="targetEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Target IP Address:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">targetEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="initiatorEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">iSCSI _Initiator Name:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">initiatorEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="authTypeCombo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="active">0</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <items> + <item translatable="yes">No credentials (discovery authentication disabled)</item> + <item translatable="yes">CHAP pair</item> + <item translatable="yes">CHAP pair and a reverse pair</item> + </items> + <signal name="changed" handler="on_auth_type_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Discovery Authentication Type:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">authTypeCombo</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">4</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label9"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><span size="small"><b>Example:</b> iqn.2012-09.com.example:diskarrays-sn-a8675309</span></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="stock">gtk-info</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="authNotebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> + <child> + <object class="GtkLabel" id="authFiller"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkGrid" id="chapGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Username:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">chapUsernameEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Password:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">chapPasswordEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="chapUsernameEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="chapPasswordEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkGrid" id="rchapGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkEntry" id="rchapUsernameEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="rchapPasswordEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="rchapReverseUsername"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="rchapReversePassword"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">●</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Username:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">rchapUsernameEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label10"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Password:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">rchapPasswordEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label11"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Reverse CHAP User_name:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">rchapReverseUsername</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Reverse CHAP Pass_word:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">rchapReversePassword</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">5</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="conditionNotebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="valign">center</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> + <child> + <object class="GtkButton" id="startButton"> + <property name="label" translatable="yes">_Start Discovery</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="vexpand">True</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="on_start_clicked" swapped="no"/> + </object> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkBox" id="waitBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="spacing">5</property> + <child> + <object class="GtkSpinner" id="waitSpinner"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="active">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Discovering iSCSI targets. This may take a moment...</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkGrid" id="errorBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkImage" id="image3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="stock">gtk-dialog-error</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label14"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>No iSCSI targets discovered.</b></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label22"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">There was an error discovering iSCSI targets. Check your target IP address and server configuration and try again.</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">6</property> + <property name="width">3</property> + <property name="height">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkGrid" id="discoveredGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="discoveredLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">The following nodes were discovered using the iSCSI initiator <b>%(initiatorName)s</b> using the target IP address <b>%(targetAddress)s</b>. Please select which nodes you wish to log into:</property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="vexpand">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="nodeTreeView"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="model">nodeStoreFiltered</property> + <property name="headers_clickable">False</property> + <property name="search_column">0</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection"/> + </child> + <child> + <object class="GtkTreeViewColumn" id="nodeSelectedColumn"> + <child> + <object class="GtkCellRendererToggle" id="nodeSelectedRenderer"> + <property name="radio">True</property> + <signal name="toggled" handler="on_row_toggled" swapped="no"/> + </object> + <attributes> + <attribute name="active">0</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="nodeNameColumn"> + <property name="title" translatable="yes">Node Name</property> + <child> + <object class="GtkCellRendererText" id="nodeNameRenderer"/> + <attributes> + <attribute name="text">2</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="loginNotebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> + <child> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="loginAuthTypeLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Node login authentication type:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginAuthTypeCombo</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="loginAuthTypeCombo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <items> + <item translatable="yes">No credentials (discovery authentication disabled)</item> + <item translatable="yes">CHAP pair</item> + <item translatable="yes">CHAP pair and a reverse pair</item> + <item translatable="yes">Use the credentials from discovery</item> + </items> + <signal name="changed" handler="on_discovered_type_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="loginBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkButton" id="loginButton"> + <property name="label" translatable="yes">_Log In</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="on_login_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkSpinner" id="loginSpinner"> + <property name="can_focus">False</property> + <property name="no_show_all">True</property> + <property name="active">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="loginAuthNotebook"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> + <child> + <object class="GtkLabel" id="noCredentialsFiller"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkGrid" id="loginChapGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="label15"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Username:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginChapUsernameEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label16"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Password:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginChapPasswordEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginChapUsernameEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginChapPasswordEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkGrid" id="loginRchapGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkEntry" id="loginRchapUsernameEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginRchapPasswordEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginRchapReverseUsername"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginRchapReversePassword"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label17"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Username:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginRchapUsernameEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label18"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Password:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginRchapPasswordEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label19"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Reverse CHAP User_name:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginRchapReverseUsername</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label20"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Reverse CHAP Pass_word:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginRchapReversePassword</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkLabel" id="sameCredentialsFiller"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="position">3</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkBox" id="loginSuccessBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="vexpand">True</property> + <property name="spacing">6</property> + <child> + <object class="GtkImage" id="image2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="stock">gtk-apply</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label21"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes"><b>Node login successful.</b> +Successfully logged in and attached to the selected nodes! Pressing 'OK' will return you to the previous disk selection screen.</property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child type="tab"> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">2</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child type="tab"> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">cancelButton</action-widget> + <action-widget response="1">okButton</action-widget> + </action-widgets> + </object> + <object class="GtkListStore" id="nodeStore"> + <columns> + <!-- column-name nodeSelected --> + <column type="gboolean"/> + <!-- column-name nodeNotLoggedIn --> + <column type="gboolean"/> + <!-- column-name nodeName --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkTreeModelFilter" id="nodeStoreFiltered"> + <property name="child_model">nodeStore</property> + </object> +</interface> diff --git a/pyanaconda/ui/gui/spokes/advstorage/iscsi.py b/pyanaconda/ui/gui/spokes/advstorage/iscsi.py new file mode 100644 index 0000000..f3d469e --- /dev/null +++ b/pyanaconda/ui/gui/spokes/advstorage/iscsi.py @@ -0,0 +1,340 @@ +# iSCSI configuration dialog +# +# Copyright (C) 2013 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Chris Lumens clumens@redhat.com +# + +from IPy import IP +from collections import namedtuple +from gi.repository import GLib, Gtk + +from pyanaconda import constants +from pyanaconda.threads import threadMgr, AnacondaThread +from pyanaconda.ui.gui import GUIObject + +import gettext +_ = lambda x: gettext.ldgettext("anaconda", x) + +__all__ = ["ISCSIDialog"] + +STYLE_NONE = 0 +STYLE_CHAP = 1 +STYLE_REVERSE_CHAP = 2 + +Credentials = namedtuple("Credentials", ["style", + "targetIP", "initiator", "username", + "password", "rUsername", "rPassword"]) + +NodeStoreRow = namedtuple("NodeStoreRow", ["selected", "notLoggedIn", "name"]) + +def login_no_credentials(builder): + return Credentials._make([STYLE_NONE, + builder.get_object("targetEntry").get_text(), + builder.get_object("initiatorEntry").get_text(), + "", "", "", ""]) + +def login_chap(builder): + return Credentials._make([STYLE_CHAP, + builder.get_object("targetEntry").get_text(), + builder.get_object("initiatorEntry").get_text(), + builder.get_object("chapUsernameEntry").get_text(), + builder.get_object("chapPasswordEntry").get_text(), + "", ""]) + +def login_reverse_chap(builder): + return Credentials._make([STYLE_REVERSE_CHAP, + builder.get_object("targetEntry").get_text(), + builder.get_object("initiatorEntry").get_text(), + builder.get_object("rchapUsernameEntry").get_text(), + builder.get_object("rchapPasswordEntry").get_text(), + builder.get_object("rchapReverseUsername").get_text(), + builder.get_object("rchapReversePassword").get_text()]) + +def login_credentials_valid(credentials): + if credentials.style == STYLE_NONE: + return True + elif credentials.style == STYLE_CHAP: + return credentials.username.strip() != "" and credentials.password != "" + elif credentials.style == STYLE_REVERSE_CHAP: + return credentials.username.strip() != "" and credentials.password != "" and \ + credentials.rUsername.strip() != "" and credentials.rPassword != "" + +# This list maps the current page from the authNotebook to a function to grab +# credentials out of the UI. This works as long as authNotebook keeps the +# filler page at the front. +loginMap = [login_no_credentials, login_chap, login_reverse_chap] + +class ISCSIDialog(GUIObject): + builderObjects = ["iscsiDialog", "nodeStore", "nodeStoreFiltered"] + mainWidgetName = "iscsiDialog" + uiFile = "spokes/advstorage/iscsi.glade" + + def __init__(self, data, storage): + GUIObject.__init__(self, data) + self.storage = storage + self.iscsi = self.storage.iscsi() + + self._discoveryError = False + self._loginError = False + + self._discoveredNodes = [] + + def refresh(self): + self._authTypeCombo = self.builder.get_object("authTypeCombo") + self._authNotebook = self.builder.get_object("authNotebook") + self._iscsiNotebook = self.builder.get_object("iscsiNotebook") + + self._loginButton = self.builder.get_object("loginButton") + self._loginAuthTypeCombo = self.builder.get_object("loginAuthTypeCombo") + self._loginAuthNotebook = self.builder.get_object("loginAuthNotebook") + + self._configureGrid = self.builder.get_object("configureGrid") + self._conditionNotebook = self.builder.get_object("conditionNotebook") + + self._startButton = self.builder.get_object("startButton") + self._okButton = self.builder.get_object("okButton") + self._cancelButton = self.builder.get_object("cancelButton") + + self._store = self.builder.get_object("nodeStore") + + self._authTypeCombo.set_active(0) + self._startButton.set_sensitive(True) + + self._loginAuthTypeCombo.set_active(0) + + self.builder.get_object("nodeStoreFiltered").set_visible_column(1) + + initiatorEntry = self.builder.get_object("initiatorEntry") + initiatorEntry.set_text(self.iscsi.initiator) + initiatorEntry.set_sensitive(self.iscsi.initiatorSet) + + @property + def selectedNames(self): + return [itr[2] for itr in self._store if itr[0]] + + def run(self): + rc = self.window.run() + self.window.destroy() + return rc + + ## + ## DISCOVERY + ## + + def on_auth_type_changed(self, widget, *args): + self._authNotebook.set_current_page(widget.get_active()) + + # When we change the notebook, we also need to reverify the credentials + # in order to set the Start button sensitivity. + self.on_login_field_changed() + + def _discover(self, credentials): + # This needs to be in its own thread, not marked with gtk_action_* because it's + # called from on_start_clicked, which is in the GTK main loop. Those decorators + # won't do anything special in that case. + if not self.iscsi.initiatorSet: + self.iscsi.initiator = credentials.initiator + + try: + self._discoveredNodes = self.iscsi.discover(credentials.targetIP, + username=credentials.username, + password=credentials.password, + r_username=credentials.rUsername, + r_password=credentials.rPassword) + except IOError as e: + self._discoveryError = True + + if len(self._discoveredNodes) == 0: + self._discoveryError = True + + def _check_discover(self, *args): + if threadMgr.get(constants.THREAD_ISCSI_DISCOVER): + return True + + # When iscsi discovery is done, update the UI. We don't need to worry + # about the user escaping from the dialog because all the buttons are + # marked insensitive. + spinner = self.builder.get_object("waitSpinner") + spinner.stop() + + if self._discoveryError: + # Failure. Display some error message and leave the user on the + # dialog to try again. + self._discoveryError = False + self._conditionNotebook.set_current_page(2) + else: + # Success. Now populate the node store and kick the user on over to + # that subscreen. + self._add_nodes(self._discoveredNodes) + self._iscsiNotebook.set_current_page(1) + self._okButton.set_sensitive(True) + + # We always want to enable this button, in case the user's had enough. + self._cancelButton.set_sensitive(True) + return False + + def on_start_clicked(self, *args): + # First, update some widgets to not be usable while discovery happens. + self._startButton.hide() + self._cancelButton.set_sensitive(False) + self._okButton.set_sensitive(False) + + self._conditionNotebook.set_current_page(1) + + # Mark all children of the configureGrid as insensitive, except for the + # wait box. + for child in self._configureGrid.get_children(): + if child != self._conditionNotebook: + child.set_sensitive(False) + + # Now get the login credentials. + credentials = loginMap[self._authNotebook.get_current_page()](self.builder) + + # FIXME + # Verify credentials, setting the UI if required. + + discoveredLabel = self.builder.get_object("discoveredLabel") + discoveredLabel.set_markup(discoveredLabel.get_label() % {"initiatorName": credentials.initiator, + "targetAddress": credentials.targetIP}) + + spinner = self.builder.get_object("waitSpinner") + spinner.start() + + threadMgr.add(AnacondaThread(name=constants.THREAD_ISCSI_DISCOVER, target=self._discover, + args=(credentials,))) + GLib.timeout_add(250, self._check_discover) + + # When the initiator name, ip address, and any auth fields are filled in + # valid, only then should the Start button be made sensitive. + def _target_ip_valid(self): + widget = self.builder.get_object("targetEntry") + text = widget.get_text() + + try: + ip = IP(text) + return True + except ValueError: + return False + + def _initiator_name_valid(self): + widget = self.builder.get_object("initiatorEntry") + text = widget.get_text() + + stripped = text.strip() + return "." in stripped and ":" in stripped + + def on_login_field_changed(self, *args): + # Make up a credentials object so we can test if it's valid. + credentials = loginMap[self._authNotebook.get_current_page()](self.builder) + sensitive = self._target_ip_valid() and self._initiator_name_valid() and login_credentials_valid(credentials) + self._startButton.set_sensitive(sensitive) + + ## + ## LOGGING IN + ## + + def _add_nodes(self, nodes): + for node in nodes: + self._store.append([False, True, node.name]) + + # We should select the first node by default. + self._store[0][0] = True + + def on_discovered_type_changed(self, widget, *args): + self._loginAuthNotebook.set_current_page(widget.get_active()) + + def on_row_toggled(self, button, path): + if not path: + return + + # First, mark all rows as unselected. + for row in self._store: + row[0] = False + + # Then, go back and mark just this row as selected. + itr = self._store.get_iter(path) + self._store[itr][0] = True + + def _login(self): + for row in self._store: + obj = NodeStoreRow._make(row) + + if not obj.selected: + continue + + for node in self._discoveredNodes: + if obj.notLoggedIn and node.name == obj.name: + (rc, msg) = self.iscsi.log_into_node(node) + if not rc: + print msg + return + + row[1] = False + + # We need to call this to get the device node to show up + # in our devicetree. + self.storage.devicetree.populate() + + # Only logging into one at a time. + break + + def _check_login(self, *args): + if threadMgr.get(constants.THREAD_ISCSI_LOGIN): + return True + + spinner = self.builder.get_object("loginSpinner") + spinner.stop() + spinner.hide() + + if self._loginError: + self._loginError = False + self._cancelButton.set_sensitive(True) + self._loginButton.set_sensitive(True) + else: + # Select the now-first target for the user in case they want to + # log into another one. + for row in self._store: + if row[1]: + row[0] = True + + # And make the login button sensitive if there are any more + # nodes to login to. + self._loginButton.set_sensitive(True) + break + + self._okButton.set_sensitive(True) + + # Once a node has been logged into, it doesn't make much sense to let + # the user cancel. Cancel what, exactly? + self._cancelButton.set_sensitive(False) + + return False + + def on_login_clicked(self, *args): + # Make the buttons unusable while we work. + self._okButton.set_sensitive(False) + self._cancelButton.set_sensitive(False) + self._loginButton.set_sensitive(False) + + spinner = self.builder.get_object("loginSpinner") + spinner.start() + spinner.set_visible(True) + spinner.show() + + threadMgr.add(AnacondaThread(name=constants.THREAD_ISCSI_LOGIN, target=self._login)) + GLib.timeout_add(250, self._check_login) diff --git a/pyanaconda/ui/gui/spokes/filter.glade b/pyanaconda/ui/gui/spokes/filter.glade index cde4c80..57a9a87 100644 --- a/pyanaconda/ui/gui/spokes/filter.glade +++ b/pyanaconda/ui/gui/spokes/filter.glade @@ -917,17 +917,338 @@ </packing> </child> <child> - <object class="GtkLabel" id="label8"> + <object class="GtkGrid" id="otherGrid"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="label" translatable="yes">Nothing here yet.</property> + <property name="margin_left">6</property> + <property name="margin_right">6</property> + <property name="margin_top">12</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Filter By:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">multipathTypeCombo</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="otherTypeCombo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <items> + <item translatable="yes">None</item> + <item translatable="yes">Vendor</item> + <item translatable="yes">Interconnect</item> + <item translatable="yes">ID</item> + </items> + <signal name="changed" handler="on_other_type_combo_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="otherTypeNotebook"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="no_show_all">True</property> + <property name="hexpand">True</property> + <property name="show_tabs">False</property> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + </object> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkBox" id="otherVendorBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_Show Only Devices From:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">otherVendorCombo</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="otherVendorCombo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkBox" id="otherInterconnectBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label20"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_Show Only Devices With:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">otherInterconnectCombo</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="otherInterconnectCombo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkBox" id="otherIDBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label21"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_Show Only Devices Containing:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">otherIDEntry</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="otherIDEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">●</property> + <property name="width_chars">30</property> + <property name="invisible_char_set">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">3</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkTreeView" id="otherTreeView"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="model">otherModel</property> + <property name="headers_clickable">False</property> + <property name="rules_hint">True</property> + <property name="enable_search">False</property> + <property name="search_column">0</property> + <property name="enable_tree_lines">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection" id="treeview-selection5"/> + </child> + <child> + <object class="GtkTreeViewColumn" id="otherSelectedCol"> + <child> + <object class="GtkCellRendererToggle" id="otherSelectedRenderer"> + <signal name="toggled" handler="on_row_toggled" swapped="no"/> + </object> + <attributes> + <attribute name="sensitive">2</attribute> + <attribute name="visible">0</attribute> + <attribute name="active">1</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="otherIDCol"> + <property name="title" translatable="yes">Identifier</property> + <property name="clickable">True</property> + <property name="sort_indicator">True</property> + <property name="sort_column_id">10</property> + <child> + <object class="GtkCellRendererText" id="otherIDRenderer"/> + <attributes> + <attribute name="sensitive">2</attribute> + <attribute name="visible">0</attribute> + <attribute name="text">10</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="otherCapacityCol"> + <property name="title" translatable="yes">Capacity</property> + <property name="clickable">True</property> + <property name="sort_indicator">True</property> + <property name="sort_column_id">6</property> + <child> + <object class="GtkCellRendererText" id="otherCapacityRenderer"/> + <attributes> + <attribute name="sensitive">2</attribute> + <attribute name="visible">0</attribute> + <attribute name="text">6</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="otherVendorCol"> + <property name="title" translatable="yes">Vendor</property> + <property name="clickable">True</property> + <property name="sort_indicator">True</property> + <property name="sort_column_id">7</property> + <child> + <object class="GtkCellRendererText" id="otherVendorRenderer"/> + <attributes> + <attribute name="yalign">2</attribute> + <attribute name="sensitive">1</attribute> + <attribute name="visible">0</attribute> + <attribute name="text">7</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn" id="otherInterconnectCol"> + <property name="title" translatable="yes">Interconnect</property> + <property name="clickable">True</property> + <property name="sort_indicator">True</property> + <property name="sort_column_id">8</property> + <child> + <object class="GtkCellRendererText" id="otherInterconnectRenderer"/> + <attributes> + <attribute name="sensitive">2</attribute> + <attribute name="visible">0</attribute> + <attribute name="text">8</attribute> + </attributes> + </child> + </object> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">5</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="otherClearButton"> + <property name="label">gtk-clear</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + <signal name="clicked" handler="on_clear_clicked" swapped="no"/> + </object> + <packing> + <property name="left_attach">4</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="otherFindButton"> + <property name="label">gtk-find</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + <signal name="clicked" handler="on_find_clicked" swapped="no"/> + </object> + <packing> + <property name="left_attach">3</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> </object> <packing> <property name="position">2</property> </packing> </child> <child type="tab"> - <object class="GtkLabel" id="label3"> + <object class="GtkLabel" id="otherLabel"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">_Other SAN Devices</property> diff --git a/pyanaconda/ui/gui/spokes/filter.py b/pyanaconda/ui/gui/spokes/filter.py index 80ea03e..a934c47 100644 --- a/pyanaconda/ui/gui/spokes/filter.py +++ b/pyanaconda/ui/gui/spokes/filter.py @@ -35,6 +35,7 @@ from pyanaconda.flags import flags from pyanaconda.ui.lib.disks import getDisks, isLocalDisk, size_str from pyanaconda.ui.gui.utils import enlightbox from pyanaconda.ui.gui.spokes import NormalSpoke +from pyanaconda.ui.gui.spokes.advstorage.iscsi import ISCSIDialog from pyanaconda.ui.gui.spokes.lib.cart import SelectedDisksDialog from pyanaconda.ui.gui.categories.storage import StorageCategory
@@ -230,6 +231,51 @@ class OtherPage(FilterPage): def ismember(self, device): return isinstance(device, iScsiDiskDevice) or isinstance(device, FcoeDiskDevice)
+ def _long_identifier(self, disk): + # For iSCSI devices, we want the long ip-address:port-iscsi-tgtname-lun-XX + # identifier, but blivet doesn't expose that in any useful way and I don't + # want to go asking udev. Instead, we dig around in the deviceLinks and + # default to the name if we can't figure anything else out. + for link in disk.deviceLinks: + if "by-path" in link: + lastSlash = link.rindex("/")+1 + return link[lastSlash:] + + return disk.name + + def setup(self, store, selectedNames, disks): + vendors = [] + interconnects = [] + + for disk in disks: + selected = disk.name in selectedNames + + if hasattr(disk, "node"): + port = str(disk.node.port) + lun = str(disk.node.tpgt) + else: + port = "" + lun = "" + + store.append([True, selected, not disk.protected, + disk.name, "", disk.model, size_str(disk.size), + disk.vendor, disk.bus, disk.serial, + self._long_identifier(disk), "", port, getattr(disk, "initiator", ""), + lun, ""]) + + if not disk.vendor in vendors: + vendors.append(disk.vendor) + + if not disk.bus in interconnects: + interconnects.append(disk.bus) + + self._combo = self.builder.get_object("otherTypeCombo") + self._combo.set_active(0) + self._combo.emit("changed") + + self.setupCombo(self.builder.get_object("otherVendorCombo"), vendors) + self.setupCombo(self.builder.get_object("otherInterconnectCombo"), interconnects) + def visible_func(self, model, itr, *args): obj = DiskStoreRow._make(model[itr]) device = self.storage.devicetree.getDeviceByName(obj.name) @@ -311,6 +357,8 @@ class FilterSpoke(NormalSpoke): self._addAdditionalCombo = self.builder.get_object("addAdditionalCombo") self._notebook = self.builder.get_object("advancedNotebook")
+ self._addAdditionalCombo.set_active(0) + if not arch.isS390(): self._notebook.remove_page(-1) self._addAdditionalCombo.remove(3) @@ -426,8 +474,29 @@ class FilterSpoke(NormalSpoke):
self._update_summary()
- def on_add_additional_changed(self, *args): - pass + def on_add_additional_changed(self, widget, *args): + active = widget.get_active() + + if active == 0: + # Nothing. + return + elif active == 1: + # iSCSI + dialog = ISCSIDialog(self.data, self.storage) + + with enlightbox(self.window, dialog.window): + dialog.refresh() + dialog.run() + elif active == 2: + # FCoE + pass + elif active == 3: + # ZFCP + pass + + # We now need to refresh so any new disks picked up by adding advanced + # storage are displayed in the UI. + self.refresh()
## ## SEARCH TAB SIGNAL HANDLERS @@ -456,3 +525,17 @@ class FilterSpoke(NormalSpoke): findButton.set_sensitive(ndx != 0) clearButton.set_sensitive(ndx != 0) notebook.set_current_page(ndx) + + ## + ## OTHER TAB SIGNAL HANDLERS + ## + def on_other_type_combo_changed(self, combo): + ndx = combo.get_active() + + notebook = self.builder.get_object("otherTypeNotebook") + findButton = self.builder.get_object("otherFindButton") + clearButton = self.builder.get_object("otherClearButton") + + findButton.set_sensitive(ndx != 0) + clearButton.set_sensitive(ndx != 0) + notebook.set_current_page(ndx) diff --git a/pyanaconda/ui/lib/disks.py b/pyanaconda/ui/lib/disks.py index ba31eac..ec352f5 100644 --- a/pyanaconda/ui/lib/disks.py +++ b/pyanaconda/ui/lib/disks.py @@ -20,7 +20,7 @@ # Chris Lumens clumens@redhat.com #
-from blivet.devices import MultipathDevice +from blivet.devices import MultipathDevice, iScsiDiskDevice from blivet.size import Size
__all__ = ["FakeDiskLabel", "FakeDisk", "getDisks", "isLocalDisk", "size_str"] @@ -65,7 +65,7 @@ def getDisks(devicetree, fake=False): return disks
def isLocalDisk(disk): - return not isinstance(disk, MultipathDevice) + return not isinstance(disk, MultipathDevice) and not isinstance(disk, iScsiDiskDevice)
def size_str(mb): if isinstance(mb, Size):
On Tue, 2013-04-09 at 12:06 -0400, Chris Lumens wrote:
This does not yet include support for CHAP/rCHAP (well, it might but I have not tested it at all), complete error handling, or correct filtering. However if you are careful and type everything in as you should, you can add iSCSI disks to the installer.
configure.ac | 1 + po/POTFILES.in | 1 + pyanaconda/constants.py | 2 + pyanaconda/ui/gui/spokes/Makefile.am | 2 +- pyanaconda/ui/gui/spokes/advstorage/Makefile.am | 25 + pyanaconda/ui/gui/spokes/advstorage/__init__.py | 0 pyanaconda/ui/gui/spokes/advstorage/iscsi.glade | 1144 +++++++++++++++++++++++ pyanaconda/ui/gui/spokes/advstorage/iscsi.py | 340 +++++++ pyanaconda/ui/gui/spokes/filter.glade | 327 ++++++- pyanaconda/ui/gui/spokes/filter.py | 87 +- pyanaconda/ui/lib/disks.py | 4 +- 11 files changed, 1925 insertions(+), 8 deletions(-) create mode 100644 pyanaconda/ui/gui/spokes/advstorage/Makefile.am create mode 100644 pyanaconda/ui/gui/spokes/advstorage/__init__.py create mode 100644 pyanaconda/ui/gui/spokes/advstorage/iscsi.glade create mode 100644 pyanaconda/ui/gui/spokes/advstorage/iscsi.py
diff --git a/pyanaconda/ui/gui/spokes/advstorage/iscsi.py b/pyanaconda/ui/gui/spokes/advstorage/iscsi.py new file mode 100644 index 0000000..f3d469e --- /dev/null +++ b/pyanaconda/ui/gui/spokes/advstorage/iscsi.py @@ -0,0 +1,340 @@ +# iSCSI configuration dialog +# +# Copyright (C) 2013 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Chris Lumens clumens@redhat.com +#
+from IPy import IP +from collections import namedtuple +from gi.repository import GLib, Gtk
Could you please add '# pylint: disable-msg=E0611' above this line?
+from pyanaconda import constants +from pyanaconda.threads import threadMgr, AnacondaThread +from pyanaconda.ui.gui import GUIObject
+import gettext +_ = lambda x: gettext.ldgettext("anaconda", x)
+__all__ = ["ISCSIDialog"]
+STYLE_NONE = 0 +STYLE_CHAP = 1 +STYLE_REVERSE_CHAP = 2
+Credentials = namedtuple("Credentials", ["style",
"targetIP", "initiator", "username","password", "rUsername", "rPassword"])+NodeStoreRow = namedtuple("NodeStoreRow", ["selected", "notLoggedIn", "name"])
+def login_no_credentials(builder):
- return Credentials._make([STYLE_NONE,
builder.get_object("targetEntry").get_text(),builder.get_object("initiatorEntry").get_text(),"", "", "", ""])+def login_chap(builder):
- return Credentials._make([STYLE_CHAP,
builder.get_object("targetEntry").get_text(),builder.get_object("initiatorEntry").get_text(),builder.get_object("chapUsernameEntry").get_text(),builder.get_object("chapPasswordEntry").get_text(),"", ""])+def login_reverse_chap(builder):
- return Credentials._make([STYLE_REVERSE_CHAP,
builder.get_object("targetEntry").get_text(),builder.get_object("initiatorEntry").get_text(),builder.get_object("rchapUsernameEntry").get_text(),builder.get_object("rchapPasswordEntry").get_text(),builder.get_object("rchapReverseUsername").get_text(),builder.get_object("rchapReversePassword").get_text()])
Is there any reason for using _make instead of Credentials(STYLE_...)?
+from IPy import IP +from collections import namedtuple +from gi.repository import GLib, Gtk
Could you please add '# pylint: disable-msg=E0611' above this line?
Done.
Is there any reason for using _make instead of Credentials(STYLE_...)?
No, only that I'd been using _make everywhere else so I didn't think about being able to do this. Changed.
- Chris
--- pyanaconda/ui/gui/spokes/filter.py | 65 ++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/filter.py b/pyanaconda/ui/gui/spokes/filter.py index a934c47..5154349 100644 --- a/pyanaconda/ui/gui/spokes/filter.py +++ b/pyanaconda/ui/gui/spokes/filter.py @@ -137,11 +137,39 @@ class SearchPage(FilterPage): self._lunEntry = self.builder.get_object("searchLUNEntry") self._wwidEntry = self.builder.get_object("searchWWIDEntry")
+ self._portCombo = self.builder.get_object("searchPortCombo") + self._targetEntry = self.builder.get_object("searchTargetEntry") + self._lunEntry = self.builder.get_object("searchLUNEntry") + def setup(self, store, selectedNames, disks): self._combo = self.builder.get_object("searchTypeCombo") self._combo.set_active(0) self._combo.emit("changed")
+ def _port_equal(self, device): + active = self._portCombo.get_active_text() + if active and hasattr(device, "node"): + return device.node.port == active + else: + return True + + def _target_equal(self, device): + active = self._targetEntry.get_text().strip() + if active: + return active in getattr(device, "initiator", "") + else: + return True + + def _lun_equal(self, device): + active = self._lunEntry.get_text().strip() + if active and hasattr(device, "node"): + try: + return int(active) == device.node.tpgt + except ValueError: + return True + else: + return True + def _filter_func(self, device): if not self.filterActive: return True @@ -151,7 +179,7 @@ class SearchPage(FilterPage): if filterBy == 0: return True elif filterBy == 1: - return True + return self._port_equal(device) and self._target_equal(device) and self._lun_equal(device) elif filterBy == 2: return hasattr(device, "wwid") and self._wwidEntry.get_text() in device.wwid elif filterBy == 3: @@ -168,6 +196,8 @@ class MultipathPage(FilterPage): self.model = self.builder.get_object("multipathModel") self.model.set_visible_func(self.visible_func)
+ self._icCombo = self.builder.get_object("multipathInterconnectCombo") + self._vendorCombo = self.builder.get_object("multipathVendorCombo") self._wwidEntry = self.builder.get_object("multipathWWIDEntry")
def ismember(self, device): @@ -196,8 +226,8 @@ class MultipathPage(FilterPage): self._combo.set_active(0) self._combo.emit("changed")
- self.setupCombo(self.builder.get_object("multipathVendorCombo"), vendors) - self.setupCombo(self.builder.get_object("multipathInterconnectCombo"), interconnects) + self.setupCombo(self._vendorCombo, vendors) + self.setupCombo(self._icCombo, interconnects)
def _filter_func(self, device): if not self.filterActive: @@ -228,6 +258,10 @@ class OtherPage(FilterPage): self.model = self.builder.get_object("otherModel") self.model.set_visible_func(self.visible_func)
+ self._icCombo = self.builder.get_object("otherInterconnectCombo") + self._idEntry = self.builder.get_object("otherIDEntry") + self._vendorCombo = self.builder.get_object("otherVendorCombo") + def ismember(self, device): return isinstance(device, iScsiDiskDevice) or isinstance(device, FcoeDiskDevice)
@@ -273,13 +307,32 @@ class OtherPage(FilterPage): self._combo.set_active(0) self._combo.emit("changed")
- self.setupCombo(self.builder.get_object("otherVendorCombo"), vendors) - self.setupCombo(self.builder.get_object("otherInterconnectCombo"), interconnects) + self.setupCombo(self._vendorCombo, vendors) + self.setupCombo(self._icCombo, interconnects) + + def _filter_func(self, device): + if not self.filterActive: + return True + + filterBy = self._combo.get_active() + + if filterBy == 0: + return True + elif filterBy == 1: + return device.vendor == self._vendorCombo.get_active_text() + elif filterBy == 2: + return device.bus == self._icCombo.get_active_text() + elif filterBy == 3: + for link in device.deviceLinks: + if "by-path" in link: + return self._idEntry.get_text().strip() in link + + return False
def visible_func(self, model, itr, *args): obj = DiskStoreRow._make(model[itr]) device = self.storage.devicetree.getDeviceByName(obj.name) - return self.ismember(device) + return self.ismember(device) and self._filter_func(device)
class RaidPage(FilterPage): def __init__(self, storage, builder):
--- pyanaconda/ui/gui/spokes/filter.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/pyanaconda/ui/gui/spokes/filter.py b/pyanaconda/ui/gui/spokes/filter.py index 5154349..7f3b527 100644 --- a/pyanaconda/ui/gui/spokes/filter.py +++ b/pyanaconda/ui/gui/spokes/filter.py @@ -146,6 +146,13 @@ class SearchPage(FilterPage): self._combo.set_active(0) self._combo.emit("changed")
+ ports = [] + for disk in disks: + if hasattr(disk, "node"): + ports.append(str(disk.node.port)) + + self.setupCombo(self.builder.get_object("searchPortCombo"), ports) + def _port_equal(self, device): active = self._portCombo.get_active_text() if active and hasattr(device, "node"): @@ -435,6 +442,7 @@ class FilterSpoke(NormalSpoke):
self._store.clear()
+ allDisks = [] multipathDisks = [] otherDisks = [] raidDisks = [] @@ -455,7 +463,9 @@ class FilterSpoke(NormalSpoke): elif self.pages[4].ismember(disk): zDisks.append(disk)
- self.pages[0].setup(self._store, self.selected_disks, []) + allDisks.append(disk) + + self.pages[0].setup(self._store, self.selected_disks, allDisks) self.pages[1].setup(self._store, self.selected_disks, multipathDisks) self.pages[2].setup(self._store, self.selected_disks, otherDisks) self.pages[3].setup(self._store, self.selected_disks, raidDisks)
On Tue, 2013-04-09 at 12:06 -0400, Chris Lumens wrote:
pyanaconda/ui/gui/spokes/filter.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/pyanaconda/ui/gui/spokes/filter.py b/pyanaconda/ui/gui/spokes/filter.py index 5154349..7f3b527 100644 --- a/pyanaconda/ui/gui/spokes/filter.py +++ b/pyanaconda/ui/gui/spokes/filter.py @@ -146,6 +146,13 @@ class SearchPage(FilterPage): self._combo.set_active(0) self._combo.emit("changed")
ports = []for disk in disks:if hasattr(disk, "node"):ports.append(str(disk.node.port))
You could use list comprehension here, but I'm not sure if it would be equally readable or worse.
There's no reason to display an empty dialog you can't do anything on. --- pyanaconda/ui/gui/spokes/advstorage/iscsi.py | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/pyanaconda/ui/gui/spokes/advstorage/iscsi.py b/pyanaconda/ui/gui/spokes/advstorage/iscsi.py index f3d469e..98f226b 100644 --- a/pyanaconda/ui/gui/spokes/advstorage/iscsi.py +++ b/pyanaconda/ui/gui/spokes/advstorage/iscsi.py @@ -297,6 +297,8 @@ class ISCSIDialog(GUIObject): if threadMgr.get(constants.THREAD_ISCSI_LOGIN): return True
+ anyLeft = False + spinner = self.builder.get_object("loginSpinner") spinner.stop() spinner.hide() @@ -311,6 +313,7 @@ class ISCSIDialog(GUIObject): for row in self._store: if row[1]: row[0] = True + anyLeft = True
# And make the login button sensitive if there are any more # nodes to login to. @@ -323,6 +326,9 @@ class ISCSIDialog(GUIObject): # the user cancel. Cancel what, exactly? self._cancelButton.set_sensitive(False)
+ if not anyLeft: + self.window.response(1) + return False
def on_login_clicked(self, *args):
This is meant for FCP devices, but is too easily confused with the port/target/lun option. That one can provide both, anyway. --- pyanaconda/ui/gui/spokes/filter.glade | 38 +---------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/filter.glade b/pyanaconda/ui/gui/spokes/filter.glade index 57a9a87..8fa5fb2 100644 --- a/pyanaconda/ui/gui/spokes/filter.glade +++ b/pyanaconda/ui/gui/spokes/filter.glade @@ -141,7 +141,6 @@ <item translatable="yes">None</item> <item translatable="yes">Port / Target / LUN #</item> <item translatable="yes">Target WWID</item> - <item translatable="yes">Target LUN</item> </items> <signal name="changed" handler="on_search_type_changed" swapped="no"/> </object> @@ -304,42 +303,7 @@ <placeholder/> </child> <child> - <object class="GtkBox" id="searchLUNBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="spacing">6</property> - <child> - <object class="GtkLabel" id="label19"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">_LUN:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">searchLUEntry</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="searchLUEntry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="invisible_char">●</property> - <property name="width_chars">30</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="position">3</property> - </packing> + <placeholder/> </child> <child type="tab"> <placeholder/>
--- pyanaconda/ui/gui/spokes/filter.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/pyanaconda/ui/gui/spokes/filter.py b/pyanaconda/ui/gui/spokes/filter.py index 7f3b527..70dff47 100644 --- a/pyanaconda/ui/gui/spokes/filter.py +++ b/pyanaconda/ui/gui/spokes/filter.py @@ -103,6 +103,12 @@ class FilterPage(object): """ pass
+ def clear(self): + """Blank out any filtering-related fields on this page and return them + to their defaults. This is called when the Clear button is clicked. + """ + pass + def visible_func(self, model, itr, *args): """This method is called for every row (disk) in the store, in order to determine if it should be displayed on this page or not. This method @@ -139,7 +145,6 @@ class SearchPage(FilterPage):
self._portCombo = self.builder.get_object("searchPortCombo") self._targetEntry = self.builder.get_object("searchTargetEntry") - self._lunEntry = self.builder.get_object("searchLUNEntry")
def setup(self, store, selectedNames, disks): self._combo = self.builder.get_object("searchTypeCombo") @@ -153,6 +158,12 @@ class SearchPage(FilterPage):
self.setupCombo(self.builder.get_object("searchPortCombo"), ports)
+ def clear(self): + self._lunEntry.set_text("") + self._portCombo.set_active(0) + self._targetEntry.set_text("") + self._wwidEntry.set_text("") + def _port_equal(self, device): active = self._portCombo.get_active_text() if active and hasattr(device, "node"): @@ -236,6 +247,11 @@ class MultipathPage(FilterPage): self.setupCombo(self._vendorCombo, vendors) self.setupCombo(self._icCombo, interconnects)
+ def clear(self): + self._icCombo.set_active(0) + self._vendorCombo.set_active(0) + self._wwidEntry.set_text("") + def _filter_func(self, device): if not self.filterActive: return True @@ -317,6 +333,11 @@ class OtherPage(FilterPage): self.setupCombo(self._vendorCombo, vendors) self.setupCombo(self._icCombo, interconnects)
+ def clear(self): + self._icCombo.set_active(0) + self._idEntry.set_text("") + self._vendorCombo.set_active(0) + def _filter_func(self, device): if not self.filterActive: return True @@ -518,6 +539,7 @@ class FilterSpoke(NormalSpoke): n = self._notebook.get_current_page() self.pages[n].filterActive = False self.pages[n].model.refilter() + self.pages[n].clear()
def on_page_switched(self, notebook, newPage, newPageNum, *args): self.pages[newPageNum].model.refilter()
--- pyanaconda/ui/gui/spokes/advstorage/iscsi.glade | 727 ++++++++++++++---------- pyanaconda/ui/gui/spokes/advstorage/iscsi.py | 167 ++++-- 2 files changed, 544 insertions(+), 350 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade b/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade index 1e5ac2c..34b0c41 100644 --- a/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade +++ b/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade @@ -109,7 +109,7 @@ <property name="can_focus">True</property> <property name="hexpand">True</property> <property name="invisible_char">●</property> - <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="changed" handler="on_discover_field_changed" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -140,7 +140,7 @@ <property name="can_focus">True</property> <property name="hexpand">True</property> <property name="invisible_char">●</property> - <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="changed" handler="on_discover_field_changed" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -291,7 +291,7 @@ <property name="can_focus">True</property> <property name="hexpand">True</property> <property name="invisible_char">●</property> - <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="changed" handler="on_discover_field_changed" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -307,7 +307,9 @@ <property name="hexpand">True</property> <property name="visibility">False</property> <property name="invisible_char">●</property> - <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="changed" handler="on_discover_field_changed" swapped="no"/> + <signal name="focus-in-event" handler="on_password_focused" swapped="no"/> + <signal name="focus-out-event" handler="on_password_unfocused" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -336,7 +338,7 @@ <property name="can_focus">True</property> <property name="hexpand">True</property> <property name="invisible_char">●</property> - <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="changed" handler="on_discover_field_changed" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -352,7 +354,9 @@ <property name="hexpand">True</property> <property name="visibility">False</property> <property name="invisible_char">●</property> - <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="changed" handler="on_discover_field_changed" swapped="no"/> + <signal name="focus-in-event" handler="on_password_focused" swapped="no"/> + <signal name="focus-out-event" handler="on_password_unfocused" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -367,7 +371,7 @@ <property name="can_focus">True</property> <property name="hexpand">True</property> <property name="invisible_char">●</property> - <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="changed" handler="on_discover_field_changed" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -383,7 +387,9 @@ <property name="hexpand">True</property> <property name="visibility">False</property> <property name="invisible_char">●</property> - <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="changed" handler="on_discover_field_changed" swapped="no"/> + <signal name="focus-in-event" handler="on_password_focused" swapped="no"/> + <signal name="focus-out-event" handler="on_password_unfocused" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -560,7 +566,7 @@ <property name="visible">True</property> <property name="can_focus">False</property> <property name="xalign">0</property> - <property name="label" translatable="yes"><b>No iSCSI targets discovered.</b></property> + <property name="label" translatable="yes"><b>Discovery login rejected.</b></property> <property name="use_markup">True</property> </object> <packing> @@ -575,7 +581,7 @@ <property name="visible">True</property> <property name="can_focus">False</property> <property name="xalign">0</property> - <property name="label" translatable="yes">There was an error discovering iSCSI targets. Check your target IP address and server configuration and try again.</property> + <property name="label" translatable="yes">The following error occurred discovering iSCSI targets. Please double check your authorization information and try again.</property> <property name="wrap">True</property> </object> <packing> @@ -586,6 +592,44 @@ </packing> </child> <child> + <object class="GtkLabel" id="discoveryErrorLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label">Error message goes here.</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="retryButton"> + <property name="label" translatable="yes">_Retry Discovery</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="on_start_clicked" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> <placeholder/> </child> </object> @@ -616,7 +660,7 @@ <placeholder/> </child> <child> - <object class="GtkGrid" id="discoveredGrid"> + <object class="GtkGrid" id="loginGrid"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="row_spacing">6</property> @@ -637,7 +681,7 @@ </packing> </child> <child> - <object class="GtkScrolledWindow" id="scrolledwindow1"> + <object class="GtkScrolledWindow" id="nodeWindow"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="vexpand">True</property> @@ -687,54 +731,315 @@ </packing> </child> <child> - <object class="GtkNotebook" id="loginNotebook"> + <object class="GtkGrid" id="grid1"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="show_tabs">False</property> - <property name="show_border">False</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> <child> - <object class="GtkGrid" id="grid1"> + <object class="GtkLabel" id="loginAuthTypeLabel"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">6</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Node login authentication type:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginAuthTypeCombo</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBoxText" id="loginAuthTypeCombo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="entry_text_column">0</property> + <property name="id_column">1</property> + <items> + <item translatable="yes">No credentials (discovery authentication disabled)</item> + <item translatable="yes">CHAP pair</item> + <item translatable="yes">CHAP pair and a reverse pair</item> + <item translatable="yes">Use the credentials from discovery</item> + </items> + <signal name="changed" handler="on_login_type_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="loginAuthNotebook"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> <child> - <object class="GtkLabel" id="loginAuthTypeLabel"> + <object class="GtkLabel" id="noCredentialsFiller"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">_Node login authentication type:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">loginAuthTypeCombo</property> + </object> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkGrid" id="loginChapGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkLabel" id="label15"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Username:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginChapUsernameEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label16"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Password:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginChapPasswordEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginChapUsernameEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginChapPasswordEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="focus-in-event" handler="on_password_focused" swapped="no"/> + <signal name="focus-out-event" handler="on_password_unfocused" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="width">1</property> - <property name="height">1</property> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <placeholder/> + </child> + <child> + <object class="GtkGrid" id="loginRchapGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkEntry" id="loginRchapUsernameEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginRchapPasswordEntry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="focus-in-event" handler="on_password_focused" swapped="no"/> + <signal name="focus-out-event" handler="on_password_unfocused" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginRchapReverseUsername"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="loginRchapReversePassword"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">●</property> + <property name="invisible_char_set">True</property> + <signal name="changed" handler="on_login_field_changed" swapped="no"/> + <signal name="focus-in-event" handler="on_password_focused" swapped="no"/> + <signal name="focus-out-event" handler="on_password_unfocused" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label17"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Username:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginRchapUsernameEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label18"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">CHAP _Password:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginRchapPasswordEntry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label19"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Reverse CHAP Username:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginRchapReverseUsername</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label20"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Reverse CHAP Pass_word:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">loginRchapReversePassword</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">2</property> </packing> </child> + <child type="tab"> + <placeholder/> + </child> <child> - <object class="GtkComboBoxText" id="loginAuthTypeCombo"> + <object class="GtkLabel" id="sameCredentialsFiller"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="entry_text_column">0</property> - <property name="id_column">1</property> - <items> - <item translatable="yes">No credentials (discovery authentication disabled)</item> - <item translatable="yes">CHAP pair</item> - <item translatable="yes">CHAP pair and a reverse pair</item> - <item translatable="yes">Use the credentials from discovery</item> - </items> - <signal name="changed" handler="on_discovered_type_changed" swapped="no"/> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - <property name="width">1</property> - <property name="height">1</property> + <property name="position">3</property> </packing> </child> + <child type="tab"> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="loginConditionNotebook"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="show_tabs">False</property> + <property name="show_border">False</property> <child> <object class="GtkBox" id="loginBox"> <property name="visible">True</property> @@ -746,6 +1051,7 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> + <property name="valign">center</property> <property name="use_underline">True</property> <signal name="clicked" handler="on_login_clicked" swapped="no"/> </object> @@ -770,321 +1076,126 @@ </packing> </child> </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> + </child> + <child type="tab"> + <placeholder/> </child> <child> - <object class="GtkNotebook" id="loginAuthNotebook"> + <object class="GtkGrid" id="loginErrorBox"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="show_tabs">False</property> - <property name="show_border">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> <child> - <object class="GtkLabel" id="noCredentialsFiller"> + <object class="GtkImage" id="image2"> <property name="visible">True</property> <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="stock">gtk-dialog-error</property> </object> - </child> - <child type="tab"> - <placeholder/> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> </child> <child> - <object class="GtkGrid" id="loginChapGrid"> + <object class="GtkLabel" id="label21"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">6</property> - <child> - <object class="GtkLabel" id="label15"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">CHAP _Username:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">loginChapUsernameEntry</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label16"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">CHAP _Password:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">loginChapPasswordEntry</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="loginChapUsernameEntry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="invisible_char">●</property> - <property name="invisible_char_set">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="loginChapPasswordEntry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="invisible_char">●</property> - <property name="invisible_char_set">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> + <property name="xalign">0</property> + <property name="label" translatable="yes"><b>Node login failed.</b></property> + <property name="use_markup">True</property> </object> <packing> - <property name="position">1</property> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> </packing> </child> - <child type="tab"> - <placeholder/> - </child> <child> - <object class="GtkGrid" id="loginRchapGrid"> + <object class="GtkLabel" id="label23"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">6</property> - <child> - <object class="GtkEntry" id="loginRchapUsernameEntry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="invisible_char">●</property> - <property name="invisible_char_set">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="loginRchapPasswordEntry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="invisible_char">●</property> - <property name="invisible_char_set">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="loginRchapReverseUsername"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="invisible_char">●</property> - <property name="invisible_char_set">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="loginRchapReversePassword"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - <property name="invisible_char">●</property> - <property name="invisible_char_set">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label17"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">CHAP _Username:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">loginRchapUsernameEntry</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label18"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">CHAP _Password:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">loginRchapPasswordEntry</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label19"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">Reverse CHAP User_name:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">loginRchapReverseUsername</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label20"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">Reverse CHAP Pass_word:</property> - <property name="use_underline">True</property> - <property name="mnemonic_widget">loginRchapReversePassword</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - <property name="width">1</property> - <property name="height">1</property> - </packing> - </child> + <property name="xalign">0</property> + <property name="label" translatable="yes">The following error occurred logging into the selected iSCSI node. Please double check your authorization information and try again</property> + <property name="wrap">True</property> </object> <packing> - <property name="position">2</property> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> </packing> </child> - <child type="tab"> - <placeholder/> - </child> <child> - <object class="GtkLabel" id="sameCredentialsFiller"> + <object class="GtkLabel" id="loginErrorLabel"> <property name="visible">True</property> <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label">Error message goes here.</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="retryLoginButton"> + <property name="label" translatable="yes">_Retry Log In</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="on_login_clicked" swapped="no"/> </object> <packing> - <property name="position">3</property> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + <property name="width">1</property> + <property name="height">1</property> </packing> </child> - <child type="tab"> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> <placeholder/> </child> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - <property name="width">1</property> - <property name="height">1</property> + <property name="position">1</property> </packing> </child> - <child> + <child type="tab"> <placeholder/> </child> <child> <placeholder/> </child> - </object> - </child> - <child type="tab"> - <placeholder/> - </child> - <child> - <object class="GtkBox" id="loginSuccessBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">center</property> - <property name="vexpand">True</property> - <property name="spacing">6</property> - <child> - <object class="GtkImage" id="image2"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="stock">gtk-apply</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label21"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes"><b>Node login successful.</b> -Successfully logged in and attached to the selected nodes! Pressing 'OK' will return you to the previous disk selection screen.</property> - <property name="use_markup">True</property> - <property name="wrap">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> + <child type="tab"> + <placeholder/> </child> </object> <packing> - <property name="position">1</property> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + <property name="width">2</property> + <property name="height">1</property> </packing> </child> - <child type="tab"> - <placeholder/> - </child> <child> <placeholder/> </child> - <child type="tab"> - <placeholder/> - </child> </object> <packing> <property name="left_attach">0</property> diff --git a/pyanaconda/ui/gui/spokes/advstorage/iscsi.py b/pyanaconda/ui/gui/spokes/advstorage/iscsi.py index 98f226b..54d79a0 100644 --- a/pyanaconda/ui/gui/spokes/advstorage/iscsi.py +++ b/pyanaconda/ui/gui/spokes/advstorage/iscsi.py @@ -42,13 +42,13 @@ Credentials = namedtuple("Credentials", ["style",
NodeStoreRow = namedtuple("NodeStoreRow", ["selected", "notLoggedIn", "name"])
-def login_no_credentials(builder): +def discover_no_credentials(builder): return Credentials._make([STYLE_NONE, builder.get_object("targetEntry").get_text(), builder.get_object("initiatorEntry").get_text(), "", "", "", ""])
-def login_chap(builder): +def discover_chap(builder): return Credentials._make([STYLE_CHAP, builder.get_object("targetEntry").get_text(), builder.get_object("initiatorEntry").get_text(), @@ -56,7 +56,7 @@ def login_chap(builder): builder.get_object("chapPasswordEntry").get_text(), "", ""])
-def login_reverse_chap(builder): +def discover_reverse_chap(builder): return Credentials._make([STYLE_REVERSE_CHAP, builder.get_object("targetEntry").get_text(), builder.get_object("initiatorEntry").get_text(), @@ -65,7 +65,38 @@ def login_reverse_chap(builder): builder.get_object("rchapReverseUsername").get_text(), builder.get_object("rchapReversePassword").get_text()])
-def login_credentials_valid(credentials): +# This list maps the current page from the authNotebook to a function to grab +# credentials out of the UI. This works as long as authNotebook keeps the +# filler page at the front. +discoverMap = [discover_no_credentials, discover_chap, discover_reverse_chap] + +def login_no_credentials(builder): + return Credentials._make([STYLE_NONE, + "", "", + "", "", "", ""]) + +def login_chap(builder): + return Credentials._make([STYLE_CHAP, + "", "", + builder.get_object("loginChapUsernameEntry").get_text(), + builder.get_object("loginChapPasswordEntry").get_text(), + "", ""]) + +def login_reverse_chap(builder): + return Credentials._make([STYLE_REVERSE_CHAP, + "", "", + builder.get_object("loginRchapUsernameEntry").get_text(), + builder.get_object("loginRchapPasswordEntry").get_text(), + builder.get_object("loginRchapReverseUsername").get_text(), + builder.get_object("loginRchapReversePassword").get_text()]) + +# And this list maps the current page from the loginAuthNotebook to a function +# to grab credentials out of the UI. This works as long as loginAuthNotebook +# keeps the filler page at the front, and we check to make sure "Use the +# credentials from discovery" is not selected first. +loginMap = [login_no_credentials, login_chap, login_reverse_chap] + +def credentials_valid(credentials): if credentials.style == STYLE_NONE: return True elif credentials.style == STYLE_CHAP: @@ -74,11 +105,6 @@ def login_credentials_valid(credentials): return credentials.username.strip() != "" and credentials.password != "" and \ credentials.rUsername.strip() != "" and credentials.rPassword != ""
-# This list maps the current page from the authNotebook to a function to grab -# credentials out of the UI. This works as long as authNotebook keeps the -# filler page at the front. -loginMap = [login_no_credentials, login_chap, login_reverse_chap] - class ISCSIDialog(GUIObject): builderObjects = ["iscsiDialog", "nodeStore", "nodeStoreFiltered"] mainWidgetName = "iscsiDialog" @@ -89,7 +115,7 @@ class ISCSIDialog(GUIObject): self.storage = storage self.iscsi = self.storage.iscsi()
- self._discoveryError = False + self._discoveryError = None self._loginError = False
self._discoveredNodes = [] @@ -102,6 +128,8 @@ class ISCSIDialog(GUIObject): self._loginButton = self.builder.get_object("loginButton") self._loginAuthTypeCombo = self.builder.get_object("loginAuthTypeCombo") self._loginAuthNotebook = self.builder.get_object("loginAuthNotebook") + self._loginGrid = self.builder.get_object("loginGrid") + self._loginConditionNotebook = self.builder.get_object("loginConditionNotebook")
self._configureGrid = self.builder.get_object("configureGrid") self._conditionNotebook = self.builder.get_object("conditionNotebook") @@ -119,9 +147,9 @@ class ISCSIDialog(GUIObject):
self.builder.get_object("nodeStoreFiltered").set_visible_column(1)
- initiatorEntry = self.builder.get_object("initiatorEntry") - initiatorEntry.set_text(self.iscsi.initiator) - initiatorEntry.set_sensitive(self.iscsi.initiatorSet) + self._initiatorEntry = self.builder.get_object("initiatorEntry") + self._initiatorEntry.set_text(self.iscsi.initiator) + self._initiatorEntry.set_sensitive(not self.iscsi.initiatorSet)
@property def selectedNames(self): @@ -141,7 +169,7 @@ class ISCSIDialog(GUIObject):
# When we change the notebook, we also need to reverify the credentials # in order to set the Start button sensitivity. - self.on_login_field_changed() + self.on_discover_field_changed()
def _discover(self, credentials): # This needs to be in its own thread, not marked with gtk_action_* because it's @@ -157,10 +185,11 @@ class ISCSIDialog(GUIObject): r_username=credentials.rUsername, r_password=credentials.rPassword) except IOError as e: - self._discoveryError = True + self._discoveryError = str(e) + return
if len(self._discoveredNodes) == 0: - self._discoveryError = True + self._discoveryError = "No nodes discovered."
def _check_discover(self, *args): if threadMgr.get(constants.THREAD_ISCSI_DISCOVER): @@ -175,8 +204,10 @@ class ISCSIDialog(GUIObject): if self._discoveryError: # Failure. Display some error message and leave the user on the # dialog to try again. - self._discoveryError = False + self.builder.get_object("discoveryErrorLabel").set_text(self._discoveryError) + self._discoveryError = None self._conditionNotebook.set_current_page(2) + self._set_configure_sensitive(True) else: # Success. Now populate the node store and kick the user on over to # that subscreen. @@ -188,6 +219,13 @@ class ISCSIDialog(GUIObject): self._cancelButton.set_sensitive(True) return False
+ def _set_configure_sensitive(self, sensitivity): + for child in self._configureGrid.get_children(): + if child == self._initiatorEntry: + self._initiatorEntry.set_sensitive(not self.iscsi.initiatorSet) + elif child != self._conditionNotebook: + child.set_sensitive(sensitivity) + def on_start_clicked(self, *args): # First, update some widgets to not be usable while discovery happens. self._startButton.hide() @@ -195,18 +233,11 @@ class ISCSIDialog(GUIObject): self._okButton.set_sensitive(False)
self._conditionNotebook.set_current_page(1) + self._set_configure_sensitive(False) + self._initiatorEntry.set_sensitive(False)
- # Mark all children of the configureGrid as insensitive, except for the - # wait box. - for child in self._configureGrid.get_children(): - if child != self._conditionNotebook: - child.set_sensitive(False) - - # Now get the login credentials. - credentials = loginMap[self._authNotebook.get_current_page()](self.builder) - - # FIXME - # Verify credentials, setting the UI if required. + # Now get the node discovery credentials. + credentials = discoverMap[self._authNotebook.get_current_page()](self.builder)
discoveredLabel = self.builder.get_object("discoveredLabel") discoveredLabel.set_markup(discoveredLabel.get_label() % {"initiatorName": credentials.initiator, @@ -238,10 +269,10 @@ class ISCSIDialog(GUIObject): stripped = text.strip() return "." in stripped and ":" in stripped
- def on_login_field_changed(self, *args): + def on_discover_field_changed(self, *args): # Make up a credentials object so we can test if it's valid. - credentials = loginMap[self._authNotebook.get_current_page()](self.builder) - sensitive = self._target_ip_valid() and self._initiator_name_valid() and login_credentials_valid(credentials) + credentials = discoverMap[self._authNotebook.get_current_page()](self.builder) + sensitive = self._target_ip_valid() and self._initiator_name_valid() and credentials_valid(credentials) self._startButton.set_sensitive(sensitive)
## @@ -255,9 +286,13 @@ class ISCSIDialog(GUIObject): # We should select the first node by default. self._store[0][0] = True
- def on_discovered_type_changed(self, widget, *args): + def on_login_type_changed(self, widget, *args): self._loginAuthNotebook.set_current_page(widget.get_active())
+ # When we change the notebook, we also need to reverify the credentials + # in order to set the Log In button sensitivity. + self.on_login_field_changed() + def on_row_toggled(self, button, path): if not path: return @@ -270,7 +305,7 @@ class ISCSIDialog(GUIObject): itr = self._store.get_iter(path) self._store[itr][0] = True
- def _login(self): + def _login(self, credentials): for row in self._store: obj = NodeStoreRow._make(row)
@@ -279,9 +314,13 @@ class ISCSIDialog(GUIObject):
for node in self._discoveredNodes: if obj.notLoggedIn and node.name == obj.name: - (rc, msg) = self.iscsi.log_into_node(node) + (rc, msg) = self.iscsi.log_into_node(node, + username=credentials.username, + password=credentials.password, + r_username=credentials.rUsername, + r_password=credentials.rPassword) if not rc: - print msg + self._loginError = msg return
row[1] = False @@ -297,17 +336,21 @@ class ISCSIDialog(GUIObject): if threadMgr.get(constants.THREAD_ISCSI_LOGIN): return True
- anyLeft = False - spinner = self.builder.get_object("loginSpinner") spinner.stop() spinner.hide()
if self._loginError: - self._loginError = False + self.builder.get_object("loginErrorLabel").set_text(self._loginError) + self._loginError = None + self._loginConditionNotebook.set_current_page(1) self._cancelButton.set_sensitive(True) self._loginButton.set_sensitive(True) else: + anyLeft = False + + self._loginConditionNotebook.set_current_page(0) + # Select the now-first target for the user in case they want to # log into another one. for row in self._store: @@ -326,21 +369,61 @@ class ISCSIDialog(GUIObject): # the user cancel. Cancel what, exactly? self._cancelButton.set_sensitive(False)
- if not anyLeft: - self.window.response(1) + if not anyLeft: + self.window.response(1)
+ self._set_login_sensitive(True) return False
+ def _set_login_sensitive(self, sensitivity): + for child in self._loginGrid.get_children(): + if child != self._loginConditionNotebook: + child.set_sensitive(sensitivity) + def on_login_clicked(self, *args): - # Make the buttons unusable while we work. + # Make the buttons UI while we work. self._okButton.set_sensitive(False) self._cancelButton.set_sensitive(False) self._loginButton.set_sensitive(False)
+ self._loginConditionNotebook.set_current_page(0) + self._set_login_sensitive(False) + spinner = self.builder.get_object("loginSpinner") spinner.start() spinner.set_visible(True) spinner.show()
- threadMgr.add(AnacondaThread(name=constants.THREAD_ISCSI_LOGIN, target=self._login)) + # Are we reusing the credentials from the discovery step? If so, grab them + # out of the UI again here. They should still be there. + page = self._loginAuthNotebook.get_current_page() + if page == 3: + credentials = discoverMap[self._authNotebook.get_current_page()](self.builder) + else: + credentials = loginMap[page](self.builder) + + threadMgr.add(AnacondaThread(name=constants.THREAD_ISCSI_LOGIN, target=self._login, + args=(credentials,))) GLib.timeout_add(250, self._check_login) + + def on_login_field_changed(self, *args): + # Make up a credentials object so we can test if it's valid. + page = self._loginAuthNotebook.get_current_page() + + if page == 3: + sensitive = True + else: + credentials = loginMap[page](self.builder) + sensitive = credentials_valid(credentials) + + self._loginButton.set_sensitive(sensitive) + + ## + ## GLOBAL SIGNAL HANDLERS + ## + + def on_password_focused(self, entry, event, *args): + entry.set_visibility(True) + + def on_password_unfocused(self, entry, event, *args): + entry.set_visibility(False)
--- anaconda.spec.in | 1 + 1 file changed, 1 insertion(+)
diff --git a/anaconda.spec.in b/anaconda.spec.in index 308308e..407b76b 100644 --- a/anaconda.spec.in +++ b/anaconda.spec.in @@ -93,6 +93,7 @@ Requires: dbus-python Requires: python-pwquality Requires: python-bugzilla Requires: python-nss +Requires: python-IPy Requires: tigervnc-server-minimal Requires: pytz Requires: libxklavier
I want to look at the patches and do proper review. For now I just tried them and noticed that iscsi binding support is missing. Something we do in RHEL 6 like this:
http://rvykydal.fedorapeople.org/rhel64iscsibind.webm
User can choose if he wants to bind targets to specific devices (internally it affects what discovery returns). You can see ifaces in list of nodes (for non-bound case there is "default"). After the first login the choice (bind or not) can't be changed.
I am attaching a patch adding the support on top your patches. The checkbox added to discovery tab should be probably UX-tuned. Also having the list of discovered nodes with interfaces each row per each node/iface combination is probably not the best:
Node Iface ------------------- target 1 p4p1 target 1 p4p2 target 2 p4p1 target 2 p4p2 target 3 p4p1
Perhaps the choice should be done in 2 stages - target and interface, eg:
Node Iface ------------------- target 1 ^p4p1 <- iface selection combobox in a cell target 2 ^p4p2. target 3 ^p4p1
or:
Node ^p4p2 <- iface selection combobox filtering the list ------------------- target1 target2
But it si something I'd do later.
I'd attach a screencast of the patch but our virt server is in bad condition today.
Also one minor thing you are probably aware of: Selecting Add iSCSI Target for 2nd time doesn't bring up the dialog unless there has been change in combobox since the 1st time.
Radek
I want to look at the patches and do proper review. For now I just tried them and noticed that iscsi binding support is missing. Something we do in RHEL 6 like this:
http://rvykydal.fedorapeople.org/rhel64iscsibind.webm
User can choose if he wants to bind targets to specific devices (internally it affects what discovery returns). You can see ifaces in list of nodes (for non-bound case there is "default"). After the first login the choice (bind or not) can't be changed.
Ahh, I was not aware you could do this. It was not included in the design because we based it upon the older screenshots in the RHEL6 documentation, which did not include this checkbox. Thanks for pointing it out.
I am attaching a patch adding the support on top your patches. The checkbox added to discovery tab should be probably UX-tuned. Also having the list of discovered nodes with interfaces each row per each node/iface combination is probably not the best:
Node Iface
target 1 p4p1 target 1 p4p2 target 2 p4p1 target 2 p4p2 target 3 p4p1
I'll have to apply your patch and play with this to really see what you mean.
Perhaps the choice should be done in 2 stages - target and interface, eg:
Node Iface
target 1 ^p4p1 <- iface selection combobox in a cell target 2 ^p4p2. target 3 ^p4p1
I'd love if we could do things like this, but remember that the CellRendererCombos don't display those little arrows by default so the user will never be able to figure out there's something they can do with that cell. We had the same problem with the reclaim dialog.
Also one minor thing you are probably aware of: Selecting Add iSCSI Target for 2nd time doesn't bring up the dialog unless there has been change in combobox since the 1st time.
Yeah, I know. I haven't decided what to do about this yet. I may just want to replace it with a completely different widget.
From 6e75cb279545f7045b26b28ca6bd7a9aea2cd3f9 Mon Sep 17 00:00:00 2001 From: Radek Vykydal rvykydal@redhat.com Date: Thu, 11 Apr 2013 11:11:53 +0200 Subject: [PATCH] Add support ford iSCSI iface binding.
pyanaconda/ui/gui/spokes/advstorage/iscsi.glade | 29 +++++++++++++++++++++++++ pyanaconda/ui/gui/spokes/advstorage/iscsi.py | 21 ++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade b/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade index 34b0c41..977f955 100644 --- a/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade +++ b/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade @@ -643,6 +643,22 @@ </object> <packing> <property name="left_attach">0</property>
<property name="top_attach">7</property><property name="width">3</property><property name="height">1</property></packing></child><child><object class="GtkCheckButton" id="bindCheckbutton"><property name="label" translatable="yes">Bind targets to network interfaces</property><property name="visible">True</property><property name="can_focus">True</property><property name="receives_default">False</property><property name="xalign">0</property><property name="draw_indicator">True</property></object><packing><property name="left_attach">0</property> <property name="top_attach">6</property> <property name="width">3</property> <property name="height">1</property>
Remember to make the checkbutton accessible with the keyboard.
@@ -243,6 +250,15 @@ class ISCSIDialog(GUIObject): discoveredLabel.set_markup(discoveredLabel.get_label() % {"initiatorName": credentials.initiator, "targetAddress": credentials.targetIP})
bind = self._bindCheckbox.get_active()if self.iscsi.mode == "none" and not bind:self.iscsi.delete_interfaces()elif (self.iscsi.mode == "bind"or self.iscsi.mode == "none" and bind):activated = set(nm.nm_activated_devices())created = set(self.iscsi.ifaces.values())self.iscsi.create_interfaces(activated - created)spinner = self.builder.get_object("waitSpinner") spinner.start()
Does this take any time at all? If so, it should go into _discover. Also, do you need to convert activated-created into a list before passing it to create_interfaces?
- Chris
The patches look good to me.
I'll have to apply your patch and play with this to really see what you mean.
screencast: http://rvykydal.fedorapeople.org/patch_iscsi_bind.webm
From 6e75cb279545f7045b26b28ca6bd7a9aea2cd3f9 Mon Sep 17 00:00:00 2001 From: Radek Vykydal rvykydal@redhat.com Date: Thu, 11 Apr 2013 11:11:53 +0200 Subject: [PATCH] Add support ford iSCSI iface binding.
pyanaconda/ui/gui/spokes/advstorage/iscsi.glade | 29 +++++++++++++++++++++++++ pyanaconda/ui/gui/spokes/advstorage/iscsi.py | 21 ++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade b/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade index 34b0c41..977f955 100644 --- a/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade +++ b/pyanaconda/ui/gui/spokes/advstorage/iscsi.glade @@ -643,6 +643,22 @@ </object> <packing> <property name="left_attach">0</property>
<property name="top_attach">7</property><property name="width">3</property><property name="height">1</property></packing></child><child><object class="GtkCheckButton" id="bindCheckbutton"><property name="label" translatable="yes">Bind targets to network interfaces</property><property name="visible">True</property><property name="can_focus">True</property><property name="receives_default">False</property><property name="xalign">0</property><property name="draw_indicator">True</property></object><packing><property name="left_attach">0</property> <property name="top_attach">6</property> <property name="width">3</property> <property name="height">1</property>Remember to make the checkbutton accessible with the keyboard.
Should be fixed in new version of the patch I'm sending.
@@ -243,6 +250,15 @@ class ISCSIDialog(GUIObject): discoveredLabel.set_markup(discoveredLabel.get_label() % {"initiatorName": credentials.initiator, "targetAddress": credentials.targetIP})
bind = self._bindCheckbox.get_active()if self.iscsi.mode == "none" and not bind:self.iscsi.delete_interfaces()elif (self.iscsi.mode == "bind"or self.iscsi.mode == "none" and bind):activated = set(nm.nm_activated_devices())created = set(self.iscsi.ifaces.values())self.iscsi.create_interfaces(activated - created)spinner = self.builder.get_object("waitSpinner") spinner.start()Does this take any time at all? If so, it should go into _discover.
It is calling iscaiadm tool, I moved it into _discover in new version.
Also, do you need to convert activated-created into a list before passing it to create_interfaces?
The return value should be just an iterable.
One more thing I noticed - values of Target and LUN in Search tab seem to be wrong for iSCSI currently. Perhaps node.tpgt for the former can be used. For the latter, I don't know where is the best place to parse it from.
Radek
I'll have to apply your patch and play with this to really see what you mean.
screencast: http://rvykydal.fedorapeople.org/patch_iscsi_bind.webm
Ah, great. Thanks. Then, my one other comment would be that the checkbox should be made insensitive once you press "Start Discovery". We don't want people modifying the widgets while things are happening in the background.
One more thing I noticed - values of Target and LUN in Search tab seem to be wrong for iSCSI currently. Perhaps node.tpgt for the former can be used. For the latter, I don't know where is the best place to parse it from.
From just looking at what was available, I thought node.port would be port and node.tpgt would be LUN, but I am honestly not all that familiar with this stuff. So other values very well could be more appropriate.
- Chris
Also one minor thing you are probably aware of: Selecting Add iSCSI Target for 2nd time doesn't bring up the dialog unless there has been change in combobox since the 1st time.
I've got a patch that will fix this up. Basically I just converted it from the one combo box to three individual buttons. It still looks fine.
- Chris
Apart from the few comments, these patches look good to me. Well, as far as my iSCSI knowledge reaches. :) We should definitely use IPy in more places.
anaconda-patches@lists.fedorahosted.org