mirror of https://github.com/portainer/portainer
merge feat66-docker-cli-compliant
commit
dc721f5870
45
README.md
45
README.md
|
@ -28,11 +28,16 @@ The `--privileged` flag is required for hosts using SELinux.
|
||||||
|
|
||||||
By default UI For Docker connects to the Docker daemon with`/var/run/docker.sock`. For this to work you need to bind mount the unix socket into the container with `-v /var/run/docker.sock:/var/run/docker.sock`.
|
By default UI For Docker connects to the Docker daemon with`/var/run/docker.sock`. For this to work you need to bind mount the unix socket into the container with `-v /var/run/docker.sock:/var/run/docker.sock`.
|
||||||
|
|
||||||
You can use the `-e` flag to change this socket:
|
You can use the `--host`, `-H` flags to change this socket:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Connect to a tcp socket:
|
# Connect to a tcp socket:
|
||||||
$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -e http://127.0.0.1:2375
|
$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -H tcp://127.0.0.1:2375
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# Connect to another unix socket:
|
||||||
|
$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -H unix:///path/to/docker.sock
|
||||||
```
|
```
|
||||||
|
|
||||||
### Swarm support
|
### Swarm support
|
||||||
|
@ -43,7 +48,7 @@ You can access a specific view for you Swarm cluster by defining the `--swarm` f
|
||||||
|
|
||||||
```
|
```
|
||||||
# Connect to a tcp socket and enable Swarm:
|
# 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 -H tcp://<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.
|
*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.
|
||||||
|
@ -56,6 +61,24 @@ UI For Docker listens on port 9000 by default. If you run UI For Docker inside a
|
||||||
$ docker run -d -p 10.20.30.1:80:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock cloudinovasi/cloudinovasi-ui
|
$ docker run -d -p 10.20.30.1:80:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock cloudinovasi/cloudinovasi-ui
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Access a Docker engine protected via TLS
|
||||||
|
|
||||||
|
Ensure that you have access to the CA, the cert and the public key used to access your Docker engine.
|
||||||
|
|
||||||
|
These files will need to be named `ca.pem`, `cert.pem` and `key.pem` respectively. Store them somewhere on your disk and mount a volume containing these files inside the UI container:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -v /path/to/certs:/certs -H https://my-docker-host.domain:2376 --tlsverify
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use the `--tlscacert`, `--tlscert` and `--tlskey` flags if you want to change the default path to the CA, certificate and key file respectively:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -v /path/to/certs:/certs -H https://my-docker-host.domain:2376 --tlsverify --tlscacert /certs/myCa.pem --tlscert /certs/myCert.pem --tlskey /certs/myKey.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note*: Replace `/path/to/certs` to the path to the certificate files on your disk.
|
||||||
|
|
||||||
### Hide containers with specific labels
|
### 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.
|
||||||
|
@ -86,10 +109,14 @@ $ 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:
|
The following options are available for the `ui-for-docker` binary:
|
||||||
|
|
||||||
* `--endpoint`, `-e`: Docker deamon endpoint (default: *"/var/run/docker.sock"*)
|
* `--host`, `-H`: Docker daemon endpoint (default: `"unix:///var/run/docker.sock"`)
|
||||||
* `--bind`, `-p`: Address and port to serve UI For Docker (default: *":9000"*)
|
* `--bind`, `-p`: Address and port to serve UI For Docker (default: `":9000"`)
|
||||||
* `--data`, `-d`: Path to the data folder (default: *"."*)
|
* `--data`, `-d`: Path to the data folder (default: `"."`)
|
||||||
* `--assets`, `-a`: Path to the assets (default: *"."*)
|
* `--assets`, `-a`: Path to the assets (default: `"."`)
|
||||||
* `--swarm`, `-s`: Swarm cluster support (default: *false*)
|
* `--swarm`, `-s`: Swarm cluster support (default: `false`)
|
||||||
* `--hide-label`, `-l`: Hide containers with a specific label in the UI (format *LABEL_NAME=LABEL_VALUE*)
|
|
||||||
* `--registries`, `-r`: Available registries in the UI (format *REGISTRY_NAME=REGISTRY_ADDRESS*)
|
* `--registries`, `-r`: Available registries in the UI (format *REGISTRY_NAME=REGISTRY_ADDRESS*)
|
||||||
|
* `--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
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>
|
<th>
|
||||||
<a ui-sref="containers" ng-click="order('State')">
|
<a ui-sref="containers" ng-click="order('Status')">
|
||||||
State
|
State
|
||||||
<span ng-show="sortType == 'State' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
<span ng-show="sortType == 'State' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||||
<span ng-show="sortType == 'State' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
<span ng-show="sortType == 'State' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
<span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
<span ng-show="sortType == 'Names' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th ng-if="state.displayIP">
|
||||||
<a ui-sref="containers" ng-click="order('IP')">
|
<a ui-sref="containers" ng-click="order('IP')">
|
||||||
IP Address
|
IP Address
|
||||||
<span ng-show="sortType == 'IP' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
<span ng-show="sortType == 'IP' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||||
|
@ -85,10 +85,10 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="container in (state.filteredContainers = ( containers | filter:state.filter | orderBy:sortType:sortReverse))">
|
<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><input type="checkbox" ng-model="container.Checked" ng-change="selectItem(container)"/></td>
|
||||||
<td><span class="label label-{{ container.State|containerstatusbadge }}">{{ container.State }}</span></td>
|
<td><span class="label label-{{ container.Status|containerstatusbadge }}">{{ container.Status|containerstatus }}</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|swarmcontainername}}</a></td>
|
||||||
<td ng-if="!swarm"><a ui-sref="container({id: container.Id})">{{ container|containername}}</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="state.displayIP">{{ container.IP ? container.IP : '-' }}</td>
|
||||||
<td ng-if="swarm">{{ container|swarmhostname}}</td>
|
<td ng-if="swarm">{{ container|swarmhostname}}</td>
|
||||||
<td><a ui-sref="image({id: container.Image})">{{ container.Image }}</a></td>
|
<td><a ui-sref="image({id: container.Image})">{{ container.Image }}</a></td>
|
||||||
<td>{{ container.Command|truncate:60 }}</td>
|
<td>{{ container.Command|truncate:60 }}</td>
|
||||||
|
|
|
@ -4,6 +4,7 @@ function ($scope, Container, Settings, Messages, Config, errorMsgFilter) {
|
||||||
|
|
||||||
$scope.state = {};
|
$scope.state = {};
|
||||||
$scope.state.displayAll = Settings.displayAll;
|
$scope.state.displayAll = Settings.displayAll;
|
||||||
|
$scope.state.displayIP = false;
|
||||||
$scope.sortType = 'State';
|
$scope.sortType = 'State';
|
||||||
$scope.sortReverse = true;
|
$scope.sortReverse = true;
|
||||||
$scope.state.selectedItemCount = 0;
|
$scope.state.selectedItemCount = 0;
|
||||||
|
@ -22,7 +23,11 @@ function ($scope, Container, Settings, Messages, Config, errorMsgFilter) {
|
||||||
containers = hideContainers(d);
|
containers = hideContainers(d);
|
||||||
}
|
}
|
||||||
$scope.containers = containers.map(function (container) {
|
$scope.containers = containers.map(function (container) {
|
||||||
return new ContainerViewModel(container);
|
var model = new ContainerViewModel(container);
|
||||||
|
if (model.IP) {
|
||||||
|
$scope.state.displayIP = true;
|
||||||
|
}
|
||||||
|
return model;
|
||||||
});
|
});
|
||||||
$('#loadContainersSpinner').hide();
|
$('#loadContainersSpinner').hide();
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,7 @@ function ($scope, $state, Config, Container, Image, Volume, Network, Messages, e
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.imageConfig = {};
|
$scope.imageConfig = {};
|
||||||
|
|
||||||
$scope.config = {
|
$scope.config = {
|
||||||
Env: [],
|
Env: [],
|
||||||
HostConfig: {
|
HostConfig: {
|
||||||
|
|
|
@ -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>
|
|
@ -4,7 +4,6 @@
|
||||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</rd-header-title>
|
</rd-header-title>
|
||||||
|
|
||||||
<rd-header-content>Images</rd-header-content>
|
<rd-header-content>Images</rd-header-content>
|
||||||
</rd-header>
|
</rd-header>
|
||||||
|
|
||||||
|
|
|
@ -21,16 +21,31 @@ angular.module('dockerui.filters', [])
|
||||||
.filter('containerstatusbadge', function () {
|
.filter('containerstatusbadge', function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
return function (text) {
|
return function (text) {
|
||||||
if (text === 'paused') {
|
var status = _.toLower(text);
|
||||||
|
if (status.indexOf('paused') !== -1) {
|
||||||
return 'warning';
|
return 'warning';
|
||||||
} else if (text === 'created') {
|
} else if (status.indexOf('created') !== -1) {
|
||||||
return 'info';
|
return 'info';
|
||||||
} else if (text === 'exited') {
|
} else if (status.indexOf('exited') !== -1) {
|
||||||
return 'danger';
|
return 'danger';
|
||||||
}
|
}
|
||||||
return 'success';
|
return 'success';
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
.filter('containerstatus', function () {
|
||||||
|
'use strict';
|
||||||
|
return function (text) {
|
||||||
|
var status = _.toLower(text);
|
||||||
|
if (status.indexOf('paused') !== -1) {
|
||||||
|
return 'paused';
|
||||||
|
} else if (status.indexOf('created') !== -1) {
|
||||||
|
return 'created';
|
||||||
|
} else if (status.indexOf('exited') !== -1) {
|
||||||
|
return 'stopped';
|
||||||
|
}
|
||||||
|
return 'running';
|
||||||
|
};
|
||||||
|
})
|
||||||
.filter('nodestatusbadge', function () {
|
.filter('nodestatusbadge', function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
return function (text) {
|
return function (text) {
|
||||||
|
|
|
@ -10,9 +10,12 @@ function ImageViewModel(data) {
|
||||||
|
|
||||||
function ContainerViewModel(data) {
|
function ContainerViewModel(data) {
|
||||||
this.Id = data.Id;
|
this.Id = data.Id;
|
||||||
this.State = data.State;
|
this.Status = data.Status;
|
||||||
this.Names = data.Names;
|
this.Names = data.Names;
|
||||||
this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
|
// Unavailable in Docker < 1.10
|
||||||
|
if (data.NetworkSettings) {
|
||||||
|
this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
|
||||||
|
}
|
||||||
this.Image = data.Image;
|
this.Image = data.Image;
|
||||||
this.Command = data.Command;
|
this.Command = data.Command;
|
||||||
this.Checked = false;
|
this.Checked = false;
|
||||||
|
|
91
dockerui.go
91
dockerui.go
|
@ -15,16 +15,22 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gorilla/securecookie"
|
"github.com/gorilla/securecookie"
|
||||||
"gopkg.in/alecthomas/kingpin.v2"
|
"gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
endpoint = kingpin.Flag("endpoint", "Dockerd endpoint").Default("/var/run/docker.sock").Short('e').String()
|
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()
|
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()
|
assets = kingpin.Flag("assets", "Path to the assets").Default(".").Short('a').String()
|
||||||
data = kingpin.Flag("data", "Path to the data").Default(".").Short('d').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()
|
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'))
|
|
||||||
registries = LabelParser(kingpin.Flag("registries", "Supported Docker registries").Short('r'))
|
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'))
|
||||||
authKey []byte
|
authKey []byte
|
||||||
authKeyFile = "authKey.dat"
|
authKeyFile = "authKey.dat"
|
||||||
)
|
)
|
||||||
|
@ -33,6 +39,13 @@ type UnixHandler struct {
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TlsFlags struct {
|
||||||
|
tls bool
|
||||||
|
caPath string
|
||||||
|
certPath string
|
||||||
|
keyPath string
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Swarm bool `json:"swarm"`
|
Swarm bool `json:"swarm"`
|
||||||
HiddenLabels Labels `json:"hiddenLabels"`
|
HiddenLabels Labels `json:"hiddenLabels"`
|
||||||
|
@ -108,35 +121,70 @@ func configurationHandler(w http.ResponseWriter, r *http.Request, c Config) {
|
||||||
json.NewEncoder(w).Encode(c)
|
json.NewEncoder(w).Encode(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTcpHandler(e string) http.Handler {
|
func createTcpHandler(u *url.URL) http.Handler {
|
||||||
u, err := url.Parse(e)
|
u.Scheme = "http";
|
||||||
|
return httputil.NewSingleHostReverseProxy(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTlsConfig(tlsFlags TlsFlags) *tls.Config {
|
||||||
|
cert, err := tls.LoadX509KeyPair(tlsFlags.certPath, tlsFlags.keyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return httputil.NewSingleHostReverseProxy(u)
|
caCert, err := ioutil.ReadFile(tlsFlags.caPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
caCertPool.AppendCertsFromPEM(caCert)
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
RootCAs: caCertPool,
|
||||||
|
}
|
||||||
|
return tlsConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTcpHandlerWithTLS(u *url.URL, tlsFlags TlsFlags) http.Handler {
|
||||||
|
u.Scheme = "https";
|
||||||
|
var tlsConfig = createTlsConfig(tlsFlags)
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(u)
|
||||||
|
proxy.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
func createUnixHandler(e string) http.Handler {
|
func createUnixHandler(e string) http.Handler {
|
||||||
return &UnixHandler{e}
|
return &UnixHandler{e}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createHandler(dir string, d string, e string, c Config) http.Handler {
|
func createHandler(dir string, d string, e string, c Config, tlsFlags TlsFlags) http.Handler {
|
||||||
var (
|
var (
|
||||||
mux = http.NewServeMux()
|
mux = http.NewServeMux()
|
||||||
fileHandler = http.FileServer(http.Dir(dir))
|
fileHandler = http.FileServer(http.Dir(dir))
|
||||||
h http.Handler
|
h http.Handler
|
||||||
)
|
)
|
||||||
|
u, perr := url.Parse(e)
|
||||||
if strings.Contains(e, "http") {
|
if perr != nil {
|
||||||
h = createTcpHandler(e)
|
log.Fatal(perr)
|
||||||
} else {
|
}
|
||||||
if _, err := os.Stat(e); err != nil {
|
if u.Scheme == "tcp" {
|
||||||
|
if tlsFlags.tls {
|
||||||
|
h = createTcpHandlerWithTLS(u, tlsFlags)
|
||||||
|
} else {
|
||||||
|
h = createTcpHandler(u)
|
||||||
|
}
|
||||||
|
} else if u.Scheme == "unix" {
|
||||||
|
var socketPath = u.Path
|
||||||
|
if _, err := os.Stat(socketPath); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
log.Fatalf("unix socket %s does not exist", e)
|
log.Fatalf("unix socket %s does not exist", socketPath)
|
||||||
}
|
}
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
h = createUnixHandler(e)
|
h = createUnixHandler(socketPath)
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Bad Docker enpoint: %s. Only unix:// and tcp:// are supported.", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use existing csrf authKey if present or generate a new one.
|
// Use existing csrf authKey if present or generate a new one.
|
||||||
|
@ -184,7 +232,14 @@ func main() {
|
||||||
Registries: *registries,
|
Registries: *registries,
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := createHandler(*assets, *data, *endpoint, configuration)
|
tlsFlags := TlsFlags{
|
||||||
|
tls: *tlsverify,
|
||||||
|
caPath: *tlscacert,
|
||||||
|
certPath: *tlscert,
|
||||||
|
keyPath: *tlskey,
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := createHandler(*assets, *data, *endpoint, configuration, tlsFlags)
|
||||||
if err := http.ListenAndServe(*addr, handler); err != nil {
|
if err := http.ListenAndServe(*addr, handler); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
14
gruntFile.js
14
gruntFile.js
|
@ -40,6 +40,7 @@ module.exports = function (grunt) {
|
||||||
grunt.registerTask('run', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:run']);
|
grunt.registerTask('run', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:run']);
|
||||||
grunt.registerTask('run-swarm', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm', 'watch:buildSwarm']);
|
grunt.registerTask('run-swarm', ['if:binaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm', 'watch:buildSwarm']);
|
||||||
grunt.registerTask('run-dev', ['if:binaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']);
|
grunt.registerTask('run-dev', ['if:binaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']);
|
||||||
|
grunt.registerTask('run-ssl', ['if:binaryNotExist', 'shell:buildImage', 'shell:runSsl', 'watch:buildSsl']);
|
||||||
grunt.registerTask('clear', ['clean:app']);
|
grunt.registerTask('clear', ['clean:app']);
|
||||||
|
|
||||||
// Print a timestamp (useful for when watching)
|
// Print a timestamp (useful for when watching)
|
||||||
|
@ -224,6 +225,10 @@ module.exports = function (grunt) {
|
||||||
buildSwarm: {
|
buildSwarm: {
|
||||||
files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'],
|
files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'],
|
||||||
tasks: ['build', 'shell:buildImage', 'shell:runSwarm', 'shell:cleanImages']
|
tasks: ['build', 'shell:buildImage', 'shell:runSwarm', 'shell:cleanImages']
|
||||||
|
},
|
||||||
|
buildSsl: {
|
||||||
|
files: ['<%= src.js %>', '<%= src.specs %>', '<%= src.css %>', '<%= src.tpl %>', '<%= src.html %>'],
|
||||||
|
tasks: ['build', 'shell:buildImage', 'shell:runSsl', 'shell:cleanImages']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
jshint: {
|
jshint: {
|
||||||
|
@ -267,7 +272,14 @@ module.exports = function (grunt) {
|
||||||
command: [
|
command: [
|
||||||
'docker stop ui-for-docker',
|
'docker stop ui-for-docker',
|
||||||
'docker rm ui-for-docker',
|
'docker rm ui-for-docker',
|
||||||
'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 -r local=192.168.2.193:5000'
|
'docker run -d -p 9000:9000 -v /tmp/docker-ui:/data --name ui-for-docker ui-for-docker -H tcp://10.0.7.10:4000 --swarm -d /data'
|
||||||
|
].join(';')
|
||||||
|
},
|
||||||
|
runSsl: {
|
||||||
|
command: [
|
||||||
|
'docker stop ui-for-docker',
|
||||||
|
'docker rm ui-for-docker',
|
||||||
|
'docker run -d -p 9000:9000 -v /tmp/docker-ui:/data -v /tmp/docker-ssl:/certs --name ui-for-docker ui-for-docker -H tcp://10.0.7.10:2376 -d /data --tlsverify'
|
||||||
].join(';')
|
].join(';')
|
||||||
},
|
},
|
||||||
cleanImages: {
|
cleanImages: {
|
||||||
|
|
Loading…
Reference in New Issue