From 4682ae4ca78cb5b4165ce4c80a9df6f6ba7e06b8 Mon Sep 17 00:00:00 2001 From: Kevan Ahlquist Date: Sun, 25 Jan 2015 15:52:40 -0600 Subject: [PATCH] Implemented remaining container creation options. --- app/app.js | 2 +- .../startContainerController.js | 143 ++++++---- .../startContainerController.spec.js | 119 +++----- .../startContainer/startcontainer.html | 260 ++++++++++++++---- .../ui-bootstrap-custom-tpls-0.12.0.min.js | 8 + gruntFile.js | 3 +- index.html | 6 +- test/unit/karma.conf.js | 1 + 8 files changed, 346 insertions(+), 196 deletions(-) create mode 100755 assets/js/ui-bootstrap/ui-bootstrap-custom-tpls-0.12.0.min.js diff --git a/app/app.js b/app/app.js index 531cf45157a1a..7596fece0a462 100644 --- a/app/app.js +++ b/app/app.js @@ -12,7 +12,7 @@ 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.16'); diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js index 23837f41d0e0a..d0bee1909b007 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -1,4 +1,4 @@ -angular.module('startContainer', []) +angular.module('startContainer', ['ui.bootstrap']) .controller('StartContainerController', ['$scope', '$routeParams', '$location', 'Container', 'Messages', 'containernameFilter', function($scope, $routeParams, $location, Container, Messages, containernameFilter) { $scope.template = 'app/components/startContainer/startcontainer.html'; @@ -10,81 +10,94 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt }); $scope.config = { - name: '', - memory: 0, - memorySwap: 0, - cpuShares: 1024, - env: [], - commands: '', - volumesFrom: [], - portBindings: [] + Env: [], + Volumes: [], + SecurityOpts: [], + PortBindings: [], + HostConfig: { + Binds: [], + Links: [], + Dns: [], + DnsSearch: [], + VolumesFrom: [], + CapAdd: [], + CapDrop: [] + } }; - $scope.commandPlaceholder = '["/bin/echo", "Hello world"]'; function failedRequestHandler(e, Messages) { Messages.send({class: 'text-error', data: e.data}); } + 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; - var ctor = Container; - var loc = $location; - var s = $scope; - var volumesFrom = $scope.config.volumesFrom.map(function(volume) { - return volume.name; - }); + config.Env = config.Env.map(function(envar) {return envar.name + '=' + envar.value;}); - var env = $scope.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 = {}; + var ExposedPorts = {}; + var PortBindings = {}; // TODO: consider using compatibility library - $scope.config.portBindings.forEach(function(portBinding) { + 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); + ExposedPorts[intPort] = {}; + if (intPort in PortBindings) { + PortBindings[intPort].push(binding); } else { - portBindings[intPort] = [binding]; + PortBindings[intPort] = [binding]; } } else { // TODO: Send warning message? Internal port need to be specified. } }); + config.ExposedPorts = ExposedPorts; + delete config.PortBindings; + config.HostConfig.PortBindings = PortBindings; - Container.create({ - Image: id, - name: $scope.config.name, - Memory: $scope.config.memory, - MemorySwap: $scope.config.memorySwap, - CpuShares: $scope.config.cpuShares, - Cmd: cmds, - VolumesFrom: volumesFrom, - Env: env, - ExposedPorts: exposedPorts, - HostConfig: { - PortBindings: portBindings - } - }, function(d) { + // 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(config, function(d) { if (d.Id) { - ctor.start({ - id: d.Id, - HostConfig: { - PortBindings: portBindings - } - }, function(cd) { + ctor.start({id: d.Id}, function(cd) { $('#create-modal').modal('hide'); loc.path('/containers/' + d.Id + '/'); }, function(e) { @@ -99,29 +112,39 @@ function($scope, $routeParams, $location, Container, Messages, containernameFilt }; $scope.addPortBinding = function() { - $scope.config.portBindings.push({ip: '', extPort: '', intPort: ''}); + $scope.config.PortBindings.push({ip: '', extPort: '', intPort: ''}); }; $scope.removePortBinding = function(portBinding) { - var idx = $scope.config.portBindings.indexOf(portBinding); - $scope.config.portBindings.splice(idx, 1); + 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.config.Env.push({name: '', value: ''}); }; $scope.removeEnv = function(envar) { var idx = $scope.config.env.indexOf(envar); - $scope.config.env.splice(idx, 1); + $scope.config.Env.splice(idx, 1); }; - $scope.addVolume = function() { - $scope.config.volumesFrom.push({name: ''}); + // Todo: refactor out + $scope.addVolumeFrom = function() { + $scope.config.HostConfig.volumesFrom.push({name: ''}); }; - $scope.removeVolume = function(volume) { - var idx = $scope.config.volumesFrom.indexOf(volume); - $scope.config.volumesFrom.splice(idx, 1); + $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); + }; + $scope.rmEntry = function(array, entry) { + var idx = array.indexOf(entry); + array.splice(idx, 1); }; }]); diff --git a/app/components/startContainer/startContainerController.spec.js b/app/components/startContainer/startContainerController.spec.js index acdfb91234e42..50fe21035c6f9 100644 --- a/app/components/startContainer/startContainerController.spec.js +++ b/app/components/startContainer/startContainerController.spec.js @@ -18,19 +18,28 @@ describe('startContainerController', function() { $httpBackend = _$httpBackend_; }); })); - + 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" + }], + "Status": "Up 2 minutes" + }]); + } 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'; var expectedBody = { "name": "container-name", - "Memory": 0, - "MemorySwap": 0, - "CpuShares": 1024, - "Cmd": null, - "VolumesFrom": [], - "Env": [], "ExposedPorts": { "9000/tcp": {}, }, @@ -44,32 +53,19 @@ describe('startContainerController', function() { } }; - $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({ + expectGetContainers(); + + $httpBackend.expectPOST('dockerapi/containers/create?name=container-name', expectedBody).respond({ "Id": id, "Warnings": null }); - $httpBackend.expectPOST('/dockerapi/containers/' + id + '/start?').respond({ + $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ "Id": id, "Warnings": null }); scope.config.name = 'container-name'; - scope.config.portBindings = [{ + scope.config.PortBindings = [{ ip: '10.20.10.15', extPort: '9999', intPort: '9000' @@ -86,44 +82,22 @@ describe('startContainerController', function() { 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": {} - } + "Env": ["SHELL=/bin/bash", "TERM=xterm-256color"] }; - $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({ + expectGetContainers(); + + $httpBackend.expectPOST('dockerapi/containers/create?name=container-name', expectedBody).respond({ "Id": id, "Warnings": null }); - $httpBackend.expectPOST('/dockerapi/containers/' + id + '/start?').respond({ + $httpBackend.expectPOST('dockerapi/containers/' + id + '/start?').respond({ "Id": id, "Warnings": null }); scope.config.name = 'container-name'; - scope.config.env = [{ + scope.config.Env = [{ name: 'SHELL', value: '/bin/bash' }, { @@ -141,45 +115,26 @@ describe('startContainerController', 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": {} - } + HostConfig: { + "VolumesFrom": ["parent", "other:ro"] + }, + "name": "container-name" }; - $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({ + expectGetContainers(); + + + $httpBackend.expectPOST('dockerapi/containers/create?name=container-name', expectedBody).respond({ "Id": id, "Warnings": null }); - $httpBackend.expectPOST('/dockerapi/containers/' + id + '/start?').respond({ + $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.config.HostConfig.VolumesFrom = [{name: "parent"}, {name:"other:ro"}]; scope.create(); $httpBackend.flush(); diff --git a/app/components/startContainer/startcontainer.html b/app/components/startContainer/startcontainer.html index 8429bd6c5ea73..3599698f4c401 100644 --- a/app/components/startContainer/startcontainer.html +++ b/app/components/startContainer/startcontainer.html @@ -6,40 +6,106 @@

Create And Start Container From Image

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 0000000000000..c9e65424f968f --- /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 2bf53a59a4166..91d8ae4543e4d 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 89e49bfc949f3..8620969fa0781 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 4b5d2412ed36d..ab13ae32c21cf 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',