From 06f54e300cfcf78710a781ee800250af37ce8c5e Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Thu, 7 Jul 2016 15:37:09 +1200 Subject: [PATCH 01/10] fix(ui): hidden containers (using label) are now removed from dashboard and swarm view (#46) --- .../dashboard/dashboardController.js | 69 ++++++++++++------- app/components/swarm/swarm.html | 12 ---- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/app/components/dashboard/dashboardController.js b/app/components/dashboard/dashboardController.js index 642ccc1aa..5c348d8fb 100644 --- a/app/components/dashboard/dashboardController.js +++ b/app/components/dashboard/dashboardController.js @@ -1,5 +1,6 @@ angular.module('dashboard', []) - .controller('DashboardController', ['$scope', 'Container', 'Image', 'Settings', 'LineChart', function ($scope, Container, Image, Settings, LineChart) { +.controller('DashboardController', ['$scope', 'Config', 'Container', 'Image', 'Settings', 'LineChart', +function ($scope, Config, Container, Image, Settings, LineChart) { $scope.containerData = {}; @@ -17,30 +18,52 @@ angular.module('dashboard', []) }); }; - Container.query({all: 1}, function (d) { - var running = 0; - var ghost = 0; - var stopped = 0; + function fetchDashboardData() { + Container.query({all: 1}, function (d) { + var running = 0; + var ghost = 0; + var stopped = 0; - // TODO: centralize that - var containers = d.filter(function (container) { - return container.Image !== 'swarm'; - }); - - 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; + var containers = d; + if (hiddenLabels) { + containers = hideContainers(d); } - } - $scope.containerData.running = running; - $scope.containerData.stopped = stopped; - $scope.containerData.ghost = ghost; - buildCharts(containers); + 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); + }); + } + + var hideContainers = function (containers) { + return containers.filter(function (container) { + var filterContainer = false; + hiddenLabels.forEach(function(label, index) { + if (_.has(container.Labels, label.name) && + container.Labels[label.name] === label.value) { + filterContainer = true; + } + }); + if (!filterContainer) { + return container; + } + }); + }; + + Config.$promise.then(function (c) { + hiddenLabels = c.hiddenLabels; + fetchDashboardData(); }); }]); diff --git a/app/components/swarm/swarm.html b/app/components/swarm/swarm.html index cf4a186fe..35e3ca6af 100644 --- a/app/components/swarm/swarm.html +++ b/app/components/swarm/swarm.html @@ -54,10 +54,6 @@ Nodes {{ swarm.Nodes }} - - Containers - {{ info.Containers }} - Images {{ info.Images }} @@ -108,13 +104,6 @@ - - - Containers - - - - Engine @@ -135,7 +124,6 @@ {{ node.name }} {{ node.ip }} - {{ node.containers }} {{ node.version }} {{ node.status }} From d2fb2cb8630f0c783a04176f5d779e5f91d4bba0 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 8 Jul 2016 11:57:24 +1200 Subject: [PATCH 02/10] feat(ui): add the ability to pull an image from a private registry (#47) --- app/components/images/images.html | 11 +++++++---- app/components/images/imagesController.js | 14 ++++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/components/images/images.html b/app/components/images/images.html index 3b060ebdc..362ee65f1 100644 --- a/app/components/images/images.html +++ b/app/components/images/images.html @@ -4,7 +4,6 @@ - Images @@ -15,14 +14,18 @@
- +
-
+
+ +
+ +
- +
diff --git a/app/components/images/imagesController.js b/app/components/images/imagesController.js index 303673ee0..d53ab25fc 100644 --- a/app/components/images/imagesController.js +++ b/app/components/images/imagesController.js @@ -8,7 +8,8 @@ function ($scope, $state, Image, Messages) { $scope.state.selectedItemCount = 0; $scope.config = { - Image: '' + Image: '', + Registry: '', }; $scope.order = function(sortType) { @@ -35,10 +36,14 @@ function ($scope, $state, Image, Messages) { } }; - function createImageConfig(imageName) { + function createImageConfig(imageName, registry) { var imageNameAndTag = imageName.split(':'); + var image = imageNameAndTag[0]; + if (registry) { + image = registry + '/' + imageNameAndTag[0]; + } var imageConfig = { - fromImage: imageNameAndTag[0], + fromImage: image, tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest' }; return imageConfig; @@ -47,7 +52,8 @@ function ($scope, $state, Image, Messages) { $scope.pullImage = function() { $('#pullImageSpinner').show(); var image = _.toLower($scope.config.Image); - var imageConfig = createImageConfig(image); + var registry = _.toLower($scope.config.Registry); + var imageConfig = createImageConfig(image, registry); Image.create(imageConfig, function (data) { var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); if (err) { From d124c21d1bebdf401db130b14230f91f9593f2e5 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 8 Jul 2016 12:52:26 +1200 Subject: [PATCH 03/10] feat(ui): add the ability to create a container from an image in a custom registry (#49) --- .../createContainer/createContainerController.js | 15 +++++++++++---- .../createContainer/createcontainer.html | 14 +++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/app/components/createContainer/createContainerController.js b/app/components/createContainer/createContainerController.js index 3581e6176..d70676c89 100644 --- a/app/components/createContainer/createContainerController.js +++ b/app/components/createContainer/createContainerController.js @@ -8,7 +8,8 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e $scope.formValues = { Console: 'none', - Volumes: [] + Volumes: [], + Registry: '' }; $scope.config = { @@ -103,10 +104,14 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e }); } - function createImageConfig(imageName) { + function createImageConfig(imageName, registry) { var imageNameAndTag = imageName.split(':'); + var image = imageNameAndTag[0]; + if (registry) { + image = registry + '/' + imageNameAndTag[0]; + } var imageConfig = { - fromImage: imageNameAndTag[0], + fromImage: image, tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest' }; return imageConfig; @@ -116,7 +121,9 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e $('#createContainerSpinner').show(); var image = _.toLower(config.Image); - var imageConfig = createImageConfig(image); + var registry = _.toLower($scope.formValues.Registry); + var imageConfig = createImageConfig(image, registry); + config.Image = imageConfig.fromImage + ':' + imageConfig.tag; Image.create(imageConfig, function (data) { var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); diff --git a/app/components/createContainer/createcontainer.html b/app/components/createContainer/createcontainer.html index 444db30c3..75d60d8ba 100644 --- a/app/components/createContainer/createcontainer.html +++ b/app/components/createContainer/createcontainer.html @@ -18,11 +18,15 @@
- +
- -
- + +
+ +
+ +
+
@@ -32,7 +36,7 @@
- +
From ca27e7f27a3d2debff73ec9d4f0867b0ba3f3aa8 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 8 Jul 2016 15:40:13 +1200 Subject: [PATCH 04/10] fix(containerCreation): fix an issue when creating an image from a custom registry without automatic pulling (#50) --- .../createContainerController.js | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/app/components/createContainer/createContainerController.js b/app/components/createContainer/createContainerController.js index d70676c89..7a761c04d 100644 --- a/app/components/createContainer/createContainerController.js +++ b/app/components/createContainer/createContainerController.js @@ -12,6 +12,8 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e Registry: '' }; + $scope.imageConfig = {}; + $scope.config = { Env: [], HostConfig: { @@ -104,6 +106,23 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e }); } + function pullImageAndCreateContainer(config) { + $('#createContainerSpinner').show(); + Image.create($scope.imageConfig, function (data) { + var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); + if (err) { + var detail = data[data.length - 1]; + $('#createContainerSpinner').hide(); + Messages.error('Error', detail.error); + } else { + createContainer(config); + } + }, function (e) { + $('#createContainerSpinner').hide(); + Messages.error('Error', 'Unable to pull image ' + image); + }); + } + function createImageConfig(imageName, registry) { var imageNameAndTag = imageName.split(':'); var image = imageNameAndTag[0]; @@ -117,27 +136,12 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e return imageConfig; } - function pullImageAndCreateContainer(config) { - $('#createContainerSpinner').show(); - + function prepareImageConfig(config) { var image = _.toLower(config.Image); - var registry = _.toLower($scope.formValues.Registry); + var registry = $scope.formValues.Registry; var imageConfig = createImageConfig(image, registry); config.Image = imageConfig.fromImage + ':' + imageConfig.tag; - - Image.create(imageConfig, function (data) { - var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); - if (err) { - var detail = data[data.length - 1]; - $('#createContainerSpinner').hide(); - Messages.error('Error', detail.error); - } else { - createContainer(config); - } - }, function (e) { - $('#createContainerSpinner').hide(); - Messages.error('Error', 'Unable to pull image ' + image); - }); + $scope.imageConfig = imageConfig; } function preparePortBindings(config) { @@ -199,6 +203,7 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e function prepareConfiguration() { var config = angular.copy($scope.config); + prepareImageConfig(config); preparePortBindings(config); prepareConsole(config); prepareEnvironmentVariables(config); From afaa1433ffbaa5b5ad97dd245e8461d6d1286364 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 8 Jul 2016 16:06:46 +1200 Subject: [PATCH 05/10] 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 9c98988a7..42126a588 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.2.0'); + .constant('UI_VERSION', 'v1.3.0'); diff --git a/bower.json b/bower.json index 3938178e9..5526ed7db 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "uifordocker", - "version": "1.2.0", + "version": "1.3.0", "homepage": "https://github.com/kevana/ui-for-docker", "authors": [ "Michael Crosby ", diff --git a/dockerui.go b/dockerui.go index 7e29a73b9..b01a2f91a 100644 --- a/dockerui.go +++ b/dockerui.go @@ -173,7 +173,7 @@ func csrfWrapper(h http.Handler) http.Handler { } func main() { - kingpin.Version("1.2.0") + kingpin.Version("1.3.0") kingpin.Parse() configuration := Config{ diff --git a/package.json b/package.json index 577f9fb4a..86990bd32 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.2.0", + "version": "1.3.0", "repository": { "type": "git", "url": "git@github.com:kevana/ui-for-docker.git" From c74e8fc7324212aa4a7502c5a493ed0443aeab1c Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 8 Jul 2016 16:20:31 +1200 Subject: [PATCH 06/10] style(lint): fix jshint issue --- app/components/images/imagesController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/images/imagesController.js b/app/components/images/imagesController.js index d53ab25fc..5eac079dd 100644 --- a/app/components/images/imagesController.js +++ b/app/components/images/imagesController.js @@ -9,7 +9,7 @@ function ($scope, $state, Image, Messages) { $scope.config = { Image: '', - Registry: '', + Registry: '' }; $scope.order = function(sortType) { From e67e20ce181fc0c890321bf8f2a7f9a29485a442 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Fri, 8 Jul 2016 17:12:33 +1200 Subject: [PATCH 07/10] feat(network): add the ability to specify a subnet/gateway when creating a network (#53) --- .../createNetwork/createNetworkController.js | 21 +++++++++++++++++-- .../createNetwork/createnetwork.html | 12 +++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/components/createNetwork/createNetworkController.js b/app/components/createNetwork/createNetworkController.js index f24622f07..56ffb79f6 100644 --- a/app/components/createNetwork/createNetworkController.js +++ b/app/components/createNetwork/createNetworkController.js @@ -2,13 +2,18 @@ angular.module('createNetwork', []) .controller('CreateNetworkController', ['$scope', '$state', 'Messages', 'Network', 'errorMsgFilter', function ($scope, $state, Messages, Network, errorMsgFilter) { $scope.formValues = { - DriverOptions: [] + DriverOptions: [], + Subnet: '', + Gateway: '' }; $scope.config = { Driver: 'bridge', CheckDuplicate: true, - Internal: false + Internal: false, + IPAM: { + Config: [] + } }; $scope.addDriverOption = function() { @@ -36,6 +41,17 @@ function ($scope, $state, Messages, Network, errorMsgFilter) { }); } + function prepareIPAMConfiguration(config) { + if ($scope.formValues.Subnet) { + var ipamConfig = {}; + ipamConfig.Subnet = $scope.formValues.Subnet; + if ($scope.formValues.Gateway) { + ipamConfig.Gateway = $scope.formValues.Gateway ; + } + config.IPAM.Config.push(ipamConfig); + } + } + function prepareDriverOptions(config) { var options = {}; $scope.formValues.DriverOptions.forEach(function (option) { @@ -46,6 +62,7 @@ function ($scope, $state, Messages, Network, errorMsgFilter) { function prepareConfiguration() { var config = angular.copy($scope.config); + prepareIPAMConfiguration(config); prepareDriverOptions(config); return config; } diff --git a/app/components/createNetwork/createnetwork.html b/app/components/createNetwork/createnetwork.html index f6f332270..31ed20d37 100644 --- a/app/components/createNetwork/createnetwork.html +++ b/app/components/createNetwork/createnetwork.html @@ -18,6 +18,18 @@
+ +
+ +
+ +
+ +
+ +
+
+
From 1fb008212a69f69385979b181322d8737bf0bc45 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Tue, 12 Jul 2016 20:31:11 +1200 Subject: [PATCH 08/10] feat(dockerui): add support for TLS enabled engines (#63) --- README.md | 14 ++++++++++++++ dockerui.go | 43 +++++++++++++++++++++++++++++++++++++++---- gruntFile.js | 14 +++++++++++++- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ce20e8267..8e15053e0 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,19 @@ UI For Docker listens on port 9000 by default. If you run UI For Docker inside a $ docker run -d -p 10.20.30.1:80:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock cloudinovasi/cloudinovasi-ui ``` +### Access a Docker engine protected via TLS + +Ensure that you have access to the CA, the cert and the public key used to access your Docker engine. + +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 +``` + +*Note*: Replace `/path/to/certs` to the path to the certificate files on your disk. + ### Hide containers with specific labels You can hide specific containers in the containers view by using the `-hide-label` or `-l` options and specifying a label. @@ -77,6 +90,7 @@ 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*) * `--hide-label`, `-l`: Hide containers with a specific label in the UI diff --git a/dockerui.go b/dockerui.go index b01a2f91a..9d3da31c6 100644 --- a/dockerui.go +++ b/dockerui.go @@ -15,6 +15,8 @@ import ( "fmt" "github.com/gorilla/securecookie" "gopkg.in/alecthomas/kingpin.v2" + "crypto/tls" + "crypto/x509" ) var ( @@ -22,6 +24,7 @@ var ( 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')) authKey []byte @@ -114,18 +117,50 @@ func createTcpHandler(e string) http.Handler { return httputil.NewSingleHostReverseProxy(u) } +func createTlsConfig(c string) *tls.Config { + cert, err := tls.LoadX509KeyPair(c + "/" + "cert.pem", c + "/" + "key.pem") + if err != nil { + log.Fatal(err) + } + caCert, err := ioutil.ReadFile(c + "/" + "ca.pem") + if err != nil { + log.Fatal(err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + 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) + proxy := httputil.NewSingleHostReverseProxy(u) + proxy.Transport = &http.Transport{ + TLSClientConfig: tlsConfig, + } + return proxy; +} + func createUnixHandler(e string) http.Handler { return &UnixHandler{e} } -func createHandler(dir string, d string, e string, c Config) http.Handler { +func createHandler(dir string, d string, certs string, e string, c Config) http.Handler { var ( mux = http.NewServeMux() fileHandler = http.FileServer(http.Dir(dir)) h http.Handler ) - - if strings.Contains(e, "http") { + 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 { @@ -181,7 +216,7 @@ func main() { HiddenLabels: *labels, } - handler := createHandler(*assets, *data, *endpoint, configuration) + handler := createHandler(*assets, *data, *certs, *endpoint, configuration) if err := http.ListenAndServe(*addr, handler); err != nil { log.Fatal(err) } diff --git a/gruntFile.js b/gruntFile.js index b5e1a6c98..b18674a1d 100644 --- a/gruntFile.js +++ b/gruntFile.js @@ -40,6 +40,7 @@ module.exports = function (grunt) { grunt.registerTask('run', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:run']); grunt.registerTask('run-swarm', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm', 'watch:buildSwarm']); grunt.registerTask('run-dev', ['if:binaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']); + grunt.registerTask('run-ssl', ['if:binaryNotExist', 'shell:buildImage', 'shell:runSsl', 'watch:buildSsl']); grunt.registerTask('clear', ['clean:app']); // Print a timestamp (useful for when watching) @@ -224,6 +225,10 @@ module.exports = function (grunt) { buildSwarm: { files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], tasks: ['build', 'shell:buildImage', 'shell:runSwarm', 'shell:cleanImages'] + }, + buildSsl: { + files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'], + tasks: ['build', 'shell:buildImage', 'shell:runSsl', 'shell:cleanImages'] } }, jshint: { @@ -267,7 +272,14 @@ module.exports = function (grunt) { command: [ 'docker stop ui-for-docker', 'docker rm ui-for-docker', - 'docker run --privileged -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 -e http://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' ].join(';') }, cleanImages: { From 71c091ae0d7058f94dd3ab954f205de8f10f5039 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 13 Jul 2016 10:53:03 +1200 Subject: [PATCH 09/10] feat(ui): docker 1.9 support (#65) --- app/components/containers/containers.html | 8 +++---- .../containers/containersController.js | 7 ++++++- app/shared/filters.js | 21 ++++++++++++++++--- app/shared/viewmodel.js | 7 +++++-- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/app/components/containers/containers.html b/app/components/containers/containers.html index 36b26fa8f..0a7ca115a 100644 --- a/app/components/containers/containers.html +++ b/app/components/containers/containers.html @@ -39,7 +39,7 @@ - + State @@ -52,7 +52,7 @@ - + IP Address @@ -85,10 +85,10 @@ - {{ container.State }} + {{ container.Status|containerstatus }} {{ container|swarmcontainername}} {{ container|containername}} - {{ container.IP ? container.IP : '-' }} + {{ container.IP ? container.IP : '-' }} {{ container|swarmhostname}} {{ container.Image }} {{ container.Command|truncate:60 }} diff --git a/app/components/containers/containersController.js b/app/components/containers/containersController.js index 76fc50428..b7662e243 100644 --- a/app/components/containers/containersController.js +++ b/app/components/containers/containersController.js @@ -4,6 +4,7 @@ function ($scope, Container, Settings, Messages, Config, errorMsgFilter) { $scope.state = {}; $scope.state.displayAll = Settings.displayAll; + $scope.state.displayIP = false; $scope.sortType = 'State'; $scope.sortReverse = true; $scope.state.selectedItemCount = 0; @@ -22,7 +23,11 @@ function ($scope, Container, Settings, Messages, Config, errorMsgFilter) { containers = hideContainers(d); } $scope.containers = containers.map(function (container) { - return new ContainerViewModel(container); + var model = new ContainerViewModel(container); + if (model.IP) { + $scope.state.displayIP = true; + } + return model; }); $('#loadContainersSpinner').hide(); }); diff --git a/app/shared/filters.js b/app/shared/filters.js index 4b5b93700..f1b40abfb 100644 --- a/app/shared/filters.js +++ b/app/shared/filters.js @@ -21,16 +21,31 @@ angular.module('dockerui.filters', []) .filter('containerstatusbadge', function () { 'use strict'; return function (text) { - if (text === 'paused') { + var status = _.toLower(text); + if (status.indexOf('paused') !== -1) { return 'warning'; - } else if (text === 'created') { + } else if (status.indexOf('created') !== -1) { return 'info'; - } else if (text === 'exited') { + } else if (status.indexOf('exited') !== -1) { return 'danger'; } return 'success'; }; }) +.filter('containerstatus', function () { + 'use strict'; + return function (text) { + var status = _.toLower(text); + if (status.indexOf('paused') !== -1) { + return 'paused'; + } else if (status.indexOf('created') !== -1) { + return 'created'; + } else if (status.indexOf('exited') !== -1) { + return 'stopped'; + } + return 'running'; + }; +}) .filter('nodestatusbadge', function () { 'use strict'; return function (text) { diff --git a/app/shared/viewmodel.js b/app/shared/viewmodel.js index 901a2fd86..1b222692e 100644 --- a/app/shared/viewmodel.js +++ b/app/shared/viewmodel.js @@ -10,9 +10,12 @@ function ImageViewModel(data) { function ContainerViewModel(data) { this.Id = data.Id; - this.State = data.State; + this.Status = data.Status; this.Names = data.Names; - this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress; + // Unavailable in Docker < 1.10 + if (data.NetworkSettings) { + this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress; + } this.Image = data.Image; this.Command = data.Command; this.Checked = false; From f6226d19b8240c59229d80116df702c2f68bfd10 Mon Sep 17 00:00:00 2001 From: Anthony Lapenna Date: Wed, 13 Jul 2016 12:42:20 +1200 Subject: [PATCH 10/10] feat(dockerui): Docker CLI compliant flags --- 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: {