├── .gitignore
├── README.md
├── generate-images.sh
├── gruntfile.js
├── images
├── DockerMenu.icns
├── DockerMenu.png
├── boxGreen.png
├── boxGreen@2x.png
├── boxRed.png
├── boxRed@2x.png
├── circleGreen.png
├── circleGreen@2x.png
├── circleRed.png
├── circleRed@2x.png
├── menuActive.png
├── menuActive@2x.png
├── menuPressed.png
├── menuPressed@2x.png
├── menuTemplate.png
└── menuTemplate@2x.png
├── lib
├── config.js
├── dialog.js
├── docker.js
├── menu.js
└── updater.js
├── main.js
├── package.json
├── screenshot1.png
├── screenshot2.png
└── svg
├── boxGreen.svg
├── boxRed.svg
├── circleGreen.svg
├── circleRed.svg
├── menuActive.svg
├── menuPressed.svg
└── menuTemplate.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | ###
2 | ###
3 | ###
4 | ###
5 | ### !!!DO NOT EDIT!!!
6 | ### AUTO-GENERATED BY https://www.gitignore.io
7 | ### FOR CUSTOM RULES, SCROLL TO THE BOTTOM
8 | ###
9 | ###
10 | ###
11 | ###
12 |
13 | ### Linux ###
14 | *~
15 |
16 | # KDE directory preferences
17 | .directory
18 |
19 | # Linux trash folder which might appear on any partition or disk
20 | .Trash-*
21 |
22 |
23 | ### Windows ###
24 | # Windows image file caches
25 | Thumbs.db
26 | ehthumbs.db
27 |
28 | # Folder config file
29 | Desktop.ini
30 |
31 | # Recycle Bin used on file shares
32 | $RECYCLE.BIN/
33 |
34 | # Windows Installer files
35 | *.cab
36 | *.msi
37 | *.msm
38 | *.msp
39 |
40 | # Windows shortcuts
41 | *.lnk
42 |
43 |
44 | ### OSX ###
45 | .DS_Store
46 | .AppleDouble
47 | .LSOverride
48 |
49 | # Icon must end with two \r
50 | Icon
51 |
52 |
53 | # Thumbnails
54 | ._*
55 |
56 | # Files that might appear in the root of a volume
57 | .DocumentRevisions-V100
58 | .fseventsd
59 | .Spotlight-V100
60 | .TemporaryItems
61 | .Trashes
62 | .VolumeIcon.icns
63 |
64 | # Directories potentially created on remote AFP share
65 | .AppleDB
66 | .AppleDesktop
67 | Network Trash Folder
68 | Temporary Items
69 | .apdisk
70 |
71 |
72 | ### Vim ###
73 | [._]*.s[a-w][a-z]
74 | [._]s[a-w][a-z]
75 | *.un~
76 | Session.vim
77 | .netrwhist
78 | *~
79 |
80 |
81 | ### SublimeText ###
82 | # cache files for sublime text
83 | *.tmlanguage.cache
84 | *.tmPreferences.cache
85 | *.stTheme.cache
86 |
87 | # workspace files are user-specific
88 | *.sublime-workspace
89 |
90 | # project files should be checked into the repository, unless a significant
91 | # proportion of contributors will probably not be using SublimeText
92 | # *.sublime-project
93 |
94 | # sftp configuration file
95 | sftp-config.json
96 |
97 |
98 | ### Node ###
99 | # Logs
100 | logs
101 | *.log
102 | npm-debug.log*
103 |
104 | # Runtime data
105 | pids
106 | *.pid
107 | *.seed
108 |
109 | # Directory for instrumented libs generated by jscoverage/JSCover
110 | lib-cov
111 |
112 | # Coverage directory used by tools like istanbul
113 | coverage
114 |
115 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
116 | .grunt
117 |
118 | # node-waf configuration
119 | .lock-wscript
120 |
121 | # Compiled binary addons (http://nodejs.org/api/addons.html)
122 | build/Release
123 |
124 | # Dependency directory
125 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
126 | node_modules
127 |
128 |
129 | ### grunt ###
130 | # Grunt usually compiles files inside this directory
131 | dist/
132 |
133 | # Grunt usually preprocesses files such as coffeescript, compass... inside the .tmp directory
134 | .tmp/
135 |
136 | ###
137 | ###
138 | ###
139 | ###
140 | ### CUSTOM RULES GO HERE
141 | ###
142 | ###
143 | ###
144 | ###
145 |
146 | /build
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # docker-menu
2 |
3 | > Menubar app for Docker built on Electron
4 |
5 |
6 |
7 |
8 |
9 | ## Installing
10 |
11 | Download the appropriate file from [Releases](https://github.com/rdsubhas/docker-menu/releases). We're working on the auto-updater.
12 |
13 | ## Building
14 |
15 | Run the following commands in the cloned source directory:
16 |
17 | * ```npm install```
18 | * ```npm run grunt```
19 |
20 | The resulting .app file will be placed in a ```build/``` subdirectory.
21 |
--------------------------------------------------------------------------------
/generate-images.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | rm images/*.png
3 | for file in $(cd svg && ls *.svg); do
4 | filename=${file%%.svg}
5 | echo "Rasterizing $filename..."
6 |
7 | convert \
8 | -density 1200 \
9 | -resize 18x18 \
10 | -background none \
11 | svg/$file images/$filename.png
12 |
13 | convert \
14 | -density 1200 \
15 | -resize 36x36 \
16 | -background none \
17 | svg/$file images/$filename@2x.png
18 | done
19 |
20 | echo 'Creating ICNS...'
21 | convert \
22 | -density 1200 \
23 | -resize 1024x1024 \
24 | -background none \
25 | svg/menuActive.svg images/DockerMenu.png
26 | mkdir images/DockerMenu.iconset
27 | sips -z 16 16 images/DockerMenu.png --out images/DockerMenu.iconset/icon_16x16.png
28 | sips -z 32 32 images/DockerMenu.png --out images/DockerMenu.iconset/icon_16x16@2x.png
29 | sips -z 32 32 images/DockerMenu.png --out images/DockerMenu.iconset/icon_32x32.png
30 | sips -z 64 64 images/DockerMenu.png --out images/DockerMenu.iconset/icon_32x32@2x.png
31 | sips -z 128 128 images/DockerMenu.png --out images/DockerMenu.iconset/icon_128x128.png
32 | sips -z 256 256 images/DockerMenu.png --out images/DockerMenu.iconset/icon_128x128@2x.png
33 | sips -z 256 256 images/DockerMenu.png --out images/DockerMenu.iconset/icon_256x256.png
34 | sips -z 512 512 images/DockerMenu.png --out images/DockerMenu.iconset/icon_256x256@2x.png
35 | sips -z 512 512 images/DockerMenu.png --out images/DockerMenu.iconset/icon_512x512.png
36 | cp images/DockerMenu.png images/DockerMenu.iconset/icon_512x512@2x.png
37 | iconutil -c icns images/DockerMenu.iconset
38 | rm -R images/DockerMenu.iconset
39 |
--------------------------------------------------------------------------------
/gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function (grunt) {
4 | require('load-grunt-tasks')(grunt)
5 | let pkg = require('./package.json')
6 |
7 | let dep_files = Object.keys(pkg.dependencies).map(function (dep) {
8 | return 'node_modules/' + dep + '/**/*'
9 | })
10 |
11 | grunt.initConfig({
12 |
13 | clean: {
14 | dist: {
15 | src: ['build/'],
16 | options: {
17 | force: true
18 | }
19 | }
20 | },
21 |
22 | copy: {
23 | sources: {
24 | cwd: '.',
25 | dest: 'build/app/',
26 | src: ['images/**/*', 'lib/**/*', 'main.js', 'package.json']
27 | },
28 | dependencies: {
29 | cwd: '.',
30 | dest: 'build/app/',
31 | src: dep_files
32 | }
33 | },
34 |
35 | electron: {
36 | osx: {
37 | options: {
38 | name: 'DockerMenu-' + pkg.version + '-Mac',
39 | dir: 'build/app',
40 | icon: 'images/DockerMenu.icns',
41 | 'app-bundle-id': 'dockermenu',
42 | 'app-version': pkg.version,
43 | asar: false,
44 | overwrite: true,
45 | out: 'build',
46 | platform: 'darwin',
47 | version: '0.34.1',
48 | arch: 'x64'
49 | }
50 | }
51 | }
52 |
53 | })
54 |
55 | grunt.registerTask('build', ['clean', 'copy', 'electron'])
56 | grunt.registerTask('default', ['build'])
57 | }
58 |
--------------------------------------------------------------------------------
/images/DockerMenu.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/DockerMenu.icns
--------------------------------------------------------------------------------
/images/DockerMenu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/DockerMenu.png
--------------------------------------------------------------------------------
/images/boxGreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/boxGreen.png
--------------------------------------------------------------------------------
/images/boxGreen@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/boxGreen@2x.png
--------------------------------------------------------------------------------
/images/boxRed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/boxRed.png
--------------------------------------------------------------------------------
/images/boxRed@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/boxRed@2x.png
--------------------------------------------------------------------------------
/images/circleGreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/circleGreen.png
--------------------------------------------------------------------------------
/images/circleGreen@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/circleGreen@2x.png
--------------------------------------------------------------------------------
/images/circleRed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/circleRed.png
--------------------------------------------------------------------------------
/images/circleRed@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/circleRed@2x.png
--------------------------------------------------------------------------------
/images/menuActive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/menuActive.png
--------------------------------------------------------------------------------
/images/menuActive@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/menuActive@2x.png
--------------------------------------------------------------------------------
/images/menuPressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/menuPressed.png
--------------------------------------------------------------------------------
/images/menuPressed@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/menuPressed@2x.png
--------------------------------------------------------------------------------
/images/menuTemplate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/menuTemplate.png
--------------------------------------------------------------------------------
/images/menuTemplate@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/images/menuTemplate@2x.png
--------------------------------------------------------------------------------
/lib/config.js:
--------------------------------------------------------------------------------
1 | const app = require('app')
2 | const path = require('path')
3 | const fs = require('fs')
4 | const prefsPath = path.join(app.getPath('userData'), 'prefs.json')
5 |
6 | var prefs = {}
7 |
8 | module.exports = {
9 |
10 | get: function (key) {
11 | return prefs[key]
12 | },
13 |
14 | set: function (key, value) {
15 | prefs[key] = value
16 | setTimeout(this.save, 0)
17 | },
18 |
19 | load: function () {
20 | try {
21 | prefs = JSON.parse(fs.readFileSync(prefsPath).toString())
22 | } catch (e) {
23 | prefs = {}
24 | }
25 | },
26 |
27 | save: function () {
28 | fs.writeFile(prefsPath, JSON.stringify(prefs))
29 | }
30 |
31 | }
32 |
33 | module.exports.load()
34 |
--------------------------------------------------------------------------------
/lib/dialog.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Lo = require('lodash')
4 | const dialog = require('dialog')
5 | const Docker = require('./docker')
6 | const Menu = require('./menu')
7 | const Config = require('./config')
8 |
9 | const MACHINE_TEMPLATE = Lo.template('Driver: <%= DRIVER %>\nURL: <%= URL %>\nState: <%= STATE %>')
10 | const CONTAINER_TEMPLATE = Lo.template('Image: <%= Image %>\nCommand: <%= Command %>\nStatus: <%= Status %>\nPorts: <%= Portmaps %>')
11 | const ALL_CONTAINERS_TEMPLATE = 'Stop all: Stops all running containers\nCleanup: Removes all stopped containers and untagged images'
12 |
13 | exports.showMachineActions = function (machine) {
14 | let buttons = machine.isRunning ? ['Stop', 'Kill', 'Cancel'] : ['Start', 'Remove', 'Cancel']
15 | dialog.showMessageBox({
16 | title: machine.NAME,
17 | message: machine.NAME,
18 | detail: MACHINE_TEMPLATE(machine),
19 | type: 'info',
20 | buttons: buttons,
21 | noLink: true
22 | }, function (actionIndex) {
23 | switch (buttons[actionIndex]) {
24 | case 'Stop':
25 | Docker.machineAction([ 'stop', machine.NAME ]).then(Menu.rebuildNow)
26 | break
27 | case 'Start':
28 | Docker.machineAction([ 'start', machine.NAME ]).then(Menu.rebuildNow)
29 | break
30 | case 'Kill':
31 | Docker.machineAction([ 'kill', machine.NAME ]).then(Menu.rebuildNow)
32 | break
33 | case 'Remove':
34 | Docker.machineAction([ 'rm', machine.NAME ]).then(Menu.rebuildNow)
35 | break
36 | }
37 | })
38 | }
39 |
40 | exports.showContainerActions = function (machine, container) {
41 | let buttons = container.isRunning ? ['Stop', 'Kill', 'Cancel'] : ['Start', 'Remove', 'Cancel']
42 | let ports = container.Ports.map(function (port) {
43 | return `${port.Type}/${port.PrivatePort}:${port.PublicPort || '-'}`
44 | }).join(', ')
45 | container.Portmaps = ports
46 | dialog.showMessageBox({
47 | title: container.bestName,
48 | message: container.bestName,
49 | detail: CONTAINER_TEMPLATE(container),
50 | type: 'info',
51 | buttons: buttons,
52 | noLink: true
53 | }, function (actionIndex) {
54 | Docker.getContainer(machine, container).then(function (containerInstance) {
55 | switch (buttons[actionIndex]) {
56 | case 'Stop':
57 | containerInstance.stop(Menu.rebuildNow)
58 | break
59 | case 'Start':
60 | containerInstance.start(Menu.rebuildNow)
61 | break
62 | case 'Remove':
63 | containerInstance.remove(Menu.rebuildNow)
64 | break
65 | case 'Kill':
66 | containerInstance.kill(Menu.rebuildNow)
67 | break
68 | }
69 | })
70 | })
71 | }
72 |
73 | exports.showAllContainerActions = function (machine, containers) {
74 | let buttons = ['Stop all', 'Cleanup', 'Cancel']
75 | dialog.showMessageBox({
76 | title: machine.NAME,
77 | message: machine.NAME,
78 | detail: ALL_CONTAINERS_TEMPLATE,
79 | type: 'info',
80 | buttons: buttons,
81 | noLink: true
82 | })
83 | }
84 |
85 | exports.locateDockerMachine = function () {
86 | dialog.showOpenDialog({
87 | title: 'Locate docker-machine',
88 | properties: ['openFile']
89 | }, function (filenames) {
90 | if (filenames && filenames.length == 1) {
91 | Config.set('machine_cmd', filenames[0])
92 | Menu.rebuildNow()
93 | }
94 | })
95 | }
96 |
--------------------------------------------------------------------------------
/lib/docker.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const app = require('app')
4 | const Config = require('./config')
5 | const Promise = require('bluebird')
6 | const child_process = Promise.promisifyAll(require('child_process'))
7 | const parseColumns = require('parse-columns')
8 | const fs = require('fs')
9 | const Docker = require('dockerode')
10 | const nullFn = function () {}
11 | Promise.promisifyAll(Docker.prototype)
12 |
13 | const dockerMachineCmd = function () {
14 | return Config.get('machine_cmd') || '/usr/local/bin/docker-machine'
15 | }
16 |
17 | const dockerMachinePaths = function () {
18 | return ['/usr/local/bin', '/usr/local/sbin', '/opt/bin', '/opt/sbin',
19 | '/opt/local/bin', '/opt/local/sbin', '/usr/bin', '/bin', '/usr/sbin', '/sbin']
20 | }
21 |
22 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
23 | process.env.PATH = dockerMachinePaths().join(':')
24 |
25 | exports.machineAction = function (args) {
26 | args.unshift(dockerMachineCmd())
27 | return child_process.execAsync(args.join(' '), { cwd: app.getAppPath() })
28 | }
29 |
30 | exports.machines = function () {
31 | return exports.machineAction([ 'ls' ]).spread(function (stdout, stderr) {
32 | return parseColumns(stdout).map(function (machine) {
33 | machine.isRunning = machine.STATE && machine.STATE.toLowerCase().indexOf('running') >= 0
34 | return machine
35 | })
36 | })
37 | }
38 |
39 | exports.connect = function (machine) {
40 | return exports.machineAction(['inspect', machine.NAME]).spread(function (stdout, stderr) {
41 | let json = JSON.parse(stdout)
42 | let driver = json.Driver
43 | let auth = json.HostOptions.AuthOptions
44 | return new Docker({
45 | host: driver.IPAddress,
46 | port: driver.Port || 2376,
47 | ca: (auth.CaCertPath ? fs.readFileSync(auth.CaCertPath) : null),
48 | cert: (auth.ClientCertPath ? fs.readFileSync(auth.ClientCertPath) : null),
49 | key: (auth.ClientKeyPath ? fs.readFileSync(auth.ClientKeyPath) : null)
50 | })
51 | })
52 | }
53 |
54 | exports.getContainer = function (machine, container) {
55 | return exports.connect(machine).then(function (docker) {
56 | return docker.getContainer(container.Id)
57 | })
58 | }
59 |
60 | exports.dockerMachineInstalled = function () {
61 | return fs.existsSync(dockerMachineCmd())
62 | }
63 |
--------------------------------------------------------------------------------
/lib/menu.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Lo = require('lodash')
4 | const app = require('app')
5 | const path = require('path')
6 | const Menu = require('menu')
7 | const MenuItem = require('menu-item')
8 | const Dialog = require('./dialog')
9 | const Promise = require('bluebird')
10 | const Docker = require('./docker')
11 | const Config = require('./config')
12 |
13 | const UPDATE_INTERVAL = 1000 * 60 * 15 // every 15 minutes
14 | const IMAGES_FOLDER = path.resolve(app.getAppPath(), 'images')
15 | var updateTimer, trayIcon
16 |
17 | const statusIcon = function (bool, successIcon, failureIcon) {
18 | return IMAGES_FOLDER + '/' + (bool ? successIcon : failureIcon) + '.png'
19 | }
20 |
21 | const buildMainMenu = Promise.coroutine(function *() {
22 | let menu = new Menu()
23 | menu.append(new MenuItem({
24 | label: 'Refresh',
25 | click: rebuildNow
26 | }))
27 | menu.append(new MenuItem({ type: 'separator' }))
28 |
29 | if (Docker.dockerMachineInstalled()) {
30 | try {
31 | yield buildSelectedMenu(menu)
32 | } catch (e) {
33 | console.error(e)
34 | menu.append(new MenuItem({
35 | label: 'Error fetching data!',
36 | enabled: false
37 | }))
38 | menu.append(new MenuItem({ type: 'separator' }))
39 | trayIcon.setImage(statusIcon(true, 'menuTemplate'))
40 | }
41 | } else {
42 | Dialog.locateDockerMachine()
43 | }
44 |
45 | menu.append(yield buildOptionsMenu())
46 | menu.append(new MenuItem({
47 | label: 'Quit',
48 | click: app.quit
49 | }))
50 |
51 | trayIcon.setContextMenu(menu)
52 | })
53 |
54 | const buildSelectedMenu = Promise.coroutine(function *(menu) {
55 | let machines = yield Docker.machines()
56 | let selectedMachine = Lo.find(machines, 'NAME', Config.get('selected_machine'))
57 | selectedMachine.isSelected = true
58 |
59 | if (selectedMachine) {
60 | yield buildContainersMenu(menu, selectedMachine)
61 | menu.append(new MenuItem({ type: 'separator' }))
62 | }
63 |
64 | yield buildMachinesMenu(menu, machines)
65 | menu.append(new MenuItem({ type: 'separator' }))
66 |
67 | menu.append(yield buildSwitchMenu(machines, selectedMachine))
68 | trayIcon.setImage(statusIcon(selectedMachine.isRunning, 'menuActive', 'menuTemplate'))
69 | })
70 |
71 | const buildSwitchMenu = Promise.coroutine(function *(machines, selectedMachine) {
72 | let menu = new Menu()
73 | for (let machine of machines) {
74 | let selected = machine.NAME === selectedMachine.NAME
75 | menu.append(new MenuItem({
76 | label: machine.NAME,
77 | checked: selected,
78 | enabled: !selected,
79 | click: function () {
80 | Config.set('selected_machine', machine.NAME)
81 | rebuildNow()
82 | }
83 | }))
84 | }
85 |
86 | return new MenuItem({
87 | label: 'Select Machine',
88 | submenu: menu
89 | })
90 | })
91 |
92 | const buildMachinesMenu = Promise.coroutine(function *(menu, machines) {
93 | menu.append(new MenuItem({
94 | label: 'Machines',
95 | enabled: false
96 | }))
97 | for (let machine of machines) {
98 | let machineMenu = yield buildMachineMenu(machine)
99 | menu.append(machineMenu)
100 | }
101 | })
102 |
103 | const buildMachineMenu = Promise.coroutine(function *(machine) {
104 | return new MenuItem({
105 | label: machine.NAME,
106 | icon: statusIcon(machine.isRunning, 'circleGreen', 'circleRed'),
107 | click: function () {
108 | Dialog.showMachineActions(machine)
109 | }
110 | })
111 | })
112 |
113 | const buildContainersMenu = Promise.coroutine(function *(menu, machine) {
114 | if (machine.isRunning) {
115 | let docker = yield Docker.connect(machine)
116 | let containers = yield docker.listContainersAsync({ all: true })
117 | menu.append(new MenuItem({
118 | label: 'Containers (' + machine.NAME + ')',
119 | enabled: false
120 | }))
121 | for (let container of containers) {
122 | let containerMenu = yield buildContainerMenu(machine, container)
123 | menu.append(containerMenu)
124 | }
125 | if (containers.length > 0) {
126 | menu.append(new MenuItem({
127 | label: 'All...',
128 | click: function () {
129 | Dialog.showAllContainerActions(machine, containers)
130 | }
131 | }))
132 | }
133 | }
134 | })
135 |
136 | const buildContainerMenu = Promise.coroutine(function *(machine, container) {
137 | container.bestName = Lo.min(container.Names, 'length').replace(/^\//, '')
138 | container.isRunning = !!container.Status.match(/^(running|up)/i)
139 | return new MenuItem({
140 | label: container.bestName,
141 | icon: statusIcon(container.isRunning, 'boxGreen', 'boxRed'),
142 | click: function () {
143 | Dialog.showContainerActions(machine, container)
144 | }
145 | })
146 | })
147 |
148 | const buildOptionsMenu = Promise.coroutine(function *() {
149 | let menu = new Menu()
150 | menu.append(new MenuItem({
151 | label: 'Locate docker-machine',
152 | click: function () {
153 | Dialog.locateDockerMachine()
154 | }
155 | }))
156 |
157 | return new MenuItem({
158 | label: 'Options',
159 | submenu: menu
160 | })
161 | })
162 |
163 | const rebuildNow = function () {
164 | setTimeout(buildMainMenu, 0)
165 | }
166 |
167 | const watch = function (_trayIcon) {
168 | trayIcon = _trayIcon
169 | clearInterval(updateTimer)
170 | updateTimer = setInterval(buildMainMenu, UPDATE_INTERVAL)
171 | rebuildNow()
172 | }
173 |
174 | exports.rebuildNow = rebuildNow
175 | exports.watch = watch
176 |
--------------------------------------------------------------------------------
/lib/updater.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const CHECK_FOR_UPDATES = false
4 |
5 | const autoUpdater = require('auto-updater')
6 | const pkg = require('../package.json')
7 | const autoUpdateUrl = `http://docker-menu-updater.herokuapp.com/v1/rdsubhas/docker-menu/${pkg.version}`
8 |
9 | autoUpdater.on('error', function () {
10 | console.log('error', arguments)
11 | })
12 |
13 | autoUpdater.on('checking-for-update', function () {
14 | console.log('checking-for-update', arguments)
15 | })
16 |
17 | autoUpdater.on('update-available', function () {
18 | console.log('update-available', arguments)
19 | })
20 |
21 | autoUpdater.on('update-not-available', function () {
22 | console.log('update-not-available', arguments)
23 | })
24 |
25 | autoUpdater.on('update-downloaded', function () {
26 | console.log('update-downloaded', arguments)
27 | })
28 |
29 | let start = function () {
30 | if (CHECK_FOR_UPDATES) {
31 | autoUpdater.setFeedUrl(autoUpdateUrl)
32 | autoUpdater.checkForUpdates()
33 | }
34 | }
35 |
36 | exports.start = start
37 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const app = require('app')
4 | const fixPath = require('fix-path')
5 | const Tray = require('tray')
6 | const Menu = require('./lib/menu')
7 | const Updater = require('./lib/updater')
8 |
9 | // report crashes to the Electron project
10 | // require('crash-reporter').start()
11 |
12 | // Make single instance
13 | let shouldQuit = app.makeSingleInstance(function () { return true })
14 | if (shouldQuit) {
15 | app.quit()
16 | }
17 |
18 | app.on('ready', function () {
19 | // Fix $PATH
20 | fixPath()
21 |
22 | // Hide in dock
23 | app.dock.hide()
24 |
25 | let trayIcon = new Tray(app.getAppPath() + '/images/menuTemplate.png')
26 | trayIcon.setPressedImage(app.getAppPath() + '/images/menuPressed.png')
27 | Menu.watch(trayIcon)
28 |
29 | // Check for updates
30 | Updater.start()
31 | })
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docker-menu",
3 | "version": "0.3.0",
4 | "private": true,
5 | "main": "main.js",
6 | "engines": {
7 | "node": "4.x"
8 | },
9 | "scripts": {
10 | "start": "electron .",
11 | "test": "standard",
12 | "grunt": "grunt"
13 | },
14 | "dependencies": {
15 | "bluebird": "^2.10.2",
16 | "fix-path": "^1.1.0",
17 | "dockerode": "^2.2.3",
18 | "lodash": "^3.10.0",
19 | "parse-columns": "^1.2.0"
20 | },
21 | "devDependencies": {
22 | "electron-prebuilt": "^0.34.1",
23 | "grunt": "^0.4.5",
24 | "grunt-cli": "^0.1.13",
25 | "grunt-contrib-clean": "^0.6.0",
26 | "grunt-contrib-copy": "^0.8.2",
27 | "grunt-electron": "^2.0.1",
28 | "load-grunt-tasks": "^3.3.0",
29 | "standard": "^5.3.1"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/screenshot1.png
--------------------------------------------------------------------------------
/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rdsubhas/menubar-docker/c8851e069886f4c29354824207f4d9bab6937a36/screenshot2.png
--------------------------------------------------------------------------------
/svg/boxGreen.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/svg/boxRed.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/svg/circleGreen.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/svg/circleRed.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/svg/menuActive.svg:
--------------------------------------------------------------------------------
1 |
2 |
112 |
--------------------------------------------------------------------------------
/svg/menuPressed.svg:
--------------------------------------------------------------------------------
1 |
2 |
122 |
--------------------------------------------------------------------------------
/svg/menuTemplate.svg:
--------------------------------------------------------------------------------
1 |
2 |
122 |
--------------------------------------------------------------------------------