connection: Allow setting a custom 'pretty name' (bz 784701)

We've had multiple requests over the years for something similar. People
might have to connect to multiple IP addresses, or really large hostnames,
that become difficult to distinguish in the UI.

Add a field in the host details page that allows setting a custom name,
and store it in gsettings.
This commit is contained in:
Cole Robinson 2015-04-11 13:39:25 -04:00
parent 4781ad6cd6
commit 3d2afbaf6f
6 changed files with 157 additions and 64 deletions

View File

@ -27,6 +27,17 @@
</key> </key>
</schema> </schema>
<!-- Relocatable per conn schema -->
<schema id="org.virt-manager.virt-manager.connection">
<key name="pretty-name" type="s">
<default>''</default>
<summary>Custom connection description</summary>
<description>Custom connection description, used in the manager window. If empty, the app generates a default on demand.</description>
</key>
</schema>
<schema id="org.virt-manager.virt-manager" <schema id="org.virt-manager.virt-manager"
path="/org/virt-manager/virt-manager/"> path="/org/virt-manager/virt-manager/">

View File

@ -130,14 +130,12 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="left_padding">20</property> <property name="left_padding">20</property>
<child> <child>
<object class="GtkTable" id="table1"> <object class="GtkGrid" id="table1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="border_width">3</property> <property name="border_width">3</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<property name="column_spacing">6</property>
<property name="row_spacing">3</property> <property name="row_spacing">3</property>
<property name="column_spacing">6</property>
<child> <child>
<object class="GtkLabel" id="label6"> <object class="GtkLabel" id="label6">
<property name="visible">True</property> <property name="visible">True</property>
@ -146,10 +144,8 @@
<property name="xalign">1</property> <property name="xalign">1</property>
</object> </object>
<packing> <packing>
<property name="top_attach">1</property> <property name="left_attach">0</property>
<property name="bottom_attach">2</property> <property name="top_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing> </packing>
</child> </child>
<child> <child>
@ -160,10 +156,8 @@
<property name="xalign">1</property> <property name="xalign">1</property>
</object> </object>
<packing> <packing>
<property name="top_attach">2</property> <property name="left_attach">0</property>
<property name="bottom_attach">3</property> <property name="top_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing> </packing>
</child> </child>
<child> <child>
@ -177,10 +171,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="top_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"/>
</packing> </packing>
</child> </child>
<child> <child>
@ -192,23 +183,20 @@
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="top_attach">3</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options"/>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="label71"> <object class="GtkLabel" id="label71">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Connection URI:</property> <property name="label" translatable="yes">Libvirt URI:</property>
<property name="lines">1</property> <property name="lines">1</property>
<property name="xalign">1</property> <property name="xalign">1</property>
</object> </object>
<packing> <packing>
<property name="x_options">GTK_FILL</property> <property name="left_attach">0</property>
<property name="y_options"/> <property name="top_attach">1</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -222,25 +210,7 @@
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="top_attach">1</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkLabel" id="label72">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">A_utoconnect:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">config-autoconnect</property>
<property name="xalign">1</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing> </packing>
</child> </child>
<child> <child>
@ -255,11 +225,48 @@
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="top_attach">4</property>
<property name="top_attach">3</property> </packing>
<property name="bottom_attach">4</property> </child>
<property name="x_options">GTK_FILL</property> <child>
<property name="y_options"/> <object class="GtkEntry" id="overview-name">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_width_chars">30</property>
<signal name="changed" handler="on_overview_name_changed" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Name:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">overview-name</property>
<property name="lines">1</property>
<property name="xalign">1</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label72">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">A_utoconnect:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">config-autoconnect</property>
<property name="xalign">1</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing> </packing>
</child> </child>
</object> </object>

View File

