mirror of https://github.com/portainer/portainer
commit
41a41cdf38
|
@ -2,5 +2,7 @@ FROM scratch
|
|||
|
||||
COPY dist /
|
||||
|
||||
VOLUME /data
|
||||
|
||||
EXPOSE 9000
|
||||
ENTRYPOINT ["/ui-for-docker"]
|
||||
|
|
15
README.md
15
README.md
|
@ -37,11 +37,11 @@ $ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -e http://127.0.0.1:23
|
|||
|
||||
**Supported Swarm version: 1.2.3**
|
||||
|
||||
You can access a specific view for you Swarm cluster by defining the `-swarm` flag:
|
||||
You can access a specific view for you Swarm cluster by defining the `--swarm` flag:
|
||||
|
||||
```
|
||||
# Connect to a tcp socket and enable Swarm:
|
||||
$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -e http://<SWARM_HOST>:<SWARM_PORT> -swarm
|
||||
$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -e http://<SWARM_HOST>:<SWARM_PORT> --swarm
|
||||
```
|
||||
|
||||
*NOTE*: Due to Swarm not exposing information in a machine readable way, the app is bound to a specific version of Swarm at the moment.
|
||||
|
@ -74,8 +74,9 @@ $ docker run -d -p 9000:9000 --privileged -v /var/run/docker.sock:/var/run/docke
|
|||
|
||||
The following options are available for the `ui-for-docker` binary:
|
||||
|
||||
* `-endpoint`, `-e`: Docker deamon endpoint (default: *"/var/run/docker.sock"*)
|
||||
* `-bind`, `-p`: Address and port to serve UI For Docker (default: *":9000"*)
|
||||
* `-assets`, `-a`: Path to the assets (default: *"."*)
|
||||
* `-swarm`, `-s`: Swarm cluster support (default: *false*)
|
||||
* `-hide-label`, `-l`: Hide containers with a specific label in the UI
|
||||
* `--endpoint`, `-e`: Docker deamon endpoint (default: *"/var/run/docker.sock"*)
|
||||
* `--bind`, `-p`: Address and port to serve UI For Docker (default: *":9000"*)
|
||||
* `--data`, `-d`: Path to the data folder (default: *"."*)
|
||||
* `--assets`, `-a`: Path to the assets (default: *"."*)
|
||||
* `--swarm`, `-s`: Swarm cluster support (default: *false*)
|
||||
* `--hide-label`, `-l`: Hide containers with a specific label in the UI
|
||||
|
|
19
app/app.js
19
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: '<ui-view/>'
|
||||
})
|
||||
.state('actions.create', {
|
||||
abstract: true,
|
||||
url: "/create",
|
||||
template: '<ui-view/>'
|
||||
})
|
||||
.state('actions.create.container', {
|
||||
url: "/container",
|
||||
templateUrl: 'app/components/createContainer/createcontainer.html',
|
||||
controller: 'CreateContainerController'
|
||||
})
|
||||
.state('docker', {
|
||||
url: '/docker/',
|
||||
templateUrl: 'app/components/docker/docker.html',
|
||||
|
@ -120,4 +135,4 @@ angular.module('uifordocker', [
|
|||
.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('CONFIG_ENDPOINT', '/config')
|
||||
.constant('UI_VERSION', 'v1.0.4');
|
||||
.constant('UI_VERSION', 'v1.1.0');
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
<div ng-include="template" ng-controller="StartContainerController"></div>
|
||||
|
||||
<rd-header>
|
||||
<rd-header-title title="Container list">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="containers" ui-sref-opts="{reload: true}">
|
||||
|
@ -23,8 +21,8 @@
|
|||
<button type="button" class="btn btn-primary" ng-click="pauseAction()" ng-disabled="!state.selectedItemCount">Pause</button>
|
||||
<button type="button" class="btn btn-primary" ng-click="unpauseAction()" ng-disabled="!state.selectedItemCount">Unpause</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount">Remove</button>
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#create-modal">Start a new container...</button>
|
||||
</div>
|
||||
<a class="btn btn-default" type="button" ui-sref="actions.create.container">Add container</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="checkbox" ng-model="state.displayAll" id="displayAll" ng-change="toggleGetAll()"/><label for="displayAll">Display All</label>
|
||||
|
@ -33,10 +31,17 @@
|
|||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><label><input type="checkbox" ng-model="state.toggle" ng-change="toggleSelectAll()" /> Select</label></th>
|
||||
<th></th>
|
||||
<th>
|
||||
<a ui-sref="containers" ng-click="order('State')">
|
||||
State
|
||||
<span ng-show="sortType == 'State' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'State' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="containers" ng-click="order('Names')">
|
||||
Name
|
||||
|
@ -44,6 +49,20 @@
|
|||
<span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="containers" ng-click="order('IP')">
|
||||
IP Address
|
||||
<span ng-show="sortType == 'IP' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'IP' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="swarm">
|
||||
<a ui-sref="containers" ng-click="order('Host')">
|
||||
Host
|
||||
<span ng-show="sortType == 'Host' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Host' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="containers" ng-click="order('Image')">
|
||||
Image
|
||||
|
@ -58,30 +77,18 @@
|
|||
<span ng-show="sortType == 'Command' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="containers" ng-click="order('Created')">
|
||||
Created
|
||||
<span ng-show="sortType == 'Created' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Created' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="containers" ng-click="order('Status')">
|
||||
Status
|
||||
<span ng-show="sortType == 'Status' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="container in (state.filteredContainers = ( containers | filter:state.filter | orderBy:sortType:sortReverse))">
|
||||
<td><input type="checkbox" ng-model="container.Checked" ng-change="selectItem(container)"/></td>
|
||||
<td><a ui-sref="container({id: container.Id})">{{ container|containername}}</a></td>
|
||||
<td><span class="label label-{{ container.State|containerstatusbadge }}">{{ container.State }}</span></td>
|
||||
<td ng-if="swarm"><a ui-sref="container({id: container.Id})">{{ container|swarmcontainername}}</a></td>
|
||||
<td ng-if="!swarm"><a ui-sref="container({id: container.Id})">{{ container|containername}}</a></td>
|
||||
<td>{{ container.IP ? container.IP : '-' }}</td>
|
||||
<td ng-if="swarm">{{ container|swarmhostname}}</td>
|
||||
<td><a ui-sref="image({id: container.Image})">{{ container.Image }}</a></td>
|
||||
<td>{{ container.Command|truncate:40 }}</td>
|
||||
<td>{{ container.Created|getdate }}</td>
|
||||
<td><span class="label label-{{ container.Status|statusbadge }}">{{ container.Status }}</span></td>
|
||||
<td>{{ container.Command|truncate:60 }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
angular.module('containers', [])
|
||||
.controller('ContainersController', ['$scope', 'Container', 'Settings', 'Messages', 'ViewSpinner', 'Config',
|
||||
function ($scope, Container, Settings, Messages, ViewSpinner, Config) {
|
||||
.controller('ContainersController', ['$scope', 'Container', 'Settings', 'Messages', 'ViewSpinner', 'Config', 'errorMsgFilter',
|
||||
function ($scope, Container, Settings, Messages, ViewSpinner, Config, errorMsgFilter) {
|
||||
|
||||
$scope.state = {};
|
||||
$scope.state.displayAll = Settings.displayAll;
|
||||
$scope.sortType = 'Created';
|
||||
$scope.sortType = 'State';
|
||||
$scope.sortReverse = true;
|
||||
$scope.state.toggle = false;
|
||||
$scope.state.selectedItemCount = 0;
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
|
@ -41,13 +40,12 @@ function ($scope, Container, Settings, Messages, ViewSpinner, Config) {
|
|||
};
|
||||
angular.forEach(items, function (c) {
|
||||
if (c.Checked) {
|
||||
counter = counter + 1;
|
||||
if (action === Container.start) {
|
||||
Container.get({id: c.Id}, function (d) {
|
||||
c = d;
|
||||
counter = counter + 1;
|
||||
action({id: c.Id, HostConfig: c.HostConfig || {}}, function (d) {
|
||||
Messages.send("Container " + msg, c.Id);
|
||||
var index = $scope.containers.indexOf(c);
|
||||
complete();
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e.data);
|
||||
|
@ -63,11 +61,24 @@ function ($scope, Container, Settings, Messages, ViewSpinner, Config) {
|
|||
complete();
|
||||
});
|
||||
}
|
||||
else if (action === Container.remove) {
|
||||
action({id: c.Id}, function (d) {
|
||||
var error = errorMsgFilter(d);
|
||||
if (error) {
|
||||
Messages.send("Error", "Unable to remove running container");
|
||||
}
|
||||
else {
|
||||
Messages.send("Container " + msg, c.Id);
|
||||
}
|
||||
complete();
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e.data);
|
||||
complete();
|
||||
});
|
||||
}
|
||||
else {
|
||||
counter = counter + 1;
|
||||
action({id: c.Id}, function (d) {
|
||||
Messages.send("Container " + msg, c.Id);
|
||||
var index = $scope.containers.indexOf(c);
|
||||
complete();
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e.data);
|
||||
|
@ -75,7 +86,6 @@ function ($scope, Container, Settings, Messages, ViewSpinner, Config) {
|
|||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
if (counter === 0) {
|
||||
|
@ -91,18 +101,6 @@ function ($scope, Container, Settings, Messages, ViewSpinner, Config) {
|
|||
}
|
||||
};
|
||||
|
||||
$scope.toggleSelectAll = function () {
|
||||
$scope.state.selectedItem = $scope.state.toggle;
|
||||
angular.forEach($scope.state.filteredContainers, function (i) {
|
||||
i.Checked = $scope.state.toggle;
|
||||
});
|
||||
if ($scope.state.toggle) {
|
||||
$scope.state.selectedItemCount = $scope.state.filteredContainers.length;
|
||||
} else {
|
||||
$scope.state.selectedItemCount = 0;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleGetAll = function () {
|
||||
Settings.displayAll = $scope.state.displayAll;
|
||||
update({all: Settings.displayAll ? 1 : 0});
|
||||
|
@ -151,9 +149,10 @@ function ($scope, Container, Settings, Messages, ViewSpinner, Config) {
|
|||
});
|
||||
};
|
||||
|
||||
var hiddenLabels;
|
||||
$scope.swarm = false;
|
||||
Config.$promise.then(function (c) {
|
||||
hiddenLabels = c.hiddenLabels;
|
||||
$scope.swarm = c.swarm;
|
||||
update({all: Settings.displayAll ? 1 : 0});
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
|
@ -0,0 +1,312 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Create container"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Containers > Add container
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_name" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="config.name" id="container_name" placeholder="e.g. myContainer">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- image input -->
|
||||
<div class="form-group">
|
||||
<label for="container_image" class="col-sm-1 control-label text-left">Image</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="config.Image" id="container_image" placeholder="ubuntu:trusty">
|
||||
</div>
|
||||
<div class="col-sm-offset-1 col-sm-11">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="state.alwaysPull"> Always pull image before creating
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !image-input -->
|
||||
<!-- restart-policy -->
|
||||
<div class="form-group">
|
||||
<label class="col-sm-1 control-label text-left">Restart policy</label>
|
||||
<div class="col-sm-11">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_restart_policy" ng-model="config.HostConfig.RestartPolicy.Name" value="no">
|
||||
Never
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_restart_policy" ng-model="config.HostConfig.RestartPolicy.Name" value="always">
|
||||
Always
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_restart_policy" ng-model="config.HostConfig.RestartPolicy.Name" value="on-failure">
|
||||
<span class="radio-value">On failure</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !restart-policy -->
|
||||
<!-- port-mapping -->
|
||||
<div class="form-group">
|
||||
<label for="container_ports" class="col-sm-1 control-label text-left">Port mapping</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addPortBinding()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map port
|
||||
</span>
|
||||
</div>
|
||||
<!-- port-mapping-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="portBinding in config.HostConfig.PortBindings" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">host</span>
|
||||
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80">
|
||||
</div>
|
||||
<div class="input-group col-sm-1 input-group-sm">
|
||||
<select class="selectpicker form-control" ng-model="portBinding.protocol">
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removePortBinding($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !port-mapping-input-list -->
|
||||
</div>
|
||||
<!-- !port-mapping -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active clickable"><a data-target="#command" data-toggle="tab">Command</a></li>
|
||||
<li class="clickable"><a data-target="#volumes" data-toggle="tab">Volumes</a></li>
|
||||
<li class="clickable"><a data-target="#network" data-toggle="tab">Network</a></li>
|
||||
<li class="clickable"><a data-target="#security" data-toggle="tab">Security/Host</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- tab-content -->
|
||||
<div class="tab-content">
|
||||
<!-- tab-command -->
|
||||
<div class="tab-pane active" id="command">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- command-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_command" class="col-sm-1 control-label text-left">Command</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Cmd" id="container_command" placeholder="e.g. /usr/bin/nginx -t -c /mynginx.conf">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !command-input -->
|
||||
<!-- entrypoint-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_entrypoint" class="col-sm-1 control-label text-left">Entry Point</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Entrypoint" id="container_entrypoint" placeholder="e.g. /bin/sh -c">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !entrypoint-input -->
|
||||
<!-- workdir-user-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_workingdir" class="col-sm-1 control-label text-left">Working Dir</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" ng-model="config.WorkingDir" id="container_workingdir" placeholder="e.g. /myapp">
|
||||
</div>
|
||||
<label for="container_user" class="col-sm-1 control-label text-left">User</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" ng-model="config.User" id="container_user" placeholder="e.g. nginx">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !workdir-user-input -->
|
||||
<!-- console -->
|
||||
<div class="form-group">
|
||||
<label for="container_console" class="col-sm-1 control-label text-left">Console</label>
|
||||
<div class="col-sm-11">
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="both">
|
||||
Interactive & TTY <span class="small text-muted">(-i -t)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="interactive">
|
||||
Interactive <span class="small text-muted">(-i)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-offset-1 col-sm-11">
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="tty">
|
||||
TTY <span class="small text-muted">(-t)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_console" ng-model="formValues.Console" value="none">
|
||||
None
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !console -->
|
||||
<!-- environment-variables -->
|
||||
<div class="form-group">
|
||||
<label for="container_env" class="col-sm-1 control-label text-left">Environment variables</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addEnvironmentVariable()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> environment variable
|
||||
</span>
|
||||
</div>
|
||||
<!-- environment-variable-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="variable in config.Env" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="variable.name" placeholder="e.g. FOO">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="variable.value" placeholder="e.g. bar">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removeEnvironmentVariable($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
<!-- !environment-variables -->
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-command -->
|
||||
<!-- tab-volume -->
|
||||
<div class="tab-pane" id="volumes">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- volumes -->
|
||||
<div class="form-group">
|
||||
<label for="container_volumes" class="col-sm-1 control-label text-left">Volumes</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addVolume()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> volume
|
||||
</span>
|
||||
</div>
|
||||
<!-- volumes-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="volume in formValues.Volumes" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-1 input-group-sm">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="volume.readOnly"> Read-only
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon"><input type="checkbox" ng-model="volume.isPath" ng-click="resetVolumePath($index)">Path</span>
|
||||
<select class="selectpicker form-control" ng-model="volume.name" ng-if="!volume.isPath">
|
||||
<option selected disabled hidden value="">Select a volume</option>
|
||||
<option ng-repeat="vol in availableVolumes" ng-value="vol.Name">{{ vol.Name|truncate:30}}</option>
|
||||
</select>
|
||||
<input ng-if="volume.isPath" type="text" class="form-control" ng-model="volume.name" placeholder="e.g. /path/on/host">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="volume.containerPath" placeholder="e.g. /path/in/container">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removeVolume($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !volumes-input-list -->
|
||||
</div>
|
||||
</form>
|
||||
<!-- !volumes -->
|
||||
</div>
|
||||
<!-- !tab-volume -->
|
||||
<!-- tab-network -->
|
||||
<div class="tab-pane" id="network">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- network-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_network" class="col-sm-1 control-label text-left">Network</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="selectpicker form-control" ng-model="config.HostConfig.NetworkMode">
|
||||
<option selected disabled hidden value="">Select a network</option>
|
||||
<option ng-repeat="net in availableNetworks" ng-value="net.Name">{{ net.Name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !network-input -->
|
||||
<!-- hostname-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_hostname" class="col-sm-1 control-label text-left">Hostname</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Hostname" id="container_hostname" placeholder="e.g. web01">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !hostname-input -->
|
||||
<!-- domainname-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_domainname" class="col-sm-1 control-label text-left">Domain Name</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" ng-model="config.Domainname" id="container_domainname" placeholder="e.g. example.com">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !domainname -->
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-network -->
|
||||
<!-- tab-security -->
|
||||
<div class="tab-pane" id="security">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- privileged-mode -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="config.HostConfig.Privileged"> Privileged mode
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !privileged-mode -->
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-security -->
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12" style="text-align: center;">
|
||||
<button type="button" class="btn btn-default btn-lg" ng-click="create()">Create</button>
|
||||
<a type="button" class="btn btn-default btn-lg" ui-sref="containers">Cancel</a>
|
||||
</div>
|
||||
</div>
|
|
@ -1,163 +0,0 @@
|
|||
angular.module('startContainer', ['ui.bootstrap'])
|
||||
.controller('StartContainerController', ['$scope', '$state', 'Container', 'Messages', 'containernameFilter', 'errorMsgFilter', 'ViewSpinner',
|
||||
function ($scope, $state, Container, 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;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.create = function () {
|
||||
// Copy the config before transforming fields to the remote API format
|
||||
$('#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 ctor = Container;
|
||||
var s = $scope;
|
||||
Container.create(config, function (d) {
|
||||
if (d.Id) {
|
||||
var reqBody = config.HostConfig || {};
|
||||
reqBody.id = d.Id;
|
||||
ctor.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);
|
||||
ctor.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.addEntry = function (array, entry) {
|
||||
array.push(entry);
|
||||
};
|
||||
$scope.rmEntry = function (array, entry) {
|
||||
var idx = array.indexOf(entry);
|
||||
array.splice(idx, 1);
|
||||
};
|
||||
}]);
|
|
@ -1,444 +0,0 @@
|
|||
<div id="create-modal" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>Start a new container</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form">
|
||||
<accordion close-others="true">
|
||||
<accordion-group heading="Container options" is-open="menuStatus.containerOpen">
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<div class="form-group">
|
||||
<label>Image:</label>
|
||||
<input type="text" placeholder='ubuntu:latest'
|
||||
ng-model="config.Image" class="form-control"/>
|
||||
</div>
|
||||
<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"/>
|
||||
<a href="" ng-click="rmEntry(config.Volumes, volume)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.Volumes, {name: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<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"/>
|
||||
<a href="" ng-click="rmEntry(config.SecurityOpts, opt)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.SecurityOpts, {name: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<a href="" ng-click="rmEntry(config.Env, envar)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.Env, {name: '', value: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Labels:</label>
|
||||
|
||||
<div ng-repeat="label in config.Labels">
|
||||
<div class="form-group form-inline">
|
||||
<div class="form-group">
|
||||
<label class="sr-only">Key:</label>
|
||||
<input type="text" ng-model="label.key" class="form-control"
|
||||
placeholder="key"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="sr-only">Value:</label>
|
||||
<input type="text" ng-model="label.value" class="form-control"
|
||||
placeholder="value"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<a href="" ng-click="rmEntry(config.Labels, label)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.Labels, {key: '', value: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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"/>
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.Binds, bind)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.Binds, {name: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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">
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.Links, link)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.Links, {name: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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"/>
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.Dns, entry)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.Dns, {name: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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"/>
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.DnsSearch, entry)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.DnsSearch, {name: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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"/>
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.CapAdd, entry)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.CapAdd, {name: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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"/>
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.CapDrop, entry)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.CapDrop, {name: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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">
|
||||
</select>
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.VolumesFrom, volume)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.VolumesFrom, {name: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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">
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.ExtraHosts, entry)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.ExtraHosts, {host: '', ip: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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">
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.LxcConf, entry)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.LxcConf, {name: '', value: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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"/>
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.Devices, device)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.Devices, { PathOnHost: '', PathInContainer: '', CgroupPermissions: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</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"/>
|
||||
<select ng-model="portBinding.protocol">
|
||||
<option value="">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
<a href="" ng-click="rmEntry(config.HostConfig.PortBindings, portBinding)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="addEntry(config.HostConfig.PortBindings, {ip: '', extPort: '', intPort: ''})">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</fieldset>
|
||||
</accordion-group>
|
||||
</accordion>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="" class="btn btn-primary btn-lg" ng-click="create()">Create</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -137,7 +137,7 @@
|
|||
<td>{{ node.ip }}</td>
|
||||
<td>{{ node.containers }}</td>
|
||||
<td>{{ node.version }}</td>
|
||||
<td><span class="label label-{{ node.status|statusbadge }}">{{ node.status }}</span></td>
|
||||
<td><span class="label label-{{ node.status|nodestatusbadge }}">{{ node.status }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -52,11 +52,10 @@ angular.module('swarm', [])
|
|||
node.ip = info[offset][1];
|
||||
node.id = info[offset + 1][1];
|
||||
node.status = info[offset + 2][1];
|
||||
node.containers = info[offset + 2][1];
|
||||
node.cpu = info[offset + 3][1];
|
||||
node.memory = info[offset + 4][1];
|
||||
node.labels = info[offset + 5][1];
|
||||
node.error = info[offset + 6][1];
|
||||
node.containers = info[offset + 3][1];
|
||||
node.cpu = info[offset + 4][1];
|
||||
node.memory = info[offset + 5][1];
|
||||
node.labels = info[offset + 6][1];
|
||||
node.version = info[offset + 8][1];
|
||||
$scope.swarm.Status.push(node);
|
||||
}
|
||||
|
|
|
@ -1,128 +1,155 @@
|
|||
angular.module('dockerui.filters', [])
|
||||
.filter('truncate', function () {
|
||||
'use strict';
|
||||
return function (text, length, end) {
|
||||
if (isNaN(length)) {
|
||||
length = 10;
|
||||
}
|
||||
.filter('truncate', function () {
|
||||
'use strict';
|
||||
return function (text, length, end) {
|
||||
if (isNaN(length)) {
|
||||
length = 10;
|
||||
}
|
||||
|
||||
if (end === undefined) {
|
||||
end = '...';
|
||||
}
|
||||
if (end === undefined) {
|
||||
end = '...';
|
||||
}
|
||||
|
||||
if (text.length <= length || text.length - end.length <= length) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return String(text).substring(0, length - end.length) + end;
|
||||
}
|
||||
};
|
||||
})
|
||||
.filter('statusbadge', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
if (text === 'Ghost') {
|
||||
return 'important';
|
||||
} else if (text === 'Unhealthy') {
|
||||
return 'danger';
|
||||
} else if (text.indexOf('Exit') !== -1 && text !== 'Exit 0') {
|
||||
return 'warning';
|
||||
}
|
||||
return 'success';
|
||||
};
|
||||
})
|
||||
.filter('trimcontainername', function () {
|
||||
'use strict';
|
||||
return function (name) {
|
||||
if (name) {
|
||||
return (name.indexOf('/') === 0 ? name.replace('/','') : name);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('getstatetext', function () {
|
||||
'use strict';
|
||||
return function (state) {
|
||||
if (state === undefined) {
|
||||
return '';
|
||||
}
|
||||
if (state.Ghost && state.Running) {
|
||||
return 'Ghost';
|
||||
}
|
||||
if (state.Running && state.Paused) {
|
||||
return 'Running (Paused)';
|
||||
}
|
||||
if (state.Running) {
|
||||
return 'Running';
|
||||
}
|
||||
return 'Stopped';
|
||||
};
|
||||
})
|
||||
.filter('getstatelabel', function () {
|
||||
'use strict';
|
||||
return function (state) {
|
||||
if (state === undefined) {
|
||||
return 'label-default';
|
||||
}
|
||||
if (text.length <= length || text.length - end.length <= length) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return String(text).substring(0, length - end.length) + end;
|
||||
}
|
||||
};
|
||||
})
|
||||
.filter('containerstatusbadge', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
if (text === 'paused') {
|
||||
return 'warning';
|
||||
} else if (text === 'created') {
|
||||
return 'info';
|
||||
} else if (text === 'exited') {
|
||||
return 'danger';
|
||||
}
|
||||
return 'success';
|
||||
};
|
||||
})
|
||||
.filter('nodestatusbadge', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
if (text === 'Unhealthy') {
|
||||
return 'danger';
|
||||
}
|
||||
return 'success';
|
||||
};
|
||||
})
|
||||
.filter('trimcontainername', function () {
|
||||
'use strict';
|
||||
return function (name) {
|
||||
if (name) {
|
||||
return (name.indexOf('/') === 0 ? name.replace('/','') : name);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('capitalize', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
return _.capitalize(text);
|
||||
};
|
||||
})
|
||||
.filter('getstatetext', function () {
|
||||
'use strict';
|
||||
return function (state) {
|
||||
if (state === undefined) {
|
||||
return '';
|
||||
}
|
||||
if (state.Ghost && state.Running) {
|
||||
return 'Ghost';
|
||||
}
|
||||
if (state.Running && state.Paused) {
|
||||
return 'Running (Paused)';
|
||||
}
|
||||
if (state.Running) {
|
||||
return 'Running';
|
||||
}
|
||||
return 'Stopped';
|
||||
};
|
||||
})
|
||||
.filter('getstatelabel', function () {
|
||||
'use strict';
|
||||
return function (state) {
|
||||
if (state === undefined) {
|
||||
return 'label-default';
|
||||
}
|
||||
|
||||
if (state.Ghost && state.Running) {
|
||||
return 'label-important';
|
||||
}
|
||||
if (state.Running) {
|
||||
return 'label-success';
|
||||
}
|
||||
return 'label-default';
|
||||
};
|
||||
})
|
||||
.filter('humansize', function () {
|
||||
'use strict';
|
||||
return function (bytes) {
|
||||
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (bytes === 0) {
|
||||
return 'n/a';
|
||||
}
|
||||
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10);
|
||||
var value = bytes / Math.pow(1024, i);
|
||||
var decimalPlaces = (i < 1) ? 0 : (i - 1);
|
||||
return value.toFixed(decimalPlaces) + ' ' + sizes[[i]];
|
||||
};
|
||||
})
|
||||
.filter('containername', function () {
|
||||
'use strict';
|
||||
return function (container) {
|
||||
var name = container.Names[0];
|
||||
return name.substring(1, name.length);
|
||||
};
|
||||
})
|
||||
.filter('repotag', function () {
|
||||
'use strict';
|
||||
return function (image) {
|
||||
if (image.RepoTags && image.RepoTags.length > 0) {
|
||||
var tag = image.RepoTags[0];
|
||||
if (tag === '<none>:<none>') {
|
||||
tag = '';
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('getdate', function () {
|
||||
'use strict';
|
||||
return function (data) {
|
||||
//Multiply by 1000 for the unix format
|
||||
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;
|
||||
};
|
||||
});
|
||||
if (state.Ghost && state.Running) {
|
||||
return 'label-important';
|
||||
}
|
||||
if (state.Running) {
|
||||
return 'label-success';
|
||||
}
|
||||
return 'label-default';
|
||||
};
|
||||
})
|
||||
.filter('humansize', function () {
|
||||
'use strict';
|
||||
return function (bytes) {
|
||||
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (bytes === 0) {
|
||||
return 'n/a';
|
||||
}
|
||||
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10);
|
||||
var value = bytes / Math.pow(1024, i);
|
||||
var decimalPlaces = (i < 1) ? 0 : (i - 1);
|
||||
return value.toFixed(decimalPlaces) + ' ' + sizes[[i]];
|
||||
};
|
||||
})
|
||||
.filter('containername', function () {
|
||||
'use strict';
|
||||
return function (container) {
|
||||
var name = container.Names[0];
|
||||
return name.substring(1, name.length);
|
||||
};
|
||||
})
|
||||
.filter('swarmcontainername', function () {
|
||||
'use strict';
|
||||
return function (container) {
|
||||
return _.split(container.Names[0], '/')[2];
|
||||
};
|
||||
})
|
||||
.filter('swarmhostname', function () {
|
||||
'use strict';
|
||||
return function (container) {
|
||||
return _.split(container.Names[0], '/')[1];
|
||||
};
|
||||
})
|
||||
.filter('repotag', function () {
|
||||
'use strict';
|
||||
return function (image) {
|
||||
if (image.RepoTags && image.RepoTags.length > 0) {
|
||||
var tag = image.RepoTags[0];
|
||||
if (tag === '<none>:<none>') {
|
||||
tag = '';
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('getdate', function () {
|
||||
'use strict';
|
||||
return function (data) {
|
||||
//Multiply by 1000 for the unix format
|
||||
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;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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'}},
|
||||
|
|
|
@ -10,11 +10,10 @@ function ImageViewModel(data) {
|
|||
|
||||
function ContainerViewModel(data) {
|
||||
this.Id = data.Id;
|
||||
this.State = data.State;
|
||||
this.Names = data.Names;
|
||||
this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
|
||||
this.Image = data.Image;
|
||||
this.Command = data.Command;
|
||||
this.Created = data.Created;
|
||||
this.SizeRw = data.SizeRw;
|
||||
this.Status = data.Status;
|
||||
this.Checked = false;
|
||||
this.Names = data.Names;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uifordocker",
|
||||
"version": "1.0.4",
|
||||
"version": "1.1.0",
|
||||
"homepage": "https://github.com/kevana/ui-for-docker",
|
||||
"authors": [
|
||||
"Michael Crosby <crosbymichael@gmail.com>",
|
||||
|
|
12
dockerui.go
12
dockerui.go
|
@ -21,6 +21,7 @@ var (
|
|||
endpoint = kingpin.Flag("endpoint", "Dockerd endpoint").Default("/var/run/docker.sock").Short('e').String()
|
||||
addr = kingpin.Flag("bind", "Address and port to serve UI For Docker").Default(":9000").Short('p').String()
|
||||
assets = kingpin.Flag("assets", "Path to the assets").Default(".").Short('a').String()
|
||||
data = kingpin.Flag("data", "Path to the data").Default(".").Short('d').String()
|
||||
swarm = kingpin.Flag("swarm", "Swarm cluster support").Default("false").Short('s').Bool()
|
||||
labels = LabelParser(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l'))
|
||||
authKey []byte
|
||||
|
@ -117,7 +118,7 @@ func createUnixHandler(e string) http.Handler {
|
|||
return &UnixHandler{e}
|
||||
}
|
||||
|
||||
func createHandler(dir string, e string, c Config) http.Handler {
|
||||
func createHandler(dir string, d string, e string, c Config) http.Handler {
|
||||
var (
|
||||
mux = http.NewServeMux()
|
||||
fileHandler = http.FileServer(http.Dir(dir))
|
||||
|
@ -137,11 +138,12 @@ func createHandler(dir string, e string, c Config) http.Handler {
|
|||
}
|
||||
|
||||
// Use existing csrf authKey if present or generate a new one.
|
||||
dat, err := ioutil.ReadFile(authKeyFile)
|
||||
var authKeyPath = d + "/" + authKeyFile
|
||||
dat, err := ioutil.ReadFile(authKeyPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
authKey = securecookie.GenerateRandomKey(32)
|
||||
err := ioutil.WriteFile(authKeyFile, authKey, 0644)
|
||||
err := ioutil.WriteFile(authKeyPath, authKey, 0644)
|
||||
if err != nil {
|
||||
fmt.Println("unable to persist auth key", err)
|
||||
}
|
||||
|
@ -171,7 +173,7 @@ func csrfWrapper(h http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
func main() {
|
||||
kingpin.Version("1.0.4")
|
||||
kingpin.Version("1.1.0")
|
||||
kingpin.Parse()
|
||||
|
||||
configuration := Config{
|
||||
|
@ -179,7 +181,7 @@ func main() {
|
|||
HiddenLabels: *labels,
|
||||
}
|
||||
|
||||
handler := createHandler(*assets, *endpoint, configuration)
|
||||
handler := createHandler(*assets, *data, *endpoint, configuration)
|
||||
if err := http.ListenAndServe(*addr, handler); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -260,14 +260,14 @@ module.exports = function (grunt) {
|
|||
command: [
|
||||
'docker stop ui-for-docker',
|
||||
'docker rm ui-for-docker',
|
||||
'docker run --privileged -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --name ui-for-docker ui-for-docker'
|
||||
'docker run --privileged -d -p 9000:9000 -v /tmp/docker-ui:/data -v /var/run/docker.sock:/var/run/docker.sock --name ui-for-docker ui-for-docker -d /data'
|
||||
].join(';')
|
||||
},
|
||||
runSwarm: {
|
||||
command: [
|
||||
'docker stop ui-for-docker',
|
||||
'docker rm ui-for-docker',
|
||||
'docker run --privileged -d -p 9000:9000 --name ui-for-docker ui-for-docker -e http://10.0.7.10:4000 -swarm'
|
||||
'docker run --privileged -d -p 9000:9000 -v /tmp/docker-ui:/data --name ui-for-docker ui-for-docker -e http://10.0.7.10:4000 --swarm -d /data'
|
||||
].join(';')
|
||||
},
|
||||
cleanImages: {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"author": "Michael Crosby & Kevan Ahlquist",
|
||||
"name": "uifordocker",
|
||||
"homepage": "https://github.com/kevana/ui-for-docker",
|
||||
"version": "1.0.4",
|
||||
"version": "1.1.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:kevana/ui-for-docker.git"
|
||||
|
|
|
@ -15,20 +15,6 @@ describe('filters', function () {
|
|||
}));
|
||||
});
|
||||
|
||||
describe('statusbadge', function () {
|
||||
it('should be "important" when input is "Ghost"', inject(function (statusbadgeFilter) {
|
||||
expect(statusbadgeFilter('Ghost')).toBe('important');
|
||||
}));
|
||||
|
||||
it('should be "success" when input is "Exit 0"', inject(function (statusbadgeFilter) {
|
||||
expect(statusbadgeFilter('Exit 0')).toBe('success');
|
||||
}));
|
||||
|
||||
it('should be "warning" when exit code is non-zero', inject(function (statusbadgeFilter) {
|
||||
expect(statusbadgeFilter('Exit 1')).toBe('warning');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getstatetext', function () {
|
||||
|
||||
it('should return an empty string when state is undefined', inject(function (getstatetextFilter) {
|
||||
|
@ -352,4 +338,4 @@ describe('filters', function () {
|
|||
expect(errorMsgFilter(response)).toBe(message);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue