Skip to content

Commit

Permalink
Added commands to restart running containers and to open TTY (#48)
Browse files Browse the repository at this point in the history
* handled GObject subclasses registration to ensure compatibility with different versions of Gnome Shell

* added restart button to the menu

* added function to open an interactive shell

This required the introduction of a new method within the Docker module and consequently a new action for DockerMenuItem.
Consider refactoring to handle all the logic to produce the final Docker command inside the Docker module.

* minor changes to error messages, deleted typos

* applied refactoring to move Docker logic in its own module

* replaced var with const for utility functions declarations

* general refactoring, moved gnome-shell version check into utils module

* added EditorConfig file, adjusted files indentation to be consistent with the configuration

* handled errors when running interactive commands

In case of failure launching an interactive command, a second shell is opened within the terminal emulator to let the user acknowledge the error.

* added fallback to /bin/sh when bash isn't available on the container

* minor refactoring

* applied refactoring to the functions managing Docker commands

* refactored code to replace enum commands with actions; switch statements to obtain labels and commands

* replaced actions enum with a dictionary containing infos about actions

This allows to avoid the switch method for the action label and to move the isInteractive information from the command to the real action
  • Loading branch information
alessandrodolci authored May 10, 2020
1 parent 06e15b5 commit e26d15c
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 102 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions docker.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

const Main = imports.ui.main;
Expand Down
37 changes: 19 additions & 18 deletions metadata.json
Original file line number Diff line number Diff line change
@@ -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"
}
"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"
}
121 changes: 105 additions & 16 deletions src/docker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.<String, {label: String, isInteractive: Boolean}>}}
*/
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");
}
};

/**
Expand Down Expand Up @@ -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
Expand Down
101 changes: 55 additions & 46 deletions src/dockerMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
29 changes: 20 additions & 9 deletions src/dockerMenuItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
Loading

0 comments on commit e26d15c

Please # to comment.