Break out shared storage UI to storagelist.py (bz 1060433)

We were already sharing a chunk of this in a haphazard way. Now officially
break it all out, similar to netlist.py. This mostly unifies the views
of host->storage and storagebrowser.py
This commit is contained in:
Cole Robinson
2015-04-08 18:29:48 -04:00
parent 6634053533
commit 058c06972e
15 changed files with 1565 additions and 1726 deletions

View File

@@ -1408,6 +1408,19 @@ ba</description>
</permissions>
</target>
</volume>
<volume type='file'>
<name>dir-vol</name>
<capacity>1000000</capacity>
<allocation>50000</allocation>
<target>
<format type='dir'/>
<permissions>
<mode>0700</mode>
<owner>10736</owner>
<group>10736</group>
</permissions>
</target>
</volume>
<volume type='file'>
<name>iso-vol</name>
<capacity>1000000</capacity>

File diff suppressed because it is too large Load Diff

View File

@@ -2,19 +2,9 @@
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.8"/>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-new</property>
</object>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-open</property>
</object>
<object class="GtkWindow" id="vmm-storage-browse">
<property name="can_focus">False</property>
<property name="border_width">12</property>
<property name="border_width">6</property>
<property name="title" translatable="yes">Choose Storage Volume</property>
<property name="default_width">750</property>
<property name="default_height">500</property>
@@ -22,265 +12,11 @@
<property name="type_hint">dialog</property>
<signal name="delete-event" handler="on_vmm_storage_browse_delete_event" swapped="no"/>
<child>
<object class="GtkBox" id="box2">
<object class="GtkAlignment" id="storage-align">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="width_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="pool-list">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection"/>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</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="GtkBox" id="box4">
<property name="visible">True</property>
<property name="can_focus">False</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="label" translatable="yes">&lt;b&gt;Volumes&lt;/b&gt;</property>
<property name="use_markup">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="pool-refresh">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_pool_refresh_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-refresh</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="vol-delete">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_vol_delete_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-delete</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="vol-list">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="row-activated" handler="on_vol_list_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection1">
<signal name="changed" handler="on_vol_list_changed" swapped="no"/>
</object>
</child>
</object>
</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>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="vexpand">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkButtonBox" id="buttonbox3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">start</property>
<child>
<object class="GtkButton" id="browse-local">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_browse_local_clicked" swapped="no"/>
<child>
<object class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">2</property>
<property name="bottom_padding">2</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Browse Local</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">browse-local</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButtonBox" id="buttonbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">start</property>
<child>
<object class="GtkButton" id="new-volume">
<property name="label" translatable="yes">_New Volume</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="image">image1</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_new_volume_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="non_homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkButton" id="browse-cancel">
<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>
<signal name="clicked" handler="on_browse_cancel_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="non_homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkButton" id="choose-volume">
<property name="label" translatable="yes">Choose _Volume</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="image">image2</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_choose_volume_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="non_homogeneous">True</property>
</packing>
</child>
</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">False</property>
<property name="position">1</property>
</packing>
<placeholder/>
</child>
</object>
</child>

634
ui/storagelist.ui Normal file
View File

@@ -0,0 +1,634 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.8"/>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-apply</property>
</object>
<object class="GtkGrid" id="storage-grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">3</property>
<child>
<object class="GtkHBox" id="hbox9">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">end</property>
<property name="border_width">3</property>
<child>
<object class="GtkHBox" id="hbox10">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton" id="pool-add">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Add Pool</property>
<signal name="clicked" handler="on_pool_add_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-add</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pool-start">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Start Pool</property>
<signal name="clicked" handler="on_pool_start_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="someicon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-play</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pool-stop">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Stop Pool</property>
<signal name="clicked" handler="on_pool_stop_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-cancel</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pool-delete">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Delete Pool</property>
<signal name="clicked" handler="on_pool_delete_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-delete</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox11">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="browse-local">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Browse local filesystem</property>
<signal name="clicked" handler="on_browse_local_clicked" swapped="no"/>
<child>
<object class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">2</property>
<property name="bottom_padding">2</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Browse Local</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="browse-cancel">
<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="tooltip_text" translatable="yes">Cancel and close dialog</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_browse_cancel_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="choose-volume">
<property name="label" translatable="yes">Choose Volume</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Choose the selected volume</property>
<property name="image">image3</property>
<signal name="clicked" handler="on_choose_volume_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pool-apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Apply pool changes</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_pool_apply_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkPaned" id="storage-pane">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="border_width">3</property>
<property name="position">200</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow6">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="pool-list">
<property name="width_request">134</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection"/>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkNotebook" id="storage-pages">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="show_border">False</property>
<child>
<object class="GtkAlignment" id="alignment13">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">6</property>
<child>
<object class="GtkVBox" id="pool-details">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkGrid" id="table5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">5</property>
<property name="column_spacing">8</property>
<child>
<object class="GtkLabel" id="label73">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Pool Type:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="pool-type">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">Pool Type</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="pool-name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">&lt;b&gt;Poolname:&lt;/b&gt;</property>
<property name="use_markup">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="pool-sizes">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">&lt;span size="large"&gt;1234 GiB Free&lt;/span&gt; / &lt;i&gt;6000 GiB In Use&lt;/i&gt;</property>
<property name="use_markup">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="pool-state-box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">3</property>
<child>
<object class="GtkImage" id="pool-state-icon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="stock">gtk-missing-image</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="pool-state">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Active</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="pool-autostart">
<property name="label">Some label</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_pool_autostart_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="pool-location">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">label</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="pool-name-entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="changed" handler="on_pool_name_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label11">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Name:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label60">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Location:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label173">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">State:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label76">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">A_utostart:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">pool-autostart</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">3</property>
<child>
<object class="GtkHBox" id="hbox12">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label77">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Volumes&lt;/b&gt;</property>
<property name="use_markup">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</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>
<child>
<object class="GtkButton" id="vol-add">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_vol_add_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-add</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pool-refresh">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Refresh volume list</property>
<signal name="clicked" handler="on_pool_refresh_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-refresh</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="vol-delete">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Delete volume</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_vol_delete_clicked" swapped="no"/>
<child>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-delete</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</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">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow5">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="vol-list">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="button-press-event" handler="on_vol_list_button_press_event" swapped="no"/>
<signal name="row-activated" handler="on_vol_list_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection1">
<signal name="changed" handler="on_vol_list_changed" swapped="no"/>
</object>
</child>
</object>
</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>
</object>
</child>
<child type="tab">
<object class="GtkLabel" id="label123">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">info</property>
</object>
<packing>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="storage-error-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">some error here</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label24">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">error</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
</interface>

View File

@@ -2025,9 +2025,8 @@ class vmmAddHardware(vmmGObjectUI):
if self.storage_browser is None:
self.storage_browser = vmmStorageBrowser(conn)
self.storage_browser.stable_defaults = self.vm.stable_defaults()
self.storage_browser.set_stable_defaults(self.vm.stable_defaults())
self.storage_browser.set_finish_cb(set_storage_cb)
self.storage_browser.set_browse_reason(reason)
self.storage_browser.show(self.topwin, conn)
self.storage_browser.show(self.topwin)

View File

