From 76d7e280f9cc4dc890c8f25cd87c2b7089320531 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sun, 18 Jan 2015 23:20:49 -0600 Subject: [PATCH 01/10] Added PortBindings to container start. --- .../startContainerController.js | 46 +++++++++++++- .../startContainerController.spec.js | 63 +++++++++++++++++++ .../startContainer/startcontainer.html | 21 +++++++ gruntFile.js | 2 +- 4 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 app/components/startContainer/startContainerController.spec.js diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js index fc99597af..674032d4e 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -9,7 +9,8 @@ function($scope, $routeParams, $location, Container, Messages) { cpuShares: 1024, env: '', commands: '', - volumesFrom: '' + volumesFrom: '', + portBindings: [] }; $scope.commandPlaceholder = '["/bin/echo", "Hello world"]'; @@ -27,6 +28,27 @@ function($scope, $routeParams, $location, Container, Messages) { var loc = $location; var s = $scope; + var exposedPorts = {}; + var portBindings = {}; + // TODO: consider using compatibility library + $scope.config.portBindings.forEach(function(portBinding) { + var intPort = portBinding.intPort + "/tcp"; + var binding = { + HostIp: portBinding.ip, + HostPort: portBinding.extPort + }; + if (portBinding.intPort) { + exposedPorts[intPort] = {}; + if (intPort in portBindings) { + portBindings[intPort].push(binding); + } else { + portBindings[intPort] = [binding]; + } + } else { + // TODO: Send warning message? Internal port need to be specified. + } + }); + Container.create({ Image: id, name: $scope.config.name, @@ -34,10 +56,19 @@ function($scope, $routeParams, $location, Container, Messages) { MemorySwap: $scope.config.memorySwap, CpuShares: $scope.config.cpuShares, Cmd: cmds, - VolumesFrom: $scope.config.volumesFrom + VolumesFrom: $scope.config.volumesFrom, + ExposedPorts: exposedPorts, + HostConfig: { + PortBindings: portBindings + } }, function(d) { if (d.Id) { - ctor.start({id: d.Id}, function(cd) { + ctor.start({ + id: d.Id, + HostConfig: { + PortBindings: portBindings + } + }, function(cd) { $('#create-modal').modal('hide'); loc.path('/containers/' + d.Id + '/'); }, function(e) { @@ -50,4 +81,13 @@ function($scope, $routeParams, $location, Container, Messages) { failedRequestHandler(e, Messages); }); }; + + $scope.addPortBinding = function() { + $scope.config.portBindings.push({ip: '', extPort: '', intPort: ''}); + }; + + $scope.removePortBinding = function(portBinding) { + var idx = $scope.config.portBindings.indexOf(portBinding); + $scope.config.portBindings.splice(idx, 1); + }; }]); diff --git a/app/components/startContainer/startContainerController.spec.js b/app/components/startContainer/startContainerController.spec.js new file mode 100644 index 000000000..97d75133e --- /dev/null +++ b/app/components/startContainer/startContainerController.spec.js @@ -0,0 +1,63 @@ +describe('startContainerController', function () { + var scope, $location, createController, mockContainer, $httpBackend; + + beforeEach(angular.mock.module('dockerui')); + + beforeEach(inject(function ($rootScope, $controller, _$location_) { + $location = _$location_; + scope = $rootScope.$new(); + + createController = function() { + return $controller('StartContainerController', { + '$scope': scope + }); + }; + + angular.mock.inject(function (_Container_, _$httpBackend_) { + mockContainer = _Container_; + $httpBackend = _$httpBackend_; + }); + })); + + describe('Starting a container with options', function () { + it('should issue a correct create request to the Docker remote API', function() { + var controller = createController(); + var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; + var expectedBody = { + "name": "container-name", + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 1024, + "Cmd": null, + "VolumesFrom": "", + "ExposedPorts":{ + "9000/tcp": {}, + }, + "HostConfig": { + "PortBindings":{ + "9000/tcp": [{ + "HostPort": "9999", + "HostIp": "10.20.10.15", + }] + }, + }}; + $httpBackend.expectPOST('/dockerapi/containers/create?name=container-name', expectedBody).respond({ + "Id": id, + "Warnings": null + }); + $httpBackend.expectPOST('/dockerapi/containers/' + id + '/start?').respond({ + "Id": id, + "Warnings": null + }); + + scope.config.name = 'container-name'; + scope.config.portBindings = [{ip: '10.20.10.15', extPort: '9999', intPort: '9000'}] + + //var response = mockContainer.create({}); + scope.create(); + + $httpBackend.flush(); + //console.log(response); + }); + }); +}); \ No newline at end of file diff --git a/app/components/startContainer/startcontainer.html b/app/components/startContainer/startcontainer.html index 42f5f5a7c..91f7ab268 100644 --- a/app/components/startContainer/startcontainer.html +++ b/app/components/startContainer/startcontainer.html @@ -33,6 +33,27 @@ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
diff --git a/gruntFile.js b/gruntFile.js index 7a96965de..2bf53a59a 100644 --- a/gruntFile.js +++ b/gruntFile.js @@ -37,7 +37,7 @@ module.exports = function (grunt) { ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' + ' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n', src: { - js: ['app/**/*.js'], + js: ['app/**/*.js', '!app/**/*.spec.js'], jsTpl: ['<%= distdir %>/templates/**/*.js'], specs: ['test/**/*.spec.js'], scenarios: ['test/**/*.scenario.js'], From 4d22a0448409e54c569c0651a661aea006a3fe12 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Tue, 20 Jan 2015 01:42:08 -0600 Subject: [PATCH 02/10] Added ENV variables to container start. --- .../startContainerController.js | 16 +++++++- .../startContainerController.spec.js | 40 ++++++++++++++++--- .../startContainer/startcontainer.html | 17 ++++++++ test/unit/karma.conf.js | 1 + 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js index 674032d4e..de22b2ebe 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -7,7 +7,7 @@ function($scope, $routeParams, $location, Container, Messages) { memory: 0, memorySwap: 0, cpuShares: 1024, - env: '', + env: [], commands: '', volumesFrom: '', portBindings: [] @@ -28,6 +28,10 @@ function($scope, $routeParams, $location, Container, Messages) { var loc = $location; var s = $scope; + var env = $scope.config.env.map(function(envar) { + return envar.name + '=' + envar.value; + }); + var exposedPorts = {}; var portBindings = {}; // TODO: consider using compatibility library @@ -57,6 +61,7 @@ function($scope, $routeParams, $location, Container, Messages) { CpuShares: $scope.config.cpuShares, Cmd: cmds, VolumesFrom: $scope.config.volumesFrom, + Env: env, ExposedPorts: exposedPorts, HostConfig: { PortBindings: portBindings @@ -90,4 +95,13 @@ function($scope, $routeParams, $location, Container, Messages) { var idx = $scope.config.portBindings.indexOf(portBinding); $scope.config.portBindings.splice(idx, 1); }; + + $scope.addEnv = function() { + $scope.config.env.push({name: '', value: ''}); + }; + + $scope.removeEnv = function(envar) { + var idx = $scope.config.env.indexOf(envar); + $scope.config.env.splice(idx, 1); + }; }]); diff --git a/app/components/startContainer/startContainerController.spec.js b/app/components/startContainer/startContainerController.spec.js index 97d75133e..6d7626a19 100644 --- a/app/components/startContainer/startContainerController.spec.js +++ b/app/components/startContainer/startContainerController.spec.js @@ -19,7 +19,7 @@ describe('startContainerController', function () { }); })); - describe('Starting a container with options', function () { + describe('Create and start a container with port bindings', function () { it('should issue a correct create request to the Docker remote API', function() { var controller = createController(); var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; @@ -30,6 +30,7 @@ describe('startContainerController', function () { "CpuShares": 1024, "Cmd": null, "VolumesFrom": "", + "Env": [], "ExposedPorts":{ "9000/tcp": {}, }, @@ -53,11 +54,40 @@ describe('startContainerController', function () { scope.config.name = 'container-name'; scope.config.portBindings = [{ip: '10.20.10.15', extPort: '9999', intPort: '9000'}] - //var response = mockContainer.create({}); scope.create(); - $httpBackend.flush(); - //console.log(response); }); }); -}); \ No newline at end of file + + describe('Create and start a container with environment variables', function () { + it('should issue a correct create request to the Docker remote API', function() { + var controller = createController(); + var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; + var expectedBody = { + "name": "container-name", + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 1024, + "Cmd": null, + "VolumesFrom": "", + "Env":["SHELL=/bin/bash", "TERM=xterm-256color"], + "ExposedPorts":{}, + "HostConfig": {"PortBindings":{}} + }; + $httpBackend.expectPOST('/dockerapi/containers/create?name=container-name', expectedBody).respond({ + "Id": id, + "Warnings": null + }); + $httpBackend.expectPOST('/dockerapi/containers/' + id + '/start?').respond({ + "Id": id, + "Warnings": null + }); + + scope.config.name = 'container-name'; + scope.config.env = [{name: 'SHELL', value: '/bin/bash'}, {name: 'TERM', value: 'xterm-256color'}]; + + scope.create(); + $httpBackend.flush(); + }); + }); +}); diff --git a/app/components/startContainer/startcontainer.html b/app/components/startContainer/startcontainer.html index 91f7ab268..ae33055b2 100644 --- a/app/components/startContainer/startcontainer.html +++ b/app/components/startContainer/startcontainer.html @@ -33,6 +33,23 @@ +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js index 00f7b04d2..4b5d2412e 100644 --- a/test/unit/karma.conf.js +++ b/test/unit/karma.conf.js @@ -6,6 +6,7 @@ files = [ JASMINE, JASMINE_ADAPTER, 'assets/js/jquery-1.11.1.min.js', + 'assets/js/bootstrap.min.js', 'assets/js/angularjs/1.2.6/angular.min.js', 'assets/js/angularjs/1.2.6/angular-route.min.js', 'assets/js/angularjs/1.2.6/angular-resource.min.js', From c8213bbf3385b2f109037f7bc80e1f3b125d0545 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Tue, 20 Jan 2015 23:31:57 -0600 Subject: [PATCH 03/10] Added VolumesFrom to container start. --- .../startContainerController.js | 28 +++- .../startContainerController.spec.js | 129 +++++++++++++++--- .../startContainer/startcontainer.html | 8 +- 3 files changed, 142 insertions(+), 23 deletions(-) diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js index de22b2ebe..23837f41d 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -1,7 +1,14 @@ angular.module('startContainer', []) -.controller('StartContainerController', ['$scope', '$routeParams', '$location', 'Container', 'Messages', -function($scope, $routeParams, $location, Container, Messages) { +.controller('StartContainerController', ['$scope', '$routeParams', '$location', 'Container', 'Messages', 'containernameFilter', +function($scope, $routeParams, $location, Container, Messages, containernameFilter) { $scope.template = 'app/components/startContainer/startcontainer.html'; + + Container.query({all: 1}, function(d) { + $scope.containerNames = d.map(function(container){ + return containernameFilter(container); + }); + }); + $scope.config = { name: '', memory: 0, @@ -9,7 +16,7 @@ function($scope, $routeParams, $location, Container, Messages) { cpuShares: 1024, env: [], commands: '', - volumesFrom: '', + volumesFrom: [], portBindings: [] }; $scope.commandPlaceholder = '["/bin/echo", "Hello world"]'; @@ -28,6 +35,10 @@ function($scope, $routeParams, $location, Container, Messages) { var loc = $location; var s = $scope; + var volumesFrom = $scope.config.volumesFrom.map(function(volume) { + return volume.name; + }); + var env = $scope.config.env.map(function(envar) { return envar.name + '=' + envar.value; }); @@ -60,7 +71,7 @@ function($scope, $routeParams, $location, Container, Messages) { MemorySwap: $scope.config.memorySwap, CpuShares: $scope.config.cpuShares, Cmd: cmds, - VolumesFrom: $scope.config.volumesFrom, + VolumesFrom: volumesFrom, Env: env, ExposedPorts: exposedPorts, HostConfig: { @@ -104,4 +115,13 @@ function($scope, $routeParams, $location, Container, Messages) { var idx = $scope.config.env.indexOf(envar); $scope.config.env.splice(idx, 1); }; + + $scope.addVolume = function() { + $scope.config.volumesFrom.push({name: ''}); + }; + + $scope.removeVolume = function(volume) { + var idx = $scope.config.volumesFrom.indexOf(volume); + $scope.config.volumesFrom.splice(idx, 1); + }; }]); diff --git a/app/components/startContainer/startContainerController.spec.js b/app/components/startContainer/startContainerController.spec.js index 6d7626a19..acdfb9123 100644 --- a/app/components/startContainer/startContainerController.spec.js +++ b/app/components/startContainer/startContainerController.spec.js @@ -1,9 +1,9 @@ -describe('startContainerController', function () { +describe('startContainerController', function() { var scope, $location, createController, mockContainer, $httpBackend; - + beforeEach(angular.mock.module('dockerui')); - beforeEach(inject(function ($rootScope, $controller, _$location_) { + beforeEach(inject(function($rootScope, $controller, _$location_) { $location = _$location_; scope = $rootScope.$new(); @@ -13,13 +13,13 @@ describe('startContainerController', function () { }); }; - angular.mock.inject(function (_Container_, _$httpBackend_) { + angular.mock.inject(function(_Container_, _$httpBackend_) { mockContainer = _Container_; $httpBackend = _$httpBackend_; }); })); - describe('Create and start a container with port bindings', function () { + describe('Create and start a container with port bindings', function() { it('should issue a correct create request to the Docker remote API', function() { var controller = createController(); var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; @@ -29,19 +29,36 @@ describe('startContainerController', function () { "MemorySwap": 0, "CpuShares": 1024, "Cmd": null, - "VolumesFrom": "", + "VolumesFrom": [], "Env": [], - "ExposedPorts":{ + "ExposedPorts": { "9000/tcp": {}, }, "HostConfig": { - "PortBindings":{ + "PortBindings": { "9000/tcp": [{ "HostPort": "9999", "HostIp": "10.20.10.15", }] }, - }}; + } + }; + + $httpBackend.expectGET('/dockerapi/containers/json?all=1').respond([{ + "Command": "./dockerui -e /docker.sock", + "Created": 1421817232, + "Id": "b17882378cee8ec0136f482681b764cca430befd52a9bfd1bde031f49b8bba9f", + "Image": "dockerui:latest", + "Names": ["/dockerui"], + "Ports": [{ + "IP": "0.0.0.0", + "PrivatePort": 9000, + "PublicPort": 9000, + "Type": "tcp" + }], + "Status": "Up 2 minutes" + }]); + $httpBackend.expectPOST('/dockerapi/containers/create?name=container-name', expectedBody).respond({ "Id": id, "Warnings": null @@ -52,14 +69,18 @@ describe('startContainerController', function () { }); scope.config.name = 'container-name'; - scope.config.portBindings = [{ip: '10.20.10.15', extPort: '9999', intPort: '9000'}] + scope.config.portBindings = [{ + ip: '10.20.10.15', + extPort: '9999', + intPort: '9000' + }] scope.create(); $httpBackend.flush(); }); }); - describe('Create and start a container with environment variables', function () { + describe('Create and start a container with environment variables', function() { it('should issue a correct create request to the Docker remote API', function() { var controller = createController(); var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; @@ -69,11 +90,29 @@ describe('startContainerController', function () { "MemorySwap": 0, "CpuShares": 1024, "Cmd": null, - "VolumesFrom": "", - "Env":["SHELL=/bin/bash", "TERM=xterm-256color"], - "ExposedPorts":{}, - "HostConfig": {"PortBindings":{}} + "VolumesFrom": [], + "Env": ["SHELL=/bin/bash", "TERM=xterm-256color"], + "ExposedPorts": {}, + "HostConfig": { + "PortBindings": {} + } }; + + $httpBackend.expectGET('/dockerapi/containers/json?all=1').respond([{ + "Command": "./dockerui -e /docker.sock", + "Created": 1421817232, + "Id": "b17882378cee8ec0136f482681b764cca430befd52a9bfd1bde031f49b8bba9f", + "Image": "dockerui:latest", + "Names": ["/dockerui"], + "Ports": [{ + "IP": "0.0.0.0", + "PrivatePort": 9000, + "PublicPort": 9000, + "Type": "tcp" + }], + "Status": "Up 2 minutes" + }]); + $httpBackend.expectPOST('/dockerapi/containers/create?name=container-name', expectedBody).respond({ "Id": id, "Warnings": null @@ -84,10 +123,66 @@ describe('startContainerController', function () { }); scope.config.name = 'container-name'; - scope.config.env = [{name: 'SHELL', value: '/bin/bash'}, {name: 'TERM', value: 'xterm-256color'}]; + scope.config.env = [{ + name: 'SHELL', + value: '/bin/bash' + }, { + name: 'TERM', + value: 'xterm-256color' + }]; scope.create(); $httpBackend.flush(); }); }); -}); + + describe('Create and start a container with volumesFrom', function() { + it('should issue a correct create request to the Docker remote API', function() { + var controller = createController(); + var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; + var expectedBody = { + "name": "container-name", + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 1024, + "Cmd": null, + "VolumesFrom": ["parent", "other:ro"], + "Env": [], + "ExposedPorts": {}, + "HostConfig": { + "PortBindings": {} + } + }; + + $httpBackend.expectGET('/dockerapi/containers/json?all=1').respond([{ + "Command": "./dockerui -e /docker.sock", + "Created": 1421817232, + "Id": "b17882378cee8ec0136f482681b764cca430befd52a9bfd1bde031f49b8bba9f", + "Image": "dockerui:latest", + "Names": ["/dockerui"], + "Ports": [{ + "IP": "0.0.0.0", + "PrivatePort": 9000, + "PublicPort": 9000, + "Type": "tcp" + }], + "Status": "Up 2 minutes" + }]); + + $httpBackend.expectPOST('/dockerapi/containers/create?name=container-name', expectedBody).respond({ + "Id": id, + "Warnings": null + }); + $httpBackend.expectPOST('/dockerapi/containers/' + id + '/start?').respond({ + "Id": id, + "Warnings": null + }); + + scope.config.name = 'container-name'; + scope.config.volumesFrom = [{name: "parent"}, {name:"other:ro"}]; + + scope.create(); + $httpBackend.flush(); + }); + }); +}); \ No newline at end of file diff --git a/app/components/startContainer/startcontainer.html b/app/components/startContainer/startcontainer.html index ae33055b2..8429bd6c5 100644 --- a/app/components/startContainer/startcontainer.html +++ b/app/components/startContainer/startcontainer.html @@ -30,8 +30,12 @@
- - + +
+ - Input commands as an array -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
- + Input commands as a raw string or JSON array +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+
-
-
- -
+
+
+ + +
+
+ + +
+
+ + + Input as comma-separated list of numbers +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+
+
+ +
+
@@ -52,34 +118,134 @@
- -
-
- -
-
- - -
-
- - -
-
- - -
-
- -
-
-
+ +
+ + +
+
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + + + + + +
+
+ +
+
+
+
diff --git a/assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js b/assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js new file mode 100755 index 000000000..c9e65424f --- /dev/null +++ b/assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js @@ -0,0 +1,8 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.12.0 - 2014-11-16 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.accordion","ui.bootstrap.collapse","ui.bootstrap.transition"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html"]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(n,o,i){this.groups=[],this.closeOthers=function(t){var e=angular.isDefined(o.closeOthers)?n.$eval(o.closeOthers):i.closeOthers;e&&angular.forEach(this.groups,function(n){n!==t&&(n.isOpen=!1)})},this.addGroup=function(n){var o=this;this.groups.push(n),n.$on("$destroy",function(){o.removeGroup(n)})},this.removeGroup=function(n){var o=this.groups.indexOf(n);-1!==o&&this.groups.splice(o,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(n){this.heading=n}},link:function(n,o,i,t){t.addGroup(n),n.$watch("isOpen",function(o){o&&t.closeOthers(n)}),n.toggleOpen=function(){n.isDisabled||(n.isOpen=!n.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(n,o,i,t,e){t.setHeading(e(n,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(n,o,i,t){n.$watch(function(){return t[i.accordionTransclude]},function(n){n&&(o.html(""),o.append(n))})}}}),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(n){return{link:function(o,i,t){function e(o){function t(){l===e&&(l=void 0)}var e=n(i,o);return l&&l.cancel(),l=e,e.then(t,t),e}function a(){u?(u=!1,r()):(i.removeClass("collapse").addClass("collapsing"),e({height:i[0].scrollHeight+"px"}).then(r))}function r(){i.removeClass("collapsing"),i.addClass("collapse in"),i.css({height:"auto"})}function c(){if(u)u=!1,s(),i.css({height:0});else{i.css({height:i[0].scrollHeight+"px"});{i[0].offsetWidth}i.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(s)}}function s(){i.removeClass("collapsing"),i.addClass("collapse")}var l,u=!0;o.$watch(t.collapse,function(n){n?c():a()})}}}]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(n,o,i){function t(n){for(var o in n)if(void 0!==a.style[o])return n[o]}var e=function(t,a,r){r=r||{};var c=n.defer(),s=e[r.animation?"animationEndEventName":"transitionEndEventName"],l=function(){i.$apply(function(){t.unbind(s,l),c.resolve(t)})};return s&&t.bind(s,l),o(function(){angular.isString(a)?t.addClass(a):angular.isFunction(a)?a(t):angular.isObject(a)&&t.css(a),s||c.resolve(t)}),c.promise.cancel=function(){s&&t.unbind(s,l),c.reject("Transition cancelled")},c.promise},a=document.createElement("trans"),r={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},c={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=t(r),e.animationEndEventName=t(c),e}]),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(n){n.put("template/accordion/accordion-group.html",'
\n
\n

\n {{heading}}\n

\n
\n
\n
\n
\n
\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(n){n.put("template/accordion/accordion.html",'
')}]); \ No newline at end of file diff --git a/gruntFile.js b/gruntFile.js index 2bf53a59a..91d8ae454 100644 --- a/gruntFile.js +++ b/gruntFile.js @@ -86,7 +86,8 @@ module.exports = function (grunt) { angular: { src:['assets/js/angularjs/1.2.6/angular.min.js', 'assets/js/angularjs/1.2.6/angular-route.min.js', - 'assets/js/angularjs/1.2.6/angular-resource.min.js'], + 'assets/js/angularjs/1.2.6/angular-resource.min.js', + 'assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js'], dest: '<%= distdir %>/angular.js' } }, diff --git a/index.html b/index.html index 89e49bfc9..8620969fa 100644 --- a/index.html +++ b/index.html @@ -23,11 +23,7 @@ - - - - - + diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js index 4b5d2412e..ab13ae32c 100644 --- a/test/unit/karma.conf.js +++ b/test/unit/karma.conf.js @@ -10,6 +10,7 @@ files = [ 'assets/js/angularjs/1.2.6/angular.min.js', 'assets/js/angularjs/1.2.6/angular-route.min.js', 'assets/js/angularjs/1.2.6/angular-resource.min.js', + 'assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js', 'test/assets/angular/angular-mocks.js', 'app/**/*.js', 'test/unit/**/*.spec.js', From f75d298fe19286966878ffea7a0ec804a22f74b4 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sun, 25 Jan 2015 17:59:08 -0600 Subject: [PATCH 05/10] Cleaned up controller methods, Added placeholders, fixed PortBindings and error messages. --- .../startContainerController.js | 45 ++----- .../startContainerController.spec.js | 126 ++++++++++++------ .../startContainer/startcontainer.html | 30 +++-- app/shared/filters.js | 11 ++ app/shared/services.js | 2 +- test/unit/shared/filters.spec.js | 9 ++ 6 files changed, 135 insertions(+), 88 deletions(-) diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js index d0bee1909..ddd606aa3 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -1,6 +1,6 @@ angular.module('startContainer', ['ui.bootstrap']) -.controller('StartContainerController', ['$scope', '$routeParams', '$location', 'Container', 'Messages', 'containernameFilter', -function($scope, $routeParams, $location, Container, Messages, containernameFilter) { +.controller('StartContainerController', ['$scope', '$routeParams', '$location', 'Container', 'Messages', 'containernameFilter', 'errorMsgFilter', +function($scope, $routeParams, $location, Container, Messages, containernameFilter, errorMsgFilter) { $scope.template = 'app/components/startContainer/startcontainer.html'; Container.query({all: 1}, function(d) { @@ -13,8 +13,8 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt Env: [], Volumes: [], SecurityOpts: [], - PortBindings: [], HostConfig: { + PortBindings: [], Binds: [], Links: [], Dns: [], @@ -25,8 +25,13 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt } }; + $scope.menuStatus = { + containerOpen: true, + hostConfigOpen: false + }; + function failedRequestHandler(e, Messages) { - Messages.send({class: 'text-error', data: e.data}); + Messages.error('Error', errorMsgFilter(e)); } function rmEmptyKeys(col) { @@ -67,7 +72,7 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt var ExposedPorts = {}; var PortBindings = {}; // TODO: consider using compatibility library - config.PortBindings.forEach(function(portBinding) { + config.HostConfig.PortBindings.forEach(function(portBinding) { var intPort = portBinding.intPort + "/tcp"; var binding = { HostIp: portBinding.ip, @@ -85,7 +90,6 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt } }); config.ExposedPorts = ExposedPorts; - delete config.PortBindings; config.HostConfig.PortBindings = PortBindings; // Remove empty fields from the request to avoid overriding defaults @@ -111,35 +115,6 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt }); }; - $scope.addPortBinding = function() { - $scope.config.PortBindings.push({ip: '', extPort: '', intPort: ''}); - }; - - $scope.removePortBinding = function(portBinding) { - var idx = $scope.config.PortBindings.indexOf(portBinding); - $scope.config.PortBindings.splice(idx, 1); - }; - - // TODO: refactor out - $scope.addEnv = function() { - $scope.config.Env.push({name: '', value: ''}); - }; - - $scope.removeEnv = function(envar) { - var idx = $scope.config.env.indexOf(envar); - $scope.config.Env.splice(idx, 1); - }; - - // Todo: refactor out - $scope.addVolumeFrom = function() { - $scope.config.HostConfig.volumesFrom.push({name: ''}); - }; - - $scope.removeVolumeFrom = function(volume) { - var idx = $scope.config.HostConfig.volumesFrom.indexOf(volume); - $scope.config.HostConfig.volumesFrom.splice(idx, 1); - }; - $scope.addEntry = function(array, entry) { array.push(entry); }; diff --git a/app/components/startContainer/startContainerController.spec.js b/app/components/startContainer/startContainerController.spec.js index 50fe21035..0ce1f15f2 100644 --- a/app/components/startContainer/startContainerController.spec.js +++ b/app/components/startContainer/startContainerController.spec.js @@ -20,18 +20,18 @@ describe('startContainerController', function() { })); function expectGetContainers() { $httpBackend.expectGET('dockerapi/containers/json?all=1').respond([{ - "Command": "./dockerui -e /docker.sock", - "Created": 1421817232, - "Id": "b17882378cee8ec0136f482681b764cca430befd52a9bfd1bde031f49b8bba9f", - "Image": "dockerui:latest", - "Names": ["/dockerui"], - "Ports": [{ - "IP": "0.0.0.0", - "PrivatePort": 9000, - "PublicPort": 9000, - "Type": "tcp" + 'Command': './dockerui -e /docker.sock', + 'Created': 1421817232, + 'Id': 'b17882378cee8ec0136f482681b764cca430befd52a9bfd1bde031f49b8bba9f', + 'Image': 'dockerui:latest', + 'Names': ['/dockerui'], + 'Ports': [{ + 'IP': '0.0.0.0', + 'PrivatePort': 9000, + 'PublicPort': 9000, + 'Type': 'tcp' }], - "Status": "Up 2 minutes" + 'Status': 'Up 2 minutes' }]); } describe('Create and start a container with port bindings', function() { @@ -39,15 +39,15 @@ describe('startContainerController', function() { var controller = createController(); var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; var expectedBody = { - "name": "container-name", - "ExposedPorts": { - "9000/tcp": {}, + 'name': 'container-name', + 'ExposedPorts': { + '9000/tcp': {}, }, - "HostConfig": { - "PortBindings": { - "9000/tcp": [{ - "HostPort": "9999", - "HostIp": "10.20.10.15", + 'HostConfig': { + 'PortBindings': { + '9000/tcp': [{ + 'HostPort': '9999', + 'HostIp': '10.20.10.15', }] }, } @@ -56,16 +56,16 @@ describe('startContainerController', function() { expectGetContainers(); $httpBackend.expectPOST('dockerapi/containers/create?name=container-name', expectedBody).respond({ - "Id": id, - "Warnings": null + 'Id': id, + 'Warnings': null }); $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ - "Id": id, - "Warnings": null + 'Id': id, + 'Warnings': null }); scope.config.name = 'container-name'; - scope.config.PortBindings = [{ + scope.config.HostConfig.PortBindings = [{ ip: '10.20.10.15', extPort: '9999', intPort: '9000' @@ -81,19 +81,19 @@ describe('startContainerController', function() { var controller = createController(); var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; var expectedBody = { - "name": "container-name", - "Env": ["SHELL=/bin/bash", "TERM=xterm-256color"] + 'name': 'container-name', + 'Env': ['SHELL=/bin/bash', 'TERM=xterm-256color'] }; expectGetContainers(); $httpBackend.expectPOST('dockerapi/containers/create?name=container-name', expectedBody).respond({ - "Id": id, - "Warnings": null + 'Id': id, + 'Warnings': null }); $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ - "Id": id, - "Warnings": null + 'Id': id, + 'Warnings': null }); scope.config.name = 'container-name'; @@ -116,25 +116,75 @@ describe('startContainerController', function() { var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; var expectedBody = { HostConfig: { - "VolumesFrom": ["parent", "other:ro"] + 'VolumesFrom': ['parent', 'other:ro'] }, - "name": "container-name" + 'name': 'container-name' }; expectGetContainers(); - $httpBackend.expectPOST('dockerapi/containers/create?name=container-name', expectedBody).respond({ - "Id": id, - "Warnings": null + 'Id': id, + 'Warnings': null }); $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ - "Id": id, - "Warnings": null + 'Id': id, + 'Warnings': null }); scope.config.name = 'container-name'; - scope.config.HostConfig.VolumesFrom = [{name: "parent"}, {name:"other:ro"}]; + scope.config.HostConfig.VolumesFrom = [{name: 'parent'}, {name:'other:ro'}]; + + scope.create(); + $httpBackend.flush(); + }); + }); + + describe('Create and start a container with multiple options', function() { + it('should issue a correct create request to the Docker remote API', function() { + var controller = createController(); + var id = '6abd8bfba81cf8a05a76a4bdefcb36c4b66cd02265f4bfcd0e236468696ebc6c'; + var expectedBody = { + Volumes: ['/var/www'], + SecurityOpts: ['label:type:svirt_apache'], + HostConfig: { + Binds: ['/app:/app'], + Links: ['web:db'], + Dns: ['8.8.8.8'], + DnsSearch: ['example.com'], + CapAdd: ['cap_sys_admin'], + CapDrop: ['cap_foo_bar'] + }, + name: 'container-name' + }; + + expectGetContainers(); + + $httpBackend.expectPOST('dockerapi/containers/create?name=container-name', expectedBody).respond({ + 'Id': id, + 'Warnings': null + }); + $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ + 'Id': id, + 'Warnings': null + }); + + scope.config.name = 'container-name'; + scope.config.Volumes = [{name: '/var/www'}]; + scope.config.SecurityOpts = [{name: 'label:type:svirt_apache'}]; + scope.config.NetworkDisabled = true; + scope.config.Tty = true; + scope.config.OpenStdin = true; + scope.config.StdinOnce = true; + + scope.config.HostConfig.Binds = [{name: '/app:/app'}]; + scope.config.HostConfig.Links = [{name: 'web:db'}]; + scope.config.HostConfig.Dns = [{name: '8.8.8.8'}]; + scope.config.HostConfig.DnsSearch = [{name: 'example.com'}]; + scope.config.HostConfig.CapAdd = [{name: 'cap_sys_admin'}]; + scope.config.HostConfig.CapDrop = [{name: 'cap_foo_bar'}]; + scope.config.HostConfig.PublishAllPorts = true; + scope.config.HostConfig.Privileged = true; scope.create(); $httpBackend.flush(); diff --git a/app/components/startContainer/startcontainer.html b/app/components/startContainer/startcontainer.html index 3599698f4..309fe03d0 100644 --- a/app/components/startContainer/startcontainer.html +++ b/app/components/startContainer/startcontainer.html @@ -8,7 +8,7 @@ +
@@ -115,15 +116,15 @@
- +
- + - +
@@ -131,7 +132,7 @@
- +
@@ -161,7 +162,7 @@
- +
@@ -171,7 +172,7 @@
- +
@@ -183,7 +184,7 @@
- +
@@ -206,10 +207,10 @@
@@ -234,10 +236,10 @@ - +
- +
diff --git a/app/shared/filters.js b/app/shared/filters.js index 2b390d9c9..f0b7c6daa 100644 --- a/app/shared/filters.js +++ b/app/shared/filters.js @@ -99,4 +99,15 @@ angular.module('dockerui.filters', []) var date = new Date(data * 1000); return date.toDateString(); }; + }) + .filter('errorMsg', function() { + return function(object) { + var idx = 0; + var msg = ''; + while (object[idx] && typeof(object[idx]) === 'string') { + msg += object[idx]; + idx++; + } + return msg; + }; }); diff --git a/app/shared/services.js b/app/shared/services.js index 5a06bc5db..6dbed1f94 100644 --- a/app/shared/services.js +++ b/app/shared/services.js @@ -119,7 +119,7 @@ angular.module('dockerui.services', ['ngResource']) $.gritter.add({ title: title, text: text, - time: 6000, + time: 10000, before_open: function() { if($('.gritter-item-wrapper').length === 4) { return false; diff --git a/test/unit/shared/filters.spec.js b/test/unit/shared/filters.spec.js index b6c753faf..f48640783 100644 --- a/test/unit/shared/filters.spec.js +++ b/test/unit/shared/filters.spec.js @@ -153,4 +153,13 @@ describe('filters', function () { expect(getdateFilter(1420424998)).toBe('Sun Jan 04 2015'); })); }); + + describe('errorMsgFilter', function() { + it('should convert the $resource object to a string message', + inject(function(errorMsgFilter) { + var response = {'0':'C','1':'o','2':'n','3':'f','4':'l','5':'i','6':'c','7':'t','8':',','9':' ','10':'T','11':'h','12':'e','13':' ','14':'n','15':'a','16':'m','17':'e','18':' ','19':'u','20':'b','21':'u','22':'n','23':'t','24':'u','25':'-','26':'s','27':'l','28':'e','29':'e','30':'p','31':'-','32':'r','33':'u','34':'n','35':'t','36':'i','37':'m','38':'e','39':' ','40':'i','41':'s','42':' ','43':'a','44':'l','45':'r','46':'e','47':'a','48':'d','49':'y','50':' ','51':'a','52':'s','53':'s','54':'i','55':'g','56':'n','57':'e','58':'d','59':' ','60':'t','61':'o','62':' ','63':'b','64':'6','65':'9','66':'e','67':'5','68':'3','69':'a','70':'6','71':'2','72':'2','73':'c','74':'8','75':'.','76':' ','77':'Y','78':'o','79':'u','80':' ','81':'h','82':'a','83':'v','84':'e','85':' ','86':'t','87':'o','88':' ','89':'d','90':'e','91':'l','92':'e','93':'t','94':'e','95':' ','96':'(','97':'o','98':'r','99':' ','100':'r','101':'e','102':'n','103':'a','104':'m','105':'e','106':')','107':' ','108':'t','109':'h','110':'a','111':'t','112':' ','113':'c','114':'o','115':'n','116':'t','117':'a','118':'i','119':'n','120':'e','121':'r','122':' ','123':'t','124':'o','125':' ','126':'b','127':'e','128':' ','129':'a','130':'b','131':'l','132':'e','133':' ','134':'t','135':'o','136':' ','137':'a','138':'s','139':'s','140':'i','141':'g','142':'n','143':' ','144':'u','145':'b','146':'u','147':'n','148':'t','149':'u','150':'-','151':'s','152':'l','153':'e','154':'e','155':'p','156':'-','157':'r','158':'u','159':'n','160':'t','161':'i','162':'m','163':'e','164':' ','165':'t','166':'o','167':' ','168':'a','169':' ','170':'c','171':'o','172':'n','173':'t','174':'a','175':'i','176':'n','177':'e','178':'r','179':' ','180':'a','181':'g','182':'a','183':'i','184':'n','185':'.','186':'\n','$promise':{},'$resolved':true}; + var message = 'Conflict, The name ubuntu-sleep-runtime is already assigned to b69e53a622c8. You have to delete (or rename) that container to be able to assign ubuntu-sleep-runtime to a container again.\n'; + expect(errorMsgFilter(response)).toBe(message); + })); + }); }); \ No newline at end of file From 314fc51f6d8a669ee0d53e4d7f1ede5a48111ac6 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sun, 25 Jan 2015 18:12:34 -0600 Subject: [PATCH 06/10] Updated /dist for release. --- dist/angular.js | 9 + .../ui-bootstrap-custom-tpls-0.12.0.min.js | 8 + dist/dockerui.js | 401 +++++++++++++++--- dist/index.html | 6 +- dist/templates/app.js | 258 +++++++++-- 5 files changed, 599 insertions(+), 83 deletions(-) create mode 100644 dist/assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js diff --git a/dist/angular.js b/dist/angular.js index cedbd4c38..09000f025 100644 --- a/dist/angular.js +++ b/dist/angular.js @@ -228,3 +228,12 @@ var B={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},r g[c]:r.defaults[c];a.isDefined(f)&&null!==f?(p=encodeURIComponent(f).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),e=e.replace(RegExp(":"+c+"(\\W|$)","g"),p+"$1")):e=e.replace(RegExp("(/?):"+c+"(\\W|$)","g"),function(a,c,d){return"/"==d.charAt(0)?d:c+d})});e=e.replace(/\/+$/,"");e=e.replace(/\/\.(?=\w+($|\?))/,".");c.url=e.replace(/\/\\\./,"/.");s(g,function(a,e){r.urlParams[e]|| (c.params=c.params||{},c.params[e]=a)})}};return t}])})(window,window.angular); //# sourceMappingURL=angular-resource.min.js.map + +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.12.0 - 2014-11-16 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.accordion","ui.bootstrap.collapse","ui.bootstrap.transition"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html"]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(n,o,i){this.groups=[],this.closeOthers=function(t){var e=angular.isDefined(o.closeOthers)?n.$eval(o.closeOthers):i.closeOthers;e&&angular.forEach(this.groups,function(n){n!==t&&(n.isOpen=!1)})},this.addGroup=function(n){var o=this;this.groups.push(n),n.$on("$destroy",function(){o.removeGroup(n)})},this.removeGroup=function(n){var o=this.groups.indexOf(n);-1!==o&&this.groups.splice(o,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(n){this.heading=n}},link:function(n,o,i,t){t.addGroup(n),n.$watch("isOpen",function(o){o&&t.closeOthers(n)}),n.toggleOpen=function(){n.isDisabled||(n.isOpen=!n.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(n,o,i,t,e){t.setHeading(e(n,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(n,o,i,t){n.$watch(function(){return t[i.accordionTransclude]},function(n){n&&(o.html(""),o.append(n))})}}}),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(n){return{link:function(o,i,t){function e(o){function t(){l===e&&(l=void 0)}var e=n(i,o);return l&&l.cancel(),l=e,e.then(t,t),e}function a(){u?(u=!1,r()):(i.removeClass("collapse").addClass("collapsing"),e({height:i[0].scrollHeight+"px"}).then(r))}function r(){i.removeClass("collapsing"),i.addClass("collapse in"),i.css({height:"auto"})}function c(){if(u)u=!1,s(),i.css({height:0});else{i.css({height:i[0].scrollHeight+"px"});{i[0].offsetWidth}i.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(s)}}function s(){i.removeClass("collapsing"),i.addClass("collapse")}var l,u=!0;o.$watch(t.collapse,function(n){n?c():a()})}}}]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(n,o,i){function t(n){for(var o in n)if(void 0!==a.style[o])return n[o]}var e=function(t,a,r){r=r||{};var c=n.defer(),s=e[r.animation?"animationEndEventName":"transitionEndEventName"],l=function(){i.$apply(function(){t.unbind(s,l),c.resolve(t)})};return s&&t.bind(s,l),o(function(){angular.isString(a)?t.addClass(a):angular.isFunction(a)?a(t):angular.isObject(a)&&t.css(a),s||c.resolve(t)}),c.promise.cancel=function(){s&&t.unbind(s,l),c.reject("Transition cancelled")},c.promise},a=document.createElement("trans"),r={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},c={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=t(r),e.animationEndEventName=t(c),e}]),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(n){n.put("template/accordion/accordion-group.html",'
\n
\n