@ -50,15 +50,32 @@ class SettingsWrapper(object):
return settingskey, value return settingskey, value
def make_vm_settings(self, key): def make_vm_settings(self, key):
"""
Initialize per-VM relocatable schema if necessary
"""
settingskey = self._parse_key(key)[0] settingskey = self._parse_key(key)[0]
if settingskey in self._settingsmap: if settingskey in self._settingsmap:
return True return True
schema = self._root + ".vm" schema = self._root + ".vm"
path = "/" + self._root.replace(".", "/") + key.rsplit("/", 1)[0] + "/" path = "/" + self._root.replace(".", "/") + key.rsplit("/", 1)[0] + "/"
self._settingsmap[settingskey] = Gio.Settings.new_with_path(schema, self._settingsmap[settingskey] = Gio.Settings.new_with_path(
path) schema, path)
return True
def make_conn_settings(self, key):
"""
Initialize per-conn relocatable schema if necessary
"""
settingskey = self._parse_key(key)[0]
if settingskey in self._settingsmap:
return True
schema = self._root + ".connection"
path = "/" + self._root.replace(".", "/") + key.rsplit("/", 1)[0] + "/"
print schema, path
self._settingsmap[settingskey] = Gio.Settings.new_with_path(
schema, path)
return True return True
def _find_settings(self, key): def _find_settings(self, key):
@ -212,6 +229,11 @@ class vmmConfig(object):
def get_objects(self): def get_objects(self):
return self._objects[:] return self._objects[:]
#####################################
# Wrappers for setting per-VM value #
#####################################
def _make_pervm_key(self, uuid, key): def _make_pervm_key(self, uuid, key):
return "/vms/%s%s" % (uuid.replace("-", ""), key) return "/vms/%s%s" % (uuid.replace("-", ""), key)
@ -232,6 +254,30 @@ class vmmConfig(object):
return self.conf.get(key) return self.conf.get(key)
########################################
# Wrappers for setting per-conn values #
########################################
def _make_perconn_key(self, uri, key):
return "/conns/%s%s" % (uri.replace("/", ""), key)
def listen_perconn(self, uri, key, *args, **kwargs):
key = self._make_perconn_key(uri, key)
self.conf.make_conn_settings(key)
return self.conf.notify_add(key, *args, **kwargs)
def set_perconn(self, uri, key, *args, **kwargs):
key = self._make_perconn_key(uri, key)
self.conf.make_conn_settings(key)
ret = self.conf.set(key, *args, **kwargs)
return ret
def get_perconn(self, uri, key):
key = self._make_perconn_key(uri, key)
self.conf.make_conn_settings(key)
return self.conf.get(key)
################### ###################
# General helpers # # General helpers #
################### ###################

View File

@ -191,6 +191,10 @@ class vmmConnection(vmmGObject):
self.record = [] self.record = []
self.hostinfo = None self.hostinfo = None
self.add_gsettings_handle(
self._on_config_pretty_name_changed(
self._config_pretty_name_changed_cb))
self._init_virtconn() self._init_virtconn()
@ -368,6 +372,8 @@ class vmmConnection(vmmGObject):
Return a pretty label for use in the manager view, and various Return a pretty label for use in the manager view, and various
connection lists. connection lists.
""" """
if self._get_config_pretty_name():
return self._get_config_pretty_name()
if self._backend.fake_name(): if self._backend.fake_name():
return self._backend.fake_name() return self._backend.fake_name()
@ -811,11 +817,6 @@ class vmmConnection(vmmGObject):
# Connection closing/opening methods # # Connection closing/opening methods #
###################################### ######################################
def get_autoconnect(self):
return self.config.get_conn_autoconnect(self.get_uri())
def set_autoconnect(self, val):
self.config.set_conn_autoconnect(self.get_uri(), val)
def _schedule_close(self): def _schedule_close(self):
self._closing = True self._closing = True
self.idle_add(self.close) self.idle_add(self.close)
@ -1351,3 +1352,23 @@ class vmmConnection(vmmGObject):
return self.disk_read_rate() + self.disk_write_rate() return self.disk_read_rate() + self.disk_write_rate()
def disk_io_max_rate(self): def disk_io_max_rate(self):
return self._get_record_helper("diskMaxRate") return self._get_record_helper("diskMaxRate")
###########################
# Per-conn config helpers #
###########################
def get_autoconnect(self):
return self.config.get_conn_autoconnect(self.get_uri())
def set_autoconnect(self, val):
self.config.set_conn_autoconnect(self.get_uri(), val)
def set_config_pretty_name(self, value):
self.config.set_perconn(self.get_uri(), "/pretty-name", value)
def _get_config_pretty_name(self):
return self.config.get_perconn(self.get_uri(), "/pretty-name")
def _on_config_pretty_name_changed(self, *args, **kwargs):
return self.config.listen_perconn(self.get_uri(), "/pretty-name",
*args, **kwargs)
def _config_pretty_name_changed_cb(self):
self.emit("state-changed")