@@ -150,7 +150,7 @@ class vmmChooseCD(vmmGObjectUI):
self.storage_browser = vmmStorageBrowser(self.conn)
self.storage_browser.set_finish_cb(self.set_storage_path)
self.storage_browser.stable_defaults = self.vm.stable_defaults()
self.storage_browser.set_stable_defaults(self.vm.stable_defaults())
if self.media_type == vmmMediaCombo.MEDIA_FLOPPY:
self.storage_browser.set_browse_reason(
@@ -158,4 +158,4 @@ class vmmChooseCD(vmmGObjectUI):
else:
self.storage_browser.set_browse_reason(
self.config.CONFIG_DIR_ISO_MEDIA)
self.storage_browser.show(self.topwin, self.conn)
self.storage_browser.show(self.topwin)

View File

@@ -832,8 +832,11 @@ class vmmCloneVM(vmmGObjectUI):
def callback(src_ignore, txt):
self.widget("change-storage-new").set_text(txt)
if self.storage_browser and self.storage_browser.conn != self.conn:
self.storage_browser.cleanup()
self.storage_browser = None
if self.storage_browser is None:
self.storage_browser = vmmStorageBrowser(self.conn)
self.storage_browser.set_finish_cb(callback)
self.storage_browser.show(self.topwin, self.conn)
self.storage_browser.show(self.topwin)

View File

@@ -2087,12 +2087,14 @@ class vmmCreate(vmmGObjectUI):
widget = self.widget(cbwidget)
widget.set_text(text)
if self.storage_browser and self.storage_browser.conn != self.conn:
self.storage_browser.cleanup()
self.storage_browser = None
if self.storage_browser is None:
self.storage_browser = vmmStorageBrowser(self.conn)
self.storage_browser.stable_defaults = self._stable_defaults()
self.storage_browser.set_stable_defaults(self._stable_defaults())
self.storage_browser.set_vm_name(self.get_config_name())
self.storage_browser.set_finish_cb(callback)
self.storage_browser.set_browse_reason(reason)
self.storage_browser.show(self.topwin, self.conn)
self.storage_browser.show(self.topwin)

View File

@@ -337,6 +337,10 @@ class vmmCreateVolume(vmmGObjectUI):
return self.err.val_err(info, details, modal=self.topwin.get_modal())
def _browse_file(self):
if self.storage_browser and self.storage_browser.conn != self.conn:
self.storage_browser.cleanup()
self.storage_browser = None
if self.storage_browser is None:
def cb(src, text):
ignore = src
@@ -346,8 +350,7 @@ class vmmCreateVolume(vmmGObjectUI):
self.storage_browser = vmmStorageBrowser(self.conn)
self.storage_browser.set_finish_cb(cb)
self.storage_browser.topwin.set_modal(self.topwin.get_modal())
self.storage_browser.can_new_volume = False
self.storage_browser.set_browse_reason(
self.config.CONFIG_DIR_IMAGE)
self.storage_browser.show(self.topwin, self.conn)
self.storage_browser.show(self.topwin)

View File

@@ -1589,7 +1589,7 @@ class vmmDetails(vmmGObjectUI):
self.storage_browser.set_finish_cb(callback)
self.storage_browser.set_browse_reason(reason)
self.storage_browser.show(self.topwin, self.conn)
self.storage_browser.show(self.topwin)
def boot_kernel_toggled(self, src):
self.widget("boot-kernel-box").set_sensitive(src.get_active())

View File

@@ -54,19 +54,27 @@ class vmmErrorDialog(vmmGObject):
self._parent = parent
self._simple = None
# Allows the error owner to easily override default modality
self._modal_default = False
def _cleanup(self):
pass
def set_modal_default(self, val):
self._modal_default = val
def set_parent(self, parent):
self._parent = parent
def get_parent(self):
return self._parent
def show_err(self, summary, details=None, title="",
modal=False, debug=True,
modal=None, debug=True,
dialog_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.CLOSE,
text2=None):
if modal is None:
modal = self._modal_default
if details is None:
details = summary
tb = "".join(traceback.format_exc()).strip()
@@ -93,6 +101,7 @@ class vmmErrorDialog(vmmGObject):
details=details, title=title,
modal=modal)
###################################
# Simple one shot message dialogs #
###################################

View File

@@ -342,16 +342,18 @@ class vmmFSDetails(vmmGObjectUI):
if path:
textent.set_text(path)
conn = self.conn
reason = (isdir and
self.config.CONFIG_DIR_FS or
self.config.CONFIG_DIR_IMAGE)
if self.storage_browser and self.storage_browser.conn != self.conn:
self.storage_browser.cleanup()
self.storage_browser = None
if self.storage_browser is None:
self.storage_browser = vmmStorageBrowser(conn)
self.storage_browser.stable_defaults = self.vm.stable_defaults()
self.storage_browser = vmmStorageBrowser(self.conn)
self.storage_browser.set_stable_defaults(self.vm.stable_defaults())
self.storage_browser.set_finish_cb(set_storage_cb)
self.storage_browser.set_browse_reason(reason)
self.storage_browser.show(self.topwin.get_ancestor(Gtk.Window), conn)
self.storage_browser.show(self.topwin.get_ancestor(Gtk.Window))

View File

