diff --git a/app/app.js b/app/app.js index 531cf4515..7596fece0 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 fc99597af..f913d55a6 100644 --- a/app/components/startContainer/startContainerController.js +++ b/app/components/startContainer/startContainerController.js @@ -1,45 +1,126 @@ -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: [], + Devices: [], + LxcConf: [], + ExtraHosts: [] + } + }; + + $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); + } else if (config.Cmd) { + config.Cmd = config.Cmd.split(' '); } - 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); + config.HostConfig.LxcConf = config.HostConfig.LxcConf.reduce(function(prev, cur, idx){ + 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 = {}; + 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 { + Messages.send('Warning', 'Internal port must be specified for PortBindings'); + } + }); + 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'); - 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); }); @@ -50,4 +131,12 @@ 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); + }; }]); diff --git a/app/components/startContainer/startContainerController.spec.js b/app/components/startContainer/startContainerController.spec.js new file mode 100644 index 000000000..6b359306e --- /dev/null +++ b/app/components/startContainer/startContainerController.spec.js @@ -0,0 +1,201 @@ +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_; + }); + })); + 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', + 'ExposedPorts': { + '9000/tcp': {}, + }, + 'HostConfig': { + 'PortBindings': { + '9000/tcp': [{ + 'HostPort': '9999', + 'HostIp': '10.20.10.15', + }] + }, + } + }; + + 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.HostConfig.PortBindings = [{ + ip: '10.20.10.15', + extPort: '9999', + intPort: '9000' + }] + + scope.create(); + $httpBackend.flush(); + }); + }); + + 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', + 'Env': ['SHELL=/bin/bash', 'TERM=xterm-256color'] + }; + + 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.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 = { + HostConfig: { + 'VolumesFrom': ['parent', 'other:ro'] + }, + '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.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'], + 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' + }; + + 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.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(); + }); + }); +}); \ No newline at end of file diff --git a/app/components/startContainer/startcontainer.html b/app/components/startContainer/startcontainer.html index 42f5f5a7c..39285dcff 100644 --- a/app/components/startContainer/startcontainer.html +++ b/app/components/startContainer/startcontainer.html @@ -6,38 +6,301 @@

Create And Start Container From Image

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/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/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/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/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/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.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 1cec52022..5ffdda001 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-31 * 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,48 +501,129 @@ 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: [], + Devices: [], + LxcConf: [], + ExtraHosts: [] + } + }; + + $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); + } else if (config.Cmd) { + config.Cmd = config.Cmd.split(' '); } - 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); + config.HostConfig.LxcConf = config.HostConfig.LxcConf.reduce(function(prev, cur, idx){ + 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 = {}; + 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 { + Messages.send('Warning', 'Internal port must be specified for PortBindings'); + } + }); + 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'); - 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); }); @@ -553,6 +634,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 +745,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 +879,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 +1532,301 @@ 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" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\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..b2b306b06 100644 --- a/dist/templates/app.js +++ b/dist/templates/app.js @@ -553,38 +553,301 @@ 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" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\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/gruntFile.js b/gruntFile.js index 7a96965de..91d8ae454 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'], @@ -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 00f7b04d2..40cb82077 100644 --- a/test/unit/karma.conf.js +++ b/test/unit/karma.conf.js @@ -6,9 +6,12 @@ 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', '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', 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