diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c8b9b37
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+# top-most EditorConfig file
+root = true
+
+[**]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
diff --git a/docker.svg b/docker.svg
index 4fd3e74..64d1e28 100644
--- a/docker.svg
+++ b/docker.svg
@@ -1,6 +1,6 @@
diff --git a/extension.js b/extension.js
index ad18e58..8688f8c 100644
--- a/extension.js
+++ b/extension.js
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
'use strict';
const Main = imports.ui.main;
diff --git a/metadata.json b/metadata.json
index 4b3a0d1..ce55b1b 100644
--- a/metadata.json
+++ b/metadata.json
@@ -1,19 +1,20 @@
{
- "uuid": "docker_status@gpouilloux",
- "shell-version": [
- "3.14",
- "3.16",
- "3.18",
- "3.20",
- "3.21",
- "3.22",
- "3.24",
- "3.26",
- "3.28",
- "3.30",
- "3.32"
- ],
- "description": "A status menu for managing docker containers.",
- "name": "Docker Integration",
- "url": "https://github.com/gpouilloux/gnome-shell-extension-docker"
-}
\ No newline at end of file
+ "uuid": "docker_status@gpouilloux",
+ "shell-version": [
+ "3.14",
+ "3.16",
+ "3.18",
+ "3.20",
+ "3.21",
+ "3.22",
+ "3.24",
+ "3.26",
+ "3.28",
+ "3.30",
+ "3.32",
+ "3.34"
+ ],
+ "description": "A status menu for managing docker containers.",
+ "name": "Docker Integration",
+ "url": "https://github.com/gpouilloux/gnome-shell-extension-docker"
+}
diff --git a/src/docker.js b/src/docker.js
index 015402f..f3a2a58 100644
--- a/src/docker.js
+++ b/src/docker.js
@@ -21,12 +21,70 @@
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
-var dockerCommandsToLabels = {
- start: 'Start',
- stop: 'Stop',
- pause: 'Pause',
- unpause: 'Unpause',
- rm: 'Remove'
+/**
+ * Dictionary for Docker actions
+ * @readonly
+ * @type {{Object.}}
+ */
+var DockerActions = Object.freeze({
+ START: {
+ label: "Start",
+ isInteractive: false
+ },
+ REMOVE: {
+ label: "Remove",
+ isInteractive: false
+ },
+ OPEN_SHELL: {
+ label: "Open shell",
+ isInteractive: true
+ },
+ RESTART: {
+ label: "Restart",
+ isInteractive: false
+ },
+ PAUSE: {
+ label: "Pause",
+ isInteractive: false
+ },
+ STOP: {
+ label: "Stop",
+ isInteractive: false
+ },
+ UNPAUSE: {
+ label: "Unpause",
+ isInteractive: false
+ },
+});
+
+/**
+ * Return the command associated to the given Docker action
+ * @param {DockerActions} dockerAction The Docker action
+ * @param {String} containerName The name of the container on which to run the command
+ * @returns {String} The complete Docker command to run
+ * @throws {Error}
+ */
+
+const getDockerActionCommand = (dockerAction, containerName) => {
+ switch (dockerAction) {
+ case DockerActions.START:
+ return "docker start " + containerName;
+ case DockerActions.REMOVE:
+ return "docker rm " + containerName;
+ case DockerActions.OPEN_SHELL:
+ return "docker exec -it " + containerName + " /bin/bash; "
+ + "if [ $? -ne 0 ]; then docker exec -it " + containerName + " /bin/sh; fi;";
+ case DockerActions.RESTART:
+ return "docker restart " + containerName;
+ case DockerActions.PAUSE:
+ return "docker pause " + containerName;
+ case DockerActions.STOP:
+ return "docker stop " + containerName;
+ case DockerActions.UNPAUSE:
+ return "docker unpause " + containerName;
+ default:
+ throw new Error("Docker action not valid");
+ }
};
/**
@@ -82,18 +140,49 @@ var getContainers = () => {
};
/**
- * Run a docker command
- * @param {String} command The command to run
- * @param {String} containerName The container
+ * Run the specified command in the background
+ * @param {String} dockerCommand The Docker command to run
* @param {Function} callback A callback that takes the status, command, and stdErr
*/
-var runCommand = (command, containerName, callback) => {
- const cmd = "docker " + command + " " + containerName;
- async(() => {
- const res = GLib.spawn_command_line_async(cmd);
- return res;
- }, (res) => callback(res));
-}
+const runBackgroundCommand = (dockerCommand, callback) => {
+ async(
+ () => GLib.spawn_command_line_async(dockerCommand),
+ (res) => callback(res)
+ );
+};
+
+/**
+ * Spawn a new terminal emulator and run the specified command within it
+ * @param {String} dockerCommand The Docker command to run
+ * @param {Function} callback A callback that takes the status, command, and stdErr
+ */
+const runInteractiveCommand = (dockerCommand, callback) => {
+ const defaultShell = GLib.getenv("SHELL");
+
+ const terminalCommand = "gnome-terminal -- "
+ + defaultShell + " -c '"
+ + dockerCommand
+ + "if [ $? -ne 0 ]; then " + defaultShell + "; fi'";
+
+ async(
+ () => GLib.spawn_command_line_async(terminalCommand),
+ (res) => callback(res)
+ );
+};
+
+/**
+ * Run a Docker action
+ * @param {String} dockerAction The action to run
+ * @param {String} containerName The container
+ * @param {Function} callback A callback that takes the status, action, and stdErr
+ */
+var runAction = (dockerAction, containerName, callback) => {
+ const dockerCommand = getDockerActionCommand(dockerAction, containerName);
+
+ dockerAction.isInteractive ?
+ runInteractiveCommand(dockerCommand, callback)
+ : runBackgroundCommand(dockerCommand, callback);
+};
/**
* Run a function in asynchronous mode using GLib
diff --git a/src/dockerMenu.js b/src/dockerMenu.js
index 6265cc7..7b20f38 100644
--- a/src/dockerMenu.js
+++ b/src/dockerMenu.js
@@ -27,73 +27,82 @@ const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Docker = Me.imports.src.docker;
const DockerSubMenuMenuItem = Me.imports.src.dockerSubMenuMenuItem;
+const Utils = Me.imports.src.utils;
// Docker icon on status menu
-var DockerMenu = class DockerMenu_DockerMenu extends PanelMenu.Button {
+
+var DockerMenu = class DockerMenu extends PanelMenu.Button {
// Init the docker menu
_init() {
- super._init(0.0, _("Docker containers"));
+ super._init(0.0, _("Docker containers"));
- const hbox = new St.BoxLayout({ style_class: "panel-status-menu-box" });
- const gicon = Gio.icon_new_for_string(Me.path + "/docker.svg");
- const dockerIcon = new St.Icon({ gicon: gicon, icon_size: "24" });
+ const hbox = new St.BoxLayout({ style_class: "panel-status-menu-box" });
+ const gicon = Gio.icon_new_for_string(Me.path + "/docker.svg");
+ const dockerIcon = new St.Icon({ gicon: gicon, icon_size: "24" });
- hbox.add_child(dockerIcon);
- this.actor.add_child(hbox);
- this.actor.connect("button_press_event", this._refreshMenu.bind(this));
+ hbox.add_child(dockerIcon);
+ this.actor.add_child(hbox);
+ this.actor.connect("button_press_event", this._refreshMenu.bind(this));
- this._renderMenu();
+ this._renderMenu();
}
// Refresh the menu everytime the user click on it
// It allows to have up-to-date information on docker containers
_refreshMenu() {
- if (this.menu.isOpen) {
- this.menu.removeAll();
- this._renderMenu();
- }
+ if (this.menu.isOpen) {
+ this.menu.removeAll();
+ this._renderMenu();
+ }
}
// Show docker menu icon only if installed and append docker containers
_renderMenu() {
- if (Docker.isDockerInstalled()) {
- if (Docker.isDockerRunning()) {
- this._feedMenu();
+ if (Docker.isDockerInstalled()) {
+ if (Docker.isDockerRunning()) {
+ this._feedMenu();
+ } else {
+ let errMsg = _("Docker daemon not started");
+ this.menu.addMenuItem(new PopupMenu.PopupMenuItem(errMsg));
+ log(errMsg);
+ }
} else {
- let errMsg = _("Docker daemon not started");
- this.menu.addMenuItem(new PopupMenu.PopupMenuItem(errMsg));
- log(errMsg);
+ let errMsg = _("Docker binary not found in PATH ");
+ this.menu.addMenuItem(new PopupMenu.PopupMenuItem(errMsg));
+ log(errMsg);
}
- } else {
- let errMsg = _("Docker binary not found in PATH ");
- this.menu.addMenuItem(new PopupMenu.PopupMenuItem(errMsg));
- log(errMsg);
- }
- this.actor.show();
+ this.actor.show();
}
// Append containers to menu
_feedMenu() {
- try {
- const containers = Docker.getContainers();
- if (containers.length > 0) {
- containers.forEach(container => {
- const subMenu = new DockerSubMenuMenuItem.DockerSubMenuMenuItem(
- container.name,
- container.status
- );
- this.menu.addMenuItem(subMenu);
- });
- } else {
- this.menu.addMenuItem(
- new PopupMenu.PopupMenuItem("No containers detected")
- );
+ try {
+ const containers = Docker.getContainers();
+ if (containers.length > 0) {
+ containers.forEach(container => {
+ const subMenu = new DockerSubMenuMenuItem.DockerSubMenuMenuItem(
+ container.name,
+ container.status
+ );
+ this.menu.addMenuItem(subMenu);
+ });
+ } else {
+ this.menu.addMenuItem(
+ new PopupMenu.PopupMenuItem("No containers detected")
+ );
+ }
+ } catch (err) {
+ const errMsg = "Error occurred when fetching containers";
+ this.menu.addMenuItem(new PopupMenu.PopupMenuItem(errMsg));
+ log(errMsg);
+ log(err);
}
- } catch (err) {
- const errMsg = "Error occurred when fetching containers";
- this.menu.addMenuItem(new PopupMenu.PopupMenuItem(errMsg));
- log(errMsg);
- log(err);
- }
}
- }
+};
+
+if (!Utils.isGnomeShellVersionLegacy()) {
+ DockerMenu = GObject.registerClass(
+ { GTypeName: 'DockerMenu' },
+ DockerMenu
+ );
+}
diff --git a/src/dockerMenuItem.js b/src/dockerMenuItem.js
index e9482db..0122816 100644
--- a/src/dockerMenuItem.js
+++ b/src/dockerMenuItem.js
@@ -18,36 +18,47 @@
'use strict';
+const GObject = imports.gi.GObject;
const PopupMenu = imports.ui.popupMenu;
const Main = imports.ui.main;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Docker = Me.imports.src.docker;
+const Utils = Me.imports.src.utils;
// Docker actions for each container
-var DockerMenuItem = class DockerMenu_DockerMenuItem extends PopupMenu.PopupMenuItem {
-
- constructor(containerName, dockerCommand) {
- super(Docker.dockerCommandsToLabels[dockerCommand]);
+var DockerMenuItem = class DockerMenuItem extends PopupMenu.PopupMenuItem {
+ _init(containerName, dockerAction) {
+ super._init(dockerAction.label);
+
this.containerName = containerName;
- this.dockerCommand = dockerCommand;
+ this.dockerAction = dockerAction;
this.connect('activate', this._dockerAction.bind(this));
}
_dockerAction() {
- Docker.runCommand(this.dockerCommand, this.containerName, (res) => {
+ Docker.runAction(this.dockerAction, this.containerName, (res) => {
if (!!res) {
- log("`" + this.dockerCommand + "` terminated successfully");
+ log("Docker: `" + this.dockerAction.label + "` action terminated successfully");
} else {
let errMsg = _(
"Docker: Failed to '" +
- this.dockerCommand + "' container '" + this.containerName + "'"
+ this.dockerAction + "' container '" + this.containerName + "'"
);
Main.notify(errMsg);
log(errMsg);
}
});
}
-};
+}
+
+
+
+if (!Utils.isGnomeShellVersionLegacy()) {
+ DockerMenuItem = GObject.registerClass(
+ { GTypeName: 'DockerMenuItem' },
+ DockerMenuItem
+ );
+}
diff --git a/src/dockerSubMenuMenuItem.js b/src/dockerSubMenuMenuItem.js
index 1138233..e06c19a 100644
--- a/src/dockerSubMenuMenuItem.js
+++ b/src/dockerSubMenuMenuItem.js
@@ -19,11 +19,14 @@
'use strict';
const St = imports.gi.St;
+const GObject = imports.gi.GObject;
const Lang = imports.lang;
const PopupMenu = imports.ui.popupMenu;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
+const DockerActions = Me.imports.src.docker.DockerActions;
const DockerMenuItem = Me.imports.src.dockerMenuItem;
+const Utils = Me.imports.src.utils;
/**
* Create a St.Icon
@@ -50,25 +53,27 @@ const getStatus = (statusMessage) => {
}
// Menu entry representing a docker container
-var DockerSubMenuMenuItem = class DockerMenu_DockerSubMenuMenuItem extends PopupMenu.PopupSubMenuMenuItem {
+var DockerSubMenuMenuItem = class DockerSubMenuMenuItem extends PopupMenu.PopupSubMenuMenuItem {
- constructor(containerName, containerStatusMessage) {
- super(containerName);
+ _init(containerName, containerStatusMessage) {
+ super._init(containerName);
switch (getStatus(containerStatusMessage)) {
case "stopped":
this.actor.insert_child_at_index(createIcon('process-stop-symbolic', 'status-stopped'), 1);
- this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, "start"));
- this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, "rm"));
+ this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, DockerActions.START));
+ this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, DockerActions.REMOVE));
break;
case "running":
this.actor.insert_child_at_index(createIcon('system-run-symbolic', 'status-running'), 1);
- this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, "pause"));
- this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, "stop"));
+ this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, DockerActions.OPEN_SHELL));
+ this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, DockerActions.RESTART));
+ this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, DockerActions.PAUSE));
+ this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, DockerActions.STOP));
break;
case "paused":
this.actor.insert_child_at_index(createIcon('media-playback-pause-symbolic', 'status-paused'), 1);
- this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, "unpause"));
+ this.menu.addMenuItem(new DockerMenuItem.DockerMenuItem(containerName, DockerActions.UNPAUSE));
break;
default:
this.actor.insert_child_at_index(createIcon('action-unavailable-symbolic', 'status-undefined'), 1);
@@ -76,3 +81,10 @@ var DockerSubMenuMenuItem = class DockerMenu_DockerSubMenuMenuItem extends Popup
}
}
};
+
+if (!Utils.isGnomeShellVersionLegacy()) {
+ DockerSubMenuMenuItem = GObject.registerClass(
+ { GTypeName: 'DockerSubMenuMenuItem' },
+ DockerSubMenuMenuItem
+ );
+}
diff --git a/src/utils.js b/src/utils.js
new file mode 100644
index 0000000..92fde47
--- /dev/null
+++ b/src/utils.js
@@ -0,0 +1,29 @@
+/*
+ * Gnome3 Docker Menu Extension
+ * Copyright (C) 2017 Guillaume Pouilloux
+ *
+ * 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 3 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, see .
+ */
+
+'use strict';
+
+const Config = imports.misc.config;
+
+var isGnomeShellVersionLegacy = () => {
+ const gnomeShellMajor = parseInt(Config.PACKAGE_VERSION.split('.')[0]);
+ const gnomeShellMinor = parseInt(Config.PACKAGE_VERSION.split('.')[1]);
+
+ return gnomeShellMajor < 3 ||
+ (gnomeShellMajor === 3 && gnomeShellMinor < 30);
+};