@@ -22,20 +22,16 @@ import logging
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gdk
from virtinst import VirtualDisk
from virtinst import StoragePool
from virtinst import Interface
from . import uiutil
from .asyncjob import vmmAsyncJob
from .createnet import vmmCreateNetwork
from .createpool import vmmCreatePool
from .createvol import vmmCreateVolume
from .createinterface import vmmCreateInterface
from .baseclass import vmmGObjectUI
from .createnet import vmmCreateNetwork
from .createinterface import vmmCreateInterface
from .graphwidgets import Sparkline
from .storagelist import vmmStorageList
INTERFACE_PAGE_INFO = 0
INTERFACE_PAGE_ERROR = 1
@@ -46,11 +42,6 @@ EDIT_NET_AUTOSTART,
EDIT_NET_QOS,
) = range(3)
EDIT_POOL_IDS = (
EDIT_POOL_NAME,
EDIT_POOL_AUTOSTART,
) = range(100, 102)
EDIT_INTERFACE_IDS = (
EDIT_INTERFACE_STARTMODE,
) = range(200, 201)
@@ -76,11 +67,7 @@ class vmmHost(vmmGObjectUI):
self.ICON_SHUTOFF = "state_shutoff"
self.addnet = None
self.addpool = None
self.addvol = None
self.addinterface = None
self.volmenu = None
self._in_refresh = False
self.active_edits = []
@@ -88,8 +75,9 @@ class vmmHost(vmmGObjectUI):
self.memory_usage_graph = None
self.init_conn_state()
self.init_net_state()
self.storagelist = None
self.init_storage_state()
self.init_net_state()
self.init_interface_state()
self.builder.connect_signals({
@@ -111,20 +99,6 @@ class vmmHost(vmmGObjectUI):
"on_net_name_changed": (lambda *x:
self.enable_net_apply(x, EDIT_NET_NAME)),
"on_pool_add_clicked" : self.add_pool,
"on_vol_add_clicked" : self.add_vol,
"on_pool_stop_clicked": self.stop_pool,
"on_pool_start_clicked": self.start_pool,
"on_pool_delete_clicked": self.delete_pool,
"on_pool_refresh_clicked": self.pool_refresh,
"on_pool_autostart_toggled": self.pool_autostart_changed,
"on_vol_delete_clicked": self.delete_vol,
"on_vol_list_button_press_event": self.popup_vol_menu,
"on_pool_apply_clicked": (lambda *x: self.pool_apply()),
"on_vol_list_changed": self.vol_selected,
"on_pool_name_changed": (lambda *x:
self.enable_pool_apply(x, EDIT_POOL_NAME)),
"on_interface_add_clicked" : self.add_interface,
"on_interface_start_clicked" : self.start_interface,
"on_interface_stop_clicked" : self.stop_interface,
@@ -153,7 +127,6 @@ class vmmHost(vmmGObjectUI):
})
self.repopulate_networks()
self.repopulate_storage_pools()
self.repopulate_interfaces()
self.conn.connect("net-added", self.repopulate_networks)
@@ -161,11 +134,6 @@ class vmmHost(vmmGObjectUI):
self.conn.connect("net-started", self.refresh_network)
self.conn.connect("net-stopped", self.refresh_network)
self.conn.connect("pool-added", self.repopulate_storage_pools)
self.conn.connect("pool-removed", self.repopulate_storage_pools)
self.conn.connect("pool-started", self.refresh_storage_pool)
self.conn.connect("pool-stopped", self.refresh_storage_pool)
self.conn.connect("interface-added", self.repopulate_interfaces)
self.conn.connect("interface-removed", self.repopulate_interfaces)
self.conn.connect("interface-started", self.refresh_interface)
@@ -173,7 +141,11 @@ class vmmHost(vmmGObjectUI):
self.conn.connect("state-changed", self.conn_state_changed)
self.conn.connect("resources-sampled", self.refresh_resources)
self.reset_state()
self.refresh_resources()
self.conn_state_changed()
self.widget("config-autoconnect").set_active(
self.conn.get_autoconnect())
def init_net_state(self):
@@ -200,51 +172,9 @@ class vmmHost(vmmGObjectUI):
netListModel.set_sort_column_id(1, Gtk.SortType.ASCENDING)
def init_storage_state(self):
self.widget("storage-pages").set_show_tabs(False)
self.storagelist = vmmStorageList(self.conn, self.builder, self.topwin)
self.widget("storage-align").add(self.storagelist.top_box)
self.volmenu = Gtk.Menu()
volCopyPath = Gtk.ImageMenuItem.new_with_label(_("Copy Volume Path"))
volCopyImage = Gtk.Image()
volCopyImage.set_from_stock(Gtk.STOCK_COPY, Gtk.IconSize.MENU)
volCopyPath.set_image(volCopyImage)
volCopyPath.show()
volCopyPath.connect("activate", self.copy_vol_path)
self.volmenu.add(volCopyPath)
volListModel = Gtk.ListStore(str, str, str, str, str)
self.widget("vol-list").set_model(volListModel)
volCol = Gtk.TreeViewColumn("Volumes")
vol_txt1 = Gtk.CellRendererText()
volCol.pack_start(vol_txt1, True)
volCol.add_attribute(vol_txt1, 'text', 1)
volCol.set_sort_column_id(1)
self.widget("vol-list").append_column(volCol)
volSizeCol = Gtk.TreeViewColumn("Size")
vol_txt2 = Gtk.CellRendererText()
volSizeCol.pack_start(vol_txt2, False)
volSizeCol.add_attribute(vol_txt2, 'text', 2)
volSizeCol.set_sort_column_id(2)
self.widget("vol-list").append_column(volSizeCol)
volFormatCol = Gtk.TreeViewColumn("Format")
vol_txt3 = Gtk.CellRendererText()
volFormatCol.pack_start(vol_txt3, False)
volFormatCol.add_attribute(vol_txt3, 'text', 3)
volFormatCol.set_sort_column_id(3)
self.widget("vol-list").append_column(volFormatCol)
volUseCol = Gtk.TreeViewColumn("Used By")
vol_txt4 = Gtk.CellRendererText()
volUseCol.pack_start(vol_txt4, False)
volUseCol.add_attribute(vol_txt4, 'text', 4)
volUseCol.set_sort_column_id(4)
self.widget("vol-list").append_column(volUseCol)
volListModel.set_sort_column_id(1, Gtk.SortType.ASCENDING)
self.init_pool_list()
def init_interface_state(self):
self.widget("interface-pages").set_show_tabs(False)
@@ -346,25 +276,17 @@ class vmmHost(vmmGObjectUI):
def _cleanup(self):
self.conn = None
self.storagelist.cleanup()
self.storagelist = None
if self.addnet:
self.addnet.cleanup()
self.addnet = None
if self.addpool:
self.addpool.cleanup()
self.addpool = None
if self.addvol:
self.addvol.cleanup()
self.addvol = None
if self.addinterface:
self.addinterface.cleanup()
self.addinterface = None
self.volmenu.destroy()
self.volmenu = None
self.cpu_usage_graph.destroy()
self.cpu_usage_graph = None
@@ -380,13 +302,6 @@ class vmmHost(vmmGObjectUI):
def exit_app(self, src_ignore):
self.emit("action-exit-app")
def reset_state(self):
self.refresh_resources()
self.conn_state_changed()
# Update autostart value
auto = self.conn.get_autoconnect()
self.widget("config-autoconnect").set_active(auto)
def page_changed(self, src, child, pagenum):
ignore = src
@@ -396,8 +311,7 @@ class vmmHost(vmmGObjectUI):
self.repopulate_networks()
self.conn.schedule_priority_tick(pollnet=True)
elif pagenum == 2:
self.repopulate_storage_volumes()
self.conn.schedule_priority_tick(pollpool=True)
self.storagelist.refresh_page()
elif pagenum == 3:
self.repopulate_interfaces()
self.conn.schedule_priority_tick(polliface=True)
@@ -425,18 +339,14 @@ class vmmHost(vmmGObjectUI):
self.widget("menu_file_restore_saved").set_sensitive(conn_active)
self.widget("net-add").set_sensitive(conn_active and
self.conn.is_network_capable())
self.widget("pool-add").set_sensitive(conn_active and
self.conn.is_storage_capable())
self.widget("interface-add").set_sensitive(conn_active and
self.conn.is_interface_capable())
if not conn_active:
self.set_net_error_page(_("Connection not active."))
self.set_storage_error_page(_("Connection not active."))
self.set_interface_error_page(_("Connection not active."))
self.repopulate_networks()
self.repopulate_storage_pools()
self.repopulate_interfaces()
return
@@ -445,10 +355,6 @@ class vmmHost(vmmGObjectUI):
_("Libvirt connection does not support virtual network "
"management."))
if not self.conn.is_storage_capable():
self.set_storage_error_page(
_("Libvirt connection does not support storage management."))
if not self.conn.is_interface_capable():
self.set_interface_error_page(
_("Libvirt connection does not support interface management."))
@@ -800,313 +706,6 @@ class vmmHost(vmmGObjectUI):
curnet and curnet.get_connkey() or None)
# ------------------------------
# Storage Manager methods
# ------------------------------
def stop_pool(self, src_ignore):
pool = self.current_pool()
if pool is None:
return
logging.debug("Stopping pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(pool.stop, [], self,
_("Error stopping pool '%s'") % pool.get_name())
def start_pool(self, src_ignore):
pool = self.current_pool()
if pool is None:
return
logging.debug("Starting pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(pool.start, [], self,
_("Error starting pool '%s'") % pool.get_name())
def delete_pool(self, src_ignore):
pool = self.current_pool()
if pool is None:
return
result = self.err.yes_no(_("Are you sure you want to permanently "
"delete the pool %s?") % pool.get_name())
if not result:
return
logging.debug("Deleting pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(pool.delete, [], self,
_("Error deleting pool '%s'") % pool.get_name())
def pool_refresh(self, src_ignore):
if self._in_refresh:
logging.debug("Already refreshing the pool, skipping")
return
if not self.confirm_changes():
return
pool = self.current_pool()
if pool is None:
return
self._in_refresh = True
def cb():
try:
pool.refresh()
self.idle_add(self.refresh_current_pool)
finally:
self._in_refresh = False
logging.debug("Refresh pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(cb, [], self,
_("Error refreshing pool '%s'") % pool.get_name())
def delete_vol(self, src_ignore):
vol = self.current_vol()
if vol is None:
return
result = self.err.yes_no(_("Are you sure you want to permanently "
"delete the volume %s?") % vol.get_name())
if not result:
return
def cb():
vol.delete()
def idlecb():
self.refresh_current_pool()
self.repopulate_storage_volumes()
self.idle_add(idlecb)
logging.debug("Deleting volume '%s'", vol.get_name())
vmmAsyncJob.simple_async_noshow(cb, [], self,
_("Error refreshing volume '%s'") % vol.get_name())
def add_pool(self, src_ignore):
logging.debug("Launching 'Add Pool' wizard")
try:
if self.addpool is None:
self.addpool = vmmCreatePool(self.conn)
self.addpool.show(self.topwin)
except Exception, e:
self.err.show_err(_("Error launching pool wizard: %s") % str(e))
def add_vol(self, src_ignore):
pool = self.current_pool()
if pool is None:
return
logging.debug("Launching 'Add Volume' wizard for pool '%s'",
pool.get_name())
try:
if self.addvol is None:
self.addvol = vmmCreateVolume(self.conn, pool)
self.addvol.connect("vol-created", self.refresh_current_pool)
else:
self.addvol.set_parent_pool(self.conn, pool)
self.addvol.show(self.topwin)
except Exception, e:
self.err.show_err(_("Error launching volume wizard: %s") % str(e))
def refresh_current_pool(self, ignore1=None):
cp = self.current_pool()
if cp is None:
return
cp.refresh()
self.refresh_storage_pool(None, cp.get_connkey())
def current_pool(self):
connkey = uiutil.get_list_selection(self.widget("pool-list"), 0)
try:
return connkey and self.conn.get_pool(connkey)
except KeyError:
return None
def current_vol(self):
pool = self.current_pool()
if not pool:
return None
connkey = uiutil.get_list_selection(self.widget("vol-list"), 0)
try:
return connkey and pool.get_volume(connkey)
except KeyError:
return None
def pool_apply(self):
pool = self.current_pool()
if pool is None:
return
logging.debug("Applying changes for pool '%s'", pool.get_name())
try:
if EDIT_POOL_AUTOSTART in self.active_edits:
auto = self.widget("pool-autostart").get_active()
pool.set_autostart(auto)
if EDIT_POOL_NAME in self.active_edits:
pool.define_name(self.widget("pool-name-entry").get_text())
self.idle_add(self.repopulate_storage_pools)
except Exception, e:
self.err.show_err(_("Error changing pool settings: %s") % str(e))
return
self.disable_pool_apply()
def disable_pool_apply(self):
for i in EDIT_POOL_IDS:
if i in self.active_edits:
self.active_edits.remove(i)
self.widget("pool-apply").set_sensitive(False)
def enable_pool_apply(self, *arglist):
edittype = arglist[-1]
self.widget("pool-apply").set_sensitive(True)
if edittype not in self.active_edits:
self.active_edits.append(edittype)
def pool_autostart_changed(self, src_ignore):
auto = self.widget("pool-autostart").get_active()
self.widget("pool-autostart").set_label(auto and
_("On Boot") or
_("Never"))
self.enable_pool_apply(EDIT_POOL_AUTOSTART)
def set_storage_error_page(self, msg):
self.reset_pool_state()
self.widget("storage-pages").set_current_page(1)
self.widget("storage-error-label").set_text(msg)
def pool_selected(self, src):
model, treeiter = src.get_selected()
if treeiter is None:
self.set_storage_error_page(_("No storage pool selected."))
return
self.widget("storage-pages").set_current_page(0)
connkey = model[treeiter][0]
try:
self.populate_pool_state(connkey)
except Exception, e:
logging.exception(e)
self.set_storage_error_page(_("Error selecting pool: %s") % e)
self.disable_pool_apply()
def populate_pool_state(self, connkey):
pool = self.conn.get_pool(connkey)
pool.tick()
auto = pool.get_autostart()
active = pool.is_active()
# Set pool details state
self.widget("pool-details").set_sensitive(True)
self.widget("pool-name").set_markup("<b>%s:</b>" %
pool.get_name())
self.widget("pool-name-entry").set_text(pool.get_name())
self.widget("pool-name-entry").set_editable(not active)
self.widget("pool-sizes").set_markup(
"""<span size="large">%s Free</span> / <i>%s In Use</i>""" %
(pool.get_pretty_available(), pool.get_pretty_allocation()))
self.widget("pool-type").set_text(
StoragePool.get_pool_type_desc(pool.get_type()))
self.widget("pool-location").set_text(
pool.get_target_path())
self.widget("pool-state-icon").set_from_icon_name(
((active and self.ICON_RUNNING) or self.ICON_SHUTOFF),
Gtk.IconSize.BUTTON)
self.widget("pool-state").set_text(
(active and _("Active")) or _("Inactive"))
self.widget("pool-autostart").set_label(
(auto and _("On Boot")) or _("Never"))
self.widget("pool-autostart").set_active(auto)
self.widget("vol-list").set_sensitive(active)
self.repopulate_storage_volumes()
self.widget("pool-delete").set_sensitive(not active)
self.widget("pool-stop").set_sensitive(active)
self.widget("pool-start").set_sensitive(not active)
self.widget("vol-add").set_sensitive(active)
self.widget("vol-add").set_tooltip_text(_("Create new volume"))
self.widget("vol-delete").set_sensitive(False)
if active and not pool.supports_volume_creation():
self.widget("vol-add").set_sensitive(False)
self.widget("vol-add").set_tooltip_text(
_("Pool does not support volume creation"))
def refresh_storage_pool(self, src, connkey):
ignore = src
refresh_pool_in_list(self.widget("pool-list"), self.conn, connkey)
curpool = self.current_pool()
if curpool.get_connkey() != connkey:
return
# Currently selected pool changed state: force a 'pool_selected' to
# update vol list
self.pool_selected(self.widget("pool-list").get_selection())
def reset_pool_state(self):
self.widget("pool-details").set_sensitive(False)
self.widget("pool-name").set_text("")
self.widget("pool-name-entry").set_text("")
self.widget("pool-sizes").set_markup("""<span size="large"> </span>""")
self.widget("pool-type").set_text("")
self.widget("pool-location").set_text("")
self.widget("pool-state-icon").set_from_icon_name(self.ICON_SHUTOFF,
Gtk.IconSize.BUTTON)
self.widget("pool-state").set_text(_("Inactive"))
self.widget("vol-list").get_model().clear()
self.widget("pool-autostart").set_label(_("Never"))
self.widget("pool-autostart").set_active(False)
self.widget("pool-delete").set_sensitive(False)
self.widget("pool-stop").set_sensitive(False)
self.widget("pool-start").set_sensitive(False)
self.widget("vol-add").set_sensitive(False)
self.widget("vol-delete").set_sensitive(False)
self.widget("vol-list").set_sensitive(False)
self.disable_pool_apply()
def vol_selected(self, src):
model, treeiter = src.get_selected()
ignore = model
if treeiter is None:
self.widget("vol-delete").set_sensitive(False)
return
self.widget("vol-delete").set_sensitive(True)
def popup_vol_menu(self, widget_ignore, event):
if event.button != 3:
return
self.volmenu.popup(None, None, None, None, 0, event.time)
def copy_vol_path(self, ignore=None):
vol = self.current_vol()
if not vol:
return
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
target_path = vol.get_target_path()
if target_path:
clipboard.set_text(target_path, -1)
def repopulate_storage_pools(self, src=None, connkey=None):
ignore = src
ignore = connkey
pool_list = self.widget("pool-list")
populate_storage_pools(pool_list, self.conn, self.current_pool())
def repopulate_storage_volumes(self):
list_widget = self.widget("vol-list")
pool = self.current_pool()
populate_storage_volumes(list_widget, pool, None)
#############################
# Interface manager methods #
#############################
@@ -1382,9 +981,7 @@ class vmmHost(vmmGObjectUI):
chktext=_("Don't warn me again."),
default=False):
if all([edit in EDIT_POOL_IDS for edit in self.active_edits]):
self.pool_apply()
elif all([edit in EDIT_NET_IDS for edit in self.active_edits]):
if all([edit in EDIT_NET_IDS for edit in self.active_edits]):
self.net_apply()
elif all([edit in EDIT_INTERFACE_IDS
for edit in self.active_edits]):
@@ -1392,113 +989,3 @@ class vmmHost(vmmGObjectUI):
self.active_edits = []
return True
def init_pool_list(self):
pool_list = self.widget("pool-list")
init_pool_list(pool_list, self.pool_selected)
sel = pool_list.get_selection()
sel.set_select_function((lambda *x: self.confirm_changes()),
None)
# These functions are broken out, since they are used by storage browser
# dialog.
def init_pool_list(pool_list, changed_func):
poolListModel = Gtk.ListStore(str, str, bool, str)
pool_list.set_model(poolListModel)
pool_list.get_selection().connect("changed", changed_func)
poolCol = Gtk.TreeViewColumn("Storage Pools")
pool_txt = Gtk.CellRendererText()
pool_per = Gtk.CellRendererText()
poolCol.pack_start(pool_per, False)
poolCol.pack_start(pool_txt, True)
poolCol.add_attribute(pool_txt, 'markup', 1)
poolCol.add_attribute(pool_txt, 'sensitive', 2)
poolCol.add_attribute(pool_per, 'markup', 3)
pool_list.append_column(poolCol)
poolListModel.set_sort_column_id(1, Gtk.SortType.ASCENDING)
def refresh_pool_in_list(pool_list, conn, connkey):
for row in pool_list.get_model():
if row[0] != connkey:
continue
# Update active sensitivity and percent available for passed key
row[3] = get_pool_size_percent(conn, connkey)
row[2] = conn.get_pool(connkey).is_active()
return
def populate_storage_pools(pool_list, conn, curpool):
model = pool_list.get_model()
# Prevent events while the model is modified
pool_list.set_model(None)
pool_list.get_selection().unselect_all()
model.clear()
for pool in conn.list_pools():
connkey = pool.get_connkey()
per = get_pool_size_percent(conn, connkey)
pool = conn.get_pool(connkey)
name = pool.get_name()
typ = StoragePool.get_pool_type_desc(pool.get_type())
label = "%s\n<span size='small'>%s</span>" % (name, typ)
model.append([connkey, label, pool.is_active(), per])
pool_list.set_model(model)
uiutil.set_row_selection(pool_list,
curpool and curpool.get_connkey() or None)
def populate_storage_volumes(list_widget, pool, sensitive_cb):
vols = pool and pool.get_volumes() or {}
model = list_widget.get_model()
list_widget.get_selection().unselect_all()
model.clear()
for key in vols.keys():
vol = vols[key]
try:
path = vol.get_target_path()
name = vol.get_pretty_name(pool.get_type())
cap = vol.get_pretty_capacity()
fmt = vol.get_format() or ""
except:
logging.debug("Error getting volume info for '%s', "
"hiding it", key, exc_info=True)
continue
namestr = None
try:
if path:
names = VirtualDisk.path_in_use_by(vol.conn.get_backend(),
path)
namestr = ", ".join(names)
if not namestr:
namestr = None
except:
logging.exception("Failed to determine if storage volume in "
"use.")
row = [key, name, cap, fmt, namestr]
if sensitive_cb:
row.append(sensitive_cb(fmt))
model.append(row)
def get_pool_size_percent(conn, connkey):
pool = conn.get_pool(connkey)
cap = pool.get_capacity()
alloc = pool.get_allocation()
if not cap or alloc is None:
per = 0
else:
per = int(((float(alloc) / float(cap)) * 100))
return "<span size='small' color='#484848'>%s%%</span>" % int(per)

