diff --git a/app/app.js b/app/app.js index 0c3158372..ac8a51cf4 100644 --- a/app/app.js +++ b/app/app.js @@ -10,11 +10,11 @@ angular.module('uifordocker', [ 'dashboard', 'container', 'containers', + 'createContainer', 'docker', 'images', 'image', 'pullImage', - 'startContainer', 'containerLogs', 'stats', 'swarm', @@ -57,6 +57,21 @@ angular.module('uifordocker', [ templateUrl: 'app/components/containerLogs/containerlogs.html', controller: 'ContainerLogsController' }) + .state('actions', { + abstract: true, + url: "/actions", + template: '' + }) + .state('actions.create', { + abstract: true, + url: "/create", + template: '' + }) + .state('actions.create.container', { + url: "/container", + templateUrl: 'app/components/createContainer/createcontainer.html', + controller: 'CreateContainerController' + }) .state('docker', { url: '/docker/', templateUrl: 'app/components/docker/docker.html', diff --git a/app/components/container/container.html b/app/components/container/container.html index 0c146d78c..05803ca02 100644 --- a/app/components/container/container.html +++ b/app/components/container/container.html @@ -5,7 +5,6 @@ -
diff --git a/app/components/containers/containers.html b/app/components/containers/containers.html index d18d5ae95..d24cf5549 100644 --- a/app/components/containers/containers.html +++ b/app/components/containers/containers.html @@ -1,5 +1,3 @@ -
- @@ -23,8 +21,8 @@ -
+ Add container
diff --git a/app/components/createContainer/createContainerController.js b/app/components/createContainer/createContainerController.js new file mode 100644 index 000000000..2e7000286 --- /dev/null +++ b/app/components/createContainer/createContainerController.js @@ -0,0 +1,213 @@ +angular.module('createContainer', []) +.controller('CreateContainerController', ['$scope', '$state', 'Config', 'Container', 'Image', 'Volume', 'Network', 'Messages', 'ViewSpinner', 'errorMsgFilter', +function ($scope, $state, Config, Container, Image, Volume, Network, Messages, ViewSpinner, errorMsgFilter) { + + $scope.state = { + alwaysPull: true + }; + + $scope.formValues = { + Console: 'none', + Volumes: [], + }; + + $scope.config = { + Env: [], + HostConfig: { + RestartPolicy: { + Name: 'no' + }, + PortBindings: [], + Binds: [], + NetworkMode: 'bridge', + Privileged: false + } + }; + + $scope.resetVolumePath = function(index) { + $scope.formValues.Volumes[index].name = ''; + }; + + $scope.addVolume = function() { + $scope.formValues.Volumes.push({ name: '', containerPath: '', readOnly: false, isPath: false }); + }; + + $scope.removeVolume = function(index) { + $scope.formValues.Volumes.splice(index, 1); + }; + + $scope.addEnvironmentVariable = function() { + $scope.config.Env.push({ name: '', value: ''}); + }; + + $scope.removeEnvironmentVariable = function(index) { + $scope.config.Env.splice(index, 1); + }; + + $scope.addPortBinding = function() { + $scope.config.HostConfig.PortBindings.push({ hostPort: '', containerPort: '', protocol: 'tcp' }); + }; + + $scope.removePortBinding = function(index) { + $scope.config.HostConfig.PortBindings.splice(index, 1); + }; + + Config.$promise.then(function (c) { + var swarm = c.swarm; + + Volume.query({}, function (d) { + $scope.availableVolumes = d.Volumes; + }, function (e) { + Messages.error("Failure", e.data); + }); + + Network.query({}, function (d) { + var networks = d; + if (swarm) { + networks = d.filter(function (network) { + if (network.Scope === 'global') { + return network; + } + }); + networks.push({Name: "bridge"}); + networks.push({Name: "host"}); + networks.push({Name: "none"}); + } + $scope.availableNetworks = networks; + }, function (e) { + Messages.error("Failure", e.data); + }); + }); + + function createContainer(config) { + ViewSpinner.spin(); + Container.create(config, function (d) { + if (d.Id) { + var reqBody = config.HostConfig || {}; + reqBody.id = d.Id; + Container.start(reqBody, function (cd) { + ViewSpinner.stop(); + Messages.send('Container Started', d.Id); + $state.go('containers', {}, {reload: true}); + }, function (e) { + ViewSpinner.stop(); + Messages.error('Error', errorMsgFilter(e)); + }); + } else { + ViewSpinner.stop(); + Messages.error('Error', errorMsgFilter(d)); + } + }, function (e) { + ViewSpinner.stop(); + Messages.error('Error', errorMsgFilter(e)); + }); + } + + function createImageConfig(imageName) { + var imageNameAndTag = imageName.split(':'); + var imageConfig = { + fromImage: imageNameAndTag[0], + tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest' + }; + return imageConfig; + } + + function pullImageAndCreateContainer(config) { + ViewSpinner.spin(); + + var image = _.toLower(config.Image); + var imageConfig = createImageConfig(image); + + Image.create(imageConfig, function (data) { + var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); + if (err) { + var detail = data[data.length - 1]; + ViewSpinner.stop(); + Messages.error('Error', detail.error); + } else { + createContainer(config); + } + }, function (e) { + ViewSpinner.stop(); + Messages.error('Error', 'Unable to pull image ' + image); + }); + } + + function preparePortBindings(config) { + var bindings = {}; + config.HostConfig.PortBindings.forEach(function (portBinding) { + if (portBinding.hostPort && portBinding.containerPort) { + var key = portBinding.containerPort + "/" + portBinding.protocol; + bindings[key] = [{ HostPort: portBinding.hostPort }]; + } + }); + config.HostConfig.PortBindings = bindings; + } + + function prepareConsole(config) { + var value = $scope.formValues.Console; + var openStdin = true; + var tty = true; + if (value === 'tty') { + openStdin = false; + } else if (value === 'interactive') { + tty = false; + } else if (value === 'none') { + openStdin = false; + tty = false; + } + config.OpenStdin = openStdin; + config.Tty = tty; + } + + function prepareEnvironmentVariables(config) { + var env = []; + config.Env.forEach(function (v) { + if (v.name && v.value) { + env.push(v.name + "=" + v.value); + } + }); + config.Env = env; + } + + function prepareVolumes(config) { + var binds = []; + var volumes = {}; + + $scope.formValues.Volumes.forEach(function (volume) { + var name = volume.name; + var containerPath = volume.containerPath; + if (name && containerPath) { + var bind = name + ':' + containerPath; + volumes[containerPath] = {}; + if (volume.readOnly) { + bind += ':ro'; + } + binds.push(bind); + } + }); + config.HostConfig.Binds = binds; + config.Volumes = volumes; + } + + function prepareConfiguration() { + var config = angular.copy($scope.config); + preparePortBindings(config); + prepareConsole(config); + prepareEnvironmentVariables(config); + prepareVolumes(config); + return config; + } + + $scope.create = function () { + var config = prepareConfiguration(); + console.log(JSON.stringify(config, null, 4)); + + if ($scope.state.alwaysPull) { + pullImageAndCreateContainer(config); + } else { + createContainer(config); + } + }; + +}]); diff --git a/app/components/createContainer/createcontainer.html b/app/components/createContainer/createcontainer.html new file mode 100644 index 000000000..b1b1f5a3f --- /dev/null +++ b/app/components/createContainer/createcontainer.html @@ -0,0 +1,312 @@ + + + + Containers > Add container + + + +
+
+ + +
+ +
+ +
+ +
+
+ + +
+ +
+ +
+
+
+ +
+
+
+ + +
+ +
+ + + +
+
+ + +
+ +
+ + map port + +
+ +
+
+
+ host + +
+
+ container + +
+
+ + + + +
+
+
+ +
+ +
+
+
+
+
+ +
+
+ + + + + +
+ +
+
+ +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+ +
+ +
+
+ + +
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ + +
+ +
+ + environment variable + +
+ +
+
+
+ name + +
+
+ value + + + + +
+
+
+ +
+ +
+
+ + +
+
+ +
+ +
+ + volume + +
+ +
+
+
+
+ +
+
+
+ Path + + +
+
+ container + + + + +
+
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ +
+
+ + +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+ + Cancel +
+
diff --git a/app/components/startContainer/startContainerController.js b/app/components/startContainer/startContainerController.js deleted file mode 100644 index 6dc4027f8..000000000 --- a/app/components/startContainer/startContainerController.js +++ /dev/null @@ -1,184 +0,0 @@ -angular.module('startContainer', ['ui.bootstrap']) -.controller('StartContainerController', ['$scope', '$state', 'Container', 'Image', 'Messages', 'containernameFilter', 'errorMsgFilter', 'ViewSpinner', -function ($scope, $state, Container, Image, Messages, containernameFilter, errorMsgFilter, ViewSpinner) { - $scope.template = 'app/components/startContainer/startcontainer.html'; - - Container.query({all: 1}, function (d) { - $scope.containerNames = d.map(function (container) { - return containernameFilter(container); - }); - }); - - $scope.config = { - Env: [], - Labels: [], - Volumes: [], - SecurityOpts: [], - HostConfig: { - PortBindings: [], - Binds: [], - Links: [], - Dns: [], - DnsSearch: [], - VolumesFrom: [], - CapAdd: [], - CapDrop: [], - Devices: [], - LxcConf: [], - ExtraHosts: [] - } - }; - - $scope.menuStatus = { - containerOpen: true, - hostConfigOpen: false - }; - - function failedRequestHandler(e, Messages) { - Messages.error('Error', errorMsgFilter(e)); - } - - function rmEmptyKeys(col) { - for (var key in col) { - if (col[key] === null || col[key] === undefined || col[key] === '' || ($.isPlainObject(col[key]) && $.isEmptyObject(col[key])) || col[key].length === 0) { - delete col[key]; - } - } - } - - function getNames(arr) { - return arr.map(function (item) { - return item.name; - }); - } - - function createContainer(config) { - Container.create(config, function (d) { - if (d.Id) { - var reqBody = config.HostConfig || {}; - reqBody.id = d.Id; - Container.start(reqBody, function (cd) { - if (cd.id) { - ViewSpinner.stop(); - Messages.send('Container Started', d.Id); - $state.go('container', {id: d.Id}, {reload: true}); - } else { - ViewSpinner.stop(); - failedRequestHandler(cd, Messages); - Container.remove({id: d.Id}, function () { - Messages.send('Container Removed', d.Id); - }); - } - }, function (e) { - ViewSpinner.stop(); - failedRequestHandler(e, Messages); - }); - } else { - ViewSpinner.stop(); - failedRequestHandler(d, Messages); - } - }, function (e) { - ViewSpinner.stop(); - failedRequestHandler(e, Messages); - }); - } - - $scope.create = function () { - $('#create-modal').modal('hide'); - ViewSpinner.spin(); - - var config = angular.copy($scope.config); - - 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; - }); - var labels = {}; - config.Labels = config.Labels.forEach(function(label) { - labels[label.key] = label.value; - }); - config.Labels = labels; - - 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"; - if (portBinding.protocol === "udp") { - intPort = portBinding.intPort + "/udp"; - } - 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 image = _.toLower(config.Image); - var imageNameAndTag = image.split(':'); - var imageConfig = { - fromImage: imageNameAndTag[0], - tag: imageNameAndTag[1] ? imageNameAndTag[1] : 'latest', - }; - - Image.create(imageConfig, function (data) { - var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error'); - if (err) { - var detail = data[data.length - 1]; - ViewSpinner.stop(); - Messages.error('Error', detail.error); - } else { - Messages.send("Image successfully pulled", image); - createContainer(config); - } - }, function (e) { - ViewSpinner.stop(); - Messages.error('Error', 'Unable to pull image ' + image); - }); - }; - - $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/startcontainer.html b/app/components/startContainer/startcontainer.html deleted file mode 100644 index 9891c9e58..000000000 --- a/app/components/startContainer/startcontainer.html +++ /dev/null @@ -1,444 +0,0 @@ - diff --git a/app/shared/filters.js b/app/shared/filters.js index ea08f0057..4b5b93700 100644 --- a/app/shared/filters.js +++ b/app/shared/filters.js @@ -49,6 +49,12 @@ angular.module('dockerui.filters', []) return ''; }; }) +.filter('capitalize', function () { + 'use strict'; + return function (text) { + return _.capitalize(text); + }; +}) .filter('getstatetext', function () { 'use strict'; return function (state) { diff --git a/app/shared/services.js b/app/shared/services.js index 1085cdd58..102d011d4 100644 --- a/app/shared/services.js +++ b/app/shared/services.js @@ -86,10 +86,10 @@ angular.module('dockerui.services', ['ngResource', 'ngSanitize']) history: {method: 'GET', params: {action: 'history'}, isArray: true}, create: { method: 'POST', isArray: true, transformResponse: [function f(data) { - var str = data.replace(/\n/g, " ").replace(/\}\W*\{/g, "}, {"); - return angular.fromJson("[" + str + "]"); + var str = "[" + data.replace(/\n/g, " ").replace(/\}\s*\{/g, "}, {") + "]"; + return angular.fromJson(str); }], - params: {action: 'create', fromImage: '@fromImage', repo: '@repo', tag: '@tag', registry: '@registry'} + params: {action: 'create', fromImage: '@fromImage', tag: '@tag'} }, insert: {method: 'POST', params: {id: '@id', action: 'insert'}}, push: {method: 'POST', params: {id: '@id', action: 'push'}}, diff --git a/assets/css/app.css b/assets/css/app.css index b8d40de69..5d99a5c85 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -146,3 +146,22 @@ .header_title_content { margin-left: 5px; } + +.form-horizontal .control-label.text-left{ + text-align: left; + font-size: 0.9em; +} + +input[type="checkbox"] { + margin-top: 1px; + vertical-align: middle; +} + +input[type="radio"] { + margin-top: 1px; + vertical-align: middle; +} + +.clickable { + cursor: pointer; +}