mirror of https://github.com/portainer/portainer
Merge branch 'release/1.4.0'
commit
1eae1c03f0
43
README.md
43
README.md
|
@ -26,11 +26,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
|
||||||
|
@ -41,7 +46,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.
|
||||||
|
@ -54,6 +59,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.
|
||||||
|
@ -74,9 +97,13 @@ $ 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
|
* `--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`)
|
||||||
|
|
|
@ -144,4 +144,4 @@ angular.module('uifordocker', [
|
||||||
.constant('DOCKER_ENDPOINT', 'dockerapi')
|
.constant('DOCKER_ENDPOINT', 'dockerapi')
|
||||||
.constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. If you have a port, prefix it with a ':' i.e. :4243
|
.constant('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('CONFIG_ENDPOINT', '/config')
|
||||||
.constant('UI_VERSION', 'v1.3.0');
|
.constant('UI_VERSION', 'v1.4.0');
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<!-- !name-input -->
|
<!-- !name-input -->
|
||||||
<!-- image-and-registry-inputs -->
|
<!-- image-and-registry-inputs -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="container_image" class="col-sm-1 control-label text-left">Name</label>
|
<label for="container_image" class="col-sm-1 control-label text-left">Image</label>
|
||||||
<div class="col-sm-7">
|
<div class="col-sm-7">
|
||||||
<input type="text" class="form-control" ng-model="config.Image" id="container_image" placeholder="e.g. ubuntu:trusty">
|
<input type="text" class="form-control" ng-model="config.Image" id="container_image" placeholder="e.g. ubuntu:trusty">
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,13 +2,18 @@ angular.module('createNetwork', [])
|
||||||
.controller('CreateNetworkController', ['$scope', '$state', 'Messages', 'Network', 'errorMsgFilter',
|
.controller('CreateNetworkController', ['$scope', '$state', 'Messages', 'Network', 'errorMsgFilter',
|
||||||
function ($scope, $state, Messages, Network, errorMsgFilter) {
|
function ($scope, $state, Messages, Network, errorMsgFilter) {
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
DriverOptions: []
|
DriverOptions: [],
|
||||||
|
Subnet: '',
|
||||||
|
Gateway: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.config = {
|
$scope.config = {
|
||||||
Driver: 'bridge',
|
Driver: 'bridge',
|
||||||
CheckDuplicate: true,
|
CheckDuplicate: true,
|
||||||
Internal: false
|
Internal: false,
|
||||||
|
IPAM: {
|
||||||
|
Config: []
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addDriverOption = function() {
|
$scope.addDriverOption = function() {
|
||||||
|
@ -36,6 +41,17 @@ function ($scope, $state, Messages, Network, 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 prepareDriverOptions(config) {
|
function prepareDriverOptions(config) {
|
||||||
var options = {};
|
var options = {};
|
||||||
$scope.formValues.DriverOptions.forEach(function (option) {
|
$scope.formValues.DriverOptions.forEach(function (option) {
|
||||||
|
@ -46,6 +62,7 @@ function ($scope, $state, Messages, Network, errorMsgFilter) {
|
||||||
|
|
||||||
function prepareConfiguration() {
|
function prepareConfiguration() {
|
||||||
var config = angular.copy($scope.config);
|
var config = angular.copy($scope.config);
|
||||||
|
prepareIPAMConfiguration(config);
|
||||||
prepareDriverOptions(config);
|
prepareDriverOptions(config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !name-input -->
|
<!-- !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 -->
|
<!-- driver-input -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="network_driver" class="col-sm-1 control-label text-left">Driver</label>
|
<label for="network_driver" class="col-sm-1 control-label text-left">Driver</label>
|
||||||
|
|
|
@ -9,7 +9,7 @@ function ($scope, $state, Image, Messages) {
|
||||||
|
|
||||||
$scope.config = {
|
$scope.config = {
|
||||||
Image: '',
|
Image: '',
|
||||||
Registry: '',
|
Registry: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.order = function(sortType) {
|
$scope.order = function(sortType) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "uifordocker",
|
"name": "uifordocker",
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"homepage": "https://github.com/kevana/ui-for-docker",
|
"homepage": "https://github.com/kevana/ui-for-docker",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Michael Crosby <crosbymichael@gmail.com>",
|
"Michael Crosby <crosbymichael@gmail.com>",
|
||||||
|
|
93
dockerui.go
93
dockerui.go
|
@ -15,15 +15,21 @@ 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'))
|
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"
|
||||||
)
|
)
|
||||||
|
@ -32,6 +38,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"`
|
||||||
|
@ -106,35 +119,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.
|
||||||
|
@ -173,7 +221,7 @@ func csrfWrapper(h http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
kingpin.Version("1.3.0")
|
kingpin.Version("1.4.0")
|
||||||
kingpin.Parse()
|
kingpin.Parse()
|
||||||
|
|
||||||
configuration := Config{
|
configuration := Config{
|
||||||
|
@ -181,7 +229,14 @@ func main() {
|
||||||
HiddenLabels: *labels,
|
HiddenLabels: *labels,
|
||||||
}
|
}
|
||||||
|
|
||||||
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'
|
'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: {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"author": "Michael Crosby & Kevan Ahlquist",
|
"author": "Michael Crosby & Kevan Ahlquist",
|
||||||
"name": "uifordocker",
|
"name": "uifordocker",
|
||||||
"homepage": "https://github.com/kevana/ui-for-docker",
|
"homepage": "https://github.com/kevana/ui-for-docker",
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git@github.com:kevana/ui-for-docker.git"
|
"url": "git@github.com:kevana/ui-for-docker.git"
|
||||||
|
|
Loading…
Reference in New Issue