diff --git a/res/stevedore.glade b/res/stevedore.glade index c5d33b1..7988818 100644 --- a/res/stevedore.glade +++ b/res/stevedore.glade @@ -2,6 +2,54 @@ + + + + Start + Start + Start a selected stopped container. + gtk-media-play + True + + + + + + Stop + Stop + gtk-media-stop + True + + + + + + Attach + Attach + gtk-jump-to + True + + + + + + Log + Log + gtk-file + True + + + + + + Delete + Delete + Delete the selected container. + gtk-delete + True + + + @@ -12,6 +60,8 @@ + + @@ -29,6 +79,35 @@ False gtk-jump-to + + + + Build + Build + gtk-properties + True + + + + + + Run + Run + gtk-execute + True + + + + + + Remove + Remove + gtk-delete + True + + + + @@ -48,20 +127,6 @@ False gtk-media-play - - Attach - Attach - gtk-jump-to - True - - - - Build - Build - gtk-properties - True - - Connect Connect @@ -69,13 +134,6 @@ True - - Log - Log - gtk-file - True - - Preferences Preferences @@ -91,35 +149,6 @@ True - - Remove - Remove - gtk-delete - True - - - - Run - Run - gtk-execute - True - - - - Start - Start - Start a selected stopped container. - gtk-media-play - True - - - - Stop - Stop - gtk-media-stop - True - - False Stevedore @@ -285,6 +314,35 @@ 3 + + + True + False + + + False + True + 4 + + + + + gtk-delete + True + delete_container_action + True + True + True + True + 0.56999999284744263 + True + + + False + True + 5 + + False @@ -306,8 +364,11 @@ 0 False both + - + + + @@ -414,23 +475,6 @@ 0 - - - gtk-delete - True - remove_image_action - True - True - True - True - True - - - False - True - 1 - - gtk-execute @@ -442,12 +486,40 @@ True True + + False + True + 1 + + + + + True + False + False True 2 + + + gtk-delete + True + remove_image_action + True + True + True + True + True + + + False + True + 3 + + False diff --git a/res/ui_builder.xml b/res/ui_builder.xml new file mode 100644 index 0000000..7758c94 --- /dev/null +++ b/res/ui_builder.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/docker_iface.py b/src/docker_iface.py index 0860097..936322b 100644 --- a/src/docker_iface.py +++ b/src/docker_iface.py @@ -60,7 +60,7 @@ class DockerContainer(object): self.command = command self.created = created self.names = self.filter_names(names) - self.status = "Unknown" + self.status = DockerContainer.UNKNOWN self.time_elapsed = 0 self.ports = [] @@ -200,7 +200,7 @@ class DockerInterface(object): """ Ask our docker server the containers created and their status """ - self.containers = [] + self.containers = dict() if self.client is not None: containers = self.client.containers(all = True) for container in containers: @@ -211,11 +211,21 @@ class DockerInterface(object): names = container['Names']) container_object.status_from_string(container['Status']) - self.containers.append(container_object) + self.containers[container['Id']] = container_object else: raise DockerNotConnectedException() + + def get_container_by_id(self, container_id): + """ + Returns the container that matched container_id + """ + if self.client is None: + raise DockerNotConnectedException() + + return self.containers.get(container_id, None) + def build(self, path, tag): """ Creates a new docker image from a Dockerfile in the specified path diff --git a/src/stevedore.py b/src/stevedore.py index e2a824a..cd7c0fa 100644 --- a/src/stevedore.py +++ b/src/stevedore.py @@ -18,7 +18,7 @@ # Generated with glade2py script # glade2py script can be found at hocr web site http://hocr.berlios.de -from gi.repository import Gtk, Gio +from gi.repository import Gtk, Gdk, Gio from pkg_resources import resource_string from docker_iface import DockerInterface, DockerImage, DockerContainer, DockerNotConnectedException @@ -35,19 +35,31 @@ class MainWindow(Gtk.Application): # create widget tree ... self.builder = Gtk.Builder() + self.ui_manager = Gtk.UIManager() try: self.builder.add_from_file(resource_string(__name__, 'stevedore.glade')) + self.ui_manager.add_ui_from_file(resource_string(__name__, 'ui_builder.xml')) except: self.builder.add_from_file('../res/stevedore.glade') + self.ui_manager.add_ui_from_file('../res/ui_builder.xml') # connect handlers self.builder.connect_signals(self) + self.ui_manager.insert_action_group(self.builder.get_object('ContainerActionGroup')) + self.ui_manager.insert_action_group(self.builder.get_object('ImagesActionGroup')) + + # Prepare certain widgets + self.status_bar = self.builder.get_object('AppStatus') + self.status_context_id = self.status_bar.get_context_id('main_app') + self.builder.get_object('StartButton').set_sensitive(False) + self.builder.get_object('StopButton').set_sensitive(False) + self.builder.get_object('AttachButton').set_sensitive(False) + self.builder.get_object('LogButton').set_sensitive(False) + self.builder.get_object('DeleteContainerButton').set_sensitive(False) # Add window to the App and show it self.window = self.builder.get_object('MainWindow') - self.status_bar = self.builder.get_object('AppStatus') - self.status_context_id = self.status_bar.get_context_id('main_app') self.add_window(self.window) self.window.show() self.set_app_status(u'Not connected to Docker server.') @@ -74,12 +86,14 @@ class MainWindow(Gtk.Application): image_list.clear() # And now we populate them back - for container in self.docker.containers: + for container_index in self.docker.containers: + container = self.docker.get_container_by_id(container_index) status = container.return_status_string() container_list.append(row = [container.names[0], unicode(container.image), unicode(container.container_id)[:12], - status]) + status, + unicode(container.container_id)]) for image in self.docker.images: size_text = self.size_to_human(image.size) @@ -214,6 +228,80 @@ class MainWindow(Gtk.Application): """ print "Not implemented: on_runimage_action_activate" + def on_ContainerListView_button_press_event(self, obj, event = None): + """ + This hook checks the mouse interaction with the ContainerListView + """ + if event is not None: + if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3: + container_view = self.builder.get_object('ContainerListView') + selection = container_view.get_selection() + model, selected = selection.get_selected() + # selection is a treeiter + if selected is not None: + selected_container_id = model[selected][4] + container = self.docker.get_container_by_id(selected_container_id) + + if container is not None: + if container.status == DockerContainer.EXITED: + popup = self.ui_manager.get_widget('/ContainerStoppedPopup') + + elif container.status == DockerContainer.UP: + popup = self.ui_manager.get_widget('/ContainerStartedPopup') + + else: + popup = self.ui_manager.get_widget('/ContainerStoppedPopup') + + popup.popup(None, None, None, None, event.button, event.time) + return True + + def on_container_view_selection_changed(self, selection): + """ + We control which element is selected on the ContainerListView to enable and disable + certain button actions + """ + start_button = self.builder.get_object('StartButton') + stop_button = self.builder.get_object('StopButton') + attach_button = self.builder.get_object('AttachButton') + log_button = self.builder.get_object('LogButton') + delete_container_button = self.builder.get_object('DeleteContainerButton') + + model, treeiter = selection.get_selected() + if treeiter is not None: + container_id = model[treeiter][4] + container = self.docker.get_container_by_id(container_id) + + if container.status == DockerContainer.UP: + start_button.set_sensitive(False) + stop_button.set_sensitive(True) + attach_button.set_sensitive(True) + log_button.set_sensitive(True) + delete_container_button.set_sensitive(False) + + elif container.status == DockerContainer.EXITED: + start_button.set_sensitive(True) + stop_button.set_sensitive(False) + attach_button.set_sensitive(False) + log_button.set_sensitive(True) + delete_container_button.set_sensitive(True) + + else: + start_button.set_sensitive(False) + stop_button.set_sensitive(False) + attach_button.set_sensitive(False) + log_button.set_sensitive(True) + delete_container_button.set_sensitive(True) + + else: + start_button.set_sensitive(False) + stop_button.set_sensitive(False) + attach_button.set_sensitive(False) + log_button.set_sensitive(False) + delete_container_button.set_sensitive(False) + + + + # run main loop def main():