Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00b2c92e39 | |||
| 0796778d17 | |||
| 1eae1c03f0 | |||
| a9209da167 | |||
| 43c2f14289 | |||
| f378d56543 | |||
| 3b0d726c2a |
@@ -1,7 +1,5 @@
|
||||
# Cloudinovasi UI for Docker
|
||||
|
||||
This UI is dedicated to CloudInovasi internal usage.
|
||||
|
||||
A fork of the amazing UI for Docker by Michael Crosby and Kevan Ahlquist (https://github.com/kevana/ui-for-docker) using the rdash-angular theme (https://github.com/rdash/rdash-angular).
|
||||
|
||||

|
||||
@@ -12,6 +10,12 @@ UI For Docker is a web interface for the Docker Remote API. The goal is to prov
|
||||
* Minimal dependencies - I really want to keep this project a pure html/js app.
|
||||
* Consistency - The web UI should be consistent with the commands found on the docker CLI.
|
||||
|
||||
## Supported Docker versions
|
||||
|
||||
The current Docker version support policy is the following: `N` to `N-2` included where `N` is the latest version.
|
||||
|
||||
At the moment, the following versions are supported: 1.9, 1.10 & 1.11.
|
||||
|
||||
## Run
|
||||
|
||||
### Quickstart
|
||||
@@ -81,7 +85,7 @@ $ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -v /path/to/certs:/cer
|
||||
|
||||
### Hide containers with specific labels
|
||||
|
||||
You can hide specific containers in the containers view by using the `--hide-label` or `-l` options and specifying a label.
|
||||
You can hide specific containers in the containers view by using the `-hide-label` or `-l` options and specifying a label.
|
||||
|
||||
For example, take a container started with the label `owner=acme`:
|
||||
|
||||
@@ -95,16 +99,6 @@ You can hide it in the view by starting the ui with:
|
||||
$ docker run -d -p 9000:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock cloudinovasi/cloudinovasi-ui -l owner=acme
|
||||
```
|
||||
|
||||
### Custom Docker registries support
|
||||
|
||||
You can specify the support of others registries than DockerHub by using the `--registries` or `-r` options and specifying a registry using the format *REGISTRY_NAME=REGISTRY_ADDRESS*.
|
||||
|
||||
For example, if I want the registry 'myCustomRegistry' pointing to *myregistry.domain.com:5000* available in the UI:
|
||||
|
||||
```
|
||||
$ docker run -d -p 9000:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock cloudinovasi/cloudinovasi-ui -r myCustomRegistry=myregistry.domain.com:5000
|
||||
```
|
||||
|
||||
### Available options
|
||||
|
||||
The following options are available for the `ui-for-docker` binary:
|
||||
@@ -114,9 +108,8 @@ The following options are available for the `ui-for-docker` binary:
|
||||
* `--data`, `-d`: Path to the data folder (default: `"."`)
|
||||
* `--assets`, `-a`: Path to the assets (default: `"."`)
|
||||
* `--swarm`, `-s`: Swarm cluster support (default: `false`)
|
||||
* `--registries`, `-r`: Available registries in the UI (format *REGISTRY_NAME=REGISTRY_ADDRESS*)
|
||||
* `--hide-label`, `-l`: Hide containers with a specific label in the UI
|
||||
* `--tlsverify`: TLS support (default: `false`)
|
||||
* `--tlscacert`: Path to the CA (default `/certs/ca.pem`)
|
||||
* `--tlscert`: Path to the TLS certificate file (default `/certs/cert.pem`)
|
||||
* `--tlskey`: Path to the TLS key (default `/certs/key.pem`)
|
||||
* `--hide-label`, `-l`: Hide containers with a specific label in the UI
|
||||
|
||||
+3
-1
@@ -19,7 +19,9 @@ angular.module('uifordocker', [
|
||||
'swarm',
|
||||
'network',
|
||||
'networks',
|
||||
'volumes'])
|
||||
'createNetwork',
|
||||
'volumes',
|
||||
'createVolume'])
|
||||
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', function ($stateProvider, $urlRouterProvider, $httpProvider) {
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e
|
||||
$scope.formValues = {
|
||||
Console: 'none',
|
||||
Volumes: [],
|
||||
AvailableRegistries: [],
|
||||
Registry: ''
|
||||
};
|
||||
|
||||
@@ -28,8 +27,12 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e
|
||||
}
|
||||
};
|
||||
|
||||
$scope.resetVolumePath = function(index) {
|
||||
$scope.formValues.Volumes[index].name = '';
|
||||
};
|
||||
|
||||
$scope.addVolume = function() {
|
||||
$scope.formValues.Volumes.push({ name: '', containerPath: '' });
|
||||
$scope.formValues.Volumes.push({ name: '', containerPath: '', readOnly: false, isPath: false });
|
||||
};
|
||||
|
||||
$scope.removeVolume = function(index) {
|
||||
@@ -55,15 +58,8 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e
|
||||
Config.$promise.then(function (c) {
|
||||
var swarm = c.swarm;
|
||||
|
||||
$scope.formValues.AvailableRegistries = c.registries;
|
||||
|
||||
Volume.query({}, function (d) {
|
||||
var persistedVolumes = d.Volumes.filter(function (volume) {
|
||||
if (volume.Driver === 'local-persist') {
|
||||
return volume;
|
||||
}
|
||||
});
|
||||
$scope.availableVolumes = _.uniqBy(persistedVolumes, 'Name');
|
||||
$scope.availableVolumes = d.Volumes;
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e.data);
|
||||
});
|
||||
@@ -76,7 +72,6 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e
|
||||
return network;
|
||||
}
|
||||
});
|
||||
$scope.globalNetworkCount = networks.length;
|
||||
networks.push({Name: "bridge"});
|
||||
networks.push({Name: "host"});
|
||||
networks.push({Name: "none"});
|
||||
|
||||
@@ -26,10 +26,7 @@
|
||||
</div>
|
||||
<label for="image_registry" class="col-sm-1 control-label text-left">Registry</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="selectpicker form-control" ng-model="formValues.Registry">
|
||||
<option value="">Docker Hub</option>
|
||||
<option ng-repeat="registry in formValues.AvailableRegistries" ng-value="registry.value">{{ registry.name }}</option>
|
||||
</select>
|
||||
<input type="text" class="form-control" ng-model="formValues.Registry" id="image_registry" placeholder="leave empty to use DockerHub">
|
||||
</div>
|
||||
<div class="col-sm-offset-1 col-sm-11">
|
||||
<div class="checkbox">
|
||||
@@ -215,22 +212,28 @@
|
||||
<!-- volumes -->
|
||||
<div class="form-group">
|
||||
<label for="container_volumes" class="col-sm-1 control-label text-left">Volumes</label>
|
||||
<div class="col-sm-11" ng-if="availableVolumes.length !== 0">
|
||||
<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>
|
||||
<div class="col-sm-11" ng-if="availableVolumes.length === 0" style="margin-top: 5px;">
|
||||
<span class="small text-muted">You don't have any persistent volumes. Head over the <a ui-sref="volumes">volumes view</a> to create one.</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">
|
||||
<select class="selectpicker form-control" ng-model="volume.name">
|
||||
<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:50}}</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>
|
||||
@@ -252,11 +255,6 @@
|
||||
<!-- tab-network -->
|
||||
<div class="tab-pane" id="network">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<div class="form-group" ng-if="globalNetworkCount === 0">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted">You don't have any shared network. Head over the <a ui-sref="networks">networks view</a> to create one.</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- network-input -->
|
||||
<div class="form-group">
|
||||
<label for="container_network" class="col-sm-1 control-label text-left">Network</label>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
angular.module('createNetwork', [])
|
||||
.controller('CreateNetworkController', ['$scope', '$state', 'Messages', 'Network', 'errorMsgFilter',
|
||||
function ($scope, $state, Messages, Network, errorMsgFilter) {
|
||||
$scope.formValues = {
|
||||
DriverOptions: [],
|
||||
Subnet: '',
|
||||
Gateway: ''
|
||||
};
|
||||
|
||||
$scope.config = {
|
||||
Driver: 'bridge',
|
||||
CheckDuplicate: true,
|
||||
Internal: false,
|
||||
IPAM: {
|
||||
Config: []
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addDriverOption = function() {
|
||||
$scope.formValues.DriverOptions.push({ name: '', value: '' });
|
||||
};
|
||||
|
||||
$scope.removeDriverOption = function(index) {
|
||||
$scope.formValues.DriverOptions.splice(index, 1);
|
||||
};
|
||||
|
||||
function createNetwork(config) {
|
||||
$('#createNetworkSpinner').show();
|
||||
Network.create(config, function (d) {
|
||||
if (d.Id) {
|
||||
Messages.send("Network created", d.Id);
|
||||
$('#createNetworkSpinner').hide();
|
||||
$state.go('networks', {}, {reload: true});
|
||||
} else {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Messages.error('Unable to create network', errorMsgFilter(d));
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Messages.error('Unable to create network', e.data);
|
||||
});
|
||||
}
|
||||
|
||||
function prepareIPAMConfiguration(config) {
|
||||
if ($scope.formValues.Subnet) {
|
||||
var ipamConfig = {};
|
||||
ipamConfig.Subnet = $scope.formValues.Subnet;
|
||||
if ($scope.formValues.Gateway) {
|
||||
ipamConfig.Gateway = $scope.formValues.Gateway ;
|
||||
}
|
||||
config.IPAM.Config.push(ipamConfig);
|
||||
}
|
||||
}
|
||||
|
||||
function prepareDriverOptions(config) {
|
||||
var options = {};
|
||||
$scope.formValues.DriverOptions.forEach(function (option) {
|
||||
options[option.name] = option.value;
|
||||
});
|
||||
config.Options = options;
|
||||
}
|
||||
|
||||
function prepareConfiguration() {
|
||||
var config = angular.copy($scope.config);
|
||||
prepareIPAMConfiguration(config);
|
||||
prepareDriverOptions(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
$scope.create = function () {
|
||||
var config = prepareConfiguration();
|
||||
createNetwork(config);
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,95 @@
|
||||
<rd-header>
|
||||
<rd-header-title title="Create network"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Networks > Add network
|
||||
</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="network_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="network_name" placeholder="e.g. myNetwork">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- subnet-gateway-inputs -->
|
||||
<div class="form-group">
|
||||
<label for="network_subnet" class="col-sm-1 control-label text-left">Subnet</label>
|
||||
<div class="col-sm-5">
|
||||
<input type="text" class="form-control" ng-model="formValues.Subnet" id="network_subnet" placeholder="e.g. 172.20.0.0/16">
|
||||
</div>
|
||||
<label for="network_gateway" class="col-sm-1 control-label text-left">Gateway</label>
|
||||
<div class="col-sm-5">
|
||||
<input type="text" class="form-control" ng-model="formValues.Gateway" id="network_gateway" placeholder="e.g. 172.20.10.11">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !subnet-gateway-inputs -->
|
||||
<!-- driver-input -->
|
||||
<div class="form-group">
|
||||
<label for="network_driver" class="col-sm-1 control-label text-left">Driver</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="config.Driver" id="network_driver" placeholder="e.g. driverName">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !driver-input -->
|
||||
<!-- driver-options -->
|
||||
<div class="form-group">
|
||||
<label for="network_driveropts" class="col-sm-1 control-label text-left">Driver options</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addDriverOption()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> driver option
|
||||
</span>
|
||||
</div>
|
||||
<!-- driver-options-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="option in formValues.DriverOptions" 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="option.name" placeholder="e.g. com.docker.network.bridge.enable_icc">
|
||||
</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="option.value" placeholder="e.g. true">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removeDriverOption($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !driver-options-input-list -->
|
||||
</div>
|
||||
<!-- !driver-options -->
|
||||
<!-- internal -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="config.Internal"> Restrict external access to the network
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !internal -->
|
||||
</form>
|
||||
</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;">
|
||||
<div>
|
||||
<i id="createNetworkSpinner" class="fa fa-cog fa-3x fa-spin" style="margin-bottom: 5px; display: none;"></i>
|
||||
</div>
|
||||
<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="networks">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
angular.module('createVolume', [])
|
||||
.controller('CreateVolumeController', ['$scope', '$state', 'Volume', 'Messages', 'errorMsgFilter',
|
||||
function ($scope, $state, Volume, Messages, errorMsgFilter) {
|
||||
|
||||
$scope.formValues = {
|
||||
DriverOptions: []
|
||||
};
|
||||
|
||||
$scope.config = {
|
||||
Driver: 'local'
|
||||
};
|
||||
|
||||
$scope.addDriverOption = function() {
|
||||
$scope.formValues.DriverOptions.push({ name: '', value: '' });
|
||||
};
|
||||
|
||||
$scope.removeDriverOption = function(index) {
|
||||
$scope.formValues.DriverOptions.splice(index, 1);
|
||||
};
|
||||
|
||||
function createVolume(config) {
|
||||
$('#createVolumeSpinner').show();
|
||||
Volume.create(config, function (d) {
|
||||
if (d.Name) {
|
||||
Messages.send("Volume created", d.Name);
|
||||
$('#createVolumeSpinner').hide();
|
||||
$state.go('volumes', {}, {reload: true});
|
||||
} else {
|
||||
$('#createVolumeSpinner').hide();
|
||||
Messages.error('Unable to create volume', errorMsgFilter(d));
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createVolumeSpinner').hide();
|
||||
Messages.error('Unable to create volume', e.data);
|
||||
});
|
||||
}
|
||||
|
||||
function prepareDriverOptions(config) {
|
||||
var options = {};
|
||||
$scope.formValues.DriverOptions.forEach(function (option) {
|
||||
options[option.name] = option.value;
|
||||
});
|
||||
config.DriverOpts = options;
|
||||
}
|
||||
|
||||
function prepareConfiguration() {
|
||||
var config = angular.copy($scope.config);
|
||||
prepareDriverOptions(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
$scope.create = function () {
|
||||
var config = prepareConfiguration();
|
||||
createVolume(config);
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,72 @@
|
||||
<rd-header>
|
||||
<rd-header-title title="Create volume"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Volumes > Add volume
|
||||
</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="volume_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="volume_name" placeholder="e.g. myVolume">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- driver-input -->
|
||||
<div class="form-group">
|
||||
<label for="volume_driver" class="col-sm-1 control-label text-left">Driver</label>
|
||||
<div class="col-sm-11">
|
||||
<input type="text" class="form-control" ng-model="config.Driver" id="volume_driver" placeholder="e.g. driverName">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !driver-input -->
|
||||
<!-- driver-options -->
|
||||
<div class="form-group">
|
||||
<label for="volume_driveropts" class="col-sm-1 control-label text-left">Driver options</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addDriverOption()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> driver option
|
||||
</span>
|
||||
</div>
|
||||
<!-- driver-options-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="option in formValues.DriverOptions" 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="option.name" placeholder="e.g. mountpoint">
|
||||
</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="option.value" placeholder="e.g. /path/on/host">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removeDriverOption($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !driver-options-input-list -->
|
||||
</div>
|
||||
<!-- !driver-options -->
|
||||
</form>
|
||||
</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;">
|
||||
<div>
|
||||
<i id="createVolumeSpinner" class="fa fa-cog fa-3x fa-spin" style="margin-bottom: 5px; display: none;"></i>
|
||||
</div>
|
||||
<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="volumes">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -22,10 +22,7 @@
|
||||
</div>
|
||||
<label for="image_registry" class="col-sm-1 control-label text-left">Registry</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="selectpicker form-control" ng-model="config.Registry">
|
||||
<option value="">Docker Hub</option>
|
||||
<option ng-repeat="registry in availableRegistries" ng-value="registry.value">{{ registry.name }}</option>
|
||||
</select>
|
||||
<input type="text" class="form-control" ng-model="config.Registry" id="image_registry" placeholder="leave empty to use DockerHub">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-and-registry-inputs -->
|
||||
@@ -86,11 +83,18 @@
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="images" ng-click="order('VirtualSize')">
|
||||
Size
|
||||
VirtualSize
|
||||
<span ng-show="sortType == 'VirtualSize' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'VirtualSize' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="images" 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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -99,6 +103,7 @@
|
||||
<td><a ui-sref="image({id: image.Id})">{{ image.Id|truncate:20}}</a></td>
|
||||
<td>{{ image|repotag }}</td>
|
||||
<td>{{ image.VirtualSize|humansize }}</td>
|
||||
<td>{{ image.Created|getdate }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
angular.module('images', [])
|
||||
.controller('ImagesController', ['$scope', '$state', 'Config', 'Image', 'Messages',
|
||||
function ($scope, $state, Config, Image, Messages) {
|
||||
.controller('ImagesController', ['$scope', '$state', 'Image', 'Messages',
|
||||
function ($scope, $state, Image, Messages) {
|
||||
$scope.state = {};
|
||||
$scope.sortType = 'RepoTags';
|
||||
$scope.sortType = 'Created';
|
||||
$scope.sortReverse = true;
|
||||
$scope.state.toggle = false;
|
||||
$scope.state.selectedItemCount = 0;
|
||||
@@ -52,7 +52,7 @@ function ($scope, $state, Config, Image, Messages) {
|
||||
$scope.pullImage = function() {
|
||||
$('#pullImageSpinner').show();
|
||||
var image = _.toLower($scope.config.Image);
|
||||
var registry = $scope.config.Registry;
|
||||
var registry = _.toLower($scope.config.Registry);
|
||||
var imageConfig = createImageConfig(image, registry);
|
||||
Image.create(imageConfig, function (data) {
|
||||
var err = data.length > 0 && data[data.length - 1].hasOwnProperty('error');
|
||||
@@ -110,9 +110,5 @@ function ($scope, $state, Config, Image, Messages) {
|
||||
});
|
||||
}
|
||||
|
||||
Config.$promise.then(function (c) {
|
||||
$scope.availableRegistries = c.registries;
|
||||
fetchImages();
|
||||
});
|
||||
|
||||
fetchImages();
|
||||
}]);
|
||||
|
||||
@@ -7,64 +7,6 @@
|
||||
<rd-header-content>Networks</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plus" title="Add a shared network">
|
||||
</rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="network_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="network_name" placeholder="e.g. myNetwork">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- advanced-settings-input -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="state.advancedSettings"> Show advanced settings
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !advanced-settings-input -->
|
||||
<!-- subnet-gateway-inputs -->
|
||||
<div class="form-group" ng-if="state.advancedSettings">
|
||||
<label for="network_subnet" class="col-sm-1 control-label text-left">Subnet</label>
|
||||
<div class="col-sm-5">
|
||||
<input type="text" class="form-control" ng-model="formValues.Subnet" id="network_subnet" placeholder="e.g. 172.20.0.0/16">
|
||||
</div>
|
||||
<label for="network_gateway" class="col-sm-1 control-label text-left">Gateway</label>
|
||||
<div class="col-sm-5">
|
||||
<input type="text" class="form-control" ng-model="formValues.Gateway" id="network_gateway" placeholder="e.g. 172.20.10.11">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !subnet-gateway-inputs -->
|
||||
<!-- tag-note -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted">Note: The network will be created using the overlay driver and will allow containers to communicate across the hosts of your cluster.</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tag-note -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-default btn-sm" ng-disabled="!config.Name" ng-click="createNetwork()">Create</button>
|
||||
<i id="createNetworkSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-sitemap" title="Networks">
|
||||
@@ -75,6 +17,7 @@
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount">Remove</button>
|
||||
<a class="btn btn-default" type="button" ui-sref="actions.create.network">Add network</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
@@ -93,6 +36,13 @@
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('Id')">
|
||||
Id
|
||||
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('Scope')">
|
||||
Scope
|
||||
@@ -100,16 +50,30 @@
|
||||
<span ng-show="sortType == 'Scope' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('Driver')">
|
||||
Driver
|
||||
<span ng-show="sortType == 'Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('IPAM.Driver')">
|
||||
IPAM Driver
|
||||
<span ng-show="sortType == 'IPAM.Driver' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'IPAM.Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('IPAM.Config[0].Subnet')">
|
||||
Subnet
|
||||
IPAM Subnet
|
||||
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'IPAM.Config[0].Subnet' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="networks" ng-click="order('IPAM.Config[0].Gateway')">
|
||||
Gateway
|
||||
IPAM Gateway
|
||||
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'IPAM.Config[0].Gateway' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
@@ -119,10 +83,13 @@
|
||||
<tbody>
|
||||
<tr ng-repeat="network in ( state.filteredNetworks = (networks | filter:state.filter | orderBy:sortType:sortReverse))">
|
||||
<td><input type="checkbox" ng-model="network.Checked" ng-change="selectItem(network)"/></td>
|
||||
<td><a ui-sref="network({id: network.Id})">{{ network.Name|truncate:40}}</a></td>
|
||||
<td><a ui-sref="network({id: network.Id})">{{ network.Name|truncate:20}}</a></td>
|
||||
<td>{{ network.Id }}</td>
|
||||
<td>{{ network.Scope }}</td>
|
||||
<td>{{ network.IPAM.Config[0].Subnet ? network.IPAM.Config[0].Subnet : '-' }}</td>
|
||||
<td>{{ network.IPAM.Config[0].Gateway ? network.IPAM.Config[0].Gateway : '-' }}</td>
|
||||
<td>{{ network.Driver }}</td>
|
||||
<td>{{ network.IPAM.Driver }}</td>
|
||||
<td>{{ network.IPAM.Config[0].Subnet }}</td>
|
||||
<td>{{ network.IPAM.Config[0].Gateway }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
angular.module('networks', [])
|
||||
.controller('NetworksController', ['$scope', '$state', 'Network', 'Messages', 'errorMsgFilter',
|
||||
function ($scope, $state, Network, Messages, errorMsgFilter) {
|
||||
.controller('NetworksController', ['$scope', 'Network', 'Messages', 'errorMsgFilter',
|
||||
function ($scope, Network, Messages, errorMsgFilter) {
|
||||
|
||||
$scope.state = {};
|
||||
$scope.state.toggle = false;
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.state.advancedSettings = false;
|
||||
$scope.sortType = 'Scope';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
$scope.formValues = {
|
||||
Subnet: '',
|
||||
Gateway: ''
|
||||
};
|
||||
|
||||
$scope.config = {
|
||||
Name: '',
|
||||
IPAM: {
|
||||
Config: []
|
||||
}
|
||||
};
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = true;
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
@@ -44,42 +32,6 @@ function ($scope, $state, Network, Messages, errorMsgFilter) {
|
||||
}
|
||||
};
|
||||
|
||||
function prepareIPAMConfiguration(config) {
|
||||
if ($scope.formValues.Subnet) {
|
||||
var ipamConfig = {};
|
||||
ipamConfig.Subnet = $scope.formValues.Subnet;
|
||||
if ($scope.formValues.Gateway) {
|
||||
ipamConfig.Gateway = $scope.formValues.Gateway ;
|
||||
}
|
||||
config.IPAM.Config.push(ipamConfig);
|
||||
}
|
||||
}
|
||||
|
||||
function prepareNetworkConfiguration() {
|
||||
var config = angular.copy($scope.config);
|
||||
prepareIPAMConfiguration(config);
|
||||
config.Driver = 'overlay';
|
||||
return config;
|
||||
}
|
||||
|
||||
$scope.createNetwork = function() {
|
||||
$('#createNetworkSpinner').show();
|
||||
var config = prepareNetworkConfiguration();
|
||||
Network.create(config, function (d) {
|
||||
if (d.Id) {
|
||||
Messages.send("Network created", d.Id);
|
||||
$('#createNetworkSpinner').hide();
|
||||
$state.go('networks', {}, {reload: true});
|
||||
} else {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Messages.error('Unable to create network', errorMsgFilter(d));
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Messages.error('Unable to create network', e.data);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
$('#loadNetworksSpinner').show();
|
||||
var counter = 0;
|
||||
|
||||
@@ -7,40 +7,6 @@
|
||||
<rd-header-content>Volumes</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plus" title="Add a persistent volume">
|
||||
</rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="volume_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="volume_name" placeholder="e.g. mysql-data">
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- tag-note -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted">Note: The volume will be created in our persisted storage and will be available across all the hosts of your cluster.</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tag-note -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-default btn-sm" ng-disabled="!config.Name" ng-click="createVolume()">Create</button>
|
||||
<i id="createVolumeSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-cubes" title="Volumes">
|
||||
@@ -51,6 +17,7 @@
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount">Remove</button>
|
||||
<a class="btn btn-default" type="button" ui-sref="actions.create.volume">Add volume</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
@@ -76,13 +43,21 @@
|
||||
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="volumes" ng-click="order('Mountpoint')">
|
||||
Mountpoint
|
||||
<span ng-show="sortType == 'Mountpoint' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Mountpoint' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="volume in (state.filteredVolumes = (volumes | filter:state.filter | orderBy:sortType:sortReverse))">
|
||||
<td><input type="checkbox" ng-model="volume.Checked" ng-change="selectItem(volume)"/></td>
|
||||
<td>{{ volume.Name|truncate:50 }}</td>
|
||||
<td>{{ volume.Name|truncate:20 }}</td>
|
||||
<td>{{ volume.Driver }}</td>
|
||||
<td>{{ volume.Mountpoint }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
angular.module('volumes', [])
|
||||
.controller('VolumesController', ['$scope', '$state', 'Volume', 'Messages', 'errorMsgFilter',
|
||||
function ($scope, $state, Volume, Messages, errorMsgFilter) {
|
||||
.controller('VolumesController', ['$scope', 'Volume', 'Messages', 'errorMsgFilter',
|
||||
function ($scope, Volume, Messages, errorMsgFilter) {
|
||||
$scope.state = {};
|
||||
$scope.state.toggle = false;
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.sortType = 'Driver';
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = true;
|
||||
|
||||
$scope.config = {
|
||||
Name: ''
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
@@ -35,32 +31,6 @@ function ($scope, $state, Volume, Messages, errorMsgFilter) {
|
||||
}
|
||||
};
|
||||
|
||||
function prepareVolumeConfiguration() {
|
||||
var config = angular.copy($scope.config);
|
||||
config.Driver = 'local-persist';
|
||||
config.DriverOpts = {};
|
||||
config.DriverOpts.mountpoint = '/volume/' + config.Name;
|
||||
return config;
|
||||
}
|
||||
|
||||
$scope.createVolume = function() {
|
||||
$('#createVolumeSpinner').show();
|
||||
var config = prepareVolumeConfiguration();
|
||||
Volume.create(config, function (d) {
|
||||
if (d.Name) {
|
||||
Messages.send("Volume created", d.Name);
|
||||
$('#createVolumeSpinner').hide();
|
||||
$state.go('volumes', {}, {reload: true});
|
||||
} else {
|
||||
$('#createVolumeSpinner').hide();
|
||||
Messages.error('Unable to create volume', errorMsgFilter(d));
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createVolumeSpinner').hide();
|
||||
Messages.error('Unable to create volume', e.data);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
$('#loadVolumesSpinner').show();
|
||||
var counter = 0;
|
||||
@@ -89,7 +59,7 @@ function ($scope, $state, Volume, Messages, errorMsgFilter) {
|
||||
function fetchVolumes() {
|
||||
$('#loadVolumesSpinner').show();
|
||||
Volume.query({}, function (d) {
|
||||
$scope.volumes = _.uniqBy(d.Volumes, 'Name');
|
||||
$scope.volumes = d.Volumes;
|
||||
$('#loadVolumesSpinner').hide();
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e.data);
|
||||
|
||||
+11
-14
@@ -20,17 +20,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
endpoint = kingpin.Flag("host", "Dockerd endpoint").Default("unix:///var/run/docker.sock").Short('H').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()
|
||||
registries = LabelParser(kingpin.Flag("registries", "Supported Docker registries").Short('r'))
|
||||
tlsverify = kingpin.Flag("tlsverify", "TLS support").Default("false").Bool()
|
||||
tlscacert = kingpin.Flag("tlscacert", "Path to the CA").Default("/certs/ca.pem").String()
|
||||
tlscert = kingpin.Flag("tlscert", "Path to the TLS certificate file").Default("/certs/cert.pem").String()
|
||||
tlskey = kingpin.Flag("tlskey", "Path to the TLS key").Default("/certs/key.pem").String()
|
||||
labels = LabelParser(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l'))
|
||||
endpoint = kingpin.Flag("host", "Dockerd endpoint").Default("unix:///var/run/docker.sock").Short('H').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()
|
||||
tlsverify = kingpin.Flag("tlsverify", "TLS support").Default("false").Bool()
|
||||
tlscacert = kingpin.Flag("tlscacert", "Path to the CA").Default("/certs/ca.pem").String()
|
||||
tlscert = kingpin.Flag("tlscert", "Path to the TLS certificate file").Default("/certs/cert.pem").String()
|
||||
tlskey = kingpin.Flag("tlskey", "Path to the TLS key").Default("/certs/key.pem").String()
|
||||
labels = LabelParser(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l'))
|
||||
authKey []byte
|
||||
authKeyFile = "authKey.dat"
|
||||
)
|
||||
@@ -49,7 +48,6 @@ type TlsFlags struct {
|
||||
type Config struct {
|
||||
Swarm bool `json:"swarm"`
|
||||
HiddenLabels Labels `json:"hiddenLabels"`
|
||||
Registries Labels `json:"registries"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
@@ -62,7 +60,7 @@ type Labels []Label
|
||||
func (l *Labels) Set(value string) error {
|
||||
parts := strings.SplitN(value, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected NAME=VALUE got '%s'", value)
|
||||
return fmt.Errorf("expected HEADER=VALUE got '%s'", value)
|
||||
}
|
||||
label := new(Label)
|
||||
label.Name = parts[0]
|
||||
@@ -229,7 +227,6 @@ func main() {
|
||||
configuration := Config{
|
||||
Swarm: *swarm,
|
||||
HiddenLabels: *labels,
|
||||
Registries: *registries,
|
||||
}
|
||||
|
||||
tlsFlags := TlsFlags{
|
||||
|
||||
Reference in New Issue
Block a user