Merge pull request #72 from kevana/master

Container start parameters
pull/2/head
Kevan Ahlquist 2015-02-01 01:35:37 -06:00
commit 4b4edc6c22
19 changed files with 1378 additions and 146 deletions

View File

@ -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');

View File

@ -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);
};
}]);

View File

@ -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();
});
});
});

View File

@ -6,38 +6,301 @@
<h3>Create And Start Container From Image</h3>
</div>
<div class="modal-body">
<form role="form">
<form role="form">
<accordion close-others="true">
<accordion-group heading="Container options" is-open="menuStatus.containerOpen">
<fieldset>
<div class="form-group">
<label>Cmd:</label>
<input type="text" placeholder="{{ commandPlaceholder }}" ng-model="config.commands" class="form-control"/>
<small>Input commands as an array</small>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>Cmd:</label>
<input type="text" placeholder='["/bin/echo", "Hello world"]' ng-model="config.Cmd" class="form-control"/>
<small>Input commands as a raw string or JSON array</small>
</div>
<div class="form-group">
<label>Entrypoint:</label>
<input type="text" ng-model="config.Entrypoint" class="form-control" placeholder="./entrypoint.sh"/>
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" ng-model="config.name" class="form-control"/>
</div>
<div class="form-group">
<label>Hostname:</label>
<input type="text" ng-model="config.Hostname" class="form-control"/>
</div>
<div class="form-group">
<label>Domainname:</label>
<input type="text" ng-model="config.Domainname" class="form-control"/>
</div>
<div class="form-group">
<label>User:</label>
<input type="text" ng-model="config.User" class="form-control"/>
</div>
<div class="form-group">
<label>Memory:</label>
<input type="number" ng-model="config.Memory" class="form-control"/>
</div>
<div class="form-group">
<label>Volumes:</label>
<div ng-repeat="volume in config.Volumes">
<div class="form-group form-inline">
<input type="text" ng-model="volume.name" class="form-control" placeholder="/var/data"/>
<button type="button" class="btn btn-danger btn-sm" ng-click="rmEntry(config.Volumes, volume)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.Volumes, {name: ''})">Add Volume</button>
</div>
</div>
<div class="form-group">
<label>Name:</label>
<input type="text" ng-model="config.name" class="form-control"/>
<div class="col-xs-6">
<div class="form-group">
<label>MemorySwap:</label>
<input type="number" ng-model="config.MemorySwap" class="form-control"/>
</div>
<div class="form-group">
<label>CpuShares:</label>
<input type="number" ng-model="config.CpuShares" class="form-control"/>
</div>
<div class="form-group">
<label>Cpuset:</label>
<input type="text" ng-model="config.Cpuset" class="form-control" placeholder="1,2"/>
<small>Input as comma-separated list of numbers</small>
</div>
<div class="form-group">
<label>WorkingDir:</label>
<input type="text" ng-model="config.WorkingDir" class="form-control" placeholder="/app"/>
</div>
<div class="form-group">
<label>MacAddress:</label>
<input type="text" ng-model="config.MacAddress" class="form-control" placeholder="12:34:56:78:9a:bc"/>
</div>
<div class="form-group">
<label for="networkDisabled">NetworkDisabled:</label>
<input id="networkDisabled" type="checkbox" ng-model="config.NetworkDisabled"/>
</div>
<div class="form-group">
<label for="tty">Tty:</label>
<input id="tty" type="checkbox" ng-model="config.Tty"/>
</div>
<div class="form-group">
<label for="openStdin">OpenStdin:</label>
<input id="openStdin" type="checkbox" ng-model="config.OpenStdin"/>
</div>
<div class="form-group">
<label for="stdinOnce">StdinOnce:</label>
<input id="stdinOnce" type="checkbox" ng-model="config.StdinOnce"/>
</div>
<div class="form-group">
<label>SecurityOpts:</label>
<div ng-repeat="opt in config.SecurityOpts">
<div class="form-group form-inline">
<input type="text" ng-model="opt.name" class="form-control" placeholder="label:type:svirt_apache"/>
<button type="button" class="btn btn-danger btn-sm" ng-click="rmEntry(config.SecurityOpts, opt)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.SecurityOpts, {name: ''})">Add Option</button>
</div>
</div>
<div class="form-group">
<label>Memory:</label>
<input type="number" ng-model="config.memory" class="form-control"/>
</div>
<div class="form-group">
<label>Memory Swap:</label>
<input type="number" ng-model="config.memorySwap" class="form-control"/>
</div>
<div class="form-group">
<label>CPU Shares:</label>
<input type="number" ng-model="config.cpuShares" class="form-control"/>
</div>
<div class="form-group">
<label>Volumes From:</label>
<input type="text" ng-model="config.volumesFrom" class="form-control"/>
</div>
<hr>
<div class="form-group">
<label>Env:</label>
<div ng-repeat="envar in config.Env">
<div class="form-group form-inline">
<div class="form-group">
<label class="sr-only">Variable Name:</label>
<input type="text" ng-model="envar.name" class="form-control" placeholder="NAME"/>
</div>
<div class="form-group">
<label class="sr-only">Variable Value:</label>
<input type="text" ng-model="envar.value" class="form-control" placeholder="value"/>
</div>
<div class="form-group">
<button class="btn btn-danger btn-xs form-control" ng-click="rmEntry(config.Env, envar)">Remove</button>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.Env, {name: '', value: ''})">Add environment variable</button>
</div>
</fieldset>
</accordion-group>
<accordion-group heading="HostConfig options" is-open="menuStatus.hostConfigOpen">
<fieldset>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>Binds:</label>
<div ng-repeat="bind in config.HostConfig.Binds">
<div class="form-group form-inline">
<input type="text" ng-model="bind.name" class="form-control" placeholder="/host:/container"/>
<button type="button" class="btn btn-danger btn-sm" ng-click="rmEntry(config.HostConfig.Binds, bind)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.Binds, {name: ''})">Add Bind</button>
</div>
<div class="form-group">
<label>Links:</label>
<div ng-repeat="link in config.HostConfig.Links">
<div class="form-group form-inline">
<input type="text" ng-model="link.name" class="form-control" placeholder="web:db">
<button type="button" class="btn btn-danger btn-sm" ng-click="rmEntry(config.HostConfig.Links, link)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.Links, {name: ''})">Add Link</button>
</div>
<div class="form-group">
<label>Dns:</label>
<div ng-repeat="entry in config.HostConfig.Dns">
<div class="form-group form-inline">
<input type="text" ng-model="entry.name" class="form-control" placeholder="8.8.8.8"/>
<button type="button" class="btn btn-danger btn-sm" ng-click="rmEntry(config.HostConfig.Dns, entry)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.Dns, {name: ''})">Add entry</button>
</div>
<div class="form-group">
<label>DnsSearch:</label>
<div ng-repeat="entry in config.HostConfig.DnsSearch">
<div class="form-group form-inline">
<input type="text" ng-model="entry.name" class="form-control" placeholder="example.com"/>
<button type="button" class="btn btn-danger btn-sm" ng-click="rmEntry(config.HostConfig.DnsSearch, entry)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.DnsSearch, {name: ''})">Add entry</button>
</div>
<div class="form-group">
<label>CapAdd:</label>
<div ng-repeat="entry in config.HostConfig.CapAdd">
<div class="form-group form-inline">
<input type="text" ng-model="entry.name" class="form-control" placeholder="cap_sys_admin"/>
<button type="button" class="btn btn-danger btn-sm" ng-click="rmEntry(config.HostConfig.CapAdd, entry)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.CapAdd, {name: ''})">Add entry</button>
</div>
<div class="form-group">
<label>CapDrop:</label>
<div ng-repeat="entry in config.HostConfig.CapDrop">
<div class="form-group form-inline">
<input type="text" ng-model="entry.name" class="form-control" placeholder="cap_sys_admin"/>
<button type="button" class="btn btn-danger btn-sm" ng-click="rmEntry(config.HostConfig.CapDrop, entry)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.CapDrop, {name: ''})">Add entry</button>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label>NetworkMode:</label>
<input type="text" ng-model="config.HostConfig.NetworkMode" class="form-control" placeholder="bridge"/>
</div>
<div class="form-group">
<label for="publishAllPorts">PublishAllPorts:</label>
<input id="publishAllPorts" type="checkbox" ng-model="config.HostConfig.PublishAllPorts"/>
</div>
<div class="form-group">
<label for="privileged">Privileged:</label>
<input id="privileged" type="checkbox" ng-model="config.HostConfig.Privileged"/>
</div>
<div class="form-group">
<label>VolumesFrom:</label>
<div ng-repeat="volume in config.HostConfig.VolumesFrom">
<div class="form-group form-inline">
<select ng-model="volume.name" ng-options="name for name in containerNames track by name" class="form-control"/>
<button class="btn btn-danger btn-xs form-control" ng-click="rmEntry(config.HostConfig.VolumesFrom, volume)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.VolumesFrom, {name: ''})">Add volume</button>
</div>
<div class="form-group">
<label>RestartPolicy:</label>
<select ng-model="config.HostConfig.RestartPolicy.name">
<option value="">disabled</option>
<option value="always">always</option>
<option value="on-failure">on-failure</option>
</select>
<label>MaximumRetryCount:</label>
<input type="number" ng-model="config.HostConfig.RestartPolicy.MaximumRetryCount"/>
</div>
</div>
</div>
<hr>
<div class="form-group">
<label>ExtraHosts:</label>
<div ng-repeat="entry in config.HostConfig.ExtraHosts">
<div class="form-group form-inline">
<div class="form-group">
<label class="sr-only">Hostname:</label>
<input type="text" ng-model="entry.host" class="form-control" placeholder="hostname"/>
</div>
<div class="form-group">
<label class="sr-only">IP Address:</label>
<input type="text" ng-model="entry.ip" class="form-control" placeholder="127.0.0.1"/>
</div>
<div class="form-group">
<button class="btn btn-danger btn-xs form-control" ng-click="rmEntry(config.HostConfig.ExtraHosts, entry)">Remove</button>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.ExtraHosts, {host: '', ip: ''})">Add extra host</button>
</div>
<div class="form-group">
<label>LxcConf:</label>
<div ng-repeat="entry in config.HostConfig.LxcConf">
<div class="form-group form-inline">
<div class="form-group">
<label class="sr-only">Name:</label>
<input type="text" ng-model="entry.name" class="form-control" placeholder="lxc.utsname"/>
</div>
<div class="form-group">
<label class="sr-only">Value:</label>
<input type="text" ng-model="entry.value" class="form-control" placeholder="docker"/>
</div>
<div class="form-group">
<button class="btn btn-danger btn-xs form-control" ng-click="rmEntry(config.HostConfig.LxcConf, entry)">Remove</button>
</div>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.LxcConf, {name: '', value: ''})">Add Entry</button>
</div>
<div class="form-group">
<label>Devices:</label>
<div ng-repeat="device in config.HostConfig.Devices">
<div class="form-group form-inline inline-four">
<label class="sr-only">PathOnHost:</label>
<input type="text" ng-model="device.PathOnHost" class="form-control" placeholder="PathOnHost"/>
<label class="sr-only">PathInContainer:</label>
<input type="text" ng-model="device.PathInContainer" class="form-control" placeholder="PathInContainer"/>
<label class="sr-only">CgroupPermissions:</label>
<input type="text" ng-model="device.CgroupPermissions" class="form-control" placeholder="CgroupPermissions"/>
<button class="btn btn-danger btn-xs form-control" ng-click="rmEntry(config.HostConfig.Devices, device)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.Devices, { PathOnHost: '', PathInContainer: '', CgroupPermissions: ''})">Add Device</button>
</div>
<div class="form-group">
<label>PortBindings:</label>
<div ng-repeat="portBinding in config.HostConfig.PortBindings">
<div class="form-group form-inline inline-four">
<label class="sr-only">Host IP:</label>
<input type="text" ng-model="portBinding.ip" class="form-control" placeholder="Host IP Address"/>
<label class="sr-only">Host Port:</label>
<input type="text" ng-model="portBinding.extPort" class="form-control" placeholder="Host Port"/>
<label class="sr-only">Container port:</label>
<input type="text" ng-model="portBinding.intPort" class="form-control" placeholder="Container Port"/>
<button class="btn btn-danger btn-xs form-control" ng-click="rmEntry(config.HostConfig.PortBindings, portBinding)">Remove</button>
</div>
</div>
<button type="button" class="btn btn-success btn-sm" ng-click="addEntry(config.HostConfig.PortBindings, {ip: '', extPort: '', intPort: ''})">Add Port Binding</button>
</div>
</fieldset>
</accordion-group>
</accordion>
</form>
</div>
<div class="modal-footer">
<a href="" class="btn btn-primary" ng-click="create()">Create</a>
<a href="" class="btn btn-primary btn-lg" ng-click="create()">Create</a>
</div>
</div>
</div>

View File

@ -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;
};
});

View File

@ -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;

View File

@ -109,3 +109,7 @@ body {
border-style: solid;
border-width: 0 0 0 1em;
}
.inline-four .form-control {
max-width: 25%;
}

View File

@ -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",'<div class="panel panel-default">\n <div class="panel-heading">\n <h4 class="panel-title">\n <a href class="accordion-toggle" ng-click="toggleOpen()" accordion-transclude="heading"><span ng-class="{\'text-muted\': isDisabled}">{{heading}}</span></a>\n </h4>\n </div>\n <div class="panel-collapse" collapse="!isOpen">\n <div class="panel-body" ng-transclude></div>\n </div>\n</div>\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(n){n.put("template/accordion/accordion.html",'<div class="panel-group" ng-transclude></div>')}]);

9
dist/angular.js vendored
View File

@ -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",'<div class="panel panel-default">\n <div class="panel-heading">\n <h4 class="panel-title">\n <a href class="accordion-toggle" ng-click="toggleOpen()" accordion-transclude="heading"><span ng-class="{\'text-muted\': isDisabled}">{{heading}}</span></a>\n </h4>\n </div>\n <div class="panel-collapse" collapse="!isOpen">\n <div class="panel-body" ng-transclude></div>\n </div>\n</div>\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(n){n.put("template/accordion/accordion.html",'<div class="panel-group" ng-transclude></div>')}]);

View File

@ -109,3 +109,7 @@ body {
border-style: solid;
border-width: 0 0 0 1em;
}
.inline-four .form-control {
max-width: 25%;
}

View File

@ -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",'<div class="panel panel-default">\n <div class="panel-heading">\n <h4 class="panel-title">\n <a href class="accordion-toggle" ng-click="toggleOpen()" accordion-transclude="heading"><span ng-class="{\'text-muted\': isDisabled}">{{heading}}</span></a>\n </h4>\n </div>\n <div class="panel-collapse" collapse="!isOpen">\n <div class="panel-body" ng-transclude></div>\n </div>\n</div>\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(n){n.put("template/accordion/accordion.html",'<div class="panel-group" ng-transclude></div>')}]);

4
dist/dockerui.css vendored
View File

@ -109,4 +109,8 @@ body {
margin: 0.5em;
border-style: solid;
border-width: 0 0 0 1em;
}
.inline-four .form-control {
max-width: 25%;
}

475
dist/dockerui.js vendored
View File

@ -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
" <h3>Create And Start Container From Image</h3>\n" +
" </div>\n" +
" <div class=\"modal-body\">\n" +
" <form role=\"form\">\n" +
" <form role=\"form\">\n" +
" <accordion close-others=\"true\">\n" +
" <accordion-group heading=\"Container options\" is-open=\"menuStatus.containerOpen\">\n" +
" <fieldset>\n" +
" <div class=\"form-group\">\n" +
" <label>Cmd:</label>\n" +
" <input type=\"text\" placeholder=\"{{ commandPlaceholder }}\" ng-model=\"config.commands\" class=\"form-control\"/>\n" +
" <small>Input commands as an array</small>\n" +
" <div class=\"row\">\n" +
" <div class=\"col-xs-6\">\n" +
" <div class=\"form-group\">\n" +
" <label>Cmd:</label>\n" +
" <input type=\"text\" placeholder='[\"/bin/echo\", \"Hello world\"]' ng-model=\"config.Cmd\" class=\"form-control\"/>\n" +
" <small>Input commands as a raw string or JSON array</small>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Entrypoint:</label>\n" +
" <input type=\"text\" ng-model=\"config.Entrypoint\" class=\"form-control\" placeholder=\"./entrypoint.sh\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Name:</label>\n" +
" <input type=\"text\" ng-model=\"config.name\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Hostname:</label>\n" +
" <input type=\"text\" ng-model=\"config.Hostname\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Domainname:</label>\n" +
" <input type=\"text\" ng-model=\"config.Domainname\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>User:</label>\n" +
" <input type=\"text\" ng-model=\"config.User\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Memory:</label>\n" +
" <input type=\"number\" ng-model=\"config.Memory\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Volumes:</label>\n" +
" <div ng-repeat=\"volume in config.Volumes\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"volume.name\" class=\"form-control\" placeholder=\"/var/data\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.Volumes, volume)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.Volumes, {name: ''})\">Add Volume</button>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Name:</label>\n" +
" <input type=\"text\" ng-model=\"config.name\" class=\"form-control\"/>\n" +
" <div class=\"col-xs-6\">\n" +
" <div class=\"form-group\">\n" +
" <label>MemorySwap:</label>\n" +
" <input type=\"number\" ng-model=\"config.MemorySwap\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>CpuShares:</label>\n" +
" <input type=\"number\" ng-model=\"config.CpuShares\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Cpuset:</label>\n" +
" <input type=\"text\" ng-model=\"config.Cpuset\" class=\"form-control\" placeholder=\"1,2\"/>\n" +
" <small>Input as comma-separated list of numbers</small>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>WorkingDir:</label>\n" +
" <input type=\"text\" ng-model=\"config.WorkingDir\" class=\"form-control\" placeholder=\"/app\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>MacAddress:</label>\n" +
" <input type=\"text\" ng-model=\"config.MacAddress\" class=\"form-control\" placeholder=\"12:34:56:78:9a:bc\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"networkDisabled\">NetworkDisabled:</label>\n" +
" <input id=\"networkDisabled\" type=\"checkbox\" ng-model=\"config.NetworkDisabled\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"tty\">Tty:</label>\n" +
" <input id=\"tty\" type=\"checkbox\" ng-model=\"config.Tty\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"openStdin\">OpenStdin:</label>\n" +
" <input id=\"openStdin\" type=\"checkbox\" ng-model=\"config.OpenStdin\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"stdinOnce\">StdinOnce:</label>\n" +
" <input id=\"stdinOnce\" type=\"checkbox\" ng-model=\"config.StdinOnce\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>SecurityOpts:</label>\n" +
" <div ng-repeat=\"opt in config.SecurityOpts\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"opt.name\" class=\"form-control\" placeholder=\"label:type:svirt_apache\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.SecurityOpts, opt)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.SecurityOpts, {name: ''})\">Add Option</button>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Memory:</label>\n" +
" <input type=\"number\" ng-model=\"config.memory\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Memory Swap:</label>\n" +
" <input type=\"number\" ng-model=\"config.memorySwap\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>CPU Shares:</label>\n" +
" <input type=\"number\" ng-model=\"config.cpuShares\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Volumes From:</label>\n" +
" <input type=\"text\" ng-model=\"config.volumesFrom\" class=\"form-control\"/>\n" +
" </div>\n" +
" <hr>\n" +
" <div class=\"form-group\">\n" +
" <label>Env:</label>\n" +
" <div ng-repeat=\"envar in config.Env\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Variable Name:</label>\n" +
" <input type=\"text\" ng-model=\"envar.name\" class=\"form-control\" placeholder=\"NAME\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Variable Value:</label>\n" +
" <input type=\"text\" ng-model=\"envar.value\" class=\"form-control\" placeholder=\"value\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.Env, envar)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.Env, {name: '', value: ''})\">Add environment variable</button>\n" +
" </div>\n" +
" </fieldset>\n" +
" </accordion-group>\n" +
" <accordion-group heading=\"HostConfig options\" is-open=\"menuStatus.hostConfigOpen\">\n" +
" <fieldset>\n" +
" <div class=\"row\">\n" +
" <div class=\"col-xs-6\">\n" +
" <div class=\"form-group\">\n" +
" <label>Binds:</label>\n" +
" <div ng-repeat=\"bind in config.HostConfig.Binds\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"bind.name\" class=\"form-control\" placeholder=\"/host:/container\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.Binds, bind)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.Binds, {name: ''})\">Add Bind</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Links:</label>\n" +
" <div ng-repeat=\"link in config.HostConfig.Links\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"link.name\" class=\"form-control\" placeholder=\"web:db\">\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.Links, link)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.Links, {name: ''})\">Add Link</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Dns:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.Dns\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"8.8.8.8\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.Dns, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.Dns, {name: ''})\">Add entry</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>DnsSearch:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.DnsSearch\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"example.com\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.DnsSearch, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.DnsSearch, {name: ''})\">Add entry</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>CapAdd:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.CapAdd\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"cap_sys_admin\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.CapAdd, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.CapAdd, {name: ''})\">Add entry</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>CapDrop:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.CapDrop\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"cap_sys_admin\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.CapDrop, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.CapDrop, {name: ''})\">Add entry</button>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"col-xs-6\">\n" +
" <div class=\"form-group\">\n" +
" <label>NetworkMode:</label>\n" +
" <input type=\"text\" ng-model=\"config.HostConfig.NetworkMode\" class=\"form-control\" placeholder=\"bridge\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"publishAllPorts\">PublishAllPorts:</label>\n" +
" <input id=\"publishAllPorts\" type=\"checkbox\" ng-model=\"config.HostConfig.PublishAllPorts\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"privileged\">Privileged:</label>\n" +
" <input id=\"privileged\" type=\"checkbox\" ng-model=\"config.HostConfig.Privileged\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>VolumesFrom:</label>\n" +
" <div ng-repeat=\"volume in config.HostConfig.VolumesFrom\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <select ng-model=\"volume.name\" ng-options=\"name for name in containerNames track by name\" class=\"form-control\"/>\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.VolumesFrom, volume)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.VolumesFrom, {name: ''})\">Add volume</button>\n" +
" </div>\n" +
" \n" +
" <div class=\"form-group\">\n" +
" <label>RestartPolicy:</label>\n" +
" <select ng-model=\"config.HostConfig.RestartPolicy.name\">\n" +
" <option value=\"\">disabled</option>\n" +
" <option value=\"always\">always</option>\n" +
" <option value=\"on-failure\">on-failure</option>\n" +
" </select>\n" +
" <label>MaximumRetryCount:</label>\n" +
" <input type=\"number\" ng-model=\"config.HostConfig.RestartPolicy.MaximumRetryCount\"/>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <hr>\n" +
" <div class=\"form-group\">\n" +
" <label>ExtraHosts:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.ExtraHosts\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Hostname:</label>\n" +
" <input type=\"text\" ng-model=\"entry.host\" class=\"form-control\" placeholder=\"hostname\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">IP Address:</label>\n" +
" <input type=\"text\" ng-model=\"entry.ip\" class=\"form-control\" placeholder=\"127.0.0.1\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.ExtraHosts, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.ExtraHosts, {host: '', ip: ''})\">Add extra host</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>LxcConf:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.LxcConf\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Name:</label>\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"lxc.utsname\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Value:</label>\n" +
" <input type=\"text\" ng-model=\"entry.value\" class=\"form-control\" placeholder=\"docker\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.LxcConf, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.LxcConf, {name: '', value: ''})\">Add Entry</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Devices:</label>\n" +
" <div ng-repeat=\"device in config.HostConfig.Devices\">\n" +
" <div class=\"form-group form-inline inline-four\">\n" +
" <label class=\"sr-only\">PathOnHost:</label>\n" +
" <input type=\"text\" ng-model=\"device.PathOnHost\" class=\"form-control\" placeholder=\"PathOnHost\"/>\n" +
" <label class=\"sr-only\">PathInContainer:</label>\n" +
" <input type=\"text\" ng-model=\"device.PathInContainer\" class=\"form-control\" placeholder=\"PathInContainer\"/>\n" +
" <label class=\"sr-only\">CgroupPermissions:</label>\n" +
" <input type=\"text\" ng-model=\"device.CgroupPermissions\" class=\"form-control\" placeholder=\"CgroupPermissions\"/>\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.Devices, device)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.Devices, { PathOnHost: '', PathInContainer: '', CgroupPermissions: ''})\">Add Device</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>PortBindings:</label>\n" +
" <div ng-repeat=\"portBinding in config.HostConfig.PortBindings\">\n" +
" <div class=\"form-group form-inline inline-four\">\n" +
" <label class=\"sr-only\">Host IP:</label>\n" +
" <input type=\"text\" ng-model=\"portBinding.ip\" class=\"form-control\" placeholder=\"Host IP Address\"/>\n" +
" <label class=\"sr-only\">Host Port:</label>\n" +
" <input type=\"text\" ng-model=\"portBinding.extPort\" class=\"form-control\" placeholder=\"Host Port\"/>\n" +
" <label class=\"sr-only\">Container port:</label>\n" +
" <input type=\"text\" ng-model=\"portBinding.intPort\" class=\"form-control\" placeholder=\"Container Port\"/>\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.PortBindings, portBinding)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.PortBindings, {ip: '', extPort: '', intPort: ''})\">Add Port Binding</button>\n" +
" </div>\n" +
" </fieldset>\n" +
" </accordion-group>\n" +
" </accordion>\n" +
" </form>\n" +
" </div>\n" +
" <div class=\"modal-footer\">\n" +
" <a href=\"\" class=\"btn btn-primary\" ng-click=\"create()\">Create</a>\n" +
" <a href=\"\" class=\"btn btn-primary btn-lg\" ng-click=\"create()\">Create</a>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +

