mirror of https://github.com/portainer/portainer
commit
9e4f8c9fee
58
README.md
58
README.md
|
@ -1,4 +1,4 @@
|
|||
## Cloudinovasi UI for Docker
|
||||
# Cloudinovasi UI for Docker
|
||||
|
||||
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).
|
||||
|
||||
|
@ -6,11 +6,14 @@ A fork of the amazing UI for Docker by Michael Crosby and Kevan Ahlquist (https:
|
|||
|
||||
UI For Docker is a web interface for the Docker Remote API. The goal is to provide a pure client side implementation so it is effortless to connect and manage docker.
|
||||
|
||||
### Goals
|
||||
## Goals
|
||||
* 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.
|
||||
|
||||
## Run
|
||||
|
||||
### Quickstart
|
||||
|
||||
1. Run: `docker run -d -p 9000:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock cloudinovasi/cloudinovasi-ui`
|
||||
|
||||
2. Open your browser to `http://<dockerd host ip>:9000`
|
||||
|
@ -25,11 +28,54 @@ By default UI For Docker connects to the Docker daemon with`/var/run/docker.sock
|
|||
|
||||
You can use the `-e` flag to change this socket:
|
||||
|
||||
# Connect to a tcp socket:
|
||||
$ docker run -d -p 9000:9000 --privileged cloudinovasi/cloudinovasi-ui -e http://127.0.0.1:2375
|
||||
```
|
||||
# Connect to a tcp socket:
|
||||
$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -e http://127.0.0.1:2375
|
||||
```
|
||||
|
||||
### Swarm support
|
||||
|
||||
**Supported Swarm version: 1.2.3**
|
||||
|
||||
You can access a specific view for you Swarm cluster by defining the `-swarm` flag:
|
||||
|
||||
```
|
||||
# Connect to a tcp socket and enable Swarm:
|
||||
$ docker run -d -p 9000:9000 cloudinovasi/cloudinovasi-ui -e http://<SWARM_HOST>:<SWARM_PORT> -swarm
|
||||
```
|
||||
|
||||
*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.
|
||||
|
||||
### Change address/port UI For Docker is served on
|
||||
UI For Docker listens on port 9000 by default. If you run UI For Docker inside a container then you can bind the container's internal port to any external address and port:
|
||||
|
||||
# Expose UI For Docker on 10.20.30.1:80
|
||||
$ docker run -d -p 10.20.30.1:80:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock cloudinovasi/cloudinovasi-ui
|
||||
```
|
||||
# Expose UI For Docker on 10.20.30.1:80
|
||||
$ docker run -d -p 10.20.30.1:80:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock cloudinovasi/cloudinovasi-ui
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
For example, take a container started with the label `owner=acme`:
|
||||
|
||||
```
|
||||
$ docker run -d --label owner=acme nginx
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### Available options
|
||||
|
||||
The following options are available for the `ui-for-docker` binary:
|
||||
|
||||
* `-endpoint`, `-e`: Docker deamon endpoint (default: *"/var/run/docker.sock"*)
|
||||
* `-bind`, `-p`: Address and port to serve UI For Docker (default: *":9000"*)
|
||||
* `-assets`, `-a`: Path to the assets (default: *"."*)
|
||||
* `-swarm`, `-s`: Swarm cluster support (default: *false*)
|
||||
* `-hide-label`, `-l`: Hide containers with a specific label in the UI
|
||||
|
|
|
@ -120,4 +120,4 @@ angular.module('uifordocker', [
|
|||
.constant('DOCKER_ENDPOINT', 'dockerapi')
|
||||
.constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. If you have a port, prefix it with a ':' i.e. :4243
|
||||
.constant('CONFIG_ENDPOINT', '/config')
|
||||
.constant('UI_VERSION', 'v1.0.3');
|
||||
.constant('UI_VERSION', 'v1.0.4');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('containers', [])
|
||||
.controller('ContainersController', ['$scope', 'Container', 'Settings', 'Messages', 'ViewSpinner',
|
||||
function ($scope, Container, Settings, Messages, ViewSpinner) {
|
||||
.controller('ContainersController', ['$scope', 'Container', 'Settings', 'Messages', 'ViewSpinner', 'Config',
|
||||
function ($scope, Container, Settings, Messages, ViewSpinner, Config) {
|
||||
|
||||
$scope.state = {};
|
||||
$scope.state.displayAll = Settings.displayAll;
|
||||
|
@ -18,9 +18,11 @@ function ($scope, Container, Settings, Messages, ViewSpinner) {
|
|||
ViewSpinner.spin();
|
||||
$scope.state.selectedItemCount = 0;
|
||||
Container.query(data, function (d) {
|
||||
$scope.containers = d.filter(function (container) {
|
||||
return container.Image !== 'swarm';
|
||||
}).map(function (container) {
|
||||
var containers = d;
|
||||
if (hiddenLabels) {
|
||||
containers = hideContainers(d);
|
||||
}
|
||||
$scope.containers = containers.map(function (container) {
|
||||
return new ContainerViewModel(container);
|
||||
});
|
||||
ViewSpinner.stop();
|
||||
|
@ -134,5 +136,24 @@ function ($scope, Container, Settings, Messages, ViewSpinner) {
|
|||
batch($scope.containers, Container.remove, "Removed");
|
||||
};
|
||||
|
||||
update({all: Settings.displayAll ? 1 : 0});
|
||||
var hideContainers = function (containers) {
|
||||
return containers.filter(function (container) {
|
||||
var filterContainer = false;
|
||||
hiddenLabels.forEach(function(label, index) {
|
||||
if (_.has(container.Labels, label.name) &&
|
||||
container.Labels[label.name] === label.value) {
|
||||
filterContainer = true;
|
||||
}
|
||||
});
|
||||
if (!filterContainer) {
|
||||
return container;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var hiddenLabels;
|
||||
Config.$promise.then(function (c) {
|
||||
hiddenLabels = c.hiddenLabels;
|
||||
update({all: Settings.displayAll ? 1 : 0});
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -9,7 +9,7 @@ angular.module('dashboard')
|
|||
return window.innerWidth;
|
||||
};
|
||||
|
||||
$scope.config = Config.get();
|
||||
$scope.config = Config;
|
||||
|
||||
$scope.$watch($scope.getWidth, function(newValue, oldValue) {
|
||||
if (newValue >= mobileView) {
|
||||
|
|
|
@ -115,6 +115,13 @@
|
|||
<span ng-show="sortType == 'Containers' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('Engine')">
|
||||
Engine
|
||||
<span ng-show="sortType == 'Engine' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Engine' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="swarm" ng-click="order('Status')">
|
||||
Status
|
||||
|
@ -129,6 +136,7 @@
|
|||
<td>{{ node.name }}</td>
|
||||
<td>{{ node.ip }}</td>
|
||||
<td>{{ node.containers }}</td>
|
||||
<td>{{ node.version }}</td>
|
||||
<td><span class="label label-{{ node.status|statusbadge }}">{{ node.status }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -42,7 +42,7 @@ angular.module('swarm', [])
|
|||
var node_offset = 4;
|
||||
for (i = 0; i < node_count; i++) {
|
||||
extractNodeInfo(info, node_offset);
|
||||
node_offset += 9;
|
||||
node_offset += 10;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,8 @@ angular.module('swarm', [])
|
|||
var node = {};
|
||||
node.name = info[offset][0];
|
||||
node.ip = info[offset][1];
|
||||
node.status = info[offset + 1][1];
|
||||
node.id = info[offset + 1][1];
|
||||
node.status = info[offset + 2][1];
|
||||
node.containers = info[offset + 2][1];
|
||||
node.cpu = info[offset + 3][1];
|
||||
node.memory = info[offset + 4][1];
|
||||
|
|
|
@ -143,7 +143,7 @@ angular.module('dockerui.services', ['ngResource', 'ngSanitize'])
|
|||
});
|
||||
}])
|
||||
.factory('Config', ['$resource', 'CONFIG_ENDPOINT', function($resource, CONFIG_ENDPOINT) {
|
||||
return $resource(CONFIG_ENDPOINT);
|
||||
return $resource(CONFIG_ENDPOINT).get();
|
||||
}])
|
||||
.factory('Settings', ['DOCKER_ENDPOINT', 'DOCKER_PORT', 'UI_VERSION', function SettingsFactory(DOCKER_ENDPOINT, DOCKER_PORT, UI_VERSION) {
|
||||
'use strict';
|
||||
|
|
|
@ -116,8 +116,11 @@
|
|||
|
||||
.logo {
|
||||
display: inline;
|
||||
width: 110px;
|
||||
max-height: 38px;
|
||||
width: 100%;
|
||||
max-width: 160px;
|
||||
height: 100%;
|
||||
max-height: 55px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.containerNameInput {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uifordocker",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"homepage": "https://github.com/kevana/ui-for-docker",
|
||||
"authors": [
|
||||
"Michael Crosby <crosbymichael@gmail.com>",
|
||||
|
|
63
dockerui.go
63
dockerui.go
|
@ -1,7 +1,6 @@
|
|||
package main // import "github.com/cloudinovasi/ui-for-docker"
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
|
@ -15,13 +14,15 @@ import (
|
|||
"io/ioutil"
|
||||
"fmt"
|
||||
"github.com/gorilla/securecookie"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
endpoint = flag.String("e", "/var/run/docker.sock", "Dockerd endpoint")
|
||||
addr = flag.String("p", ":9000", "Address and port to serve UI For Docker")
|
||||
assets = flag.String("a", ".", "Path to the assets")
|
||||
swarm = flag.Bool("s", false, "Swarm mode")
|
||||
endpoint = kingpin.Flag("endpoint", "Dockerd endpoint").Default("/var/run/docker.sock").Short('e').String()
|
||||
addr = kingpin.Flag("bind", "Address and port to serve UI For Docker").Default(":9000").Short('p').String()
|
||||
assets = kingpin.Flag("assets", "Path to the assets").Default(".").Short('a').String()
|
||||
swarm = kingpin.Flag("swarm", "Swarm cluster support").Default("false").Short('s').Bool()
|
||||
labels = LabelParser(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l'))
|
||||
authKey []byte
|
||||
authKeyFile = "authKey.dat"
|
||||
)
|
||||
|
@ -32,6 +33,40 @@ type UnixHandler struct {
|
|||
|
||||
type Config struct {
|
||||
Swarm bool `json:"swarm"`
|
||||
HiddenLabels Labels `json:"hiddenLabels"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Labels []Label
|
||||
|
||||
func (l *Labels) Set(value string) error {
|
||||
parts := strings.SplitN(value, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected HEADER=VALUE got '%s'", value)
|
||||
}
|
||||
label := new(Label)
|
||||
label.Name = parts[0]
|
||||
label.Value = parts[1]
|
||||
*l = append(*l, *label)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Labels) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l *Labels) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func LabelParser(s kingpin.Settings) (target *[]Label) {
|
||||
target = new([]Label)
|
||||
s.SetValue((*Labels)(target))
|
||||
return
|
||||
}
|
||||
|
||||
func (h *UnixHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -82,7 +117,7 @@ func createUnixHandler(e string) http.Handler {
|
|||
return &UnixHandler{e}
|
||||
}
|
||||
|
||||
func createHandler(dir string, e string, s bool) http.Handler {
|
||||
func createHandler(dir string, e string, c Config) http.Handler {
|
||||
var (
|
||||
mux = http.NewServeMux()
|
||||
fileHandler = http.FileServer(http.Dir(dir))
|
||||
|
@ -120,14 +155,10 @@ func createHandler(dir string, e string, s bool) http.Handler {
|
|||
csrf.Secure(false),
|
||||
)
|
||||
|
||||
configuration := Config{
|
||||
Swarm: s,
|
||||
}
|
||||
|
||||
mux.Handle("/dockerapi/", http.StripPrefix("/dockerapi", h))
|
||||
mux.Handle("/", fileHandler)
|
||||
mux.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
|
||||
configurationHandler(w, r, configuration)
|
||||
configurationHandler(w, r, c)
|
||||
})
|
||||
return CSRF(csrfWrapper(mux))
|
||||
}
|
||||
|
@ -140,9 +171,15 @@ func csrfWrapper(h http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
kingpin.Version("1.0.4")
|
||||
kingpin.Parse()
|
||||
|
||||
handler := createHandler(*assets, *endpoint, *swarm)
|
||||
configuration := Config{
|
||||
Swarm: *swarm,
|
||||
HiddenLabels: *labels,
|
||||
}
|
||||
|
||||
handler := createHandler(*assets, *endpoint, configuration)
|
||||
if err := http.ListenAndServe(*addr, handler); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ module.exports = function (grunt) {
|
|||
'copy'
|
||||
]);
|
||||
grunt.registerTask('release', [
|
||||
'clean:app',
|
||||
'clean:all',
|
||||
'if:binaryNotExist',
|
||||
'html2js',
|
||||
'uglify',
|
||||
|
@ -267,7 +267,7 @@ module.exports = function (grunt) {
|
|||
command: [
|
||||
'docker stop ui-for-docker',
|
||||
'docker rm ui-for-docker',
|
||||
'docker run --privileged -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --name ui-for-docker ui-for-docker -s'
|
||||
'docker run --privileged -d -p 9000:9000 --name ui-for-docker ui-for-docker -e http://10.0.7.10:4000 -swarm'
|
||||
].join(';')
|
||||
},
|
||||
cleanImages: {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"author": "Michael Crosby & Kevan Ahlquist",
|
||||
"name": "uifordocker",
|
||||
"homepage": "https://github.com/kevana/ui-for-docker",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:kevana/ui-for-docker.git"
|
||||
|
|
Loading…
Reference in New Issue