From 3b0d726c2a79feccee0cbc2c25697634573dbfee Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 13 Jul 2016 12:44:31 +1200 Subject: [PATCH 1/5] feat(dockerui): Docker CLI compliant flags (#67) --- README.md | 35 +++++++++++++++------- dockerui.go | 82 ++++++++++++++++++++++++++++++++-------------------- gruntFile.js | 4 +-- 3 files changed, 77 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 8e15053e0..ce004a2a2 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,16 @@ The `--privileged` flag is required for hosts using SELinux. By default UI For Docker connects to the Docker daemon with`/var/run/docker.sock`. For this to work you need to bind mount the unix socket into the container with `-v /var/run/docker.sock:/var/run/docker.sock`. -You can use the `-e` flag to change this socket: +You can use the `--host`, `-H` flags to change this socket: ``` # Connect to a tcp socket: -$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -e http://127.0.0.1:2375 +$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -H tcp://127.0.0.1:2375 +``` + +``` +# Connect to another unix socket: +$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -H unix:///path/to/docker.sock ``` ### Swarm support @@ -41,7 +46,7 @@ You can access a specific view for you Swarm cluster by defining the `--swarm` f ``` # Connect to a tcp socket and enable Swarm: -$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -e http://: --swarm +$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -H tcp://: --swarm ``` *NOTE*: Due to Swarm not exposing information in a machine readable way, the app is bound to a specific version of Swarm at the moment. @@ -61,8 +66,13 @@ Ensure that you have access to the CA, the cert and the public key used to acces These files will need to be named `ca.pem`, `cert.pem` and `key.pem` respectively. Store them somewhere on your disk and mount a volume containing these files inside the UI container: ``` -# Note the access to the endpoint via https -$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -v /path/to/certs:/certs -e https://my-docker-host.domain:2376 +$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -v /path/to/certs:/certs -H https://my-docker-host.domain:2376 --tlsverify +``` + +You can also use the `--tlscacert`, `--tlscert` and `--tlskey` flags if you want to change the default path to the CA, certificate and key file respectively: + +``` +$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -v /path/to/certs:/certs -H https://my-docker-host.domain:2376 --tlsverify --tlscacert /certs/myCa.pem --tlscert /certs/myCert.pem --tlskey /certs/myKey.pem ``` *Note*: Replace `/path/to/certs` to the path to the certificate files on your disk. @@ -87,10 +97,13 @@ $ docker run -d -p 9000:9000 --privileged -v /var/run/docker.sock:/var/run/docke The following options are available for the `ui-for-docker` binary: -* `--endpoint`, `-e`: Docker deamon endpoint (default: *"/var/run/docker.sock"*) -* `--bind`, `-p`: Address and port to serve UI For Docker (default: *":9000"*) -* `--data`, `-d`: Path to the data folder (default: *"."*) -* `--certs`, `-c`: Path to the certificates used for TLS (default: *"/certs"*) -* `--assets`, `-a`: Path to the assets (default: *"."*) -* `--swarm`, `-s`: Swarm cluster support (default: *false*) +* `--host`, `-H`: Docker daemon endpoint (default: `"unix:///var/run/docker.sock"`) +* `--bind`, `-p`: Address and port to serve UI For Docker (default: `":9000"`) +* `--data`, `-d`: Path to the data folder (default: `"."`) +* `--assets`, `-a`: Path to the assets (default: `"."`) +* `--swarm`, `-s`: Swarm cluster support (default: `false`) * `--hide-label`, `-l`: Hide containers with a specific label in the UI +* `--tlsverify`: TLS support (default: `false`) +* `--tlscacert`: Path to the CA (default `/certs/ca.pem`) +* `--tlscert`: Path to the TLS certificate file (default `/certs/cert.pem`) +* `--tlskey`: Path to the TLS key (default `/certs/key.pem`) diff --git a/dockerui.go b/dockerui.go index 9d3da31c6..d2614c4c1 100644 --- a/dockerui.go +++ b/dockerui.go @@ -20,13 +20,16 @@ import ( ) var ( - endpoint = kingpin.Flag("endpoint", "Dockerd endpoint").Default("/var/run/docker.sock").Short('e').String() - addr = kingpin.Flag("bind", "Address and port to serve UI For Docker").Default(":9000").Short('p').String() - assets = kingpin.Flag("assets", "Path to the assets").Default(".").Short('a').String() - data = kingpin.Flag("data", "Path to the data").Default(".").Short('d').String() - certs = kingpin.Flag("certs", "Path to the certs").Default("/certs").Short('c').String() - swarm = kingpin.Flag("swarm", "Swarm cluster support").Default("false").Short('s').Bool() - labels = LabelParser(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')) + endpoint = kingpin.Flag("host", "Dockerd endpoint").Default("unix:///var/run/docker.sock").Short('H').String() + addr = kingpin.Flag("bind", "Address and port to serve UI For Docker").Default(":9000").Short('p').String() + assets = kingpin.Flag("assets", "Path to the assets").Default(".").Short('a').String() + data = kingpin.Flag("data", "Path to the data").Default(".").Short('d').String() + swarm = kingpin.Flag("swarm", "Swarm cluster support").Default("false").Short('s').Bool() + tlsverify = kingpin.Flag("tlsverify", "TLS support").Default("false").Bool() + tlscacert = kingpin.Flag("tlscacert", "Path to the CA").Default("/certs/ca.pem").String() + tlscert = kingpin.Flag("tlscert", "Path to the TLS certificate file").Default("/certs/cert.pem").String() + tlskey = kingpin.Flag("tlskey", "Path to the TLS key").Default("/certs/key.pem").String() + labels = LabelParser(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')) authKey []byte authKeyFile = "authKey.dat" ) @@ -35,6 +38,13 @@ type UnixHandler struct { path string } +type TlsFlags struct { + tls bool + caPath string + certPath string + keyPath string +} + type Config struct { Swarm bool `json:"swarm"` HiddenLabels Labels `json:"hiddenLabels"` @@ -109,20 +119,17 @@ func configurationHandler(w http.ResponseWriter, r *http.Request, c Config) { json.NewEncoder(w).Encode(c) } -func createTcpHandler(e string) http.Handler { - u, err := url.Parse(e) - if err != nil { - log.Fatal(err) - } +func createTcpHandler(u *url.URL) http.Handler { + u.Scheme = "http"; return httputil.NewSingleHostReverseProxy(u) } -func createTlsConfig(c string) *tls.Config { - cert, err := tls.LoadX509KeyPair(c + "/" + "cert.pem", c + "/" + "key.pem") +func createTlsConfig(tlsFlags TlsFlags) *tls.Config { + cert, err := tls.LoadX509KeyPair(tlsFlags.certPath, tlsFlags.keyPath) if err != nil { log.Fatal(err) } - caCert, err := ioutil.ReadFile(c + "/" + "ca.pem") + caCert, err := ioutil.ReadFile(tlsFlags.caPath) if err != nil { log.Fatal(err) } @@ -135,12 +142,9 @@ func createTlsConfig(c string) *tls.Config { return tlsConfig; } -func createTcpHandlerWithTLS(e string, c string) http.Handler { - u, err := url.Parse(e) - if err != nil { - log.Fatal(err) - } - var tlsConfig = createTlsConfig(c) +func createTcpHandlerWithTLS(u *url.URL, tlsFlags TlsFlags) http.Handler { + u.Scheme = "https"; + var tlsConfig = createTlsConfig(tlsFlags) proxy := httputil.NewSingleHostReverseProxy(u) proxy.Transport = &http.Transport{ TLSClientConfig: tlsConfig, @@ -152,24 +156,33 @@ func createUnixHandler(e string) http.Handler { return &UnixHandler{e} } -func createHandler(dir string, d string, certs string, e string, c Config) http.Handler { +func createHandler(dir string, d string, e string, c Config, tlsFlags TlsFlags) http.Handler { var ( mux = http.NewServeMux() fileHandler = http.FileServer(http.Dir(dir)) h http.Handler ) - if strings.Contains(e, "https") { - h = createTcpHandlerWithTLS(e, certs) - } else if strings.Contains(e, "http") { - h = createTcpHandler(e) - } else { - if _, err := os.Stat(e); err != nil { + u, perr := url.Parse(e) + if perr != nil { + log.Fatal(perr) + } + if u.Scheme == "tcp" { + if tlsFlags.tls { + h = createTcpHandlerWithTLS(u, tlsFlags) + } else { + h = createTcpHandler(u) + } + } else if u.Scheme == "unix" { + var socketPath = u.Path + if _, err := os.Stat(socketPath); err != nil { if os.IsNotExist(err) { - log.Fatalf("unix socket %s does not exist", e) + log.Fatalf("unix socket %s does not exist", socketPath) } log.Fatal(err) } - h = createUnixHandler(e) + h = createUnixHandler(socketPath) + } else { + log.Fatalf("Bad Docker enpoint: %s. Only unix:// and tcp:// are supported.", e) } // Use existing csrf authKey if present or generate a new one. @@ -216,7 +229,14 @@ func main() { HiddenLabels: *labels, } - handler := createHandler(*assets, *data, *certs, *endpoint, configuration) + tlsFlags := TlsFlags{ + tls: *tlsverify, + caPath: *tlscacert, + certPath: *tlscert, + keyPath: *tlskey, + } + + handler := createHandler(*assets, *data, *endpoint, configuration, tlsFlags) if err := http.ListenAndServe(*addr, handler); err != nil { log.Fatal(err) } diff --git a/gruntFile.js b/gruntFile.js index b18674a1d..3ff9bbc31 100644 --- a/gruntFile.js +++ b/gruntFile.js @@ -272,14 +272,14 @@ module.exports = function (grunt) { command: [ 'docker stop ui-for-docker', 'docker rm ui-for-docker', - 'docker run -d -p 9000:9000 -v /tmp/docker-ui:/data --name ui-for-docker ui-for-docker -e http://10.0.7.10:4000 --swarm -d /data' + 'docker run -d -p 9000:9000 -v /tmp/docker-ui:/data --name ui-for-docker ui-for-docker -H tcp://10.0.7.10:4000 --swarm -d /data' ].join(';') }, runSsl: { command: [ 'docker stop ui-for-docker', 'docker rm ui-for-docker', - 'docker run -d -p 9000:9000 -v /tmp/docker-ui:/data -v /tmp/docker-ssl:/certs --name ui-for-docker ui-for-docker -e https://10.0.7.10:2376 -d /data' + 'docker run -d -p 9000:9000 -v /tmp/docker-ui:/data -v /tmp/docker-ssl:/certs --name ui-for-docker ui-for-docker -H tcp://10.0.7.10:2376 -d /data --tlsverify' ].join(';') }, cleanImages: { From f378d565434af57191ce92771e9ddc3d3a41662e Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 13 Jul 2016 13:47:15 +1200 Subject: [PATCH 2/5] fix(ui): fix bad name for image field in container creation view --- app/components/createContainer/createcontainer.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/createContainer/createcontainer.html b/app/components/createContainer/createcontainer.html index 75d60d8ba..c6bd3d04f 100644 --- a/app/components/createContainer/createcontainer.html +++ b/app/components/createContainer/createcontainer.html @@ -20,7 +20,7 @@
- +
From 43c2f142890998bf50a9a6dc86b59b56aea49b69 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 13 Jul 2016 14:47:24 +1200 Subject: [PATCH 3/5] docs(docker): add info about Docker version support (#64) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index ce004a2a2..89a0c7d31 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ UI For Docker is a web interface for the Docker Remote API. The goal is to prov * Minimal dependencies - I really want to keep this project a pure html/js app. * Consistency - The web UI should be consistent with the commands found on the docker CLI. +## Supported Docker versions + +The current Docker version support policy is the following: `N` to `N-2` included where `N` is the latest version. + +At the moment, the following versions are supported: 1.9, 1.10 & 1.11. + ## Run ### Quickstart From a9209da1676a742f1650b5ca8e3fd9d473e52996 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 13 Jul 2016 14:53:24 +1200 Subject: [PATCH 4/5] chore(version): bump version number --- app/app.js | 2 +- bower.json | 2 +- dockerui.go | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/app.js b/app/app.js index 42126a588..586194607 100644 --- a/app/app.js +++ b/app/app.js @@ -144,4 +144,4 @@ angular.module('uifordocker', [ .constant('DOCKER_ENDPOINT', 'dockerapi') .constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. If you have a port, prefix it with a ':' i.e. :4243 .constant('CONFIG_ENDPOINT', '/config') - .constant('UI_VERSION', 'v1.3.0'); + .constant('UI_VERSION', 'v1.4.0'); diff --git a/bower.json b/bower.json index 5526ed7db..f1e37a8f1 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "uifordocker", - "version": "1.3.0", + "version": "1.4.0", "homepage": "https://github.com/kevana/ui-for-docker", "authors": [ "Michael Crosby ", diff --git a/dockerui.go b/dockerui.go index d2614c4c1..5f995f205 100644 --- a/dockerui.go +++ b/dockerui.go @@ -221,7 +221,7 @@ func csrfWrapper(h http.Handler) http.Handler { } func main() { - kingpin.Version("1.3.0") + kingpin.Version("1.4.0") kingpin.Parse() configuration := Config{ diff --git a/package.json b/package.json index 86990bd32..2fa2f25f0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Michael Crosby & Kevan Ahlquist", "name": "uifordocker", "homepage": "https://github.com/kevana/ui-for-docker", - "version": "1.3.0", + "version": "1.4.0", "repository": { "type": "git", "url": "git@github.com:kevana/ui-for-docker.git" From c8a5b82c89aaa16ccb974e1838e4978301843ed4 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 14 Jul 2016 10:58:39 +1200 Subject: [PATCH 5/5] feat(ui): new dashboard view (#75) --- app/components/dashboard/dashboard.html | 169 ++++++++++++------ .../dashboard/dashboardController.js | 117 +++++++----- app/shared/filters.js | 6 + assets/css/app.css | 12 ++ 4 files changed, 206 insertions(+), 98 deletions(-) diff --git a/app/components/dashboard/dashboard.html b/app/components/dashboard/dashboard.html index b1b80222c..61ba5f16e 100644 --- a/app/components/dashboard/dashboard.html +++ b/app/components/dashboard/dashboard.html @@ -1,76 +1,131 @@ - + + + Dashboard
-
+
- -
- -
-
{{ containerData.total }}
-
Containers
+ + + + + + + + + + + + + + + + + + + + + +
Name{{ infoData.Name }}
Docker version{{ infoData.ServerVersion }}
CPU{{ infoData.NCPU }}
Memory{{ infoData.MemTotal|humansize }}
-
+
- -
- -
-
{{ containerData.running }}
-
Running
-
-
-
-
- - -
- -
-
{{ containerData.stopped }}
-
Stopped
-
-
-
-
- - -
- -
-
{{ containerData.ghost }}
-
Ghost
+ + + + + + + + + + + + + + + + + + + + + +
Nodes{{ infoData.SystemStatus[3][1] }}
Swarm version{{ infoData.ServerVersion|swarmversion }}
Total CPU{{ infoData.NCPU }}
Total memory{{ infoData.MemTotal|humansize }}
-
- - - - -

You are using an outdated browser. Please upgrade your browser to improve your experience.

-
-
-
+ - + +
+
diff --git a/app/components/dashboard/dashboardController.js b/app/components/dashboard/dashboardController.js index 5c348d8fb..404f78955 100644 --- a/app/components/dashboard/dashboardController.js +++ b/app/components/dashboard/dashboardController.js @@ -1,49 +1,83 @@ angular.module('dashboard', []) -.controller('DashboardController', ['$scope', 'Config', 'Container', 'Image', 'Settings', 'LineChart', -function ($scope, Config, Container, Image, Settings, LineChart) { +.controller('DashboardController', ['$scope', '$q', 'Config', 'Container', 'Image', 'Network', 'Volume', 'Info', +function ($scope, $q, Config, Container, Image, Network, Volume, Info) { - $scope.containerData = {}; - - var buildCharts = function (data) { - $scope.containerData.total = data.length; - LineChart.build('#containers-started-chart', data, function (c) { - return new Date(c.Created * 1000).toLocaleDateString(); - }); - var s = $scope; - Image.query({}, function (d) { - s.totalImages = d.length; - LineChart.build('#images-created-chart', d, function (c) { - return new Date(c.Created * 1000).toLocaleDateString(); - }); - }); + $scope.containerData = { + total: 0 + }; + $scope.imageData = { + total: 0 + }; + $scope.networkData = { + total: 0 + }; + $scope.volumeData = { + total: 0 }; + function prepareContainerData(d) { + var running = 0; + var stopped = 0; + + var containers = d; + if (hiddenLabels) { + containers = hideContainers(d); + } + + for (var i = 0; i < containers.length; i++) { + var item = containers[i]; + if (item.Status.indexOf('Up') !== -1) { + running += 1; + } else if (item.Status.indexOf('Exit') !== -1) { + stopped += 1; + } + } + $scope.containerData.running = running; + $scope.containerData.stopped = stopped; + $scope.containerData.total = containers.length; + } + + function prepareImageData(d) { + var images = d; + var totalImageSize = 0; + for (var i = 0; i < images.length; i++) { + var item = images[i]; + totalImageSize += item.VirtualSize; + } + $scope.imageData.total = images.length; + $scope.imageData.size = totalImageSize; + } + + function prepareVolumeData(d) { + var volumes = d.Volumes; + $scope.volumeData.total = volumes.length; + } + + function prepareNetworkData(d) { + var networks = d; + $scope.networkData.total = networks.length; + } + + function prepareInfoData(d) { + var info = d; + $scope.infoData = info; + } + function fetchDashboardData() { - Container.query({all: 1}, function (d) { - var running = 0; - var ghost = 0; - var stopped = 0; - - var containers = d; - if (hiddenLabels) { - containers = hideContainers(d); - } - - for (var i = 0; i < containers.length; i++) { - var item = containers[i]; - if (item.Status === "Ghost") { - ghost += 1; - } else if (item.Status.indexOf('Exit') !== -1) { - stopped += 1; - } else { - running += 1; - } - } - $scope.containerData.running = running; - $scope.containerData.stopped = stopped; - $scope.containerData.ghost = ghost; - - buildCharts(containers); + $('#loadingViewSpinner').show(); + $q.all([ + Container.query({all: 1}).$promise, + Image.query({}).$promise, + Volume.query({}).$promise, + Network.query({}).$promise, + Info.get({}).$promise, + ]).then(function (d) { + prepareContainerData(d[0]); + prepareImageData(d[1]); + prepareVolumeData(d[2]); + prepareNetworkData(d[3]); + prepareInfoData(d[4]); + $('#loadingViewSpinner').hide(); }); } @@ -63,6 +97,7 @@ function ($scope, Config, Container, Image, Settings, LineChart) { }; Config.$promise.then(function (c) { + $scope.swarm = c.swarm; hiddenLabels = c.hiddenLabels; fetchDashboardData(); }); diff --git a/app/shared/filters.js b/app/shared/filters.js index f1b40abfb..f1e69027c 100644 --- a/app/shared/filters.js +++ b/app/shared/filters.js @@ -130,6 +130,12 @@ angular.module('dockerui.filters', []) return _.split(container.Names[0], '/')[2]; }; }) +.filter('swarmversion', function () { + 'use strict'; + return function (text) { + return _.split(text, '/')[1]; + }; +}) .filter('swarmhostname', function () { 'use strict'; return function (container) { diff --git a/assets/css/app.css b/assets/css/app.css index 5d99a5c85..300c7a496 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -165,3 +165,15 @@ input[type="radio"] { .clickable { cursor: pointer; } + +.text-icon { + margin-right: 5px; +} + +.green-icon { + color: #23ae89; +} + +.red-icon { + color: #ae2323; +}