Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c3f7b3ec2 | ||
|
|
3aa0f4d263 | ||
|
|
2f35f04207 | ||
|
|
3b3b23142c | ||
|
|
9bd88fd10d | ||
|
|
3092d0b7eb | ||
|
|
d924d340d7 | ||
|
|
c1ffd02491 | ||
|
|
8e9dd8c2df | ||
|
|
1bfd6bbe95 | ||
|
|
715638e368 | ||
|
|
08c868bc1c | ||
|
|
9f46b12625 | ||
|
|
6fc25691bd | ||
|
|
c1713e0d01 | ||
|
|
8187f17d33 | ||
|
|
f0e194f63b | ||
|
|
eabf1f10e4 | ||
|
|
c913d858ee | ||
|
|
17f35ef705 | ||
|
|
0bdbb4a75d | ||
|
|
f9327b3337 | ||
|
|
bf6c9c8b3b | ||
|
|
45015a573b | ||
|
|
d4f0145161 | ||
|
|
fa53339fea | ||
|
|
e5396091a7 | ||
|
|
1ae18e1577 | ||
|
|
b953850a1f | ||
|
|
d0954abe29 | ||
|
|
c3cf5b5f9d | ||
|
|
6589730acc | ||
|
|
442dcff0f1 | ||
|
|
8bac1955a8 | ||
|
|
09a5534499 | ||
|
|
65c126f6a1 | ||
|
|
6adec680a4 | ||
|
|
b81d4fa7f2 | ||
|
|
d8f2e3da86 | ||
|
|
b0c0512515 |
30
LICENSE
30
LICENSE
@@ -1,22 +1,22 @@
|
||||
Portainer: Copyright (c) 2016 Portainer.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Portainer contains code which was originally under this license:
|
||||
|
||||
UI For Docker: Copyright (c) 2013-2016 Michael Crosby (crosbymichael.com), Kevan Ahlquist (kevanahlquist.com), Anthony Lapenna (portainer.io)
|
||||
|
||||
|
||||
80
README.md
80
README.md
@@ -1,79 +1,47 @@
|
||||
# Portainer
|
||||
|
||||
The easiest way to manage Docker.
|
||||
<p align="center">
|
||||
<img title="portainer" src='http://portainer.io/images/logo_alt.png' />
|
||||
</p>
|
||||
|
||||
[](https://microbadger.com/images/portainer/portainer "Latest version on Docker Hub")
|
||||
[](http://microbadger.com/images/portainer/portainer "Image size")
|
||||
[](http://portainer.readthedocs.io/en/stable/?badge=stable)
|
||||
[](http://portainer.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://gitter.im/portainer/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
Portainer is a lightweight management UI which allows you to **easily** manage your Docker host or Swarm cluster.
|
||||
**_Portainer_** is a lightweight management UI which allows you to **easily** manage your Docker host or Swarm cluster.
|
||||
|
||||
# Usage
|
||||
**_Portainer_** is meant to be as **simple** to deploy as it is to use. It consists of a single container that can run on any Docker engine (Docker for Linux and Docker for Windows are supported).
|
||||
|
||||
It's really simple to deploy it using Docker:
|
||||
**_Portainer_** allows you to manage your Docker containers, images, volumes, networks and more ! It is compatible with the *standalone Docker* engine and with *Docker Swarm*.
|
||||
|
||||
```shell
|
||||
$ docker run -d -p 9000:9000 portainer/portainer -H tcp://<DOCKER_HOST>:<DOCKER_PORT>
|
||||
```
|
||||
## Demo
|
||||
|
||||
Just point it at your targeted Docker host and then access Portainer by hitting [http://localhost:9000](http://localhost:9000) with a web browser.
|
||||
<img src="http://portainer.io/images/screenshots/portainer.gif" width="77%"/>
|
||||
|
||||
If your target is a Docker Swarm cluster or a Docker cluster using *swarm mode*, just add the flag `--swarm`:
|
||||
You can try out the public demo instance: http://demo.portainer.io/ (login with the username **demo** and the password **tryportainer**).
|
||||
|
||||
```shell
|
||||
$ docker run -d -p 9000:9000 portainer/portainer -H tcp://<SWARM_HOST>:<SWARM_PORT> --swarm
|
||||
```
|
||||
Please note that the public demo cluster is **reset every 15min**.
|
||||
|
||||
If you don't specify any target, its default behaviour is to use a bind mount on the Docker socket so you can easily deploy it to manage your local Docker host:
|
||||
## Getting started
|
||||
|
||||
```shell
|
||||
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
|
||||
```
|
||||
* [Deploy Portainer](https://portainer.readthedocs.io/en/latest/deployment.html)
|
||||
* [Documentation](https://portainer.readthedocs.io)
|
||||
|
||||
Have a look at our [documentation](http://portainer.readthedocs.io/en/stable/deployment.html) for more deployment options.
|
||||
## Getting help
|
||||
|
||||
# Configuration
|
||||
* Issues: https://github.com/portainer/portainer/issues
|
||||
* FAQ: https://portainer.readthedocs.io/en/latest/faq.html
|
||||
* Gitter (chat): https://gitter.im/portainer/Lobby
|
||||
* Slack: http://portainer.io/slack/
|
||||
|
||||
Portainer is easy to tune using CLI flags.
|
||||
## Reporting bugs and contributing
|
||||
|
||||
## Hiding specific containers
|
||||
* Want to report a bug or request a feature? Please open [an issue](https://github.com/portainer/portainer/issues/new).
|
||||
* Want to help us build **_portainer_**? Follow our [contribution guidelines](https://portainer.readthedocs.io/en/latest/contribute.html) to build it locally and make a pull request. We need all the help we can get!
|
||||
|
||||
Portainer allows you to hide container with a specific label by using the `-l` flag.
|
||||
## Limitations
|
||||
|
||||
For example, take a container started with the label `owner=acme`:
|
||||
```shell
|
||||
$ docker run -d --label owner=acme nginx
|
||||
```
|
||||
|
||||
Simply add the `-l owner=acme` option on the CLI when starting Portainer:
|
||||
```shell
|
||||
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer -l owner=acme
|
||||
```
|
||||
|
||||
## Use your own templates
|
||||
|
||||
Portainer allows you to rapidly deploy containers using `App Templates`.
|
||||
|
||||
By default [Portainer templates](https://raw.githubusercontent.com/portainer/templates/master/templates.json) will be used but you can also define your own templates.
|
||||
|
||||
Add the `--templates` flag and specify the external location of your templates when starting Portainer:
|
||||
|
||||
```shell
|
||||
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer --templates http://my-host.my-domain/templates.json
|
||||
```
|
||||
|
||||
For more information about hosting your own template definitions and the format, see the [templates documentation](http://portainer.readthedocs.io/en/stable/templates.html).
|
||||
|
||||
Check our [documentation](http://portainer.readthedocs.io/en/stable/configuration.html) for more configuration options.
|
||||
|
||||
# FAQ
|
||||
|
||||
Be sure to check our [FAQ](http://portainer.readthedocs.io/en/stable/faq.html) if you are missing some information.
|
||||
|
||||
# Limitations
|
||||
|
||||
Portainer has full support for the following Docker versions:
|
||||
**_Portainer_** has full support for the following Docker versions:
|
||||
|
||||
* Docker 1.10 to Docker 1.12 (including `swarm-mode`)
|
||||
* Docker Swarm >= 1.2.3
|
||||
|
||||
@@ -33,6 +33,7 @@ type (
|
||||
|
||||
func (a *api) run(settings *Settings) {
|
||||
handler := a.newHandler(settings)
|
||||
log.Printf("Starting portainer on %s", a.bindAddress)
|
||||
if err := http.ListenAndServe(a.bindAddress, handler); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ func (a *api) newHandler(settings *Settings) http.Handler {
|
||||
)
|
||||
|
||||
handler := a.newAPIHandler()
|
||||
CSRFHandler := newCSRFHandler(a.dataPath)
|
||||
|
||||
mux.Handle("/", fileHandler)
|
||||
mux.Handle("/dockerapi/", http.StripPrefix("/dockerapi", handler))
|
||||
@@ -28,7 +27,12 @@ func (a *api) newHandler(settings *Settings) http.Handler {
|
||||
mux.HandleFunc("/templates", func(w http.ResponseWriter, r *http.Request) {
|
||||
templatesHandler(w, r, a.templatesURL)
|
||||
})
|
||||
return CSRFHandler(newCSRFWrapper(mux))
|
||||
// CSRF protection is disabled for the moment
|
||||
// CSRFHandler := newCSRFHandler(a.dataPath)
|
||||
// return CSRFHandler(newCSRFWrapper(mux))
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
mux.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// newAPIHandler initializes a new http.Handler based on the URL scheme
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
// main is the entry point of the program
|
||||
func main() {
|
||||
kingpin.Version("1.9.2")
|
||||
kingpin.Version("1.10.0")
|
||||
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 Portainer").Default(":9000").Short('p').String()
|
||||
|
||||
16
app/app.js
16
app/app.js
@@ -33,9 +33,6 @@ angular.module('portainer', [
|
||||
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', function ($stateProvider, $urlRouterProvider, $httpProvider) {
|
||||
'use strict';
|
||||
|
||||
$httpProvider.defaults.xsrfCookieName = 'csrfToken';
|
||||
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
|
||||
|
||||
$urlRouterProvider.otherwise('/');
|
||||
|
||||
$stateProvider
|
||||
@@ -161,6 +158,8 @@ angular.module('portainer', [
|
||||
});
|
||||
|
||||
// The Docker API likes to return plaintext errors, this catches them and disp
|
||||
// $httpProvider.defaults.xsrfCookieName = 'csrfToken';
|
||||
// $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
|
||||
$httpProvider.interceptors.push(function() {
|
||||
return {
|
||||
'response': function(response) {
|
||||
@@ -172,10 +171,11 @@ angular.module('portainer', [
|
||||
time: 10000
|
||||
});
|
||||
}
|
||||
var csrfToken = response.headers('X-Csrf-Token');
|
||||
if (csrfToken) {
|
||||
document.cookie = 'csrfToken=' + csrfToken;
|
||||
}
|
||||
// CSRF protection is disabled for the moment
|
||||
// var csrfToken = response.headers('X-Csrf-Token');
|
||||
// if (csrfToken) {
|
||||
// document.cookie = 'csrfToken=' + csrfToken;
|
||||
// }
|
||||
return response;
|
||||
}
|
||||
};
|
||||
@@ -188,4 +188,4 @@ angular.module('portainer', [
|
||||
.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', 'settings')
|
||||
.constant('TEMPLATES_ENDPOINT', 'templates')
|
||||
.constant('UI_VERSION', 'v1.9.2');
|
||||
.constant('UI_VERSION', 'v1.10.0');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
Containers > <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a>
|
||||
<a ui-sref="containers">Containers</a> > <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
<rd-widget-header icon="fa-cogs" title="Actions"></rd-widget-header>
|
||||
<rd-widget-body classes="padding">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<button class="btn btn-primary" ng-click="start()" ng-if="!container.State.Running"><i class="fa fa-play btn-ico" aria-hidden="true"></i>Start</button>
|
||||
<button class="btn btn-danger" ng-click="stop()" ng-if="container.State.Running"><i class="fa fa-stop btn-ico" aria-hidden="true"></i>Stop</button>
|
||||
<button class="btn btn-danger" ng-click="kill()" ng-if="container.State.Running"><i class="fa fa-bomb btn-ico" aria-hidden="true"></i>Kill</button>
|
||||
<button class="btn btn-primary" ng-click="restart()" ng-if="container.State.Running"><i class="fa fa-refresh btn-ico" aria-hidden="true"></i>Restart</button>
|
||||
<button class="btn btn-primary" ng-click="pause()" ng-if="container.State.Running && !container.State.Paused"><i class="fa fa-pause btn-ico" aria-hidden="true"></i>Pause</button>
|
||||
<button class="btn btn-primary" ng-click="unpause()" ng-if="container.State.Paused"><i class="fa fa-play btn-ico" aria-hidden="true"></i>Resume</button>
|
||||
<button class="btn btn-danger" ng-click="remove()" ng-disabled="container.State.Running"><i class="fa fa-trash btn-ico" aria-hidden="true"></i>Remove</button>
|
||||
<button class="btn btn-primary" ng-click="start()" ng-if="!container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
|
||||
<button class="btn btn-danger" ng-click="stop()" ng-if="container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
|
||||
<button class="btn btn-danger" ng-click="kill()" ng-if="container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
|
||||
<button class="btn btn-primary" ng-click="restart()" ng-if="container.State.Running"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
|
||||
<button class="btn btn-primary" ng-click="pause()" ng-if="container.State.Running && !container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
|
||||
<button class="btn btn-primary" ng-click="unpause()" ng-if="container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
|
||||
<button class="btn btn-danger" ng-click="remove()" ng-disabled="container.State.Running"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
@@ -52,7 +52,7 @@
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td>
|
||||
<i ng-class="{true: 'fa fa-heartbeat text-icon green-icon', false: 'fa fa-heartbeat text-icon red-icon'}[container.State.Running]"></i>
|
||||
<i ng-class="{true: 'fa fa-heartbeat space-right green-icon', false: 'fa fa-heartbeat space-right red-icon'}[container.State.Running]"></i>
|
||||
{{ container.State|getstatetext }} since {{ activityTime }}<span ng-if="!container.State.Running"> with exit code {{ container.State.ExitCode }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -67,9 +67,9 @@
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="stats({id: container.Id})"><i class="fa fa-area-chart btn-ico" aria-hidden="true"></i>Stats</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="logs({id: container.Id})"><i class="fa fa-exclamation-circle btn-ico" aria-hidden="true"></i>Logs</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="console({id: container.Id})"><i class="fa fa-terminal btn-ico" aria-hidden="true"></i>Console</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i>Stats</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="logs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
|
||||
<a class="btn btn-outline-secondary" type="button" ui-sref="console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -202,3 +202,34 @@
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="!(container.NetworkSettings.Networks | emptyobject)">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-sitemap" title="Connected networks"></rd-widget-header>
|
||||
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>Network Name</th>
|
||||
<th>IP Address</th>
|
||||
<th>Gateway</th>
|
||||
<th>MacAddress</th>
|
||||
<th>Actions</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="(key, value) in container.NetworkSettings.Networks">
|
||||
<td><a ui-sref="network({id: value.NetworkID})">{{ key }}</a></td>
|
||||
<td>{{ value.IPAddress || '-' }}</td>
|
||||
<td>{{ value.Gateway || '-' }}</td>
|
||||
<td>{{ value.MacAddress || '-' }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-xs btn-danger" ng-click="containerLeaveNetwork(container, value.NetworkID)"><i class="fa fa-trash space-right" aria-hidden="true"></i>Leave Network</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module('container', [])
|
||||
.controller('ContainerController', ['$scope', '$state','$stateParams', '$filter', 'Container', 'ContainerCommit', 'ImageHelper', 'Messages',
|
||||
function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, ImageHelper, Messages) {
|
||||
.controller('ContainerController', ['$scope', '$state','$stateParams', '$filter', 'Container', 'ContainerCommit', 'ImageHelper', 'Network', 'Messages',
|
||||
function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, ImageHelper, Network, Messages) {
|
||||
$scope.activityTime = 0;
|
||||
$scope.portBindings = [];
|
||||
$scope.config = {
|
||||
@@ -153,5 +153,22 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
||||
$scope.container.edit = false;
|
||||
};
|
||||
|
||||
$scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) {
|
||||
$('#loadingViewSpinner').show();
|
||||
Network.disconnect({id: networkId}, { Container: $stateParams.id, Force: false }, function (d) {
|
||||
if (d.message) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Messages.send("Error", {}, d.message);
|
||||
} else {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Messages.send("Container left network", $stateParams.id);
|
||||
$state.go('container', {id: $stateParams.id}, {reload: true});
|
||||
}
|
||||
}, function (e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Messages.error("Failure", e, "Unable to disconnect container from network");
|
||||
});
|
||||
};
|
||||
|
||||
update();
|
||||
}]);
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<rd-header-title title="Container console">
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
Containers > <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a> > Console
|
||||
<rd-header-content ng-if="state.loaded">
|
||||
<a ui-sref="containers">Containers</a> > <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a> > Console
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="row" ng-if="state.loaded">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-terminal" title="Console">
|
||||
@@ -16,18 +16,27 @@
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- command-list -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-3">
|
||||
<select class="selectpicker form-control" ng-model="state.command">
|
||||
<option value="bash">/bin/bash</option>
|
||||
<option value="sh">/bin/sh</option>
|
||||
</select>
|
||||
<form>
|
||||
<div class="row">
|
||||
<!-- command-list -->
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-linux" aria-hidden="true" ng-if="imageOS == 'linux'"></i>
|
||||
<i class="fa fa-windows" aria-hidden="true" ng-if="imageOS == 'windows'"></i>
|
||||
</span>
|
||||
<select class="form-control" ng-model="state.command" id="command">
|
||||
<option value="bash" ng-if="imageOS == 'linux'">/bin/bash</option>
|
||||
<option value="sh" ng-if="imageOS == 'linux'">/bin/sh</option>
|
||||
<option value="powershell" ng-if="imageOS == 'windows'">powershell</option>
|
||||
<option value="cmd.exe" ng-if="imageOS == 'windows'">cmd.exe</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 pull-left">
|
||||
<button type="button" class="btn btn-primary" ng-click="connect()" ng-disabled="connected">Connect</button>
|
||||
<button type="button" class="btn btn-default" ng-click="disconnect()" ng-disabled="!connected">Disconnect</button>
|
||||
<!-- !command-list -->
|
||||
<div class="col-sm-8">
|
||||
<button type="button" class="btn btn-primary" ng-click="connect()" ng-disabled="state.connected">Connect</button>
|
||||
<button type="button" class="btn btn-default" ng-click="disconnect()" ng-disabled="!state.connected">Disconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
angular.module('containerConsole', [])
|
||||
.controller('ContainerConsoleController', ['$scope', '$stateParams', 'Settings', 'Container', 'Exec', '$timeout', 'Messages',
|
||||
function ($scope, $stateParams, Settings, Container, Exec, $timeout, Messages) {
|
||||
.controller('ContainerConsoleController', ['$scope', '$stateParams', 'Settings', 'Container', 'Image', 'Exec', '$timeout', 'Messages',
|
||||
function ($scope, $stateParams, Settings, Container, Image, Exec, $timeout, Messages) {
|
||||
$scope.state = {};
|
||||
$scope.state.command = "bash";
|
||||
$scope.connected = false;
|
||||
$scope.state.loaded = false;
|
||||
$scope.state.connected = false;
|
||||
|
||||
var socket, term;
|
||||
|
||||
@@ -16,6 +16,22 @@ function ($scope, $stateParams, Settings, Container, Exec, $timeout, Messages) {
|
||||
|
||||
Container.get({id: $stateParams.id}, function(d) {
|
||||
$scope.container = d;
|
||||
if (d.message) {
|
||||
Messages.error("Error", d, 'Unable to retrieve container details');
|
||||
$('#loadingViewSpinner').hide();
|
||||
} else {
|
||||
Image.get({id: d.Image}, function(imgData) {
|
||||
$scope.imageOS = imgData.Os;
|
||||
$scope.state.command = imgData.Os === 'windows' ? 'powershell' : 'bash';
|
||||
$scope.state.loaded = true;
|
||||
$('#loadingViewSpinner').hide();
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e, 'Unable to retrieve image details');
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e, 'Unable to retrieve container details');
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
|
||||
@@ -54,7 +70,7 @@ function ($scope, $stateParams, Settings, Container, Exec, $timeout, Messages) {
|
||||
};
|
||||
|
||||
$scope.disconnect = function() {
|
||||
$scope.connected = false;
|
||||
$scope.state.connected = false;
|
||||
if (socket !== null) {
|
||||
socket.close();
|
||||
}
|
||||
@@ -79,7 +95,7 @@ function ($scope, $stateParams, Settings, Container, Exec, $timeout, Messages) {
|
||||
function initTerm(url, height, width) {
|
||||
socket = new WebSocket(url);
|
||||
|
||||
$scope.connected = true;
|
||||
$scope.state.connected = true;
|
||||
socket.onopen = function(evt) {
|
||||
$('#loadConsoleSpinner').hide();
|
||||
term = new Terminal();
|
||||
@@ -95,11 +111,10 @@ function ($scope, $stateParams, Settings, Container, Exec, $timeout, Messages) {
|
||||
term.write(e.data);
|
||||
};
|
||||
socket.onerror = function (error) {
|
||||
$scope.connected = false;
|
||||
|
||||
$scope.state.connected = false;
|
||||
};
|
||||
socket.onclose = function(evt) {
|
||||
$scope.connected = false;
|
||||
$scope.state.connected = false;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
Containers > <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a> > Logs
|
||||
<a ui-sref="containers">Containers</a> > <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a> > Logs
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left">
|
||||
<div class="btn-group" role="group" aria-label="...">
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="startAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-play btn-ico" aria-hidden="true"></i>Start</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="stopAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-stop btn-ico" aria-hidden="true"></i>Stop</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="killAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-bomb btn-ico" aria-hidden="true"></i>Kill</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="restartAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-refresh btn-ico" aria-hidden="true"></i>Restart</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="pauseAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-pause btn-ico" aria-hidden="true"></i>Pause</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="unpauseAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-play btn-ico" aria-hidden="true"></i>Resume</button>
|
||||
<button type="button" class="btn btn-danger btn-responsive" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash btn-ico" aria-hidden="true"></i>Remove</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="startAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="stopAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="killAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="restartAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="pauseAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
|
||||
<button type="button" class="btn btn-primary btn-responsive" ng-click="unpauseAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
|
||||
<button type="button" class="btn btn-danger btn-responsive" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<a class="btn btn-default btn-responsive" type="button" ui-sref="actions.create.container">Add container</a>
|
||||
</div>
|
||||
@@ -98,6 +98,9 @@
|
||||
<span ng-if="container.Ports.length == 0" >-</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!containers">
|
||||
<td colspan="8" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="containers.length == 0">
|
||||
<td colspan="8" class="text-center text-muted">No containers available.</td>
|
||||
</tr>
|
||||
|
||||
@@ -8,7 +8,6 @@ function ($scope, Container, ContainerHelper, Info, Settings, Messages, Config)
|
||||
$scope.sortReverse = false;
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.swarm_mode = false;
|
||||
$scope.containers = [];
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
@@ -34,6 +33,10 @@ function ($scope, Container, ContainerHelper, Info, Settings, Messages, Config)
|
||||
return model;
|
||||
});
|
||||
$('#loadContainersSpinner').hide();
|
||||
}, function (e) {
|
||||
$('#loadContainersSpinner').hide();
|
||||
Messages.error("Failure", e, "Unable to retrieve containers");
|
||||
$scope.containers = [];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
angular.module('createContainer', [])
|
||||
.controller('CreateContainerController', ['$scope', '$state', 'Config', 'Info', 'Container', 'Image', 'Volume', 'Network', 'Messages',
|
||||
function ($scope, $state, Config, Info, Container, Image, Volume, Network, Messages) {
|
||||
|
||||
$scope.state = {
|
||||
alwaysPull: true
|
||||
};
|
||||
.controller('CreateContainerController', ['$scope', '$state', '$stateParams', 'Config', 'Info', 'Container', 'Image', 'Volume', 'Network', 'Messages',
|
||||
function ($scope, $state, $stateParams, Config, Info, Container, Image, Volume, Network, Messages) {
|
||||
|
||||
$scope.formValues = {
|
||||
alwaysPull: true,
|
||||
Console: 'none',
|
||||
Volumes: [],
|
||||
AvailableRegistries: [],
|
||||
Registry: ''
|
||||
};
|
||||
|
||||
$scope.imageConfig = {};
|
||||
|
||||
$scope.config = {
|
||||
Image: '',
|
||||
Env: [],
|
||||
ExposedPorts: {},
|
||||
HostConfig: {
|
||||
@@ -61,8 +58,6 @@ function ($scope, $state, Config, Info, Container, Image, Volume, Network, Messa
|
||||
}
|
||||
});
|
||||
|
||||
$scope.formValues.AvailableRegistries = c.registries;
|
||||
|
||||
Volume.query({}, function (d) {
|
||||
$scope.availableVolumes = d.Volumes;
|
||||
}, function (e) {
|
||||
@@ -83,6 +78,9 @@ function ($scope, $state, Config, Info, Container, Image, Volume, Network, Messa
|
||||
networks.push({Name: "none"});
|
||||
}
|
||||
$scope.availableNetworks = networks;
|
||||
if (!_.find(networks, {'Name': 'bridge'})) {
|
||||
$scope.config.HostConfig.NetworkMode = 'nat';
|
||||
}
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e, "Unable to retrieve networks");
|
||||
});
|
||||
@@ -232,11 +230,10 @@ function ($scope, $state, Config, Info, Container, Image, Volume, Network, Messa
|
||||
$scope.create = function () {
|
||||
var config = prepareConfiguration();
|
||||
$('#createContainerSpinner').show();
|
||||
if ($scope.state.alwaysPull) {
|
||||
if ($scope.formValues.alwaysPull) {
|
||||
pullImageAndCreateContainer(config);
|
||||
} else {
|
||||
createContainer(config);
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<rd-header>
|
||||
<rd-header-title title="Create container"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Containers > Add container
|
||||
<a ui-sref="containers">Containers</a> > Add container
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="col-sm-offset-1 col-sm-11">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="state.alwaysPull"> Always pull image before creating
|
||||
<input type="checkbox" ng-model="formValues.alwaysPull"> Always pull image before creating
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,6 +53,10 @@
|
||||
<input type="radio" name="container_restart_policy" ng-model="config.HostConfig.RestartPolicy.Name" value="on-failure">
|
||||
<span class="radio-value">On failure</span>
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="container_restart_policy" ng-model="config.HostConfig.RestartPolicy.Name" value="unless-stopped">
|
||||
<span class="radio-value">Unless stopped</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !restart-policy -->
|
||||
@@ -60,7 +64,7 @@
|
||||
<div class="form-group">
|
||||
<label for="container_ports" class="col-sm-1 control-label text-left">Port mapping</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addPortBinding()">
|
||||
<span class="label label-default interactive" ng-click="addPortBinding()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map port
|
||||
</span>
|
||||
</div>
|
||||
@@ -102,10 +106,10 @@
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active clickable"><a data-target="#command" data-toggle="tab">Command</a></li>
|
||||
<li class="clickable"><a data-target="#volumes" data-toggle="tab">Volumes</a></li>
|
||||
<li class="clickable"><a data-target="#network" data-toggle="tab">Network</a></li>
|
||||
<li class="clickable"><a data-target="#security" data-toggle="tab">Security/Host</a></li>
|
||||
<li class="active interactive"><a data-target="#command" data-toggle="tab">Command</a></li>
|
||||
<li class="interactive"><a data-target="#volumes" data-toggle="tab">Volumes</a></li>
|
||||
<li class="interactive"><a data-target="#network" data-toggle="tab">Network</a></li>
|
||||
<li class="interactive"><a data-target="#security" data-toggle="tab">Security/Host</a></li>
|
||||
</ul>
|
||||
<!-- tab-content -->
|
||||
<div class="tab-content">
|
||||
@@ -177,7 +181,7 @@
|
||||
<div class="form-group">
|
||||
<label for="container_env" class="col-sm-1 control-label text-left">Environment variables</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addEnvironmentVariable()">
|
||||
<span class="label label-default interactive" ng-click="addEnvironmentVariable()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> environment variable
|
||||
</span>
|
||||
</div>
|
||||
@@ -212,7 +216,7 @@
|
||||
<div class="form-group">
|
||||
<label for="container_volumes" class="col-sm-1 control-label text-left">Volumes</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addVolume()">
|
||||
<span class="label label-default interactive" ng-click="addVolume()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> volume
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<rd-header>
|
||||
<rd-header-title title="Create network"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Networks > Add network
|
||||
<a ui-sref="networks">Networks</a> > Add network
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<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()">
|
||||
<span class="label label-default interactive" ng-click="addDriverOption()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> driver option
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ function ($scope, $state, Service, Volume, Network, ImageHelper, Messages) {
|
||||
WorkingDir: '',
|
||||
User: '',
|
||||
Env: [],
|
||||
Labels: [],
|
||||
Volumes: [],
|
||||
Network: '',
|
||||
ExtraNetworks: [],
|
||||
@@ -50,6 +51,14 @@ function ($scope, $state, Service, Volume, Network, ImageHelper, Messages) {
|
||||
$scope.formValues.Env.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addLabel = function() {
|
||||
$scope.formValues.Labels.push({ name: '', value: ''});
|
||||
};
|
||||
|
||||
$scope.removeLabel = function(index) {
|
||||
$scope.formValues.Labels.splice(index, 1);
|
||||
};
|
||||
|
||||
function prepareImageConfig(config, input) {
|
||||
var imageConfig = ImageHelper.createImageConfig(input.Image, input.Registry);
|
||||
config.TaskTemplate.ContainerSpec.Image = imageConfig.repo + ':' + imageConfig.tag;
|
||||
@@ -97,6 +106,16 @@ function ($scope, $state, Service, Volume, Network, ImageHelper, Messages) {
|
||||
config.TaskTemplate.ContainerSpec.Env = env;
|
||||
}
|
||||
|
||||
function prepareLabelsConfig(config, input) {
|
||||
var labels = {};
|
||||
input.Labels.forEach(function (label) {
|
||||
if (label.name && label.value) {
|
||||
labels[label.name] = label.value;
|
||||
}
|
||||
});
|
||||
config.TaskTemplate.ContainerSpec.Labels = labels;
|
||||
}
|
||||
|
||||
function prepareVolumes(config, input) {
|
||||
input.Volumes.forEach(function (volume) {
|
||||
if (volume.Source && volume.Target) {
|
||||
@@ -138,6 +157,7 @@ function ($scope, $state, Service, Volume, Network, ImageHelper, Messages) {
|
||||
preparePortsConfig(config, input);
|
||||
prepareCommandConfig(config, input);
|
||||
prepareEnvConfig(config, input);
|
||||
prepareLabelsConfig(config, input);
|
||||
prepareVolumes(config, input);
|
||||
prepareNetworks(config, input);
|
||||
return config;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<rd-header>
|
||||
<rd-header-title title="Create service"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Services > Add service
|
||||
<a ui-sref="services">Services</a> > Add service
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<div class="form-group">
|
||||
<label for="container_ports" class="col-sm-1 control-label text-left">Port mapping</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addPortBinding()">
|
||||
<span class="label label-default interactive" ng-click="addPortBinding()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map port
|
||||
</span>
|
||||
</div>
|
||||
@@ -98,9 +98,10 @@
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active clickable"><a data-target="#command" data-toggle="tab">Command</a></li>
|
||||
<li class="clickable"><a data-target="#volumes" data-toggle="tab">Volumes</a></li>
|
||||
<li class="clickable"><a data-target="#network" data-toggle="tab">Network</a></li>
|
||||
<li class="active interactive"><a data-target="#command" data-toggle="tab">Command</a></li>
|
||||
<li class="interactive"><a data-target="#volumes" data-toggle="tab">Volumes</a></li>
|
||||
<li class="interactive"><a data-target="#network" data-toggle="tab">Network</a></li>
|
||||
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
||||
</ul>
|
||||
<!-- tab-content -->
|
||||
<div class="tab-content">
|
||||
@@ -131,7 +132,7 @@
|
||||
<div class="form-group">
|
||||
<label for="service_env" class="col-sm-1 control-label text-left">Environment variables</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addEnvironmentVariable()">
|
||||
<span class="label label-default interactive" ng-click="addEnvironmentVariable()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> environment variable
|
||||
</span>
|
||||
</div>
|
||||
@@ -166,7 +167,7 @@
|
||||
<div class="form-group">
|
||||
<label for="service_volumes" class="col-sm-1 control-label text-left">Volumes</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addVolume()">
|
||||
<span class="label label-default interactive" ng-click="addVolume()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> volume
|
||||
</span>
|
||||
</div>
|
||||
@@ -224,7 +225,7 @@
|
||||
<div class="form-group">
|
||||
<label for="service_extra_networks" class="col-sm-1 control-label text-left">Extra networks</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default clickable" ng-click="addExtraNetwork()">
|
||||
<span class="label label-default interactive" ng-click="addExtraNetwork()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> network
|
||||
</span>
|
||||
</div>
|
||||
@@ -251,6 +252,42 @@
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-network -->
|
||||
<!-- tab-labels -->
|
||||
<div class="tab-pane" id="labels">
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- labels -->
|
||||
<div class="form-group">
|
||||
<label for="service_env" class="col-sm-1 control-label text-left">Labels</label>
|
||||
<div class="col-sm-11">
|
||||
<span class="label label-default interactive" ng-click="addLabel()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> label
|
||||
</span>
|
||||
</div>
|
||||
<!-- labels-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="label in formValues.Labels" 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="label.name" placeholder="e.g. com.example.foo">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removeLabel($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !labels-input-list -->
|
||||
</div>
|
||||
<!-- !labels-->
|
||||
</form>
|
||||
</div>
|
||||
<!-- !tab-labels -->
|
||||
|
||||
<!-- tab-security -->
|
||||
<div class="tab-pane" id="security">
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<rd-header>
|
||||
<rd-header-title title="Create volume"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Volumes > Add volume
|
||||
<a ui-sref="volumes">Volumes</a> > Add volume
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<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()">
|
||||
<span class="label label-default interactive" ng-click="addDriverOption()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> driver option
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -93,8 +93,8 @@
|
||||
<i class="fa fa-server"></i>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div><i class="fa fa-heartbeat text-icon green-icon"></i>{{ containerData.running }} running</div>
|
||||
<div><i class="fa fa-heartbeat text-icon red-icon"></i>{{ containerData.stopped }} stopped</div>
|
||||
<div><i class="fa fa-heartbeat space-right green-icon"></i>{{ containerData.running }} running</div>
|
||||
<div><i class="fa fa-heartbeat space-right red-icon"></i>{{ containerData.stopped }} stopped</div>
|
||||
</div>
|
||||
<div class="title">{{ containerData.total }}</div>
|
||||
<div class="comment">Containers</div>
|
||||
@@ -110,7 +110,7 @@
|
||||
<i class="fa fa-clone"></i>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<div><i class="fa fa-pie-chart text-icon"></i>{{ imageData.size|humansize }}</div>
|
||||
<div><i class="fa fa-pie-chart space-right"></i>{{ imageData.size|humansize }}</div>
|
||||
</div>
|
||||
<div class="title">{{ imageData.total }}</div>
|
||||
<div class="comment">Images</div>
|
||||
@@ -126,7 +126,7 @@
|
||||
<i class="fa fa-cubes"></i>
|
||||
</div>
|
||||
<div class="pull-right" ng-if="infoData.Driver">
|
||||
<div><i class="fa fa-hdd-o text-icon"></i>{{ infoData.Driver }} driver</div>
|
||||
<div><i class="fa fa-hdd-o space-right"></i>{{ infoData.Driver }} driver</div>
|
||||
</div>
|
||||
<div class="title">{{ volumeData.total }}</div>
|
||||
<div class="comment">Volumes</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ function ($scope, $cookieStore, Settings, Config, Info) {
|
||||
$scope.swarm_mode = false;
|
||||
|
||||
Config.$promise.then(function (c) {
|
||||
$scope.logo = c.logo;
|
||||
$scope.swarm = c.swarm;
|
||||
Info.get({}, function(d) {
|
||||
if ($scope.swarm && !_.startsWith(d.ServerVersion, 'swarm')) {
|
||||
|
||||
@@ -3,139 +3,113 @@
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="docker" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||
</a>
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Docker</rd-header-content>
|
||||
</rd-header>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-6 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div class="widget-icon pull-left">
|
||||
<i class="fa fa-code"></i>
|
||||
</div>
|
||||
<div class="title">{{ docker.Version }}</div>
|
||||
<div class="comment">Docker version</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div class="widget-icon pull-left">
|
||||
<i class="fa fa-code"></i>
|
||||
</div>
|
||||
<div class="title">{{ docker.ApiVersion }}</div>
|
||||
<div class="comment">API version</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-body>
|
||||
<div class="widget-icon pull-left">
|
||||
<i class="fa fa-code"></i>
|
||||
</div>
|
||||
<div class="title">{{ docker.GoVersion }}</div>
|
||||
<div class="comment">Go version</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="row" ng-if="state.loaded">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-object-group" title="Engine status"></rd-widget-header>
|
||||
<rd-widget-header icon="fa-code" title="Engine version"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Containers</td>
|
||||
<td>{{ info.Containers }}</td>
|
||||
<td>Version</td>
|
||||
<td>{{ version.Version }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Images</td>
|
||||
<td>{{ info.Images }}</td>
|
||||
<td>API version</td>
|
||||
<td>{{ version.ApiVersion }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Debug</td>
|
||||
<td>{{ info.Debug }}</td>
|
||||
<td>Go version</td>
|
||||
<td>{{ version.GoVersion }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CPUs</td>
|
||||
<td>{{ info.NCPU }}</td>
|
||||
<td>OS type</td>
|
||||
<td>{{ version.Os }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Memory</td>
|
||||
<td>{{ info.MemTotal|humansize }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Operating System</td>
|
||||
<td>OS</td>
|
||||
<td>{{ info.OperatingSystem }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kernel Version</td>
|
||||
<td>{{ info.KernelVersion }}</td>
|
||||
<td>Architecture</td>
|
||||
<td>{{ version.Arch }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>{{ info.ID }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Labels</td>
|
||||
<td>{{ info.Labels }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>File Descriptors</td>
|
||||
<td>{{ info.NFd }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Goroutines</td>
|
||||
<td>{{ info.NGoroutines }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Storage Driver</td>
|
||||
<td>{{ info.Driver }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Storage Driver Status</td>
|
||||
<td>
|
||||
<p ng-repeat="val in info.DriverStatus">
|
||||
{{ val[0] }}: {{ val[1] }}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Execution Driver</td>
|
||||
<td>{{ info.ExecutionDriver }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IPv4 Forwarding</td>
|
||||
<td>{{ info.IPv4Forwarding }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Index Server Address</td>
|
||||
<td>{{ info.IndexServerAddress }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Init Path</td>
|
||||
<td>{{ info.InitPath }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Docker Root Directory</td>
|
||||
<td>{{ info.DockerRootDir }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Init SHA1</td>
|
||||
<td>{{ info.InitSha1 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Memory Limit</td>
|
||||
<td>{{ info.MemoryLimit }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Swap Limit</td>
|
||||
<td>{{ info.SwapLimit }}</td>
|
||||
<td>Kernel version</td>
|
||||
<td>{{ version.KernelVersion }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="state.loaded">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-th" title="Engine status"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total CPU</td>
|
||||
<td>{{ info.NCPU }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total memory</td>
|
||||
<td>{{ info.MemTotal|humansize }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Docker root directory</td>
|
||||
<td>{{ info.DockerRootDir }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Storage driver</td>
|
||||
<td>{{ info.Driver }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Logging driver</td>
|
||||
<td>{{ info.LoggingDriver }}</td>
|
||||
</tr>
|
||||
<tr ng-if="info.CgroupDriver">
|
||||
<td>Cgroup driver</td>
|
||||
<td>{{ info.CgroupDriver }}</td>
|
||||
</tr>
|
||||
<tr ng-if="info.ExecutionDriver">
|
||||
<td>Execution driver</td>
|
||||
<td>{{ info.ExecutionDriver }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="state.loaded && info.Plugins">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plug" title="Engine plugins"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr ng-if="info.Plugins.Volume">
|
||||
<td>Volume</td>
|
||||
<td>{{ info.Plugins.Volume|arraytostr: ', '}}</td>
|
||||
</tr>
|
||||
<tr ng-if="info.Plugins.Network">
|
||||
<td>Network</td>
|
||||
<td>{{ info.Plugins.Network|arraytostr: ', '}}</td>
|
||||
</tr>
|
||||
<tr ng-if="info.Plugins.Authorization">
|
||||
<td>Authorization</td>
|
||||
<td>{{ info.Plugins.Authorization|arraytostr: ', '}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
angular.module('docker', [])
|
||||
.controller('DockerController', ['$scope', 'Info', 'Version', 'Settings',
|
||||
function ($scope, Info, Version, Settings) {
|
||||
|
||||
$scope.info = {};
|
||||
$scope.docker = {};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
.controller('DockerController', ['$scope', 'Info', 'Version', 'Messages',
|
||||
function ($scope, Info, Version, Messages) {
|
||||
$scope.state = {
|
||||
loaded: false
|
||||
};
|
||||
$scope.info = {};
|
||||
$scope.version = {};
|
||||
|
||||
Version.get({}, function (d) {
|
||||
$scope.docker = d;
|
||||
});
|
||||
Info.get({}, function (d) {
|
||||
$scope.info = d;
|
||||
Info.get({}, function (infoData) {
|
||||
$scope.info = infoData;
|
||||
Version.get({}, function (versionData) {
|
||||
$scope.version = versionData;
|
||||
$scope.state.loaded = true;
|
||||
$('#loadingViewSpinner').hide();
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e, 'Unable to retrieve engine details');
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e, 'Unable to retrieve engine information');
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}]);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
Images > <a ui-sref="image({id: image.Id})">{{ image.Id }}</a>
|
||||
<a ui-sref="images">Images</a> > <a ui-sref="image({id: image.Id})">{{ image.Id }}</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<rd-widget-header icon="fa fa-tags" title="Image tags"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div style="margin: 5px 10px;">
|
||||
<span ng-repeat="tag in RepoTags" class="label label-primary image-tag">
|
||||
<span ng-repeat="tag in RepoTags" class="label label-primary image-tag space-right">
|
||||
<a data-toggle="tooltip" class="interactive" title="Push to registry" ng-click="pushImage(tag)">
|
||||
<i class="fa fa-upload white-icon" aria-hidden="true"></i>
|
||||
</a>
|
||||
@@ -82,7 +82,7 @@
|
||||
<td>ID</td>
|
||||
<td>
|
||||
{{ image.Id }}
|
||||
<button class="btn btn-xs btn-danger" ng-click="removeImage(image.Id)"><i class="fa fa-trash btn-ico" aria-hidden="true"></i>Delete this image</button>
|
||||
<button class="btn btn-xs btn-danger" ng-click="removeImage(image.Id)"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete this image</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="image.Parent">
|
||||
@@ -130,7 +130,7 @@
|
||||
<tr ng-if="image.ContainerConfig.ExposedPorts">
|
||||
<td>EXPOSE</td>
|
||||
<td>
|
||||
<span class="label label-default tag" ng-repeat="port in exposedPorts">
|
||||
<span class="label label-default space-right" ng-repeat="port in exposedPorts">
|
||||
{{ port }}
|
||||
</span>
|
||||
</td>
|
||||
@@ -138,7 +138,7 @@
|
||||
<tr ng-if="image.ContainerConfig.Volumes">
|
||||
<td>VOLUME</td>
|
||||
<td>
|
||||
<span class="label label-default tag" ng-repeat="volume in volumes">
|
||||
<span class="label label-default space-right" ng-repeat="volume in volumes">
|
||||
{{ volume }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</rd-widget-header>
|
||||
<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"><i class="fa fa-trash btn-ico" aria-hidden="true"></i>Remove</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
@@ -107,6 +107,9 @@
|
||||
<td>{{ image.VirtualSize|humansize }}</td>
|
||||
<td>{{ image.Created|getisodatefromtimestamp }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!images">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="images.length == 0">
|
||||
<td colspan="5" class="text-center text-muted">No images available.</td>
|
||||
</tr>
|
||||
|
||||
@@ -5,7 +5,6 @@ function ($scope, $state, Config, Image, Messages) {
|
||||
$scope.sortType = 'RepoTags';
|
||||
$scope.sortReverse = true;
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.images = [];
|
||||
|
||||
$scope.config = {
|
||||
Image: '',
|
||||
@@ -98,11 +97,11 @@ function ($scope, $state, Config, Image, Messages) {
|
||||
}, function (e) {
|
||||
$('#loadImagesSpinner').hide();
|
||||
Messages.error("Failure", e, "Unable to retrieve images");
|
||||
$scope.images = [];
|
||||
});
|
||||
}
|
||||
|
||||
Config.$promise.then(function (c) {
|
||||
$scope.availableRegistries = c.registries;
|
||||
fetchImages();
|
||||
});
|
||||
}]);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
Networks > <a ui-sref="network({id: network.Id})">{{ network.Name }}</a>
|
||||
<a ui-sref="networks">Networks</a> > <a ui-sref="network({id: network.Id})">{{ network.Name }}</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<td>ID</td>
|
||||
<td>
|
||||
{{ network.Id }}
|
||||
<button class="btn btn-xs btn-danger" ng-click="removeNetwork(network.Id)"><i class="fa fa-trash btn-ico" aria-hidden="true"></i>Delete this network</button>
|
||||
<button class="btn btn-xs btn-danger" ng-click="removeNetwork(network.Id)"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete this network</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</rd-widget-header>
|
||||
<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"><i class="fa fa-trash btn-ico" aria-hidden="true"></i>Remove</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
@@ -131,6 +131,9 @@
|
||||
<td>{{ network.IPAM.Config[0].Subnet ? network.IPAM.Config[0].Subnet : '-' }}</td>
|
||||
<td>{{ network.IPAM.Config[0].Gateway ? network.IPAM.Config[0].Gateway : '-' }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!networks">
|
||||
<td colspan="8" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="networks.length == 0">
|
||||
<td colspan="8" class="text-center text-muted">No networks available.</td>
|
||||
</tr>
|
||||
|
||||
@@ -6,8 +6,6 @@ function ($scope, $state, Network, Config, Messages) {
|
||||
$scope.state.advancedSettings = false;
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
$scope.networks = [];
|
||||
|
||||
$scope.config = {
|
||||
Name: ''
|
||||
};
|
||||
@@ -93,6 +91,7 @@ function ($scope, $state, Network, Config, Messages) {
|
||||
}, function (e) {
|
||||
$('#loadNetworksSpinner').hide();
|
||||
Messages.error("Failure", e, "Unable to retrieve networks");
|
||||
$scope.networks = [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
Services > <a ui-sref="service({id: service.Id})">{{ service.Name }}</a>
|
||||
<a ui-sref="services">Services</a> > <a ui-sref="service({id: service.Id})">{{ service.Name }}</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<td>ID</td>
|
||||
<td>
|
||||
{{ service.Id }}
|
||||
<button class="btn btn-xs btn-danger" ng-click="removeService()"><i class="fa fa-trash btn-ico" aria-hidden="true"></i>Delete this service</button>
|
||||
<button class="btn btn-xs btn-danger" ng-click="removeService()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete this service</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -43,20 +43,29 @@
|
||||
<tr ng-if="service.Mode === 'replicated'">
|
||||
<td>Replicas</td>
|
||||
<td>
|
||||
<span ng-if="service.Mode === 'replicated' && !service.Scale">
|
||||
<span ng-if="service.Mode === 'replicated' && !service.EditReplicas">
|
||||
{{ service.Replicas }}
|
||||
<a class="interactive" ng-click="service.Scale = true; service.ReplicaCount = service.Replicas;"><i class="fa fa-arrows-v" aria-hidden="true"></i> Scale</a>
|
||||
<a class="interactive" ng-click="service.EditReplicas = true;"><i class="fa fa-arrows-v" aria-hidden="true"></i> Scale</a>
|
||||
</span>
|
||||
<span ng-if="service.Mode === 'replicated' && service.Scale">
|
||||
<input class="input-sm" type="number" ng-model="service.Replicas" />
|
||||
<a class="interactive" ng-click="service.Scale = false;"><i class="fa fa-times"></i></a>
|
||||
<span ng-if="service.Mode === 'replicated' && service.EditReplicas">
|
||||
<input class="input-sm" type="number" ng-model="service.newServiceReplicas" />
|
||||
<a class="interactive" ng-click="service.EditReplicas = false;"><i class="fa fa-times"></i></a>
|
||||
<a class="interactive" ng-click="scaleService(service)"><i class="fa fa-check-square-o"></i></a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Image</td>
|
||||
<td>{{ service.Image }}</td>
|
||||
<td ng-if="!service.EditImage">
|
||||
{{ service.Image }}
|
||||
<a href="" data-toggle="tooltip" title="Edit service image" ng-click="service.EditImage = true;"><i class="fa fa-edit"></i></a>
|
||||
</td>
|
||||
|
||||
<td ng-if="service.EditImage">
|
||||
<input type="text" class="containerNameInput" ng-model="service.newServiceImage">
|
||||
<a class="interactive" ng-click="service.EditImage = false;"><i class="fa fa-times"></i></a>
|
||||
<a class="interactive" ng-click="changeServiceImage(service)"><i class="fa fa-check-square-o"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="service.Ports">
|
||||
<td>Published ports</td>
|
||||
@@ -66,31 +75,77 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="service.Env">
|
||||
<td>Env</td>
|
||||
<tr ng-if="service.EnvironmentVariables">
|
||||
<td>Environment variables</td>
|
||||
<td>
|
||||
<table class="table table-bordered table-condensed">
|
||||
<tr ng-repeat="var in service.Env">
|
||||
<td>{{ var|key: '=' }}</td>
|
||||
<td>{{ var|value: '=' }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-11 nopadding">
|
||||
<span class="label label-default interactive fit-text-size" ng-click="addEnvironmentVariable(service)">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> environment variable
|
||||
</span>
|
||||
</div>
|
||||
<!-- environment-variable-input-list -->
|
||||
<div class="col-sm-11 form-inline nopadding" style="margin-top: 10px;">
|
||||
<div ng-repeat="var in service.EnvironmentVariables" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon fit-text-size">name</span>
|
||||
<input type="text" class="form-control" ng-model="var.key" ng-disabled="var.added" placeholder="e.g. FOO">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon fit-text-size">value</span>
|
||||
<input type="text" class="form-control" ng-model="var.value" ng-change="updateEnvironmentVariable(service, var)" placeholder="e.g. bar">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removeEnvironmentVariable(service, $index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !environment-variable-input-list -->
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="service.Labels">
|
||||
<tr>
|
||||
<td>Labels</td>
|
||||
<td>
|
||||
<table class="table table-bordered table-condensed">
|
||||
<tr ng-repeat="(k, v) in service.Labels">
|
||||
<td>{{ k }}</td>
|
||||
<td>{{ v }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-11 nopadding">
|
||||
<span class="label label-default interactive fit-text-size" ng-click="addLabel(service)">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> label
|
||||
</span>
|
||||
</div>
|
||||
<!-- labels-input-list -->
|
||||
<div class="col-sm-11 form-inline nopadding" style="margin-top: 10px;">
|
||||
<div ng-repeat="label in service.ServiceLabels" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon fit-text-size">name</span>
|
||||
<input type="text" class="form-control" ng-model="label.key" placeholder="e.g. com.example.foo" ng-change="updateLabel(service, label)">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon fit-text-size">value</span>
|
||||
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" ng-change="updateLabel(service, label)">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removeLabel(service, $index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !labels-input-list -->
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
<rd-widget-footer ng-if="service.hasChanges">
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" ng-click="updateService(service)">Save changes</button>
|
||||
<button type="button" class="btn btn-default" ng-click="cancelChanges(service)">Reset</button>
|
||||
</div>
|
||||
</rd-widget-footer>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,43 +8,83 @@ function ($scope, $stateParams, $state, Service, ServiceHelper, Task, Node, Mess
|
||||
$scope.sortType = 'Status';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
var previousServiceValues = {};
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.renameService = function renameService(service) {
|
||||
updateServiceAttribute(service, 'Name', service.newServiceName || service.name);
|
||||
service.EditName = false;
|
||||
};
|
||||
$scope.changeServiceImage = function changeServiceImage(service) {
|
||||
updateServiceAttribute(service, 'Image', service.newServiceImage || service.image);
|
||||
service.EditImage = false;
|
||||
};
|
||||
$scope.scaleService = function scaleService(service) {
|
||||
updateServiceAttribute(service, 'Replicas', service.newServiceReplicas || service.Replicas);
|
||||
service.EditReplicas = false;
|
||||
};
|
||||
|
||||
$scope.addEnvironmentVariable = function addEnvironmentVariable(service) {
|
||||
service.EnvironmentVariables.push({ key: '', value: '', originalValue: '' });
|
||||
service.hasChanges = true;
|
||||
};
|
||||
$scope.removeEnvironmentVariable = function removeEnvironmentVariable(service, index) {
|
||||
var removedElement = service.EnvironmentVariables.splice(index, 1);
|
||||
service.hasChanges = service.hasChanges || removedElement !== null;
|
||||
};
|
||||
$scope.updateEnvironmentVariable = function updateEnvironmentVariable(service, variable) {
|
||||
service.hasChanges = service.hasChanges || variable.value !== variable.originalValue;
|
||||
};
|
||||
$scope.addLabel = function addLabel(service) {
|
||||
service.hasChanges = true;
|
||||
service.ServiceLabels.push({ key: '', value: '', originalValue: '' });
|
||||
};
|
||||
$scope.removeLabel = function removeLabel(service, index) {
|
||||
var removedElement = service.ServiceLabels.splice(index, 1);
|
||||
service.hasChanges = service.hasChanges || removedElement !== null;
|
||||
};
|
||||
$scope.updateLabel = function updateLabel(service, label) {
|
||||
service.hasChanges = service.hasChanges || label.value !== label.originalValue;
|
||||
};
|
||||
|
||||
$scope.cancelChanges = function changeServiceImage(service) {
|
||||
Object.keys(previousServiceValues).forEach(function(attribute) {
|
||||
service[attribute] = previousServiceValues[attribute]; // reset service values
|
||||
service['newService' + attribute] = previousServiceValues[attribute]; // reset edit fields
|
||||
});
|
||||
previousServiceValues = {}; // clear out all changes
|
||||
// clear out environment variable changes
|
||||
service.EnvironmentVariables = translateEnvironmentVariables(service.Env);
|
||||
service.ServiceLabels = translateLabelsToServiceLabels(service.Labels);
|
||||
|
||||
service.hasChanges = false;
|
||||
};
|
||||
|
||||
$scope.updateService = function updateService(service) {
|
||||
$('#loadServicesSpinner').show();
|
||||
var serviceName = service.Name;
|
||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
config.Name = service.newServiceName;
|
||||
config.TaskTemplate.ContainerSpec.Env = translateEnvironmentVariablesToEnv(service.EnvironmentVariables);
|
||||
config.TaskTemplate.ContainerSpec.Labels = translateServiceLabelsToLabels(service.ServiceLabels);
|
||||
config.TaskTemplate.ContainerSpec.Image = service.newServiceImage;
|
||||
if (service.Mode === 'replicated') {
|
||||
config.Mode.Replicated.Replicas = service.Replicas;
|
||||
}
|
||||
|
||||
Service.update({ id: service.Id, version: service.Version }, config, function (data) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
Messages.send("Service successfully renamed", "New name: " + service.newServiceName);
|
||||
Messages.send("Service successfully updated", "Service updated");
|
||||
$state.go('service', {id: service.Id}, {reload: true});
|
||||
}, function (e) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
service.EditName = false;
|
||||
service.Name = serviceName;
|
||||
Messages.error("Failure", e, "Unable to rename service");
|
||||
Messages.error("Failure", e, "Unable to update service");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.scaleService = function scaleService(service) {
|
||||
$('#loadServicesSpinner').show();
|
||||
var config = ServiceHelper.serviceToConfig(service.Model);
|
||||
config.Mode.Replicated.Replicas = service.Replicas;
|
||||
Service.update({ id: service.Id, version: service.Version }, config, function (data) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
Messages.send("Service successfully scaled", "New replica count: " + service.Replicas);
|
||||
$state.go('service', {id: service.Id}, {reload: true});
|
||||
}, function (e) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
service.Scale = false;
|
||||
service.Replicas = service.ReplicaCount;
|
||||
Messages.error("Failure", e, "Unable to scale service");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeService = function removeService() {
|
||||
$('#loadingViewSpinner').show();
|
||||
@@ -68,6 +108,11 @@ function ($scope, $stateParams, $state, Service, ServiceHelper, Task, Node, Mess
|
||||
Service.get({id: $stateParams.id}, function (d) {
|
||||
var service = new ServiceViewModel(d);
|
||||
service.newServiceName = service.Name;
|
||||
service.newServiceImage = service.Image;
|
||||
service.newServiceReplicas = service.Replicas;
|
||||
service.EnvironmentVariables = translateEnvironmentVariables(service.Env);
|
||||
service.ServiceLabels = translateLabelsToServiceLabels(service.Labels);
|
||||
|
||||
$scope.service = service;
|
||||
Task.query({filters: {service: [service.Name]}}, function (tasks) {
|
||||
Node.query({}, function (nodes) {
|
||||
@@ -93,5 +138,59 @@ function ($scope, $stateParams, $state, Service, ServiceHelper, Task, Node, Mess
|
||||
});
|
||||
}
|
||||
|
||||
function updateServiceAttribute(service, name, newValue) {
|
||||
// ensure we only capture the original previous value, in case we update the attribute multiple times
|
||||
if (!previousServiceValues[name]) {
|
||||
previousServiceValues[name] = service[name];
|
||||
}
|
||||
// update the attribute
|
||||
service[name] = newValue;
|
||||
service.hasChanges = true;
|
||||
}
|
||||
|
||||
function translateEnvironmentVariables(env) {
|
||||
if (env) {
|
||||
var variables = [];
|
||||
env.forEach(function(variable) {
|
||||
var keyValue = variable.split('=');
|
||||
var originalValue = (keyValue.length > 1) ? keyValue[1] : '';
|
||||
variables.push({ key: keyValue[0], value: originalValue, originalValue: originalValue, added: true});
|
||||
});
|
||||
return variables;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
function translateEnvironmentVariablesToEnv(env) {
|
||||
if (env) {
|
||||
var variables = [];
|
||||
env.forEach(function(variable) {
|
||||
if (variable.key && variable.key !== '' && variable.value && variable.value !== '') {
|
||||
variables.push(variable.key + '=' + variable.value);
|
||||
}
|
||||
});
|
||||
return variables;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function translateLabelsToServiceLabels(Labels) {
|
||||
var labels = [];
|
||||
if (Labels) {
|
||||
Object.keys(Labels).forEach(function(key) {
|
||||
labels.push({ key: key, value: Labels[key], originalValue: Labels[key], added: true});
|
||||
});
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
function translateServiceLabelsToLabels(labels) {
|
||||
var Labels = {};
|
||||
if (labels) {
|
||||
labels.forEach(function(label) {
|
||||
Labels[label.key] = label.value;
|
||||
});
|
||||
}
|
||||
return Labels;
|
||||
}
|
||||
|
||||
fetchServiceDetails();
|
||||
}]);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12 col-md-12 col-xs-12">
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash btn-ico" aria-hidden="true"></i>Remove</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<a class="btn btn-default btn-responsive" type="button" ui-sref="actions.create.service">Add service</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
@@ -69,6 +69,12 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!services">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="services.length == 0">
|
||||
<td colspan="4" class="text-center text-muted">No services available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
angular.module('services', [])
|
||||
.controller('ServicesController', ['$scope', '$stateParams', '$state', 'Service', 'ServiceHelper', 'Messages',
|
||||
function ($scope, $stateParams, $state, Service, ServiceHelper, Messages) {
|
||||
|
||||
$scope.services = [];
|
||||
$scope.state = {};
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.sortType = 'Name';
|
||||
@@ -77,6 +75,7 @@ function ($scope, $stateParams, $state, Service, ServiceHelper, Messages) {
|
||||
}, function(e) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
Messages.error("Failure", e, "Unable to retrieve services");
|
||||
$scope.services = [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<rd-header>
|
||||
<rd-header-title title="Container stats"></rd-header-title>
|
||||
<rd-header-content>
|
||||
Containers > <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a> > Stats
|
||||
<a ui-sref="containers">Containers</a> > <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a> > Stats
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
@@ -59,11 +59,17 @@
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-repeat="title in containerTop.Titles">{{title}}</th>
|
||||
<th ng-repeat="title in containerTop.Titles">
|
||||
<a ui-sref="stats({id: container.Id})" ng-click="order(title)">
|
||||
{{title}}
|
||||
<span ng-show="sortType == title && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == title && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="processInfos in containerTop.Processes">
|
||||
<tr ng-repeat="processInfos in state.filteredProcesses = (containerTop.Processes | orderBy:sortType:sortReverse)">
|
||||
<td ng-repeat="processInfo in processInfos track by $index">{{processInfo}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -4,6 +4,13 @@ function (Settings, $scope, Messages, $timeout, Container, ContainerTop, $stateP
|
||||
// TODO: Force scale to 0-100 for cpu, fix charts on dashboard,
|
||||
// TODO: Force memory scale to 0 - max memory
|
||||
$scope.ps_args = '';
|
||||
$scope.state = {};
|
||||
$scope.sortType = 'CMD';
|
||||
$scope.sortReverse = false;
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
$scope.getTop = function () {
|
||||
ContainerTop.get($stateParams.id, {
|
||||
ps_args: $scope.ps_args
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
Services > <a ui-sref="service({id: task.ServiceID})">{{ serviceName }}</a> > {{ task.ID }}
|
||||
<a ui-sref="services">Services</a> > <a ui-sref="service({id: task.ServiceID})">{{ serviceName }}</a> > {{ task.ID }}
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<rd-header-content>Templates</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="selectedTemplate">
|
||||
<div class="row" ng-if="state.selectedTemplate">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-custom-header icon="selectedTemplate.logo" title="selectedTemplate.image">
|
||||
<rd-widget-custom-header icon="state.selectedTemplate.logo" title="state.selectedTemplate.image">
|
||||
</rd-widget-custom-header>
|
||||
<rd-widget-body classes="padding">
|
||||
<form class="form-horizontal">
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-and-network-inputs -->
|
||||
<div ng-repeat="var in selectedTemplate.env" ng-if="!var.set" class="form-group">
|
||||
<div ng-repeat="var in state.selectedTemplate.env" ng-if="!var.set" class="form-group">
|
||||
<label for="field_{{ $index }}" class="col-sm-2 control-label text-left">{{ var.label }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select ng-if="(!swarm || swarm && swarm_mode) && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="selectpicker form-control" ng-model="var.value">
|
||||
@@ -51,6 +51,50 @@
|
||||
<input ng-if="!var.type || !var.type === 'container'" type="text" class="form-control" ng-model="var.value" id="field_{{ $index }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<a class="small interactive" ng-if="!state.showAdvancedOptions" ng-click="state.showAdvancedOptions = true;">
|
||||
<i class="fa fa-plus space-right" aria-hidden="true"></i> Show advanced options
|
||||
</a>
|
||||
<a class="small interactive" ng-if="state.showAdvancedOptions" ng-click="state.showAdvancedOptions = false;">
|
||||
<i class="fa fa-minus space-right" aria-hidden="true"></i> Hide advanced options
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-if="state.showAdvancedOptions">
|
||||
<label for="container_ports" class="col-sm-1 control-label text-left">Port mapping</label>
|
||||
<div class="col-sm-11" style="margin-top: 5px;">
|
||||
<span class="label label-default interactive" ng-click="addPortBinding()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional port
|
||||
</span>
|
||||
</div>
|
||||
<!-- port-mapping-input-list -->
|
||||
<div class="col-sm-offset-1 col-sm-11 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="portBinding in formValues.ports" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">host</span>
|
||||
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80 (optional)">
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">container</span>
|
||||
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80">
|
||||
</div>
|
||||
<div class="input-group col-sm-1 input-group-sm">
|
||||
<select class="selectpicker form-control" ng-model="portBinding.protocol">
|
||||
<option value="tcp">tcp</option>
|
||||
<option value="udp">udp</option>
|
||||
</select>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="removePortBinding($index)">
|
||||
<i class="fa fa-minus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !port-mapping-input-list -->
|
||||
</div>
|
||||
<!-- !port-mapping -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-default btn-sm" ng-disabled="!formValues.network" ng-click="createTemplate()">Create</button>
|
||||
@@ -63,7 +107,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="selectedTemplate">
|
||||
<div class="row" ng-if="state.selectedTemplate">
|
||||
|
||||
</div>
|
||||
|
||||
@@ -77,11 +121,14 @@
|
||||
</rd-widget-header>
|
||||
<rd-widget-body classes="padding">
|
||||
<div class="template-list">
|
||||
<div ng-repeat="tpl in templates" class="container-template hvr-grow" id="template_{{ $index }}" ng-click="selectTemplate($index)">
|
||||
<div ng-repeat="tpl in templates" class="container-template hvr-underline-from-center" id="template_{{ $index }}" ng-click="selectTemplate($index)">
|
||||
<img class="logo" ng-src="{{ tpl.logo }}" />
|
||||
<div class="title">{{ tpl.title }}</div>
|
||||
<div class="description">{{ tpl.description }}</div>
|
||||
</div>
|
||||
<div ng-if="!templates" class="text-center text-muted">
|
||||
Loading...
|
||||
</div>
|
||||
<div ng-if="templates.length == 0" class="text-center text-muted">
|
||||
No templates available.
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
angular.module('templates', [])
|
||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$filter', 'Config', 'Info', 'Container', 'ContainerHelper', 'Image', 'Volume', 'Network', 'Templates', 'Messages',
|
||||
function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper, Image, Volume, Network, Templates, Messages) {
|
||||
$scope.templates = [];
|
||||
$scope.selectedTemplate = null;
|
||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$filter', 'Config', 'Info', 'Container', 'ContainerHelper', 'Image', 'Volume', 'Network', 'Templates', 'TemplateHelper', 'Messages',
|
||||
function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper, Image, Volume, Network, Templates, TemplateHelper, Messages) {
|
||||
$scope.state = {
|
||||
selectedTemplate: null,
|
||||
showAdvancedOptions: false
|
||||
};
|
||||
$scope.formValues = {
|
||||
network: "",
|
||||
name: ""
|
||||
name: "",
|
||||
ports: []
|
||||
};
|
||||
$scope.templates = [];
|
||||
|
||||
var selectedItem = -1;
|
||||
|
||||
$scope.addPortBinding = function() {
|
||||
$scope.formValues.ports.push({ hostPort: '', containerPort: '', protocol: 'tcp' });
|
||||
};
|
||||
|
||||
$scope.removePortBinding = function(index) {
|
||||
$scope.formValues.ports.splice(index, 1);
|
||||
};
|
||||
|
||||
// TODO: centralize, already present in createContainerController
|
||||
function createContainer(config) {
|
||||
Container.create(config, function (d) {
|
||||
@@ -76,6 +86,26 @@ function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper,
|
||||
};
|
||||
}
|
||||
|
||||
function preparePortBindings(config, ports) {
|
||||
var bindings = {};
|
||||
ports.forEach(function (portBinding) {
|
||||
if (portBinding.containerPort) {
|
||||
var key = portBinding.containerPort + "/" + portBinding.protocol;
|
||||
var binding = {};
|
||||
if (portBinding.hostPort && portBinding.hostPort.indexOf(':') > -1) {
|
||||
var hostAndPort = portBinding.hostPort.split(':');
|
||||
binding.HostIp = hostAndPort[0];
|
||||
binding.HostPort = hostAndPort[1];
|
||||
} else {
|
||||
binding.HostPort = portBinding.hostPort;
|
||||
}
|
||||
bindings[key] = [binding];
|
||||
config.ExposedPorts[key] = {};
|
||||
}
|
||||
});
|
||||
config.HostConfig.PortBindings = bindings;
|
||||
}
|
||||
|
||||
function createConfigFromTemplate(template) {
|
||||
var containerConfig = getInitialConfiguration();
|
||||
containerConfig.Image = template.image;
|
||||
@@ -97,12 +127,7 @@ function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper,
|
||||
}
|
||||
});
|
||||
}
|
||||
if (template.ports) {
|
||||
template.ports.forEach(function (p) {
|
||||
containerConfig.ExposedPorts[p] = {};
|
||||
containerConfig.HostConfig.PortBindings[p] = [{ HostPort: ""}];
|
||||
});
|
||||
}
|
||||
preparePortBindings(containerConfig, $scope.formValues.ports);
|
||||
return containerConfig;
|
||||
}
|
||||
|
||||
@@ -130,7 +155,7 @@ function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper,
|
||||
|
||||
$scope.createTemplate = function() {
|
||||
$('#createContainerSpinner').show();
|
||||
var template = $scope.selectedTemplate;
|
||||
var template = $scope.state.selectedTemplate;
|
||||
var containerConfig = createConfigFromTemplate(template);
|
||||
var imageConfig = {
|
||||
fromImage: template.image.split(':')[0],
|
||||
@@ -146,11 +171,13 @@ function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper,
|
||||
$('#template_' + id).toggleClass("container-template--selected");
|
||||
if (selectedItem === id) {
|
||||
selectedItem = -1;
|
||||
$scope.selectedTemplate = null;
|
||||
$scope.state.selectedTemplate = null;
|
||||
} else {
|
||||
$('#template_' + selectedItem).toggleClass("container-template--selected");
|
||||
selectedItem = id;
|
||||
$scope.selectedTemplate = $scope.templates[id];
|
||||
var selectedTemplate = $scope.templates[id];
|
||||
$scope.state.selectedTemplate = selectedTemplate;
|
||||
$scope.formValues.ports = selectedTemplate.ports ? TemplateHelper.getPortBindings(selectedTemplate.ports) : [];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -161,6 +188,7 @@ function ($scope, $q, $state, $filter, Config, Info, Container, ContainerHelper,
|
||||
}, function (e) {
|
||||
$('#loadTemplatesSpinner').hide();
|
||||
Messages.error("Failure", e, "Unable to retrieve apps list");
|
||||
$scope.templates = [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</rd-widget-header>
|
||||
<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"><i class="fa fa-trash btn-ico" aria-hidden="true"></i>Remove</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
<a class="btn btn-default" type="button" ui-sref="actions.create.volume">Add volume</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
@@ -59,6 +59,9 @@
|
||||
<td>{{ volume.Driver }}</td>
|
||||
<td>{{ volume.Mountpoint }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!volumes">
|
||||
<td colspan="4" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="volumes.length == 0">
|
||||
<td colspan="4" class="text-center text-muted">No volumes available.</td>
|
||||
</tr>
|
||||
|
||||
@@ -5,8 +5,6 @@ function ($scope, $state, Volume, Messages) {
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = true;
|
||||
$scope.volumes = [];
|
||||
|
||||
$scope.config = {
|
||||
Name: ''
|
||||
};
|
||||
@@ -61,6 +59,7 @@ function ($scope, $state, Volume, Messages) {
|
||||
}, function (e) {
|
||||
$('#loadVolumesSpinner').hide();
|
||||
Messages.error("Failure", e, "Unable to retrieve volumes");
|
||||
$scope.volumes = [];
|
||||
});
|
||||
}
|
||||
fetchVolumes();
|
||||
|
||||
@@ -8,7 +8,7 @@ angular
|
||||
icon: '='
|
||||
},
|
||||
transclude: true,
|
||||
template: '<div class="widget-header"><div class="row"><span class="pull-left"><img class="custom-header-ico" ng-src="{{icon}}"></img> <span class="small text-muted"> {{title}} </span> </span><span class="pull-right col-xs-6 col-sm-4" ng-transclude></span></div></div>',
|
||||
template: '<div class="widget-header"><div class="row"><span class="pull-left"><img class="custom-header-ico" ng-src="{{icon}}"></img> <span class="text-muted"> {{title}} </span> </span><span class="pull-right col-xs-6 col-sm-4" ng-transclude></span></div></div>',
|
||||
restrict: 'E'
|
||||
};
|
||||
return directive;
|
||||
|
||||
@@ -213,4 +213,13 @@ angular.module('portainer.filters', [])
|
||||
return function (ip) {
|
||||
return ip.slice(0, ip.indexOf('/'));
|
||||
};
|
||||
})
|
||||
.filter('arraytostr', function () {
|
||||
'use strict';
|
||||
return function (arr, separator) {
|
||||
if (arr) {
|
||||
return _.join(arr, separator);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
});
|
||||
|
||||
@@ -48,4 +48,43 @@ angular.module('portainer.helpers', [])
|
||||
};
|
||||
}
|
||||
};
|
||||
}])
|
||||
.factory('TemplateHelper', [function TemplateHelperFactory() {
|
||||
'use strict';
|
||||
return {
|
||||
getPortBindings: function(ports) {
|
||||
var bindings = [];
|
||||
ports.forEach(function (port) {
|
||||
var portAndProtocol = _.split(port, '/');
|
||||
var binding = {
|
||||
containerPort: portAndProtocol[0],
|
||||
protocol: portAndProtocol[1]
|
||||
};
|
||||
bindings.push(binding);
|
||||
});
|
||||
return bindings;
|
||||
},
|
||||
//Not used atm, may prove useful later
|
||||
getVolumeBindings: function(volumes) {
|
||||
var bindings = [];
|
||||
volumes.forEach(function (volume) {
|
||||
bindings.push({ containerPath: volume });
|
||||
});
|
||||
return bindings;
|
||||
},
|
||||
//Not used atm, may prove useful later
|
||||
getEnvBindings: function(env) {
|
||||
var bindings = [];
|
||||
env.forEach(function (envvar) {
|
||||
var binding = {
|
||||
name: envvar.name
|
||||
};
|
||||
if (envvar.set) {
|
||||
binding.value = envvar.set;
|
||||
}
|
||||
bindings.push(binding);
|
||||
});
|
||||
return bindings;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -35,8 +35,8 @@ function ServiceViewModel(data) {
|
||||
} else {
|
||||
this.Mode = 'global';
|
||||
}
|
||||
if (data.Spec.Labels) {
|
||||
this.Labels = data.Spec.Labels;
|
||||
if (data.Spec.TaskTemplate.ContainerSpec) {
|
||||
this.Labels = data.Spec.TaskTemplate.ContainerSpec.Labels;
|
||||
}
|
||||
if (data.Spec.TaskTemplate.ContainerSpec.Env) {
|
||||
this.Env = data.Spec.TaskTemplate.ContainerSpec.Env;
|
||||
@@ -54,7 +54,7 @@ function ContainerViewModel(data) {
|
||||
this.Status = data.Status;
|
||||
this.Names = data.Names;
|
||||
// Unavailable in Docker < 1.10
|
||||
if (data.NetworkSettings) {
|
||||
if (data.NetworkSettings && !_.isEmpty(data.NetworkSettings.Networks)) {
|
||||
this.IP = data.NetworkSettings.Networks[Object.keys(data.NetworkSettings.Networks)[0]].IPAddress;
|
||||
}
|
||||
this.Image = data.Image;
|
||||
|
||||
@@ -1,98 +1,7 @@
|
||||
.container > hr {
|
||||
margin: 60px 0;
|
||||
}
|
||||
|
||||
.jumbotron {
|
||||
margin: 80px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jumbotron h1 {
|
||||
font-size: 100px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.jumbotron .lead {
|
||||
font-size: 24px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.jumbotron .btn {
|
||||
padding: 14px 24px;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.marketing {
|
||||
margin: 60px 0;
|
||||
}
|
||||
|
||||
.marketing p + h4 {
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.masthead .nav {
|
||||
margin: 0;
|
||||
margin: 0 0 2em 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.masthead .nav.well {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.masthead .nav li {
|
||||
display: table-cell;
|
||||
float: none;
|
||||
width: 1%;
|
||||
}
|
||||
|
||||
.masthead .nav li a {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
border-right: 1px solid rgba(0,0,0,.1);
|
||||
border-left: 1px solid rgba(255,255,255,.75);
|
||||
}
|
||||
|
||||
.masthead .nav li:first-child a {
|
||||
border-left: 0;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
.masthead .nav li:last-child a {
|
||||
border-right: 0;
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
.btn-group button {
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.detail {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.center {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.btn-remove {
|
||||
max-width: 70%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container-bottom {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.well {
|
||||
padding: 10px 15px 0 15px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
max-height: 50px;
|
||||
overflow-x: hidden;
|
||||
@@ -106,14 +15,6 @@
|
||||
border-width: 0 0 0 1em;
|
||||
}
|
||||
|
||||
.inline-four .form-control {
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: inline;
|
||||
width: 100%;
|
||||
@@ -162,11 +63,7 @@ input[type="radio"] {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-icon {
|
||||
.space-right {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@@ -182,16 +79,17 @@ input[type="radio"] {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.image-tag {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.label.tag {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.widget .widget-body table tbody .image-tag {
|
||||
font-size: 90% !important;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.widget .widget-body table tbody .fit-text-size {
|
||||
font-size: 90% !important;
|
||||
}
|
||||
|
||||
.nopadding {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
@@ -203,30 +101,73 @@ input[type="radio"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-group {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.btn-ico {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.custom-header-ico {
|
||||
max-width: 16px;
|
||||
max-height: 16px;
|
||||
max-width: 32px;
|
||||
max-height: 32px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.btn-responsive {
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1107px) {
|
||||
.btn-responsive {
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.42857143;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Underline From Center */
|
||||
.hvr-underline-from-center {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
-webkit-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
box-shadow: 0 0 1px rgba(0, 0, 0, 0);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hvr-underline-from-center:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
bottom: 0;
|
||||
background: #85898b;
|
||||
height: 2px;
|
||||
-webkit-transition-property: left, right;
|
||||
transition-property: left, right;
|
||||
-webkit-transition-duration: 0.3s;
|
||||
transition-duration: 0.3s;
|
||||
-webkit-transition-timing-function: ease-out;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
.hvr-underline-from-center:hover:before, .hvr-underline-from-center:focus:before, .hvr-underline-from-center:active:before {
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.container-template {
|
||||
font-size: 1em;
|
||||
width: 256px;
|
||||
height: 128px;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
margin: 15px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@@ -259,20 +200,5 @@ input[type="radio"] {
|
||||
.container-template .description {
|
||||
text-align: center;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.btn-responsive {
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1107px) {
|
||||
.btn-responsive {
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.42857143;
|
||||
border-radius: 4px;
|
||||
}
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "portainer",
|
||||
"version": "1.9.2",
|
||||
"version": "1.10.0",
|
||||
"homepage": "https://github.com/portainer/portainer",
|
||||
"authors": [
|
||||
"Anthony Lapenna <anthony.lapenna at gmail dot com>"
|
||||
@@ -35,7 +35,6 @@
|
||||
"bootstrap": "~3.3.6",
|
||||
"font-awesome": "~4.6.3",
|
||||
"filesize": "~3.3.0",
|
||||
"Hover": "2.0.2",
|
||||
"jquery": "1.11.1",
|
||||
"jquery.gritter": "1.7.4",
|
||||
"lodash": "4.12.0",
|
||||
|
||||
26
build.sh
26
build.sh
@@ -8,9 +8,27 @@ if [[ $# -ne 1 ]] ; then
|
||||
fi
|
||||
|
||||
grunt release
|
||||
rm -rf /tmp/portainer-build && mkdir -pv /tmp/portainer-build/portainer
|
||||
mv dist/* /tmp/portainer-build/portainer
|
||||
cd /tmp/portainer-build
|
||||
tar cvpfz portainer-${VERSION}.tar.gz portainer
|
||||
rm -rf /tmp/portainer-build-unix && mkdir -pv /tmp/portainer-build-unix/portainer
|
||||
mv dist/* /tmp/portainer-build-unix/portainer
|
||||
cd /tmp/portainer-build-unix && tar cvpfz portainer-${VERSION}-linux-amd64.tar.gz portainer
|
||||
cd -
|
||||
|
||||
grunt release-win
|
||||
rm -rf /tmp/portainer-build-win && mkdir -pv /tmp/portainer-build-win/portainer
|
||||
mv dist/* /tmp/portainer-build-win/portainer
|
||||
cd /tmp/portainer-build-win
|
||||
tar cvpfz portainer-${VERSION}-windows-amd64.tar.gz portainer
|
||||
|
||||
grunt release-arm
|
||||
rm -rf /tmp/portainer-build-arm && mkdir -pv /tmp/portainer-build-arm/portainer
|
||||
mv dist/* /tmp/portainer-build-arm/portainer
|
||||
cd /tmp/portainer-build-arm
|
||||
tar cvpfz portainer-${VERSION}-linux-arm.tar.gz portainer
|
||||
|
||||
grunt release-macos
|
||||
rm -rf /tmp/portainer-build-darwin && mkdir -pv /tmp/portainer-build-darwin/portainer
|
||||
mv dist/* /tmp/portainer-build-darwin/portainer
|
||||
cd /tmp/portainer-build-darwin
|
||||
tar cvpfz portainer-${VERSION}-darwin-amd64.tar.gz portainer
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -4,6 +4,8 @@ COPY dist /
|
||||
|
||||
VOLUME /data
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/portainer"]
|
||||
9
build/windows/microsoftservercore/Dockerfile
Normal file
9
build/windows/microsoftservercore/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM microsoft/windowsservercore
|
||||
|
||||
COPY dist /
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/portainer.exe"]
|
||||
9
build/windows/nanoserver/Dockerfile
Normal file
9
build/windows/nanoserver/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM microsoft/nanoserver
|
||||
|
||||
COPY dist /
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/portainer.exe"]
|
||||
113
gruntFile.js
113
gruntFile.js
@@ -16,7 +16,7 @@ module.exports = function (grunt) {
|
||||
grunt.registerTask('default', ['jshint', 'build', 'karma:unit']);
|
||||
grunt.registerTask('build', [
|
||||
'clean:app',
|
||||
'if:binaryNotExist',
|
||||
'if:unixBinaryNotExist',
|
||||
'html2js',
|
||||
'concat',
|
||||
'clean:tmpl',
|
||||
@@ -25,7 +25,43 @@ module.exports = function (grunt) {
|
||||
]);
|
||||
grunt.registerTask('release', [
|
||||
'clean:all',
|
||||
'if:binaryNotExist',
|
||||
'if:unixBinaryNotExist',
|
||||
'html2js',
|
||||
'uglify',
|
||||
'clean:tmpl',
|
||||
'jshint',
|
||||
//'karma:unit',
|
||||
'concat:index',
|
||||
'recess:min',
|
||||
'copy'
|
||||
]);
|
||||
grunt.registerTask('release-win', [
|
||||
'clean:all',
|
||||
'if:windowsBinaryNotExist',
|
||||
'html2js',
|
||||
'uglify',
|
||||
'clean:tmpl',
|
||||
'jshint',
|
||||
//'karma:unit',
|
||||
'concat:index',
|
||||
'recess:min',
|
||||
'copy'
|
||||
]);
|
||||
grunt.registerTask('release-arm', [
|
||||
'clean:all',
|
||||
'if:unixArmBinaryNotExist',
|
||||
'html2js',
|
||||
'uglify',
|
||||
'clean:tmpl',
|
||||
'jshint',
|
||||
//'karma:unit',
|
||||
'concat:index',
|
||||
'recess:min',
|
||||
'copy'
|
||||
]);
|
||||
grunt.registerTask('release-macos', [
|
||||
'clean:all',
|
||||
'if:darwinBinaryNotExist',
|
||||
'html2js',
|
||||
'uglify',
|
||||
'clean:tmpl',
|
||||
@@ -37,10 +73,11 @@ module.exports = function (grunt) {
|
||||
]);
|
||||
grunt.registerTask('lint', ['jshint']);
|
||||
grunt.registerTask('test-watch', ['karma:watch']);
|
||||
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-dev', ['if:binaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']);
|
||||
grunt.registerTask('run-ssl', ['if:binaryNotExist', 'shell:buildImage', 'shell:runSsl', 'watch:buildSsl']);
|
||||
grunt.registerTask('run', ['if:unixBinaryNotExist', 'build', 'shell:buildImage', 'shell:run']);
|
||||
grunt.registerTask('run-swarm', ['if:unixBinaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarm', 'watch:buildSwarm']);
|
||||
grunt.registerTask('run-swarm-local', ['if:unixBinaryNotExist', 'build', 'shell:buildImage', 'shell:runSwarmLocal', 'watch:buildSwarm']);
|
||||
grunt.registerTask('run-dev', ['if:unixBinaryNotExist', 'shell:buildImage', 'shell:run', 'watch:build']);
|
||||
grunt.registerTask('run-ssl', ['if:unixBinaryNotExist', 'shell:buildImage', 'shell:runSsl', 'watch:buildSsl']);
|
||||
grunt.registerTask('clear', ['clean:app']);
|
||||
|
||||
// Print a timestamp (useful for when watching)
|
||||
@@ -88,8 +125,7 @@ module.exports = function (grunt) {
|
||||
'bower_components/font-awesome/css/font-awesome.min.css',
|
||||
'bower_components/rdash-ui/dist/css/rdash.min.css',
|
||||
'bower_components/angular-ui-select/dist/select.min.css',
|
||||
'bower_components/xterm.js/dist/xterm.css',
|
||||
'bower_components/Hover/css/hover-min.css'
|
||||
'bower_components/xterm.js/dist/xterm.css'
|
||||
]
|
||||
},
|
||||
clean: {
|
||||
@@ -254,7 +290,7 @@ module.exports = function (grunt) {
|
||||
},
|
||||
shell: {
|
||||
buildImage: {
|
||||
command: 'docker build --rm -t portainer .'
|
||||
command: 'docker build --rm -t portainer -f build/linux/Dockerfile .'
|
||||
},
|
||||
buildBinary: {
|
||||
command: [
|
||||
@@ -264,6 +300,30 @@ module.exports = function (grunt) {
|
||||
'mv api/portainer dist/'
|
||||
].join(' && ')
|
||||
},
|
||||
buildUnixArmBinary: {
|
||||
command: [
|
||||
'docker run --rm -v $(pwd)/api:/src -e BUILD_GOOS="linux" -e BUILD_GOARCH="arm" centurylink/golang-builder-cross',
|
||||
'shasum api/portainer-linux-arm > portainer-checksum.txt',
|
||||
'mkdir -p dist',
|
||||
'mv api/portainer-linux-arm dist/portainer'
|
||||
].join(' && ')
|
||||
},
|
||||
buildDarwinBinary: {
|
||||
command: [
|
||||
'docker run --rm -v $(pwd)/api:/src -e BUILD_GOOS="darwin" -e BUILD_GOARCH="amd64" centurylink/golang-builder-cross',
|
||||
'shasum api/portainer-darwin-amd64 > portainer-checksum.txt',
|
||||
'mkdir -p dist',
|
||||
'mv api/portainer-darwin-amd64 dist/portainer'
|
||||
].join(' && ')
|
||||
},
|
||||
buildWindowsBinary: {
|
||||
command: [
|
||||
'docker run --rm -v $(pwd)/api:/src -e BUILD_GOOS="windows" -e BUILD_GOARCH="amd64" centurylink/golang-builder-cross',
|
||||
'shasum api/portainer-windows-amd64 > portainer-checksum.txt',
|
||||
'mkdir -p dist',
|
||||
'mv api/portainer-windows-amd64 dist/portainer.exe'
|
||||
].join(' && ')
|
||||
},
|
||||
run: {
|
||||
command: [
|
||||
'docker stop portainer',
|
||||
@@ -278,6 +338,13 @@ module.exports = function (grunt) {
|
||||
'docker run -d -p 9000:9000 -v /tmp/portainer:/data --name portainer portainer -H tcp://10.0.7.10:2375 --swarm -d /data'
|
||||
].join(';')
|
||||
},
|
||||
runSwarmLocal: {
|
||||
command: [
|
||||
'docker stop portainer',
|
||||
'docker rm portainer',
|
||||
'docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --name portainer portainer --swarm'
|
||||
].join(';')
|
||||
},
|
||||
runSsl: {
|
||||
command: [
|
||||
'docker stop portainer',
|
||||
@@ -290,11 +357,29 @@ module.exports = function (grunt) {
|
||||
}
|
||||
},
|
||||
'if': {
|
||||
binaryNotExist: {
|
||||
options: {
|
||||
executable: 'dist/portainer'
|
||||
},
|
||||
ifFalse: ['shell:buildBinary']
|
||||
unixBinaryNotExist: {
|
||||
options: {
|
||||
executable: 'dist/portainer'
|
||||
},
|
||||
ifFalse: ['shell:buildBinary']
|
||||
},
|
||||
unixArmBinaryNotExist: {
|
||||
options: {
|
||||
executable: 'dist/portainer'
|
||||
},
|
||||
ifFalse: ['shell:buildUnixArmBinary']
|
||||
},
|
||||
darwinBinaryNotExist: {
|
||||
options: {
|
||||
executable: 'dist/portainer'
|
||||
},
|
||||
ifFalse: ['shell:buildDarwinBinary']
|
||||
},
|
||||
windowsBinaryNotExist: {
|
||||
options: {
|
||||
executable: 'dist/portainer.exe'
|
||||
},
|
||||
ifFalse: ['shell:buildWindowsBinary']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
<ul class="sidebar">
|
||||
<li class="sidebar-main">
|
||||
<a ng-click="toggleSidebar()">
|
||||
<img ng-if="config.logo" ng-src="{{ config.logo }}" class="img-responsive logo">
|
||||
<img ng-if="!config.logo" src="images/logo.png" class="img-responsive logo" alt="Portainer">
|
||||
<img ng-if="logo" ng-src="{{ logo }}" class="img-responsive logo">
|
||||
<img ng-if="!logo" src="images/logo.png" class="img-responsive logo" alt="Portainer">
|
||||
<span class="menu-icon glyphicon glyphicon-transfer"></span>
|
||||
</a>
|
||||
</li>
|
||||
@@ -66,7 +66,7 @@
|
||||
<a ui-sref="swarm">Swarm <span class="menu-icon fa fa-object-group"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="!swarm">
|
||||
<a ui-sref="docker">Docker <span class="menu-icon fa fa-cogs"></span></a>
|
||||
<a ui-sref="docker">Docker <span class="menu-icon fa fa-th"></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Portainer.io",
|
||||
"name": "portainer",
|
||||
"homepage": "http://portainer.io",
|
||||
"version": "1.9.2",
|
||||
"version": "1.10.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
|
||||
Reference in New Issue
Block a user