commit
8c3f7b3ec2
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)
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
**_Portainer_** is a lightweight management UI which allows you to **easily** manage your Docker host or Swarm cluster.
|
||||
|
||||
**_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 for Linux engine. A Docker for Windows version is on its way !
|
||||
**_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).
|
||||
|
||||
**_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*.
|
||||
|
||||
|
|
|
@ -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.3")
|
||||
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.3');
|
||||
.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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -102,7 +102,6 @@ function ($scope, $state, Config, Image, Messages) {
|
|||
}
|
||||
|
||||
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" />
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -1,14 +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.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: []
|
||||
};
|
||||
|
||||
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) {
|
||||
|
@ -74,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;
|
||||
|
@ -95,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;
|
||||
}
|
||||
|
||||
|
@ -128,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],
|
||||
|
@ -144,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) : [];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,14 +101,6 @@ input[type="radio"] {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-group {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.btn-ico {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "portainer",
|
||||
"version": "1.9.3",
|
||||
"version": "1.10.0",
|
||||
"homepage": "https://github.com/portainer/portainer",
|
||||
"authors": [
|
||||
"Anthony Lapenna <anthony.lapenna at gmail dot com>"
|
||||
|
|
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"]
|
|
@ -0,0 +1,9 @@
|
|||
FROM microsoft/windowsservercore
|
||||
|
||||
COPY dist /
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/portainer.exe"]
|
|
@ -0,0 +1,9 @@
|
|||
FROM microsoft/nanoserver
|
||||
|
||||
COPY dist /
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/portainer.exe"]
|
110
gruntFile.js
110
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)
|
||||
|
@ -253,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: [
|
||||
|
@ -263,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',
|
||||
|
@ -277,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',
|
||||
|
@ -289,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.3",
|
||||
"version": "1.10.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
|
|
Loading…
Reference in New Issue