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/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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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;
+}