\n {{heading}}\n

\n
\n
\n
\n
\n
\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(n){n.put("template/accordion/accordion.html",'
')}]); \ No newline at end of file diff --git a/dist/assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js b/dist/assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js new file mode 100644 index 000000000..c9e65424f --- /dev/null +++ b/dist/assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js @@ -0,0 +1,8 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.12.0 - 2014-11-16 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.accordion","ui.bootstrap.collapse","ui.bootstrap.transition"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html"]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(n,o,i){this.groups=[],this.closeOthers=function(t){var e=angular.isDefined(o.closeOthers)?n.$eval(o.closeOthers):i.closeOthers;e&&angular.forEach(this.groups,function(n){n!==t&&(n.isOpen=!1)})},this.addGroup=function(n){var o=this;this.groups.push(n),n.$on("$destroy",function(){o.removeGroup(n)})},this.removeGroup=function(n){var o=this.groups.indexOf(n);-1!==o&&this.groups.splice(o,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(n){this.heading=n}},link:function(n,o,i,t){t.addGroup(n),n.$watch("isOpen",function(o){o&&t.closeOthers(n)}),n.toggleOpen=function(){n.isDisabled||(n.isOpen=!n.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(n,o,i,t,e){t.setHeading(e(n,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(n,o,i,t){n.$watch(function(){return t[i.accordionTransclude]},function(n){n&&(o.html(""),o.append(n))})}}}),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(n){return{link:function(o,i,t){function e(o){function t(){l===e&&(l=void 0)}var e=n(i,o);return l&&l.cancel(),l=e,e.then(t,t),e}function a(){u?(u=!1,r()):(i.removeClass("collapse").addClass("collapsing"),e({height:i[0].scrollHeight+"px"}).then(r))}function r(){i.removeClass("collapsing"),i.addClass("collapse in"),i.css({height:"auto"})}function c(){if(u)u=!1,s(),i.css({height:0});else{i.css({height:i[0].scrollHeight+"px"});{i[0].offsetWidth}i.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(s)}}function s(){i.removeClass("collapsing"),i.addClass("collapse")}var l,u=!0;o.$watch(t.collapse,function(n){n?c():a()})}}}]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(n,o,i){function t(n){for(var o in n)if(void 0!==a.style[o])return n[o]}var e=function(t,a,r){r=r||{};var c=n.defer(),s=e[r.animation?"animationEndEventName":"transitionEndEventName"],l=function(){i.$apply(function(){t.unbind(s,l),c.resolve(t)})};return s&&t.bind(s,l),o(function(){angular.isString(a)?t.addClass(a):angular.isFunction(a)?a(t):angular.isObject(a)&&t.css(a),s||c.resolve(t)}),c.promise.cancel=function(){s&&t.unbind(s,l),c.reject("Transition cancelled")},c.promise},a=document.createElement("trans"),r={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},c={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=t(r),e.animationEndEventName=t(c),e}]),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(n){n.put("template/accordion/accordion-group.html",'
\n
\n

\n {{heading}}\n

\n
\n
\n
\n
\n
\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(n){n.put("template/accordion/accordion.html",'
')}]); \ No newline at end of file diff --git a/dist/dockerui.js b/dist/dockerui.js index 1cec52022..67b0d0359 100644 --- a/dist/dockerui.js +++ b/dist/dockerui.js @@ -1,4 +1,4 @@ -/*! dockerui - v0.6.0 - 2015-01-18 +/*! dockerui - v0.6.0 - 2015-01-25 * https://github.com/crosbymichael/dockerui * Copyright (c) 2015 Michael Crosby; * Licensed MIT @@ -17,10 +17,10 @@ angular.module('dockerui', ['dockerui.templates', 'ngRoute', 'dockerui.services' }]) // This is your docker url that the api will use to make requests // You need to set this to the api endpoint without the port i.e. http://192.168.1.9 - .constant('DOCKER_ENDPOINT', '/dockerapi') + .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('UI_VERSION', 'v0.6.0') - .constant('DOCKER_API_VERSION', 'v1.15'); + .constant('DOCKER_API_VERSION', 'v1.16'); angular.module('builder', []) .controller('BuilderController', ['$scope', 'Dockerfile', 'Messages', @@ -501,44 +501,108 @@ function($scope, Container, Settings) { }); }]); -angular.module('startContainer', []) -.controller('StartContainerController', ['$scope', '$routeParams', '$location', 'Container', 'Messages', -function($scope, $routeParams, $location, Container, Messages) { +angular.module('startContainer', ['ui.bootstrap']) +.controller('StartContainerController', ['$scope', '$routeParams', '$location', 'Container', 'Messages', 'containernameFilter', 'errorMsgFilter', +function($scope, $routeParams, $location, Container, Messages, containernameFilter, errorMsgFilter) { $scope.template = 'app/components/startContainer/startcontainer.html'; + + Container.query({all: 1}, function(d) { + $scope.containerNames = d.map(function(container){ + return containernameFilter(container); + }); + }); + $scope.config = { - name: '', - memory: 0, - memorySwap: 0, - cpuShares: 1024, - env: '', - commands: '', - volumesFrom: '' + Env: [], + Volumes: [], + SecurityOpts: [], + HostConfig: { + PortBindings: [], + Binds: [], + Links: [], + Dns: [], + DnsSearch: [], + VolumesFrom: [], + CapAdd: [], + CapDrop: [] + } + }; + + $scope.menuStatus = { + containerOpen: true, + hostConfigOpen: false }; - $scope.commandPlaceholder = '["/bin/echo", "Hello world"]'; function failedRequestHandler(e, Messages) { - Messages.send({class: 'text-error', data: e.data}); + Messages.error('Error', errorMsgFilter(e)); + } + + function rmEmptyKeys(col) { + for (var key in col) { + if (col[key] === null || col[key] === undefined || col[key] === '' || $.isEmptyObject(col[key]) || col[key].length === 0) { + delete col[key]; + } + } + } + + function getNames(arr) { + return arr.map(function(item) {return item.name;}); } $scope.create = function() { - var cmds = null; - if ($scope.config.commands !== '') { - cmds = angular.fromJson($scope.config.commands); + // Copy the config before transforming fields to the remote API format + var config = angular.copy($scope.config); + + config.Image = $routeParams.id; + + if (config.Cmd && config.Cmd[0] === "[") { + config.Cmd = angular.fromJson(config.Cmd); } - var id = $routeParams.id; + + config.Env = config.Env.map(function(envar) {return envar.name + '=' + envar.value;}); + + config.Volumes = getNames(config.Volumes); + config.SecurityOpts = getNames(config.SecurityOpts); + + config.HostConfig.VolumesFrom = getNames(config.HostConfig.VolumesFrom); + config.HostConfig.Binds = getNames(config.HostConfig.Binds); + config.HostConfig.Links = getNames(config.HostConfig.Links); + config.HostConfig.Dns = getNames(config.HostConfig.Dns); + config.HostConfig.DnsSearch = getNames(config.HostConfig.DnsSearch); + config.HostConfig.CapAdd = getNames(config.HostConfig.CapAdd); + config.HostConfig.CapDrop = getNames(config.HostConfig.CapDrop); + + var ExposedPorts = {}; + var PortBindings = {}; + // TODO: consider using compatibility library + config.HostConfig.PortBindings.forEach(function(portBinding) { + var intPort = portBinding.intPort + "/tcp"; + var binding = { + HostIp: portBinding.ip, + HostPort: portBinding.extPort + }; + if (portBinding.intPort) { + ExposedPorts[intPort] = {}; + if (intPort in PortBindings) { + PortBindings[intPort].push(binding); + } else { + PortBindings[intPort] = [binding]; + } + } else { + // TODO: Send warning message? Internal port need to be specified. + } + }); + config.ExposedPorts = ExposedPorts; + config.HostConfig.PortBindings = PortBindings; + + // Remove empty fields from the request to avoid overriding defaults + rmEmptyKeys(config.HostConfig); + rmEmptyKeys(config); + var ctor = Container; var loc = $location; var s = $scope; - - Container.create({ - Image: id, - name: $scope.config.name, - Memory: $scope.config.memory, - MemorySwap: $scope.config.memorySwap, - CpuShares: $scope.config.cpuShares, - Cmd: cmds, - VolumesFrom: $scope.config.volumesFrom - }, function(d) { + Container.create(config, function(d) { if (d.Id) { ctor.start({id: d.Id}, function(cd) { $('#create-modal').modal('hide'); @@ -553,6 +617,14 @@ function($scope, $routeParams, $location, Container, Messages) { failedRequestHandler(e, Messages); }); }; + + $scope.addEntry = function(array, entry) { + array.push(entry); + }; + $scope.rmEntry = function(array, entry) { + var idx = array.indexOf(entry); + array.splice(idx, 1); + }; }]); angular.module('dockerui.filters', []) @@ -656,6 +728,17 @@ angular.module('dockerui.filters', []) var date = new Date(data * 1000); return date.toDateString(); }; + }) + .filter('errorMsg', function() { + return function(object) { + var idx = 0; + var msg = ''; + while (object[idx] && typeof(object[idx]) === 'string') { + msg += object[idx]; + idx++; + } + return msg; + }; }); angular.module('dockerui.services', ['ngResource']) @@ -779,7 +862,7 @@ angular.module('dockerui.services', ['ngResource']) $.gritter.add({ title: title, text: text, - time: 6000, + time: 10000, before_open: function() { if($('.gritter-item-wrapper').length === 4) { return false; @@ -1432,38 +1515,248 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "

Create And Start Container From Image

\n" + " \n" + "
\n" + - " \n" + + " \n" + + " \n" + + " \n" + "
\n" + - "
\n" + - " \n" + - " \n" + - " Input commands as an array\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " Input commands as a raw string or JSON array\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + "
\n" + - "
\n" + - " \n" + - " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " Input as comma-separated list of numbers\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + "
\n" + - "
\n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - " \n" + - " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + "
\n" + + " \n" + + "
\n" + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + " \n" + "
\n" + "
\n" + - " Create\n" + + " Create\n" + "
\n" + " \n" + " \n" + diff --git a/dist/index.html b/dist/index.html index 50008da8d..11d129a16 100644 --- a/dist/index.html +++ b/dist/index.html @@ -23,11 +23,7 @@ - - - - - + diff --git a/dist/templates/app.js b/dist/templates/app.js index dc76c3ec4..008a307e9 100644 --- a/dist/templates/app.js +++ b/dist/templates/app.js @@ -553,38 +553,248 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "

Create And Start Container From Image

\n" + " \n" + "
\n" + - "
\n" + + " \n" + + " \n" + + " \n" + "
\n" + - "
\n" + - " \n" + - " \n" + - " Input commands as an array\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " Input commands as a raw string or JSON array\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + "
\n" + - "
\n" + - " \n" + - " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " Input as comma-separated list of numbers\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + "
\n" + - "
\n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - " \n" + - " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + "
\n" + + " \n" + + "
\n" + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + "
\n" + "
\n" + "
\n" + - " Create\n" + + " Create\n" + "
\n" + " \n" + " \n" + From 687ed7bac2cd6b3484e947a9c7a3552b16e39eb2 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sun, 25 Jan 2015 19:18:46 -0600 Subject: [PATCH 07/10] Fixed Cmd parsing for strings, added error handling to start step. --- .../startContainer/startContainerController.js | 17 +++++++++++++---- .../startContainerController.spec.js | 8 ++++---- dist/dockerui.js | 17 +++++++++++++---- test/unit/karma.conf.js | 1 + 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js index ddd606aa3..c4da4e99b 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -54,6 +54,8 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt if (config.Cmd && config.Cmd[0] === "[") { config.Cmd = angular.fromJson(config.Cmd); + } else if (config.Cmd) { + config.Cmd = config.Cmd.split(' '); } config.Env = config.Env.map(function(envar) {return envar.name + '=' + envar.value;}); @@ -71,7 +73,6 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt var ExposedPorts = {}; var PortBindings = {}; - // TODO: consider using compatibility library config.HostConfig.PortBindings.forEach(function(portBinding) { var intPort = portBinding.intPort + "/tcp"; var binding = { @@ -86,7 +87,7 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt PortBindings[intPort] = [binding]; } } else { - // TODO: Send warning message? Internal port need to be specified. + Messages.send('Warning', 'Internal port must be specified for PortBindings'); } }); config.ExposedPorts = ExposedPorts; @@ -102,8 +103,16 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt Container.create(config, function(d) { if (d.Id) { ctor.start({id: d.Id}, function(cd) { - $('#create-modal').modal('hide'); - loc.path('/containers/' + d.Id + '/'); + if (cd.id) { + Messages.send('Container Started', d.Id); + $('#create-modal').modal('hide'); + loc.path('/containers/' + d.Id + '/'); + } else { + failedRequestHandler(cd, Messages); + ctor.remove({id: d.Id}, function() { + Messages.send('Container Removed', d.Id); + }); + } }, function(e) { failedRequestHandler(e, Messages); }); diff --git a/app/components/startContainer/startContainerController.spec.js b/app/components/startContainer/startContainerController.spec.js index 0ce1f15f2..8b51e029a 100644 --- a/app/components/startContainer/startContainerController.spec.js +++ b/app/components/startContainer/startContainerController.spec.js @@ -60,7 +60,7 @@ describe('startContainerController', function() { 'Warnings': null }); $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ - 'Id': id, + 'id': id, 'Warnings': null }); @@ -92,7 +92,7 @@ describe('startContainerController', function() { 'Warnings': null }); $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ - 'Id': id, + 'id': id, 'Warnings': null }); @@ -128,7 +128,7 @@ describe('startContainerController', function() { 'Warnings': null }); $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ - 'Id': id, + 'id': id, 'Warnings': null }); @@ -165,7 +165,7 @@ describe('startContainerController', function() { 'Warnings': null }); $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ - 'Id': id, + 'id': id, 'Warnings': null }); diff --git a/dist/dockerui.js b/dist/dockerui.js index 67b0d0359..847ebbf09 100644 --- a/dist/dockerui.js +++ b/dist/dockerui.js @@ -557,6 +557,8 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt if (config.Cmd && config.Cmd[0] === "[") { config.Cmd = angular.fromJson(config.Cmd); + } else if (config.Cmd) { + config.Cmd = config.Cmd.split(' '); } config.Env = config.Env.map(function(envar) {return envar.name + '=' + envar.value;}); @@ -574,7 +576,6 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt var ExposedPorts = {}; var PortBindings = {}; - // TODO: consider using compatibility library config.HostConfig.PortBindings.forEach(function(portBinding) { var intPort = portBinding.intPort + "/tcp"; var binding = { @@ -589,7 +590,7 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt PortBindings[intPort] = [binding]; } } else { - // TODO: Send warning message? Internal port need to be specified. + Messages.send('Warning', 'Internal port must be specified for PortBindings'); } }); config.ExposedPorts = ExposedPorts; @@ -605,8 +606,16 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt Container.create(config, function(d) { if (d.Id) { ctor.start({id: d.Id}, function(cd) { - $('#create-modal').modal('hide'); - loc.path('/containers/' + d.Id + '/'); + if (cd.id) { + Messages.send('Container Started', d.Id); + $('#create-modal').modal('hide'); + loc.path('/containers/' + d.Id + '/'); + } else { + failedRequestHandler(cd, Messages); + ctor.remove({id: d.Id}, function() { + Messages.send('Container Removed', d.Id); + }); + } }, function(e) { failedRequestHandler(e, Messages); }); diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js index ab13ae32c..40cb82077 100644 --- a/test/unit/karma.conf.js +++ b/test/unit/karma.conf.js @@ -6,6 +6,7 @@ files = [ JASMINE, JASMINE_ADAPTER, 'assets/js/jquery-1.11.1.min.js', + 'assets/js/jquery.gritter.min.js', 'assets/js/bootstrap.min.js', 'assets/js/angularjs/1.2.6/angular.min.js', 'assets/js/angularjs/1.2.6/angular-route.min.js', From a80971dd2718df4023ab39e570a179a240579a28 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Wed, 28 Jan 2015 02:28:41 -0600 Subject: [PATCH 08/10] Implemented RestartPolicy, Devices, and LxcConf. Unified label convention. --- .../startContainerController.js | 8 +- .../startContainerController.spec.js | 8 +- .../startContainer/startcontainer.html | 80 ++++++++++++----- dist/dockerui.js | 90 +++++++++++++------ dist/templates/app.js | 80 ++++++++++++----- 5 files changed, 193 insertions(+), 73 deletions(-) diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js index c4da4e99b..b7f699d84 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -21,7 +21,9 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt DnsSearch: [], VolumesFrom: [], CapAdd: [], - CapDrop: [] + CapDrop: [], + Devices: [], + LxcConf: [] } }; @@ -70,6 +72,10 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt config.HostConfig.DnsSearch = getNames(config.HostConfig.DnsSearch); config.HostConfig.CapAdd = getNames(config.HostConfig.CapAdd); config.HostConfig.CapDrop = getNames(config.HostConfig.CapDrop); + config.HostConfig.LxcConf = config.HostConfig.LxcConf.reduce(function(prev, cur, idx){ + prev[cur.name] = cur.value; + return prev; + }, {}); var ExposedPorts = {}; var PortBindings = {}; diff --git a/app/components/startContainer/startContainerController.spec.js b/app/components/startContainer/startContainerController.spec.js index 8b51e029a..b8c6f398e 100644 --- a/app/components/startContainer/startContainerController.spec.js +++ b/app/components/startContainer/startContainerController.spec.js @@ -153,7 +153,10 @@ describe('startContainerController', function() { Dns: ['8.8.8.8'], DnsSearch: ['example.com'], CapAdd: ['cap_sys_admin'], - CapDrop: ['cap_foo_bar'] + CapDrop: ['cap_foo_bar'], + Devices: [{ 'PathOnHost': '/dev/deviceName', 'PathInContainer': '/dev/deviceName', 'CgroupPermissions': 'mrw'}], + LxcConf: {'lxc.utsname':'docker'}, + RestartPolicy: {name: 'always', MaximumRetryCount: 5} }, name: 'container-name' }; @@ -185,6 +188,9 @@ describe('startContainerController', function() { scope.config.HostConfig.CapDrop = [{name: 'cap_foo_bar'}]; scope.config.HostConfig.PublishAllPorts = true; scope.config.HostConfig.Privileged = true; + scope.config.HostConfig.RestartPolicy = {name: 'always', MaximumRetryCount: 5}; + scope.config.HostConfig.Devices = [{ 'PathOnHost': '/dev/deviceName', 'PathInContainer': '/dev/deviceName', 'CgroupPermissions': 'mrw'}]; + scope.config.HostConfig.LxcConf = [{name: 'lxc.utsname', value: 'docker'}]; scope.create(); $httpBackend.flush(); diff --git a/app/components/startContainer/startcontainer.html b/app/components/startContainer/startcontainer.html index 309fe03d0..4e5290e6f 100644 --- a/app/components/startContainer/startcontainer.html +++ b/app/components/startContainer/startcontainer.html @@ -54,11 +54,11 @@
- +
- +
@@ -91,7 +91,7 @@
- +
@@ -104,7 +104,7 @@

- +
@@ -120,7 +120,7 @@
- +
@@ -149,14 +149,14 @@
- +
- +
@@ -166,7 +166,7 @@
- +
@@ -176,10 +176,8 @@
- + - -
@@ -188,8 +186,10 @@
- +
+ +
@@ -203,7 +203,7 @@
- +
+
+
+ + +
+
+ +
+
+
+ +
+
+ +
+
+ + + + + + + +
+
+ +
+
+
diff --git a/dist/dockerui.js b/dist/dockerui.js index 847ebbf09..381fc5c10 100644 --- a/dist/dockerui.js +++ b/dist/dockerui.js @@ -1,4 +1,4 @@ -/*! dockerui - v0.6.0 - 2015-01-25 +/*! dockerui - v0.6.0 - 2015-01-28 * https://github.com/crosbymichael/dockerui * Copyright (c) 2015 Michael Crosby; * Licensed MIT @@ -524,7 +524,9 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt DnsSearch: [], VolumesFrom: [], CapAdd: [], - CapDrop: [] + CapDrop: [], + Devices: [], + LxcConf: [] } }; @@ -573,6 +575,10 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt config.HostConfig.DnsSearch = getNames(config.HostConfig.DnsSearch); config.HostConfig.CapAdd = getNames(config.HostConfig.CapAdd); config.HostConfig.CapDrop = getNames(config.HostConfig.CapDrop); + config.HostConfig.LxcConf = config.HostConfig.LxcConf.reduce(function(prev, cur, idx){ + prev[cur.name] = cur.value; + return prev; + }, {}); var ExposedPorts = {}; var PortBindings = {}; @@ -1572,11 +1578,11 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + "
\n" + "
\n" + - " \n" + + " \n" + " \n" + "
\n" + "
\n" + - " \n" + + " \n" + " \n" + "
\n" + "
\n" + @@ -1609,7 +1615,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + @@ -1622,7 +1628,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + "
\n" + @@ -1638,7 +1644,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + " \n" + " \n" + @@ -1667,14 +1673,14 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + @@ -1684,7 +1690,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + @@ -1694,10 +1700,8 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + - " \n" + - "
\n" + "
\n" + " \n" + "
\n" + @@ -1706,8 +1710,10 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + + " \n" + + "
\n" + "
\n" + " \n" + " \n" + @@ -1721,7 +1727,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + "
\n" + "
\n" + " \n" + diff --git a/dist/templates/app.js b/dist/templates/app.js index 008a307e9..0015a9879 100644 --- a/dist/templates/app.js +++ b/dist/templates/app.js @@ -601,11 +601,11 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + "
\n" + "
\n" + - " \n" + + " \n" + " \n" + "
\n" + "
\n" + - " \n" + + " \n" + " \n" + "
\n" + "
\n" + @@ -638,7 +638,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + @@ -651,7 +651,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + "
\n" + @@ -667,7 +667,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + " \n" + " \n" + @@ -696,14 +696,14 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + @@ -713,7 +713,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + @@ -723,10 +723,8 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + - " \n" + - "
\n" + "
\n" + " \n" + "
\n" + @@ -735,8 +733,10 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + + " \n" + + "
\n" + "
\n" + " \n" + " \n" + @@ -750,7 +750,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t " \n" + "
\n" + "
\n" + - " \n" + + " \n" + "
\n" + "
\n" + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + "
\n" + "
\n" + " \n" + From fd68039cb9237f52344be38c6be39a3dde9aac82 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Wed, 28 Jan 2015 19:26:48 -0600 Subject: [PATCH 09/10] Added ExtraHosts option. --- .../startContainerController.js | 4 +++- .../startContainerController.spec.js | 2 ++ .../startContainer/startcontainer.html | 19 +++++++++++++++ dist/dockerui.js | 23 ++++++++++++++++++- dist/templates/app.js | 19 +++++++++++++++ 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js index b7f699d84..f913d55a6 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -23,7 +23,8 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt CapAdd: [], CapDrop: [], Devices: [], - LxcConf: [] + LxcConf: [], + ExtraHosts: [] } }; @@ -76,6 +77,7 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt prev[cur.name] = cur.value; return prev; }, {}); + config.HostConfig.ExtraHosts = config.HostConfig.ExtraHosts.map(function(entry) {return entry.host + ':' + entry.ip;}); var ExposedPorts = {}; var PortBindings = {}; diff --git a/app/components/startContainer/startContainerController.spec.js b/app/components/startContainer/startContainerController.spec.js index b8c6f398e..6b359306e 100644 --- a/app/components/startContainer/startContainerController.spec.js +++ b/app/components/startContainer/startContainerController.spec.js @@ -156,6 +156,7 @@ describe('startContainerController', function() { CapDrop: ['cap_foo_bar'], Devices: [{ 'PathOnHost': '/dev/deviceName', 'PathInContainer': '/dev/deviceName', 'CgroupPermissions': 'mrw'}], LxcConf: {'lxc.utsname':'docker'}, + ExtraHosts: ['hostname:127.0.0.1'], RestartPolicy: {name: 'always', MaximumRetryCount: 5} }, name: 'container-name' @@ -191,6 +192,7 @@ describe('startContainerController', function() { scope.config.HostConfig.RestartPolicy = {name: 'always', MaximumRetryCount: 5}; scope.config.HostConfig.Devices = [{ 'PathOnHost': '/dev/deviceName', 'PathInContainer': '/dev/deviceName', 'CgroupPermissions': 'mrw'}]; scope.config.HostConfig.LxcConf = [{name: 'lxc.utsname', value: 'docker'}]; + scope.config.HostConfig.ExtraHosts = [{host: 'hostname', ip: '127.0.0.1'}]; scope.create(); $httpBackend.flush(); diff --git a/app/components/startContainer/startcontainer.html b/app/components/startContainer/startcontainer.html index 4e5290e6f..681aa34e1 100644 --- a/app/components/startContainer/startcontainer.html +++ b/app/components/startContainer/startcontainer.html @@ -226,6 +226,25 @@

+
+ +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ +
diff --git a/dist/dockerui.js b/dist/dockerui.js index 381fc5c10..5b7e62e8e 100644 --- a/dist/dockerui.js +++ b/dist/dockerui.js @@ -526,7 +526,8 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt CapAdd: [], CapDrop: [], Devices: [], - LxcConf: [] + LxcConf: [], + ExtraHosts: [] } }; @@ -579,6 +580,7 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt prev[cur.name] = cur.value; return prev; }, {}); + config.HostConfig.ExtraHosts = config.HostConfig.ExtraHosts.map(function(entry) {return entry.host + ':' + entry.ip;}); var ExposedPorts = {}; var PortBindings = {}; @@ -1751,6 +1753,25 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + "
\n" + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + " \n" + "
\n" + "
\n" + diff --git a/dist/templates/app.js b/dist/templates/app.js index 0015a9879..b87ec2b2b 100644 --- a/dist/templates/app.js +++ b/dist/templates/app.js @@ -774,6 +774,25 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + "
\n" + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + " \n" + "
\n" + "
\n" + From 990456dbdd2c3094cc34e1fa48c6d1930b0da97a Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sat, 31 Jan 2015 14:42:10 -0600 Subject: [PATCH 10/10] Style tweaks for container start modal. --- app/components/startContainer/startcontainer.html | 12 ++++++------ assets/css/app.css | 4 ++++ dist/assets/css/app.css | 4 ++++ dist/dockerui.css | 4 ++++ dist/dockerui.js | 14 +++++++------- dist/templates/app.js | 12 ++++++------ 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/app/components/startContainer/startcontainer.html b/app/components/startContainer/startcontainer.html index 681aa34e1..39285dcff 100644 --- a/app/components/startContainer/startcontainer.html +++ b/app/components/startContainer/startcontainer.html @@ -106,7 +106,7 @@
-
+
@@ -205,7 +205,7 @@
-
+
@@ -248,7 +248,7 @@
-
+
@@ -267,7 +267,7 @@
-
+
@@ -282,7 +282,7 @@
-
+
diff --git a/assets/css/app.css b/assets/css/app.css index 6087c9c28..b7b7bd8f0 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -109,3 +109,7 @@ body { border-style: solid; border-width: 0 0 0 1em; } + +.inline-four .form-control { + max-width: 25%; +} diff --git a/dist/assets/css/app.css b/dist/assets/css/app.css index 6087c9c28..b7b7bd8f0 100644 --- a/dist/assets/css/app.css +++ b/dist/assets/css/app.css @@ -109,3 +109,7 @@ body { border-style: solid; border-width: 0 0 0 1em; } + +.inline-four .form-control { + max-width: 25%; +} diff --git a/dist/dockerui.css b/dist/dockerui.css index 14513e91c..a12442daa 100644 --- a/dist/dockerui.css +++ b/dist/dockerui.css @@ -109,4 +109,8 @@ body { margin: 0.5em; border-style: solid; border-width: 0 0 0 1em; +} + +.inline-four .form-control { + max-width: 25%; } \ No newline at end of file diff --git a/dist/dockerui.js b/dist/dockerui.js index 5b7e62e8e..5ffdda001 100644 --- a/dist/dockerui.js +++ b/dist/dockerui.js @@ -1,4 +1,4 @@ -/*! dockerui - v0.6.0 - 2015-01-28 +/*! dockerui - v0.6.0 - 2015-01-31 * https://github.com/crosbymichael/dockerui * Copyright (c) 2015 Michael Crosby; * Licensed MIT @@ -1632,7 +1632,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + "
\n" + " \n" + " \n" + @@ -1731,7 +1731,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + " \n" + @@ -1774,7 +1774,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + "
\n" + " \n" + " \n" + @@ -1793,7 +1793,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + " \n" + " \n" + " \n" + @@ -1808,7 +1808,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + " \n" + " \n" + " \n" + diff --git a/dist/templates/app.js b/dist/templates/app.js index b87ec2b2b..b2b306b06 100644 --- a/dist/templates/app.js +++ b/dist/templates/app.js @@ -653,7 +653,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + "
\n" + " \n" + " \n" + @@ -752,7 +752,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + " \n" + @@ -795,7 +795,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + "
\n" + " \n" + " \n" + @@ -814,7 +814,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + " \n" + " \n" + " \n" + @@ -829,7 +829,7 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t "
\n" + " \n" + "
\n" + - "
\n" + + "
\n" + " \n" + " \n" + " \n" +