View File

@ -60,9 +60,7 @@ class vmmHost(vmmGObjectUI):
vmmGObjectUI.__init__(self, "host.ui", "vmm-host") vmmGObjectUI.__init__(self, "host.ui", "vmm-host")
self.conn = conn self.conn = conn
self.title = conn.get_pretty_desc() + " " + self.topwin.get_title() self._orig_title = self.topwin.get_title()
self.topwin.set_title(self.title)
self.ICON_RUNNING = "state_running" self.ICON_RUNNING = "state_running"
self.ICON_SHUTOFF = "state_shutoff" self.ICON_SHUTOFF = "state_shutoff"
@ -107,6 +105,7 @@ class vmmHost(vmmGObjectUI):
"on_interface_apply_clicked" : (lambda *x: self.interface_apply()), "on_interface_apply_clicked" : (lambda *x: self.interface_apply()),
"on_interface_list_changed": self.interface_selected, "on_interface_list_changed": self.interface_selected,
"on_overview_name_changed": self._overview_name_changed,
"on_config_autoconnect_toggled": self.toggle_autoconnect, "on_config_autoconnect_toggled": self.toggle_autoconnect,
"on_qos_inbound_average_changed": (lambda *x: "on_qos_inbound_average_changed": (lambda *x:
@ -325,6 +324,12 @@ class vmmHost(vmmGObjectUI):
def conn_state_changed(self, ignore1=None): def conn_state_changed(self, ignore1=None):
conn_active = self.conn.is_active() conn_active = self.conn.is_active()
self.topwin.set_title(
self.conn.get_pretty_desc() + " " + self._orig_title)
if not self.widget("overview-name").has_focus():
self.widget("overview-name").set_text(self.conn.get_pretty_desc())
self.widget("menu_file_restore_saved").set_sensitive(conn_active) self.widget("menu_file_restore_saved").set_sensitive(conn_active)
self.widget("net-add").set_sensitive(conn_active and self.widget("net-add").set_sensitive(conn_active and
self.conn.is_network_capable()) self.conn.is_network_capable())
@ -356,13 +361,17 @@ class vmmHost(vmmGObjectUI):
if self.addnet: if self.addnet:
self.addnet.close() self.addnet.close()
def _overview_name_changed(self, src):
src = self.widget("overview-name")
self.conn.set_config_pretty_name(src.get_text())
def toggle_autoconnect(self, src): def toggle_autoconnect(self, src):
self.conn.set_autoconnect(src.get_active()) self.conn.set_autoconnect(src.get_active())
# ------------------------- #############################
# Virtual Network functions # Virtual Network functions #
# ------------------------- #############################
def delete_network(self, src_ignore): def delete_network(self, src_ignore):
net = self.current_network() net = self.current_network()

View File

@ -804,10 +804,9 @@ class vmmManager(vmmGObjectUI):
it = model.iter_next(it) it = model.iter_next(it)
def conn_state_changed(self, conn, newname=None): def conn_state_changed(self, conn):
row = self.rows[conn.get_uri()] row = self.rows[conn.get_uri()]
if newname: row[ROW_SORT_KEY] = conn.get_pretty_desc()
row[ROW_SORT_KEY] = newname
row[ROW_MARKUP] = self._build_conn_markup(conn, row[ROW_SORT_KEY]) row[ROW_MARKUP] = self._build_conn_markup(conn, row[ROW_SORT_KEY])
row[ROW_IS_CONN_CONNECTED] = not conn.is_disconnected() row[ROW_IS_CONN_CONNECTED] = not conn.is_disconnected()
row[ROW_COLOR] = self._build_conn_color(conn) row[ROW_COLOR] = self._build_conn_color(conn)