View File

@@ -20,13 +20,9 @@
import logging
from gi.repository import Gtk
from . import host
from .asyncjob import vmmAsyncJob
from .createvol import vmmCreateVolume
from .baseclass import vmmGObjectUI
from . import uiutil
from .baseclass import vmmGObjectUI
from .storagelist import vmmStorageList
class vmmStorageBrowser(vmmGObjectUI):
@@ -34,43 +30,33 @@ class vmmStorageBrowser(vmmGObjectUI):
vmmGObjectUI.__init__(self, "storagebrowse.ui", "vmm-storage-browse")
self.conn = conn
self.conn_signal_ids = []
self.can_new_volume = True
self._first_run = False
self._finish_cb = None
# Add Volume wizard
self.addvol = None
# Passed to browse_local
self._browse_reason = None
# Name of VM we are choosing storage for, can be used to recommend
# volume name if creating
self.vm_name = None
# Whether we should abide stable defaults
self._stable_defaults = False
# Arguments to pass to util.browse_local for local storage
self.browse_reason = None
self.local_args = {}
self.stable_defaults = False
self._in_refresh = False
self.storagelist = vmmStorageList(self.conn, self.builder, self.topwin,
self._vol_sensitive_cb)
self._init_ui()
self.builder.connect_signals({
"on_vmm_storage_browse_delete_event" : self.close,
"on_browse_cancel_clicked" : self.close,
"on_browse_local_clicked" : self.browse_local,
"on_new_volume_clicked" : self.new_volume,
"on_choose_volume_clicked" : self.finish,
"on_pool_refresh_clicked": self.pool_refresh,
"on_vol_delete_clicked": self.delete_vol,
"on_vol_list_row_activated" : self.finish,
"on_vol_list_changed": self.vol_selected,
})
self.bind_escape_key_close()
self.set_initial_state()
def show(self, parent, conn):
def show(self, parent):
logging.debug("Showing storage browser")
self.reset_state(conn)
if not self._first_run:
self._first_run = True
pool = self.conn.get_default_pool()
uiutil.set_row_selection(self.storagelist.widget("pool-list"),
pool and pool.get_connkey() or None)
self.topwin.set_transient_for(parent)
self.topwin.present()
self.conn.schedule_priority_tick(pollpool=True)
@@ -78,319 +64,112 @@ class vmmStorageBrowser(vmmGObjectUI):
def close(self, ignore1=None, ignore2=None):
logging.debug("Closing storage browser")
self.topwin.hide()
if self.addvol:
self.addvol.close()
self.storagelist.close()
return 1
def _cleanup(self):
self.remove_conn()
self.conn = None
if self.addvol:
self.addvol.cleanup()
self.addvol = None
self.storagelist.cleanup()
self.storagelist = None
def remove_conn(self):
if not self.conn:
return
for i in self.conn_signal_ids:
self.conn.disconnect(i)
##############
# Public API #
##############
def set_finish_cb(self, callback):
self._finish_cb = callback
def set_browse_reason(self, reason):
self.browse_reason = reason
def set_local_arg(self, arg, val):
self.local_args[arg] = val
self._browse_reason = reason
def set_vm_name(self, name):
self.vm_name = name
self.storagelist.set_name_hint(name)
def set_stable_defaults(self, val):
self._stable_defaults = val
def set_initial_state(self):
pool_list = self.widget("pool-list")
host.init_pool_list(pool_list, self.pool_selected)
def _init_ui(self):
self.storagelist.connect("browse-clicked", self._browse_clicked)
self.storagelist.connect("volume-chosen", self._volume_chosen)
self.storagelist.connect("cancel-clicked", self.close)
# (Key, Name, Cap, Format, Used By, sensitive)
vol_list = self.widget("vol-list")
volListModel = Gtk.ListStore(str, str, str, str, str, bool)
vol_list.set_model(volListModel)
volCol = Gtk.TreeViewColumn(_("Name"))
vol_txt1 = Gtk.CellRendererText()
volCol.pack_start(vol_txt1, True)
volCol.add_attribute(vol_txt1, 'text', 1)
volCol.add_attribute(vol_txt1, 'sensitive', 5)
volCol.set_sort_column_id(1)
vol_list.append_column(volCol)
volSizeCol = Gtk.TreeViewColumn(_("Size"))
vol_txt2 = Gtk.CellRendererText()
volSizeCol.pack_start(vol_txt2, False)
volSizeCol.add_attribute(vol_txt2, 'text', 2)
volSizeCol.add_attribute(vol_txt2, 'sensitive', 5)
volSizeCol.set_sort_column_id(2)
vol_list.append_column(volSizeCol)
volPathCol = Gtk.TreeViewColumn(_("Format"))
vol_txt4 = Gtk.CellRendererText()
volPathCol.pack_start(vol_txt4, False)
volPathCol.add_attribute(vol_txt4, 'text', 3)
volPathCol.add_attribute(vol_txt4, 'sensitive', 5)
volPathCol.set_sort_column_id(3)
vol_list.append_column(volPathCol)
volUseCol = Gtk.TreeViewColumn(_("Used By"))
vol_txt5 = Gtk.CellRendererText()
volUseCol.pack_start(vol_txt5, False)
volUseCol.add_attribute(vol_txt5, 'text', 4)
volUseCol.add_attribute(vol_txt5, 'sensitive', 5)
volUseCol.set_sort_column_id(4)
vol_list.append_column(volUseCol)
volListModel.set_sort_column_id(1, Gtk.SortType.ASCENDING)
def reset_state(self, conn):
self.remove_conn()
self.conn = conn
self.repopulate_storage_pools()
ids = []
ids.append(self.conn.connect("pool-added",
self.repopulate_storage_pools))
ids.append(self.conn.connect("pool-removed",
self.repopulate_storage_pools))
ids.append(self.conn.connect("pool-started",
self.refresh_storage_pool))
ids.append(self.conn.connect("pool-stopped",
self.refresh_storage_pool))
self.conn_signal_ids = ids
# FIXME: Need a connection specific "vol-added" function?
# Won't be able to pick that change up from outside?
if not self._first_run:
self._first_run = True
pool = self.conn.get_default_pool()
uiutil.set_row_selection(self.widget("pool-list"),
pool and pool.get_connkey() or None)
# Manually trigger vol_selected, so buttons are in the correct state
self.vol_selected()
self.pool_selected()
self.widget("storage-align").add(self.storagelist.top_box)
self.err.set_modal_default(True)
self.storagelist.err.set_modal_default(True)
tooltip = ""
is_remote = self.conn.is_remote()
self.widget("browse-local").set_sensitive(not is_remote)
self.storagelist.widget("browse-local").set_sensitive(not is_remote)
if is_remote:
tooltip = _("Cannot use local storage on remote connection.")
self.widget("browse-local").set_tooltip_text(tooltip)
self.storagelist.widget("browse-local").set_tooltip_text(tooltip)
# Set data based on browse type
self.local_args["dialog_type"] = None
self.local_args["browse_reason"] = self.browse_reason
uiutil.set_grid_row_visible(
self.storagelist.widget("pool-autostart"), False)
uiutil.set_grid_row_visible(
self.storagelist.widget("pool-name-entry"), False)
uiutil.set_grid_row_visible(
self.storagelist.widget("pool-type"), False)
uiutil.set_grid_row_visible(
self.storagelist.widget("pool-state-box"), False)
self.storagelist.widget("browse-local").set_visible(True)
self.storagelist.widget("browse-cancel").set_visible(True)
self.storagelist.widget("choose-volume").set_visible(True)
self.storagelist.widget("choose-volume").set_sensitive(False)
self.storagelist.widget("pool-apply").set_visible(False)
data = self.config.browse_reason_data.get(self.browse_reason)
data = self.config.browse_reason_data.get(self._browse_reason)
allow_create = True
if data:
self.topwin.set_title(data["storage_title"])
self.local_args["dialog_name"] = data["local_title"]
self.local_args["dialog_type"] = data.get("dialog_type")
self.local_args["choose_button"] = data.get("choose_button")
allow_create = data["enable_create"]
self.widget("new-volume").set_visible(
self.can_new_volume and self.allow_create())
self.storagelist.widget("vol-add").set_sensitive(allow_create)
# Convenience helpers
def allow_create(self):
data = self.config.browse_reason_data.get(self.browse_reason)
if not data:
return True
#############
# Listeners #
#############
return data["enable_create"]
def current_pool(self):
row = uiutil.get_list_selection(self.widget("pool-list"), None)
if not row:
return
connkey = row[0]
try:
return self.conn.get_pool(connkey)
except KeyError:
return None
def current_vol_row(self):
if not self.current_pool():
return
return uiutil.get_list_selection(self.widget("vol-list"), None)
def current_vol(self):
pool = self.current_pool()
row = self.current_vol_row()
if not pool or not row:
return
return pool.get_volume(row[0])
def refresh_storage_pool(self, src, connkey):
def _browse_clicked(self, src):
ignore = src
return self._browse_local()
pool_list = self.widget("pool-list")
host.refresh_pool_in_list(pool_list, self.conn, connkey)
curpool = self.current_pool()
if curpool.get_connkey() != connkey:
return
# Currently selected pool changed state: force a 'pool_selected' to
# update vol list
self.pool_selected(self.widget("pool-list").get_selection())
def repopulate_storage_pools(self, src=None, connkey=None):
def _volume_chosen(self, src, volume):
ignore = src
ignore = connkey
pool_list = self.widget("pool-list")
host.populate_storage_pools(pool_list, self.conn, self.current_pool())
self._finish(volume.get_target_path())
def delete_vol(self, src_ignore):
vol = self.current_vol()
if vol is None:
return
def _vol_sensitive_cb(self, fmt):
if ((self._browse_reason == self.config.CONFIG_DIR_FS)
and fmt != 'dir'):
return False
elif self._stable_defaults:
if fmt == "vmdk":
return False
return True
result = self.err.yes_no(_("Are you sure you want to permanently "
"delete the volume %s?") % vol.get_name())
if not result:
return
def cb():
vol.delete()
def idlecb():
self.refresh_current_pool()
self.populate_storage_volumes()
self.idle_add(idlecb)
####################
# Internal helpers #
####################
logging.debug("Deleting volume '%s'", vol.get_name())
vmmAsyncJob.simple_async_noshow(cb, [], self,
_("Error refreshing volume '%s'") % vol.get_name())
def _browse_local(self):
dialog_type = None
dialog_name = None
choose_button = None
def pool_refresh(self, src_ignore):
if self._in_refresh:
logging.debug("Already refreshing the pool, skipping")
return
data = self.config.browse_reason_data.get(self._browse_reason)
if data:
dialog_name = data["local_title"] or None
dialog_type = data.get("dialog_type")
choose_button = data.get("choose_button")
pool = self.current_pool()
if pool is None:
return
self._in_refresh = True
def cb():
try:
pool.refresh()
self.idle_add(self.refresh_current_pool)
finally:
self._in_refresh = False
logging.debug("Refresh pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(cb, [], self,
_("Error refreshing pool '%s'") % pool.get_name())
# Listeners
def pool_selected(self, src_ignore=None):
pool = self.current_pool()
can_new_vol = False
tt = ""
if pool:
pool.tick()
can_new_vol = (pool.is_active() and
pool.supports_volume_creation())
if not can_new_vol:
tt = _("Pool does not support volume creation")
self.widget("new-volume").set_sensitive(can_new_vol)
self.widget("new-volume").set_tooltip_text(tt)
self.populate_storage_volumes()
def vol_selected(self, ignore=None):
vol = self.current_vol_row()
canchoose = bool(vol and vol[5])
self.widget("choose-volume").set_sensitive(canchoose)
self.widget("vol-delete").set_sensitive(canchoose)
def refresh_current_pool(self, createvol=None):
cp = self.current_pool()
if cp is None:
return
cp.refresh()
self.refresh_storage_pool(None, cp.get_connkey())
name = createvol and createvol.vol.name or None
vol_list = self.widget("vol-list")
def select_volume(model, path, it, volume_name):
if model.get(it, 0)[0] == volume_name:
uiutil.set_list_selection(vol_list, path)
vol_list.get_model().foreach(select_volume, name)
def new_volume(self, src_ignore):
pool = self.current_pool()
if pool is None:
return
try:
if self.addvol is None:
self.addvol = vmmCreateVolume(self.conn, pool)
self.addvol.connect("vol-created", self.refresh_current_pool)
else:
self.addvol.set_parent_pool(self.conn, pool)
self.addvol.set_modal(True)
self.addvol.set_name_hint(self.vm_name)
self.addvol.show(self.topwin)
except Exception, e:
self.show_err(_("Error launching volume wizard: %s") % str(e))
def browse_local(self, src_ignore):
if not self.local_args.get("dialog_name"):
self.local_args["dialog_name"] = None
filename = self.err.browse_local(
self.conn, **self.local_args)
filename = self.err.browse_local(self.conn,
dialog_type=dialog_type, browse_reason=self._browse_reason,
dialog_name=dialog_name, choose_button=choose_button)
if filename:
self._do_finish(path=filename)
self._finish(filename)
def finish(self, ignore=None, ignore1=None, ignore2=None):
self._do_finish()
def _do_finish(self, path=None):
if not path:
path = self.current_vol().get_target_path()
def _finish(self, path):
if self._finish_cb:
self._finish_cb(self, path)
self.close()
# Do stuff!
def populate_storage_volumes(self):
list_widget = self.widget("vol-list")
pool = self.current_pool()
def sensitive_cb(fmt):
if ((self.browse_reason == self.config.CONFIG_DIR_FS)
and fmt != 'dir'):
return False
elif self.stable_defaults:
if fmt == "vmdk":
return False
return True
host.populate_storage_volumes(list_widget, pool, sensitive_cb)
def show_err(self, info, details=None):
self.err.show_err(info,
details=details,
modal=True)