6
dist/index.html vendored
View File

@ -23,11 +23,7 @@
<script src="assets/js/spin.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/angularjs/1.2.6/angular.min.js"></script>
<script src="assets/js/angularjs/1.2.6/angular-resource.min.js"></script>
<script src="assets/js/angularjs/1.2.6/angular-route.min.js"></script>
<script src="angular.js"></script>
<script src="assets/js/jquery.gritter.min.js"></script>
<script src="assets/js/Chart.min.js"></script>
<script src="assets/js/legend.js"></script>

311
dist/templates/app.js vendored
View File

@ -553,38 +553,301 @@ angular.module("app/components/startContainer/startcontainer.html", []).run(["$t
" <h3>Create And Start Container From Image</h3>\n" +
" </div>\n" +
" <div class=\"modal-body\">\n" +
" <form role=\"form\">\n" +
" <form role=\"form\">\n" +
" <accordion close-others=\"true\">\n" +
" <accordion-group heading=\"Container options\" is-open=\"menuStatus.containerOpen\">\n" +
" <fieldset>\n" +
" <div class=\"form-group\">\n" +
" <label>Cmd:</label>\n" +
" <input type=\"text\" placeholder=\"{{ commandPlaceholder }}\" ng-model=\"config.commands\" class=\"form-control\"/>\n" +
" <small>Input commands as an array</small>\n" +
" <div class=\"row\">\n" +
" <div class=\"col-xs-6\">\n" +
" <div class=\"form-group\">\n" +
" <label>Cmd:</label>\n" +
" <input type=\"text\" placeholder='[\"/bin/echo\", \"Hello world\"]' ng-model=\"config.Cmd\" class=\"form-control\"/>\n" +
" <small>Input commands as a raw string or JSON array</small>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Entrypoint:</label>\n" +
" <input type=\"text\" ng-model=\"config.Entrypoint\" class=\"form-control\" placeholder=\"./entrypoint.sh\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Name:</label>\n" +
" <input type=\"text\" ng-model=\"config.name\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Hostname:</label>\n" +
" <input type=\"text\" ng-model=\"config.Hostname\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Domainname:</label>\n" +
" <input type=\"text\" ng-model=\"config.Domainname\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>User:</label>\n" +
" <input type=\"text\" ng-model=\"config.User\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Memory:</label>\n" +
" <input type=\"number\" ng-model=\"config.Memory\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Volumes:</label>\n" +
" <div ng-repeat=\"volume in config.Volumes\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"volume.name\" class=\"form-control\" placeholder=\"/var/data\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.Volumes, volume)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.Volumes, {name: ''})\">Add Volume</button>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Name:</label>\n" +
" <input type=\"text\" ng-model=\"config.name\" class=\"form-control\"/>\n" +
" <div class=\"col-xs-6\">\n" +
" <div class=\"form-group\">\n" +
" <label>MemorySwap:</label>\n" +
" <input type=\"number\" ng-model=\"config.MemorySwap\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>CpuShares:</label>\n" +
" <input type=\"number\" ng-model=\"config.CpuShares\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Cpuset:</label>\n" +
" <input type=\"text\" ng-model=\"config.Cpuset\" class=\"form-control\" placeholder=\"1,2\"/>\n" +
" <small>Input as comma-separated list of numbers</small>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>WorkingDir:</label>\n" +
" <input type=\"text\" ng-model=\"config.WorkingDir\" class=\"form-control\" placeholder=\"/app\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>MacAddress:</label>\n" +
" <input type=\"text\" ng-model=\"config.MacAddress\" class=\"form-control\" placeholder=\"12:34:56:78:9a:bc\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"networkDisabled\">NetworkDisabled:</label>\n" +
" <input id=\"networkDisabled\" type=\"checkbox\" ng-model=\"config.NetworkDisabled\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"tty\">Tty:</label>\n" +
" <input id=\"tty\" type=\"checkbox\" ng-model=\"config.Tty\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"openStdin\">OpenStdin:</label>\n" +
" <input id=\"openStdin\" type=\"checkbox\" ng-model=\"config.OpenStdin\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"stdinOnce\">StdinOnce:</label>\n" +
" <input id=\"stdinOnce\" type=\"checkbox\" ng-model=\"config.StdinOnce\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>SecurityOpts:</label>\n" +
" <div ng-repeat=\"opt in config.SecurityOpts\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"opt.name\" class=\"form-control\" placeholder=\"label:type:svirt_apache\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.SecurityOpts, opt)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.SecurityOpts, {name: ''})\">Add Option</button>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Memory:</label>\n" +
" <input type=\"number\" ng-model=\"config.memory\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Memory Swap:</label>\n" +
" <input type=\"number\" ng-model=\"config.memorySwap\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>CPU Shares:</label>\n" +
" <input type=\"number\" ng-model=\"config.cpuShares\" class=\"form-control\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Volumes From:</label>\n" +
" <input type=\"text\" ng-model=\"config.volumesFrom\" class=\"form-control\"/>\n" +
" </div>\n" +
" <hr>\n" +
" <div class=\"form-group\">\n" +
" <label>Env:</label>\n" +
" <div ng-repeat=\"envar in config.Env\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Variable Name:</label>\n" +
" <input type=\"text\" ng-model=\"envar.name\" class=\"form-control\" placeholder=\"NAME\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Variable Value:</label>\n" +
" <input type=\"text\" ng-model=\"envar.value\" class=\"form-control\" placeholder=\"value\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.Env, envar)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.Env, {name: '', value: ''})\">Add environment variable</button>\n" +
" </div>\n" +
" </fieldset>\n" +
" </accordion-group>\n" +
" <accordion-group heading=\"HostConfig options\" is-open=\"menuStatus.hostConfigOpen\">\n" +
" <fieldset>\n" +
" <div class=\"row\">\n" +
" <div class=\"col-xs-6\">\n" +
" <div class=\"form-group\">\n" +
" <label>Binds:</label>\n" +
" <div ng-repeat=\"bind in config.HostConfig.Binds\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"bind.name\" class=\"form-control\" placeholder=\"/host:/container\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.Binds, bind)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.Binds, {name: ''})\">Add Bind</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Links:</label>\n" +
" <div ng-repeat=\"link in config.HostConfig.Links\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"link.name\" class=\"form-control\" placeholder=\"web:db\">\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.Links, link)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.Links, {name: ''})\">Add Link</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Dns:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.Dns\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"8.8.8.8\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.Dns, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.Dns, {name: ''})\">Add entry</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>DnsSearch:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.DnsSearch\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"example.com\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.DnsSearch, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.DnsSearch, {name: ''})\">Add entry</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>CapAdd:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.CapAdd\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"cap_sys_admin\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.CapAdd, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.CapAdd, {name: ''})\">Add entry</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>CapDrop:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.CapDrop\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"cap_sys_admin\"/>\n" +
" <button type=\"button\" class=\"btn btn-danger btn-sm\" ng-click=\"rmEntry(config.HostConfig.CapDrop, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.CapDrop, {name: ''})\">Add entry</button>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"col-xs-6\">\n" +
" <div class=\"form-group\">\n" +
" <label>NetworkMode:</label>\n" +
" <input type=\"text\" ng-model=\"config.HostConfig.NetworkMode\" class=\"form-control\" placeholder=\"bridge\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"publishAllPorts\">PublishAllPorts:</label>\n" +
" <input id=\"publishAllPorts\" type=\"checkbox\" ng-model=\"config.HostConfig.PublishAllPorts\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label for=\"privileged\">Privileged:</label>\n" +
" <input id=\"privileged\" type=\"checkbox\" ng-model=\"config.HostConfig.Privileged\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>VolumesFrom:</label>\n" +
" <div ng-repeat=\"volume in config.HostConfig.VolumesFrom\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <select ng-model=\"volume.name\" ng-options=\"name for name in containerNames track by name\" class=\"form-control\"/>\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.VolumesFrom, volume)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.VolumesFrom, {name: ''})\">Add volume</button>\n" +
" </div>\n" +
" \n" +
" <div class=\"form-group\">\n" +
" <label>RestartPolicy:</label>\n" +
" <select ng-model=\"config.HostConfig.RestartPolicy.name\">\n" +
" <option value=\"\">disabled</option>\n" +
" <option value=\"always\">always</option>\n" +
" <option value=\"on-failure\">on-failure</option>\n" +
" </select>\n" +
" <label>MaximumRetryCount:</label>\n" +
" <input type=\"number\" ng-model=\"config.HostConfig.RestartPolicy.MaximumRetryCount\"/>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <hr>\n" +
" <div class=\"form-group\">\n" +
" <label>ExtraHosts:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.ExtraHosts\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Hostname:</label>\n" +
" <input type=\"text\" ng-model=\"entry.host\" class=\"form-control\" placeholder=\"hostname\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">IP Address:</label>\n" +
" <input type=\"text\" ng-model=\"entry.ip\" class=\"form-control\" placeholder=\"127.0.0.1\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.ExtraHosts, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.ExtraHosts, {host: '', ip: ''})\">Add extra host</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>LxcConf:</label>\n" +
" <div ng-repeat=\"entry in config.HostConfig.LxcConf\">\n" +
" <div class=\"form-group form-inline\">\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Name:</label>\n" +
" <input type=\"text\" ng-model=\"entry.name\" class=\"form-control\" placeholder=\"lxc.utsname\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label class=\"sr-only\">Value:</label>\n" +
" <input type=\"text\" ng-model=\"entry.value\" class=\"form-control\" placeholder=\"docker\"/>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.LxcConf, entry)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.LxcConf, {name: '', value: ''})\">Add Entry</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>Devices:</label>\n" +
" <div ng-repeat=\"device in config.HostConfig.Devices\">\n" +
" <div class=\"form-group form-inline inline-four\">\n" +
" <label class=\"sr-only\">PathOnHost:</label>\n" +
" <input type=\"text\" ng-model=\"device.PathOnHost\" class=\"form-control\" placeholder=\"PathOnHost\"/>\n" +
" <label class=\"sr-only\">PathInContainer:</label>\n" +
" <input type=\"text\" ng-model=\"device.PathInContainer\" class=\"form-control\" placeholder=\"PathInContainer\"/>\n" +
" <label class=\"sr-only\">CgroupPermissions:</label>\n" +
" <input type=\"text\" ng-model=\"device.CgroupPermissions\" class=\"form-control\" placeholder=\"CgroupPermissions\"/>\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.Devices, device)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.Devices, { PathOnHost: '', PathInContainer: '', CgroupPermissions: ''})\">Add Device</button>\n" +
" </div>\n" +
" <div class=\"form-group\">\n" +
" <label>PortBindings:</label>\n" +
" <div ng-repeat=\"portBinding in config.HostConfig.PortBindings\">\n" +
" <div class=\"form-group form-inline inline-four\">\n" +
" <label class=\"sr-only\">Host IP:</label>\n" +
" <input type=\"text\" ng-model=\"portBinding.ip\" class=\"form-control\" placeholder=\"Host IP Address\"/>\n" +
" <label class=\"sr-only\">Host Port:</label>\n" +
" <input type=\"text\" ng-model=\"portBinding.extPort\" class=\"form-control\" placeholder=\"Host Port\"/>\n" +
" <label class=\"sr-only\">Container port:</label>\n" +
" <input type=\"text\" ng-model=\"portBinding.intPort\" class=\"form-control\" placeholder=\"Container Port\"/>\n" +
" <button class=\"btn btn-danger btn-xs form-control\" ng-click=\"rmEntry(config.HostConfig.PortBindings, portBinding)\">Remove</button>\n" +
" </div>\n" +
" </div>\n" +
" <button type=\"button\" class=\"btn btn-success btn-sm\" ng-click=\"addEntry(config.HostConfig.PortBindings, {ip: '', extPort: '', intPort: ''})\">Add Port Binding</button>\n" +
" </div>\n" +
" </fieldset>\n" +
" </accordion-group>\n" +
" </accordion>\n" +
" </form>\n" +
" </div>\n" +
" <div class=\"modal-footer\">\n" +
" <a href=\"\" class=\"btn btn-primary\" ng-click=\"create()\">Create</a>\n" +
" <a href=\"\" class=\"btn btn-primary btn-lg\" ng-click=\"create()\">Create</a>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +

View File

@ -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'
}
},

View File

@ -23,11 +23,7 @@
<script src="assets/js/spin.js" type="text/javascript" charset="utf-8"></script>
<script src="assets/js/angularjs/1.2.6/angular.min.js"></script>
<script src="assets/js/angularjs/1.2.6/angular-resource.min.js"></script>
<script src="assets/js/angularjs/1.2.6/angular-route.min.js"></script>
<script src="angular.js"></script>
<script src="assets/js/jquery.gritter.min.js"></script>
<script src="assets/js/Chart.min.js"></script>
<script src="assets/js/legend.js"></script>

View File

@ -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',

View File

@ -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);
}));
});
});