712
virtManager/storagelist.py Normal file
View File

@@ -0,0 +1,712 @@
#
# Copyright (C) 2015 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
#
import logging
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GObject
from virtinst import StoragePool
from virtinst import VirtualDisk
from . import uiutil
from .asyncjob import vmmAsyncJob
from .baseclass import vmmGObjectUI
from .createpool import vmmCreatePool
from .createvol import vmmCreateVolume
EDIT_POOL_IDS = (
EDIT_POOL_NAME,
EDIT_POOL_AUTOSTART,
) = range(2)
VOL_NUM_COLUMNS = 6
(VOL_COLUMN_KEY,
VOL_COLUMN_NAME,
VOL_COLUMN_CAPACITY,
VOL_COLUMN_FORMAT,
VOL_COLUMN_INUSEBY,
VOL_COLUMN_SENSITIVE) = range(VOL_NUM_COLUMNS)
POOL_NUM_COLUMNS = 4
(POOL_COLUMN_CONNKEY,
POOL_COLUMN_LABEL,
POOL_COLUMN_ISACTIVE,
POOL_COLUMN_PERCENT) = range(POOL_NUM_COLUMNS)
ICON_RUNNING = "state_running"
ICON_SHUTOFF = "state_shutoff"
def _get_pool_size_percent(pool):
cap = pool.get_capacity()
alloc = pool.get_allocation()
if not cap or alloc is None:
per = 0
else:
per = int(((float(alloc) / float(cap)) * 100))
return "<span size='small' color='#484848'>%s%%</span>" % int(per)
class vmmStorageList(vmmGObjectUI):
__gsignals__ = {
"browse-clicked": (GObject.SignalFlags.RUN_FIRST, None, []),
"volume-chosen": (GObject.SignalFlags.RUN_FIRST, None, [object]),
"cancel-clicked": (GObject.SignalFlags.RUN_FIRST, None, []),
}
def __init__(self, conn, builder, topwin, vol_sensitive_cb=None):
vmmGObjectUI.__init__(self, "storagelist.ui",
None, builder=builder, topwin=topwin)
self.conn = conn
# Callback function for setting volume row sensitivity. Used
# by storage browser to disallow selecting certain volumes
self._vol_sensitive_cb = vol_sensitive_cb
# Name hint passed to addvol. Set by storagebrowser
self._name_hint = None
self._active_edits = []
self._addpool = None
self._addvol = None
self._in_refresh = False
self._volmenu = None
self.top_box = self.widget("storage-grid")
self.builder.connect_signals({
"on_pool_add_clicked" : self._pool_add,
"on_pool_stop_clicked": self._pool_stop,
"on_pool_start_clicked": self._pool_start,
"on_pool_delete_clicked": self._pool_delete,
"on_pool_refresh_clicked": self._pool_refresh,
"on_pool_apply_clicked": (lambda *x: self._pool_apply()),
"on_vol_delete_clicked": self._vol_delete,
"on_vol_list_button_press_event": self._vol_popup_menu,
"on_vol_list_changed": self._vol_selected,
"on_vol_add_clicked" : self._vol_add,
"on_browse_cancel_clicked" : self._cancel_clicked,
"on_browse_local_clicked" : self._browse_local_clicked,
"on_choose_volume_clicked" : self._choose_volume_clicked,
"on_vol_list_row_activated" : self._vol_list_row_activated,
"on_pool_name_changed": (lambda *x:
self._enable_pool_apply(x, EDIT_POOL_NAME)),
"on_pool_autostart_toggled": self._pool_autostart_changed,
})
self._init_ui()
def _cleanup(self):
try:
self.conn.disconnect_by_func(self._conn_pools_changed)
self.conn.disconnect_by_func(self._conn_pools_changed)
self.conn.disconnect_by_func(self._reselect_pool)
self.conn.disconnect_by_func(self._reselect_pool)
self.conn.disconnect_by_func(self._conn_state_changed)
except:
pass
self.conn = None
if self._addpool:
self._addpool.cleanup()
self._addpool = None
if self._addvol:
self._addvol.cleanup()
self._addvol = None
self._volmenu.destroy()
self._volmenu = None
def close(self, ignore1=None, ignore2=None):
if self._addvol:
self._addvol.close()
if self._addpool:
self._addpool.close()
if self._volmenu:
self._volmenu.hide()
##########################
# Initialization methods #
##########################
def _init_ui(self):
self.widget("storage-pages").set_show_tabs(False)
# These are enabled in storagebrowser.py
self.widget("browse-local").set_visible(False)
self.widget("browse-cancel").set_visible(False)
self.widget("choose-volume").set_visible(False)
# Volume list popup menu
self._volmenu = Gtk.Menu()
volCopyPath = Gtk.ImageMenuItem.new_with_label(_("Copy Volume Path"))
volCopyImage = Gtk.Image()
volCopyImage.set_from_stock(Gtk.STOCK_COPY, Gtk.IconSize.MENU)
volCopyPath.set_image(volCopyImage)
volCopyPath.show()
volCopyPath.connect("activate", self._vol_copy_path)
self._volmenu.add(volCopyPath)
# Volume list
# [key, name, capacity, format, in use by string, sensitive]
volListModel = Gtk.ListStore(str, str, str, str, str, bool)
self.widget("vol-list").set_model(volListModel)
volCol = Gtk.TreeViewColumn("Volumes")
vol_txt1 = Gtk.CellRendererText()
volCol.pack_start(vol_txt1, True)
volCol.add_attribute(vol_txt1, 'text', VOL_COLUMN_NAME)
volCol.add_attribute(vol_txt1, 'sensitive', VOL_COLUMN_SENSITIVE)
volCol.set_sort_column_id(VOL_COLUMN_NAME)
self.widget("vol-list").append_column(volCol)
volSizeCol = Gtk.TreeViewColumn("Size")
vol_txt2 = Gtk.CellRendererText()
volSizeCol.pack_start(vol_txt2, False)
volSizeCol.add_attribute(vol_txt2, 'text', VOL_COLUMN_CAPACITY)
volSizeCol.add_attribute(vol_txt2, 'sensitive', VOL_COLUMN_SENSITIVE)
volSizeCol.set_sort_column_id(VOL_COLUMN_CAPACITY)
self.widget("vol-list").append_column(volSizeCol)
volFormatCol = Gtk.TreeViewColumn("Format")
vol_txt3 = Gtk.CellRendererText()
volFormatCol.pack_start(vol_txt3, False)
volFormatCol.add_attribute(vol_txt3, 'text', VOL_COLUMN_FORMAT)
volFormatCol.add_attribute(vol_txt3, 'sensitive', VOL_COLUMN_SENSITIVE)
volFormatCol.set_sort_column_id(VOL_COLUMN_FORMAT)
self.widget("vol-list").append_column(volFormatCol)
volUseCol = Gtk.TreeViewColumn("Used By")
vol_txt4 = Gtk.CellRendererText()
volUseCol.pack_start(vol_txt4, False)
volUseCol.add_attribute(vol_txt4, 'text', VOL_COLUMN_INUSEBY)
volUseCol.add_attribute(vol_txt4, 'sensitive', VOL_COLUMN_SENSITIVE)
volUseCol.set_sort_column_id(VOL_COLUMN_INUSEBY)
self.widget("vol-list").append_column(volUseCol)
volListModel.set_sort_column_id(VOL_COLUMN_NAME,
Gtk.SortType.ASCENDING)
# Init pool list
# [connkey, label, pool.is_active(), percent string]
pool_list = self.widget("pool-list")
poolListModel = Gtk.ListStore(str, str, bool, str)
pool_list.set_model(poolListModel)
poolCol = Gtk.TreeViewColumn("Storage Pools")
pool_txt = Gtk.CellRendererText()
pool_per = Gtk.CellRendererText()
poolCol.pack_start(pool_per, False)
poolCol.pack_start(pool_txt, True)
poolCol.add_attribute(pool_txt, 'markup', POOL_COLUMN_LABEL)
poolCol.add_attribute(pool_txt, 'sensitive', POOL_COLUMN_ISACTIVE)
poolCol.add_attribute(pool_per, 'markup', POOL_COLUMN_PERCENT)
pool_list.append_column(poolCol)
poolListModel.set_sort_column_id(POOL_COLUMN_LABEL,
Gtk.SortType.ASCENDING)
pool_list.get_selection().connect("changed", self._pool_selected)
pool_list.get_selection().set_select_function(
(lambda *x: self._confirm_changes()), None)
# Populate list and connect conn signals
self._populate_pools()
self.conn.connect("pool-added", self._conn_pools_changed)
self.conn.connect("pool-removed", self._conn_pools_changed)
self.conn.connect("pool-started", self._reselect_pool)
self.conn.connect("pool-stopped", self._reselect_pool)
self.conn.connect("state-changed", self._conn_state_changed)
self._conn_state_changed()
###############
# Public APIs #
###############
def refresh_page(self):
self._populate_vols()
self.conn.schedule_priority_tick(pollpool=True)
def set_name_hint(self, val):
self._name_hint = val
####################
# Internal helpers #
####################
def _current_pool(self):
connkey = uiutil.get_list_selection(self.widget("pool-list"), 0)
try:
return connkey and self.conn.get_pool(connkey)
except KeyError:
return None
def _current_vol(self):
pool = self._current_pool()
if not pool:
return None
connkey = uiutil.get_list_selection(self.widget("vol-list"), 0)
try:
return connkey and pool.get_volume(connkey)
except KeyError:
return None
def _enable_pool_apply(self, *arglist):
edittype = arglist[-1]
self.widget("pool-apply").set_sensitive(True)
if edittype not in self._active_edits:
self._active_edits.append(edittype)
def _disable_pool_apply(self):
for i in EDIT_POOL_IDS:
if i in self._active_edits:
self._active_edits.remove(i)
self.widget("pool-apply").set_sensitive(False)
def _reselect_pool(self, src, connkey):
ignore = src
for row in self.widget("pool-list").get_model():
if row[POOL_COLUMN_CONNKEY] != connkey:
continue
# Update active sensitivity and percent available for passed key
pool = self.conn.get_pool(connkey)
row[POOL_COLUMN_ISACTIVE] = pool.is_active()
row[POOL_COLUMN_PERCENT] = _get_pool_size_percent(pool)
break
curpool = self._current_pool()
if curpool.get_connkey() != connkey:
return
# Currently selected pool changed state: force a 'pool_selected' to
# update vol list
self._pool_selected(self.widget("pool-list").get_selection())
def _reset_pool_state(self):
self.widget("pool-details").set_sensitive(False)
self.widget("pool-name").set_text("")
self.widget("pool-name-entry").set_text("")
self.widget("pool-sizes").set_markup("""<span size="large"> </span>""")
self.widget("pool-type").set_text("")
self.widget("pool-location").set_text("")
self.widget("pool-state-icon").set_from_icon_name(
ICON_SHUTOFF, Gtk.IconSize.BUTTON)
self.widget("pool-state").set_text(_("Inactive"))
self.widget("vol-list").get_model().clear()
self.widget("pool-autostart").set_label(_("Never"))
self.widget("pool-autostart").set_active(False)
self.widget("pool-delete").set_sensitive(False)
self.widget("pool-stop").set_sensitive(False)
self.widget("pool-start").set_sensitive(False)
self.widget("vol-add").set_sensitive(False)
self.widget("vol-delete").set_sensitive(False)
self.widget("vol-list").set_sensitive(False)
self._disable_pool_apply()
def _populate_pool_state(self, connkey):
pool = self.conn.get_pool(connkey)
pool.tick()
auto = pool.get_autostart()
active = pool.is_active()
# Set pool details state
self.widget("pool-details").set_sensitive(True)
self.widget("pool-name").set_markup("<b>%s:</b>" % pool.get_name())
self.widget("pool-name-entry").set_text(pool.get_name())
self.widget("pool-name-entry").set_editable(not active)
self.widget("pool-sizes").set_markup(
"""<span size="large">%s Free</span> / <i>%s In Use</i>""" %
(pool.get_pretty_available(), pool.get_pretty_allocation()))
self.widget("pool-type").set_text(
StoragePool.get_pool_type_desc(pool.get_type()))
self.widget("pool-location").set_text(
pool.get_target_path())
self.widget("pool-state-icon").set_from_icon_name(
((active and ICON_RUNNING) or ICON_SHUTOFF),
Gtk.IconSize.BUTTON)
self.widget("pool-state").set_text(
(active and _("Active")) or _("Inactive"))
self.widget("pool-autostart").set_label(
(auto and _("On Boot")) or _("Never"))
self.widget("pool-autostart").set_active(auto)
self.widget("vol-list").set_sensitive(active)
self._populate_vols()
self.widget("pool-delete").set_sensitive(not active)
self.widget("pool-stop").set_sensitive(active)
self.widget("pool-start").set_sensitive(not active)
self.widget("vol-add").set_sensitive(active)
self.widget("vol-add").set_tooltip_text(_("Create new volume"))
self.widget("vol-delete").set_sensitive(False)
if active and not pool.supports_volume_creation():
self.widget("vol-add").set_sensitive(False)
self.widget("vol-add").set_tooltip_text(
_("Pool does not support volume creation"))
def _set_storage_error_page(self, msg):
self._reset_pool_state()
self.widget("storage-pages").set_current_page(1)
self.widget("storage-error-label").set_text(msg)
def _populate_pools(self):
pool_list = self.widget("pool-list")
curpool = self._current_pool()
# Prevent events while the model is modified
model = pool_list.get_model()
pool_list.set_model(None)
pool_list.get_selection().unselect_all()
model.clear()
for pool in self.conn.list_pools():
name = pool.get_name()
typ = StoragePool.get_pool_type_desc(pool.get_type())
label = "%s\n<span size='small'>%s</span>" % (name, typ)
row = [None] * POOL_NUM_COLUMNS
row[POOL_COLUMN_CONNKEY] = pool.get_connkey()
row[POOL_COLUMN_LABEL] = label
row[POOL_COLUMN_ISACTIVE] = pool.is_active()
row[POOL_COLUMN_PERCENT] = _get_pool_size_percent(pool)
model.append(row)
pool_list.set_model(model)
uiutil.set_row_selection(pool_list,
curpool and curpool.get_connkey() or None)
def _populate_vols(self):
list_widget = self.widget("vol-list")
pool = self._current_pool()
vols = pool and pool.get_volumes() or {}
model = list_widget.get_model()
list_widget.get_selection().unselect_all()
model.clear()
for key in vols.keys():
vol = vols[key]
try:
path = vol.get_target_path()
name = vol.get_pretty_name(pool.get_type())
cap = vol.get_pretty_capacity()
fmt = vol.get_format() or ""
except:
logging.debug("Error getting volume info for '%s', "
"hiding it", key, exc_info=True)
continue
namestr = None
try:
if path:
names = VirtualDisk.path_in_use_by(vol.conn.get_backend(),
path)
namestr = ", ".join(names)
if not namestr:
namestr = None
except:
logging.exception("Failed to determine if storage volume in "
"use.")
sensitive = True
if self._vol_sensitive_cb:
sensitive = self._vol_sensitive_cb(fmt)
row = [None] * VOL_NUM_COLUMNS
row[VOL_COLUMN_KEY] = key
row[VOL_COLUMN_NAME] = name
row[VOL_COLUMN_CAPACITY] = cap
row[VOL_COLUMN_FORMAT] = fmt
row[VOL_COLUMN_INUSEBY] = namestr
row[VOL_COLUMN_SENSITIVE] = sensitive
model.append(row)
def _confirm_changes(self):
if not self._active_edits:
return True
if self.err.chkbox_helper(
self.config.get_confirm_unapplied,
self.config.set_confirm_unapplied,
text1=(_("There are unapplied changes. "
"Would you like to apply them now?")),
chktext=_("Don't warn me again."),
default=False):
if all([edit in EDIT_POOL_IDS for edit in self._active_edits]):
self._pool_apply()
self._active_edits = []
return True
#############
# Listeners #
#############
def _conn_state_changed(self, ignore=None):
conn_active = self.conn.is_active()
self.widget("pool-add").set_sensitive(conn_active and
self.conn.is_storage_capable())
if not conn_active:
self._set_storage_error_page(_("Connection not active."))
self._populate_pools()
return
if not self.conn.is_storage_capable():
self._set_storage_error_page(
_("Libvirt connection does not support storage management."))
def _cancel_clicked(self, src):
ignore = src
self.emit("cancel-clicked")
def _browse_local_clicked(self, src):
ignore = src
self.emit("browse-clicked")
def _choose_volume_clicked(self, src):
ignore = src
self.emit("volume-chosen", self._current_vol())
def _vol_list_row_activated(self, src, treeiter, viewcol):
ignore = src
ignore = treeiter
ignore = viewcol
self.emit("volume-chosen", self._current_vol())
def _pool_selected(self, src):
model, treeiter = src.get_selected()
if treeiter is None:
self._set_storage_error_page(_("No storage pool selected."))
return
self.widget("storage-pages").set_current_page(0)
connkey = model[treeiter][0]
try:
self._populate_pool_state(connkey)
except Exception, e:
logging.exception(e)
self._set_storage_error_page(_("Error selecting pool: %s") % e)
self._disable_pool_apply()
def _reselect_current_pool(self, ignore1=None):
cp = self._current_pool()
if cp is None:
return
cp.refresh()
self._reselect_pool(None, cp.get_connkey())
def _conn_pools_changed(self, src, connkey):
ignore = src
ignore = connkey
self._populate_pools()
def _pool_autostart_changed(self, src):
ignore = src
auto = self.widget("pool-autostart").get_active()
self.widget("pool-autostart").set_label(
auto and _("On Boot") or _("Never"))
self._enable_pool_apply(EDIT_POOL_AUTOSTART)
def _vol_selected(self, src):
model, treeiter = src.get_selected()
self.widget("vol-delete").set_sensitive(bool(treeiter))
can_choose = bool(treeiter and model[treeiter][VOL_COLUMN_SENSITIVE])
self.widget("choose-volume").set_sensitive(can_choose)
def _vol_popup_menu(self, widget_ignore, event):
if event.button != 3:
return
self._volmenu.popup(None, None, None, None, 0, event.time)
#########################
# Pool action listeners #
#########################
def _pool_stop(self, src_ignore):
pool = self._current_pool()
if pool is None:
return
logging.debug("Stopping pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(pool.stop, [], self,
_("Error stopping pool '%s'") % pool.get_name())
def _pool_start(self, src):
ignore = src
pool = self._current_pool()
if pool is None:
return
logging.debug("Starting pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(pool.start, [], self,
_("Error starting pool '%s'") % pool.get_name())
def _pool_add(self, src):
ignore = src
logging.debug("Launching 'Add Pool' wizard")
try:
if self._addpool is None:
self._addpool = vmmCreatePool(self.conn)
self._addpool.show(self.topwin)
except Exception, e:
self.err.show_err(_("Error launching pool wizard: %s") % str(e))
def _pool_delete(self, src):
ignore = src
pool = self._current_pool()
if pool is None:
return
result = self.err.yes_no(_("Are you sure you want to permanently "
"delete the pool %s?") % pool.get_name())
if not result:
return
logging.debug("Deleting pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(pool.delete, [], self,
_("Error deleting pool '%s'") % pool.get_name())
def _pool_refresh(self, src):
ignore = src
if self._in_refresh:
logging.debug("Already refreshing the pool, skipping")
return
if not self._confirm_changes():
return
pool = self._current_pool()
if pool is None:
return
self._in_refresh = True
def cb():
try:
pool.refresh()
self.idle_add(self._reselect_current_pool)
finally:
self._in_refresh = False
logging.debug("Refresh pool '%s'", pool.get_name())
vmmAsyncJob.simple_async_noshow(cb, [], self,
_("Error refreshing pool '%s'") % pool.get_name())
def _pool_apply(self):
pool = self._current_pool()
if pool is None:
return
logging.debug("Applying changes for pool '%s'", pool.get_name())
try:
if EDIT_POOL_AUTOSTART in self._active_edits:
auto = self.widget("pool-autostart").get_active()
pool.set_autostart(auto)
if EDIT_POOL_NAME in self._active_edits:
pool.define_name(self.widget("pool-name-entry").get_text())
self.idle_add(self._populate_pools)
except Exception, e:
self.err.show_err(_("Error changing pool settings: %s") % str(e))
return
self._disable_pool_apply()
###########################
# Volume action listeners #
###########################
def _vol_copy_path(self, src):
ignore = src
vol = self._current_vol()
if not vol:
return
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
target_path = vol.get_target_path()
if target_path:
clipboard.set_text(target_path, -1)
def _vol_add(self, src):
ignore = src
pool = self._current_pool()
if pool is None:
return
logging.debug("Launching 'Add Volume' wizard for pool '%s'",
pool.get_name())
try:
if self._addvol is None:
self._addvol = vmmCreateVolume(self.conn, pool)
self._addvol.connect("vol-created", self._reselect_current_pool)
else:
self._addvol.set_parent_pool(self.conn, pool)
self._addvol.set_modal(self.topwin.get_modal())
self._addvol.set_name_hint(self._name_hint)
self._addvol.show(self.topwin)
except Exception, e:
self.err.show_err(_("Error launching volume wizard: %s") % str(e))
def _vol_delete(self, src_ignore):
vol = self._current_vol()
if vol is None:
return
result = self.err.yes_no(_("Are you sure you want to permanently "
"delete the volume %s?") % vol.get_name())
if not result:
return
def cb():
vol.delete()
def idlecb():
self._reselect_current_pool()
self._populate_vols()
self.idle_add(idlecb)
logging.debug("Deleting volume '%s'", vol.get_name())
vmmAsyncJob.simple_async_noshow(cb, [], self,
_("Error refreshing volume '%s'") % vol.get_name())