feat(state): introduce endpoint state (#529)

pull/531/head
Anthony Lapenna 2017-01-23 12:14:34 +13:00 committed by GitHub
parent 7ebe4af77d
commit fa4ec04c47
26 changed files with 267 additions and 122 deletions

View File

@ -160,3 +160,15 @@ func (service *EndpointService) SetActive(endpoint *portainer.Endpoint) error {
return nil
})
}
// DeleteActive deletes the active endpoint.
func (service *EndpointService) DeleteActive() error {
return service.store.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(activeEndpointBucketName))
err := bucket.Delete(internal.Itob(activeEndpointID))
if err != nil {
return err
}
return nil
})
}

View File

@ -260,6 +260,7 @@ type putEndpointsRequest struct {
}
// handleDeleteEndpoint handles DELETE requests on /endpoints/:id
// DELETE /endpoints/0 deletes the active endpoint
func (handler *EndpointHandler) handleDeleteEndpoint(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
@ -270,7 +271,14 @@ func (handler *EndpointHandler) handleDeleteEndpoint(w http.ResponseWriter, r *h
return
}
endpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(endpointID))
var endpoint *portainer.Endpoint
if id == "0" {
endpoint, err = handler.EndpointService.GetActive()
endpointID = int(endpoint.ID)
} else {
endpoint, err = handler.EndpointService.Endpoint(portainer.EndpointID(endpointID))
}
if err == portainer.ErrEndpointNotFound {
Error(w, err, http.StatusNotFound, handler.Logger)
return
@ -284,6 +292,13 @@ func (handler *EndpointHandler) handleDeleteEndpoint(w http.ResponseWriter, r *h
Error(w, err, http.StatusInternalServerError, handler.Logger)
return
}
if id == "0" {
err = handler.EndpointService.DeleteActive()
if err != nil {
Error(w, err, http.StatusInternalServerError, handler.Logger)
return
}
}
if endpoint.TLS {
err = handler.FileService.DeleteTLSFiles(portainer.EndpointID(endpointID))

View File

@ -94,6 +94,7 @@ type (
DeleteEndpoint(ID EndpointID) error
GetActive() (*Endpoint, error)
SetActive(endpoint *Endpoint) error
DeleteActive() error
}
// CryptoService represents a service for encrypting/hashing data.

View File

@ -49,8 +49,8 @@ angular.module('portainer', [
.setPrefix('portainer');
jwtOptionsProvider.config({
tokenGetter: ['localStorageService', function(localStorageService) {
return localStorageService.get('JWT');
tokenGetter: ['LocalStorage', function(LocalStorage) {
return LocalStorage.getJWT();
}],
unauthenticatedRedirector: ['$state', function($state) {
$state.go('auth', {error: 'Your session has expired'});
@ -528,21 +528,18 @@ angular.module('portainer', [
};
});
}])
.run(['$rootScope', '$state', 'Authentication', 'authManager', 'EndpointMode', function ($rootScope, $state, Authentication, authManager, EndpointMode) {
.run(['$rootScope', '$state', 'Authentication', 'authManager', 'StateManager', function ($rootScope, $state, Authentication, authManager, StateManager) {
authManager.checkAuthOnRefresh();
authManager.redirectWhenUnauthenticated();
Authentication.init();
StateManager.init();
$rootScope.$state = $state;
$rootScope.$on('tokenHasExpired', function($state) {
$state.go('auth', {error: 'Your session has expired'});
});
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {
if (toState.name !== 'endpointInit' && (fromState.name === 'auth' || fromState.name === '' || fromState.name === 'endpointInit') && Authentication.isAuthenticated()) {
EndpointMode.determineEndpointMode();
}
});
}])
// This is your docker url that the api will use to make requests
// You need to set this to the api endpoint without the port i.e. http://192.168.1.9

View File

@ -1,6 +1,6 @@
angular.module('auth', [])
.controller('AuthenticationController', ['$scope', '$state', '$stateParams', '$window', '$timeout', '$sanitize', 'Config', 'Authentication', 'Users', 'EndpointService', 'Messages',
function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Config, Authentication, Users, EndpointService, Messages) {
.controller('AuthenticationController', ['$scope', '$state', '$stateParams', '$window', '$timeout', '$sanitize', 'Config', 'Authentication', 'Users', 'EndpointService', 'StateManager', 'Messages',
function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Config, Authentication, Users, EndpointService, StateManager, Messages) {
$scope.authData = {
username: 'admin',
@ -60,7 +60,12 @@ function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Config, Au
var password = $sanitize($scope.authData.password);
Authentication.login(username, password).then(function success() {
EndpointService.getActive().then(function success(data) {
$state.go('dashboard');
StateManager.updateEndpointState(true)
.then(function success() {
$state.go('dashboard');
}, function error(err) {
Messages.error("Failure", err, 'Unable to connect to the Docker endpoint');
});
}, function error(err) {
if (err.status === 404) {
$state.go('endpointInit');

View File

@ -68,7 +68,7 @@
<span ng-show="sortType == 'IP' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th ng-if="endpointMode.provider === 'DOCKER_SWARM'">
<th ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<a ui-sref="containers" ng-click="order('Host')">
Host IP
<span ng-show="sortType == 'Host' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
@ -88,11 +88,11 @@
<tr dir-paginate="container in (state.filteredContainers = ( containers | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: pagination_count))">
<td><input type="checkbox" ng-model="container.Checked" ng-change="selectItem(container)"/></td>
<td><span class="label label-{{ container.Status|containerstatusbadge }}">{{ container.Status }}</span></td>
<td ng-if="endpointMode.provider === 'DOCKER_SWARM'"><a ui-sref="container({id: container.Id})">{{ container|swarmcontainername}}</a></td>
<td ng-if="endpointMode.provider !== 'DOCKER_SWARM'"><a ui-sref="container({id: container.Id})">{{ container|containername}}</a></td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'"><a ui-sref="container({id: container.Id})">{{ container|swarmcontainername}}</a></td>
<td ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM'"><a ui-sref="container({id: container.Id})">{{ container|containername}}</a></td>
<td><a ui-sref="image({id: container.Image})">{{ container.Image }}</a></td>
<td ng-if="state.displayIP">{{ container.IP ? container.IP : '-' }}</td>
<td ng-if="endpointMode.provider === 'DOCKER_SWARM'">{{ container.hostIP }}</td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">{{ container.hostIP }}</td>
<td>
<a ng-if="container.Ports.length > 0" ng-repeat="p in container.Ports" class="image-tag" ng-href="http://{{p.host}}:{{p.public}}" target="_blank">
<i class="fa fa-external-link" aria-hidden="true"></i> {{p.public}}:{{ p.private }}

View File

@ -28,7 +28,7 @@ function ($scope, $filter, Container, ContainerHelper, Info, Settings, Messages,
if (model.IP) {
$scope.state.displayIP = true;
}
if ($scope.endpointMode.provider === 'DOCKER_SWARM') {
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM') {
model.hostIP = $scope.swarm_hosts[_.split(container.Names[0], '/')[1]];
}
return model;
@ -161,7 +161,7 @@ function ($scope, $filter, Container, ContainerHelper, Info, Settings, Messages,
Config.$promise.then(function (c) {
$scope.containersToHideLabels = c.hiddenLabels;
if ($scope.endpointMode.provider === 'DOCKER_SWARM') {
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM') {
Info.get({}, function (d) {
$scope.swarm_hosts = retrieveSwarmHostsInfo(d);
update({all: Settings.displayAll ? 1 : 0});

View File

@ -72,7 +72,7 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
Network.query({}, function (d) {
var networks = d;
if ($scope.endpointMode.provider === 'DOCKER_SWARM' || $scope.endpointMode.provider === 'DOCKER_SWARM_MODE') {
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
networks = d.filter(function (network) {
if (network.Scope === 'global') {
return network;
@ -220,7 +220,7 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
var containerName = container;
if (container && typeof container === 'object') {
containerName = $filter('trimcontainername')(container.Names[0]);
if ($scope.endpointMode.provider === 'DOCKER_SWARM') {
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM') {
containerName = $filter('swarmcontainername')(container);
}
}

View File

@ -269,7 +269,7 @@
<!-- tab-network -->
<div class="tab-pane" id="network">
<form class="form-horizontal" style="margin-top: 15px;">
<div class="form-group" ng-if="globalNetworkCount === 0 && endpointMode.provider !== 'DOCKER_SWARM_MODE'">
<div class="form-group" ng-if="globalNetworkCount === 0 && applicationState.endpoint.mode.provider !== 'DOCKER_SWARM_MODE'">
<div class="col-sm-12">
<span class="small text-muted">You don't have any shared network. Head over the <a ui-sref="networks">networks view</a> to create one.</span>
</div>
@ -289,10 +289,10 @@
<div class="form-group" ng-if="config.HostConfig.NetworkMode == 'container'">
<label for="container_network_container" class="col-sm-1 control-label text-left">Container</label>
<div class="col-sm-9">
<select ng-if="endpointMode.provider !== 'DOCKER_SWARM'" ng-options="container|containername for container in runningContainers" class="form-control" ng-model="formValues.NetworkContainer">
<select ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM'" ng-options="container|containername for container in runningContainers" class="form-control" ng-model="formValues.NetworkContainer">
<option selected disabled hidden value="">Select a container</option>
</select>
<select ng-if="endpointMode.provider === 'DOCKER_SWARM'" ng-options="container|swarmcontainername for container in runningContainers" class="form-control" ng-model="formValues.NetworkContainer">
<select ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'" ng-options="container|swarmcontainername for container in runningContainers" class="form-control" ng-model="formValues.NetworkContainer">
<option selected disabled hidden value="">Select a container</option>
</select>
</div>

View File

@ -6,7 +6,7 @@
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="endpointMode.provider !== 'DOCKER_SWARM'">
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM'">
<rd-widget>
<rd-widget-header icon="fa-tachometer" title="Node info"></rd-widget-header>
<rd-widget-body classes="no-padding">
@ -33,7 +33,7 @@
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="endpointMode.provider === 'DOCKER_SWARM'">
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<rd-widget>
<rd-widget-header icon="fa-tachometer" title="Cluster info"></rd-widget-header>
<rd-widget-body classes="no-padding">
@ -60,7 +60,7 @@
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<rd-widget>
<rd-widget-header icon="fa-tachometer" title="Swarm info"></rd-widget-header>
<rd-widget-body classes="no-padding">

View File

@ -21,10 +21,10 @@
<!-- endpoin-type radio -->
<div class="form-group">
<div class="radio">
<label><input type="radio" name="endpointType" value="local" ng-model="formValues.endpointType">Manage the Docker instance where Portainer is running</label>
<label><input type="radio" name="endpointType" value="local" ng-model="formValues.endpointType" ng-click="cleanError()">Manage the Docker instance where Portainer is running</label>
</div>
<div class="radio">
<label><input type="radio" name="endpointType" value="remote" ng-model="formValues.endpointType">Manage a remote Docker instance</label>
<label><input type="radio" name="endpointType" value="remote" ng-model="formValues.endpointType" ng-click="cleanError()">Manage a remote Docker instance</label>
</div>
</div>
<!-- endpoint-type radio -->
@ -41,7 +41,10 @@
<p class="pull-left text-danger" ng-if="state.error" style="margin: 5px;">
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ state.error }}
</p>
<button type="submit" class="btn btn-primary pull-right" ng-click="createLocalEndpoint()"><i class="fa fa-plug" aria-hidden="true"></i> Connect</button>
<span class="pull-right">
<i id="initEndpointSpinner" class="fa fa-cog fa-spin" style="margin-right: 5px; display: none;"></i>
<button type="submit" class="btn btn-primary" ng-click="createLocalEndpoint()"><i class="fa fa-plug" aria-hidden="true"></i> Connect</button>
</span>
</div>
</div>
<!-- !connect button -->
@ -122,7 +125,10 @@
<p class="pull-left text-danger" ng-if="state.error" style="margin: 5px;">
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ state.error }}
</p>
<button type="submit" class="btn btn-primary pull-right" ng-disabled="!formValues.Name || !formValues.URL || (formValues.TLS && (!formValues.TLSCACert || !formValues.TLSCert || !formValues.TLSKey))" ng-click="createRemoteEndpoint()"><i class="fa fa-plug" aria-hidden="true"></i> Connect</button>
<span class="pull-right">
<i id="initEndpointSpinner" class="fa fa-cog fa-spin" style="margin-right: 5px; display: none;"></i>
<button type="submit" class="btn btn-primary" ng-disabled="!formValues.Name || !formValues.URL || (formValues.TLS && (!formValues.TLSCACert || !formValues.TLSCert || !formValues.TLSKey))" ng-click="createRemoteEndpoint()"><i class="fa fa-plug" aria-hidden="true"></i> Connect</button>
</span>
</div>
</div>
<!-- !connect button -->

View File

@ -1,6 +1,6 @@
angular.module('endpointInit', [])
.controller('EndpointInitController', ['$scope', '$state', 'EndpointService', 'Messages',
function ($scope, $state, EndpointService, Messages) {
.controller('EndpointInitController', ['$scope', '$state', 'EndpointService', 'StateManager', 'Messages',
function ($scope, $state, EndpointService, StateManager, Messages) {
$scope.state = {
error: '',
uploadInProgress: false
@ -15,27 +15,39 @@ function ($scope, $state, EndpointService, Messages) {
TLSKey: null
};
EndpointService.getActive().then(function success(data) {
if (!_.isEmpty($scope.applicationState.endpoint)) {
$state.go('dashboard');
}, function error(err) {
if (err.status !== 404) {
Messages.error("Failure", err, 'Unable to verify Docker endpoint existence');
}
});
}
$scope.cleanError = function() {
$scope.state.error = '';
};
$scope.createLocalEndpoint = function() {
$('#initEndpointSpinner').show();
$scope.state.error = '';
var name = "local";
var URL = "unix:///var/run/docker.sock";
var TLS = false;
EndpointService.createLocalEndpoint(name, URL, TLS, true).then(function success(data) {
$state.go('dashboard');
StateManager.updateEndpointState(false)
.then(function success() {
$state.go('dashboard');
}, function error(err) {
EndpointService.deleteEndpoint(0)
.then(function success() {
$('#initEndpointSpinner').hide();
$scope.state.error = 'Unable to connect to the Docker endpoint';
});
});
}, function error(err) {
$('#initEndpointSpinner').hide();
$scope.state.error = 'Unable to create endpoint';
});
};
$scope.createRemoteEndpoint = function() {
$('#initEndpointSpinner').show();
$scope.state.error = '';
var name = $scope.formValues.Name;
var URL = $scope.formValues.URL;
@ -43,9 +55,20 @@ function ($scope, $state, EndpointService, Messages) {
var TLSCAFile = $scope.formValues.TLSCACert;
var TLSCertFile = $scope.formValues.TLSCert;
var TLSKeyFile = $scope.formValues.TLSKey;
EndpointService.createRemoteEndpoint(name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, TLS ? false : true).then(function success(data) {
$state.go('dashboard');
EndpointService.createRemoteEndpoint(name, URL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, TLS ? false : true)
.then(function success(data) {
StateManager.updateEndpointState(false)
.then(function success() {
$state.go('dashboard');
}, function error(err) {
EndpointService.deleteEndpoint(0)
.then(function success() {
$('#initEndpointSpinner').hide();
$scope.state.error = 'Unable to connect to the Docker endpoint';
});
});
}, function error(err) {
$('#initEndpointSpinner').hide();
$scope.state.uploadInProgress = false;
$scope.state.error = err.msg;
}, function update(evt) {

View File

@ -1,6 +1,6 @@
angular.module('main', [])
.controller('MainController', ['$scope', '$cookieStore',
function ($scope, $cookieStore) {
.controller('MainController', ['$scope', '$cookieStore', 'StateManager',
function ($scope, $cookieStore, StateManager) {
/**
* Sidebar Toggle & Cookie Control
@ -10,6 +10,8 @@ function ($scope, $cookieStore) {
return window.innerWidth;
};
$scope.applicationState = StateManager.getState();
$scope.$watch($scope.getWidth, function(newValue, oldValue) {
if (newValue >= mobileView) {
if (angular.isDefined($cookieStore.get('toggle'))) {

View File

@ -23,12 +23,12 @@
</div>
<!-- !name-input -->
<!-- tag-note -->
<div class="form-group" ng-if="endpointMode.provider === 'DOCKER_SWARM' || endpointMode.provider === 'DOCKER_SWARM_MODE'">
<div class="form-group" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<div class="col-sm-12">
<span class="small text-muted">Note: The network will be created using the overlay driver and will allow containers to communicate across the hosts of your cluster.</span>
</div>
</div>
<div class="form-group" ng-if="endpointMode.provider === 'DOCKER_STANDALONE'">
<div class="form-group" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE'">
<div class="col-sm-12">
<span class="small text-muted">Note: The network will be created using the bridge driver.</span>
</div>

View File

@ -13,7 +13,7 @@ function ($scope, $state, Network, Config, Messages, Settings) {
function prepareNetworkConfiguration() {
var config = angular.copy($scope.config);
if ($scope.endpointMode.provider === 'DOCKER_SWARM' || $scope.endpointMode.provider === 'DOCKER_SWARM_MODE') {
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
config.Driver = 'overlay';
// Force IPAM Driver to 'default', should not be required.
// See: https://github.com/docker/docker/issues/25735

View File

@ -22,7 +22,7 @@
<li class="sidebar-list">
<a ui-sref="templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket"></span></a>
</li>
<li class="sidebar-list" ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<a ui-sref="services" ui-sref-active="active">Services <span class="menu-icon fa fa-list-alt"></span></a>
</li>
<li class="sidebar-list">
@ -37,13 +37,13 @@
<li class="sidebar-list">
<a ui-sref="volumes" ui-sref-active="active">Volumes <span class="menu-icon fa fa-cubes"></span></a>
</li>
<li class="sidebar-list" ng-if="endpointMode.provider === 'DOCKER_STANDALONE'">
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE'">
<a ui-sref="events" ui-sref-active="active">Events <span class="menu-icon fa fa-history"></span></a>
</li>
<li class="sidebar-list" ng-if="endpointMode.provider === 'DOCKER_SWARM' || (endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER')">
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || (applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER')">
<a ui-sref="swarm" ui-sref-active="active">Swarm <span class="menu-icon fa fa-object-group"></span></a>
</li>
<li class="sidebar-list" ng-if="endpointMode.provider === 'DOCKER_STANDALONE'">
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE'">
<a ui-sref="docker">Docker <span class="menu-icon fa fa-th"></span></a>
</li>
<li class="sidebar-title"><span>Portainer settings</span></li>

View File

@ -1,6 +1,6 @@
angular.module('sidebar', [])
.controller('SidebarController', ['$scope', '$state', 'Settings', 'Config', 'EndpointService', 'EndpointMode', 'Messages',
function ($scope, $state, Settings, Config, EndpointService, EndpointMode, Messages) {
.controller('SidebarController', ['$scope', '$state', 'Settings', 'Config', 'EndpointService', 'StateManager', 'Messages',
function ($scope, $state, Settings, Config, EndpointService, StateManager, Messages) {
Config.$promise.then(function (c) {
$scope.logo = c.logo;
@ -10,8 +10,12 @@ function ($scope, $state, Settings, Config, EndpointService, EndpointMode, Messa
$scope.switchEndpoint = function(endpoint) {
EndpointService.setActive(endpoint.Id).then(function success(data) {
EndpointMode.determineEndpointMode();
$state.reload();
StateManager.updateEndpointState(true)
.then(function success() {
$state.reload();
}, function error(err) {
Messages.error("Failure", err, "Unable to connect to the Docker endpoint");
});
}, function error(err) {
Messages.error("Failure", err, "Unable to switch to new endpoint");
});

View File

@ -16,14 +16,14 @@
<tbody>
<tr>
<td>Nodes</td>
<td ng-if="endpointMode.provider === 'DOCKER_SWARM'">{{ swarm.Nodes }}</td>
<td ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">{{ info.Swarm.Nodes }}</td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">{{ swarm.Nodes }}</td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">{{ info.Swarm.Nodes }}</td>
</tr>
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
<tr ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<td>Images</td>
<td>{{ info.Images }}</td>
</tr>
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
<tr ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<td>Swarm version</td>
<td>{{ docker.Version|swarmversion }}</td>
</tr>
@ -31,29 +31,29 @@
<td>Docker API version</td>
<td>{{ docker.ApiVersion }}</td>
</tr>
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
<tr ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<td>Strategy</td>
<td>{{ swarm.Strategy }}</td>
</tr>
<tr>
<td>Total CPU</td>
<td ng-if="endpointMode.provider === 'DOCKER_SWARM'">{{ info.NCPU }}</td>
<td ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">{{ totalCPU }}</td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">{{ info.NCPU }}</td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">{{ totalCPU }}</td>
</tr>
<tr>
<td>Total memory</td>
<td ng-if="endpointMode.provider === 'DOCKER_SWARM'">{{ info.MemTotal|humansize: 2 }}</td>
<td ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">{{ totalMemory|humansize: 2 }}</td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">{{ info.MemTotal|humansize: 2 }}</td>
<td ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">{{ totalMemory|humansize: 2 }}</td>
</tr>
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
<tr ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<td>Operating system</td>
<td>{{ info.OperatingSystem }}</td>
</tr>
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
<tr ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<td>Kernel version</td>
<td>{{ info.KernelVersion }}</td>
</tr>
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
<tr ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<td>Go version</td>
<td>{{ docker.GoVersion }}</td>
</tr>
@ -65,7 +65,7 @@
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="endpointMode.provider === 'DOCKER_SWARM'">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<rd-widget>
<rd-widget-header icon="fa-hdd-o" title="Node status"></rd-widget-header>
<rd-widget-body classes="no-padding">
@ -133,7 +133,7 @@
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<rd-widget>
<rd-widget-header icon="fa-hdd-o" title="Node status"></rd-widget-header>
<rd-widget-body classes="no-padding">

View File

@ -22,7 +22,7 @@ function ($scope, Info, Version, Node, Settings) {
Info.get({}, function (d) {
$scope.info = d;
if ($scope.endpointMode.provider === 'DOCKER_SWARM_MODE') {
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
Node.query({}, function(d) {
$scope.nodes = d;
var CPU = 0, memory = 0;

View File

@ -13,12 +13,12 @@
</rd-widget-custom-header>
<rd-widget-body classes="padding">
<form class="form-horizontal">
<div class="form-group" ng-if="globalNetworkCount === 0 && endpointMode.provider === 'DOCKER_SWARM'">
<div class="form-group" ng-if="globalNetworkCount === 0 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<div class="col-sm-12">
<span class="small text-muted">When using Swarm, we recommend deploying containers in a shared network. Looks like you don't have any shared network, head over the <a ui-sref="networks">networks view</a> to create one.</span>
</div>
</div>
<div class="form-group" ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">
<div class="form-group" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<div class="col-sm-12">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
<span class="small text-muted">App templates cannot be used with swarm-mode at the moment. You can still use them to quickly deploy containers to the Docker host.</span>
@ -41,10 +41,10 @@
<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="endpointMode.provider !== 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="form-control" ng-model="var.value">
<select ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="form-control" ng-model="var.value">
<option selected disabled hidden value="">Select a container</option>
</select>
<select ng-if="endpointMode.provider === 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|swarmcontainername for container in runningContainers" class="form-control" ng-model="var.value">
<select ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|swarmcontainername for container in runningContainers" class="form-control" ng-model="var.value">
<option selected disabled hidden value="">Select a container</option>
</select>
<input ng-if="!var.type || !var.type === 'container'" type="text" class="form-control" ng-model="var.value" id="field_{{ $index }}">

View File

@ -115,7 +115,7 @@ function ($scope, $q, $state, $filter, $anchorScroll, Config, Info, Container, C
if (v.value || v.set) {
var val;
if (v.type && v.type === 'container') {
if ($scope.endpointMode.provider === 'DOCKER_SWARM' && $scope.formValues.network.Scope === 'global') {
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM' && $scope.formValues.network.Scope === 'global') {
val = $filter('swarmcontainername')(v.value);
} else {
var container = v.value;
@ -206,7 +206,7 @@ function ($scope, $q, $state, $filter, $anchorScroll, Config, Info, Container, C
var containersToHideLabels = c.hiddenLabels;
Network.query({}, function (d) {
var networks = d;
if ($scope.endpointMode.provider === 'DOCKER_SWARM' || $scope.endpointMode.provider === 'DOCKER_SWARM_MODE') {
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || $scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
networks = d.filter(function (network) {
if (network.Scope === 'global') {
return network;

View File

@ -1,4 +1,35 @@
angular.module('portainer.helpers', [])
.factory('InfoHelper', [function InfoHelperFactory() {
'use strict';
return {
determineEndpointMode: function(info) {
var mode = {
provider: '',
role: ''
};
if (_.startsWith(info.ServerVersion, 'swarm')) {
mode.provider = "DOCKER_SWARM";
if (info.SystemStatus[0][1] === 'primary') {
mode.role = "PRIMARY";
} else {
mode.role = "REPLICA";
}
} else {
if (!info.Swarm || _.isEmpty(info.Swarm.NodeID)) {
mode.provider = "DOCKER_STANDALONE";
} else {
mode.provider = "DOCKER_SWARM_MODE";
if (info.Swarm.ControlAvailable) {
mode.role = "MANAGER";
} else {
mode.role = "WORKER";
}
}
}
return mode;
}
};
}])
.factory('ImageHelper', [function ImageHelperFactory() {
'use strict';
return {

View File

@ -240,44 +240,11 @@ angular.module('portainer.services', ['ngResource', 'ngSanitize'])
initAdminUser: { method: 'POST', params: { username: 'admin', action: 'init' } }
});
}])
.factory('EndpointMode', ['$rootScope', 'Info', function EndpointMode($rootScope, Info) {
'use strict';
return {
determineEndpointMode: function() {
Info.get({}, function(d) {
var mode = {
provider: '',
role: ''
};
if (_.startsWith(d.ServerVersion, 'swarm')) {
mode.provider = "DOCKER_SWARM";
if (d.SystemStatus[0][1] === 'primary') {
mode.role = "PRIMARY";
} else {
mode.role = "REPLICA";
}
} else {
if (!d.Swarm || _.isEmpty(d.Swarm.NodeID)) {
mode.provider = "DOCKER_STANDALONE";
} else {
mode.provider = "DOCKER_SWARM_MODE";
if (d.Swarm.ControlAvailable) {
mode.role = "MANAGER";
} else {
mode.role = "WORKER";
}
}
}
$rootScope.endpointMode = mode;
});
}
};
}])
.factory('Authentication', ['$q', '$rootScope', 'Auth', 'jwtHelper', 'localStorageService', function AuthenticationFactory($q, $rootScope, Auth, jwtHelper, localStorageService) {
.factory('Authentication', ['$q', '$rootScope', 'Auth', 'jwtHelper', 'LocalStorage', function AuthenticationFactory($q, $rootScope, Auth, jwtHelper, LocalStorage) {
'use strict';
return {
init: function() {
var jwt = localStorageService.get('JWT');
var jwt = LocalStorage.getJWT();
if (jwt) {
var tokenPayload = jwtHelper.decodeToken(jwt);
$rootScope.username = tokenPayload.username;
@ -287,7 +254,7 @@ angular.module('portainer.services', ['ngResource', 'ngSanitize'])
return $q(function (resolve, reject) {
Auth.login({username: username, password: password}).$promise
.then(function(data) {
localStorageService.set('JWT', data.jwt);
LocalStorage.storeJWT(data.jwt);
$rootScope.username = username;
resolve();
}, function() {
@ -296,10 +263,10 @@ angular.module('portainer.services', ['ngResource', 'ngSanitize'])
});
},
logout: function() {
localStorageService.remove('JWT');
LocalStorage.deleteJWT();
},
isAuthenticated: function() {
var jwt = localStorageService.get('JWT');
var jwt = LocalStorage.getJWT();
return jwt && !jwtHelper.isTokenExpired(jwt);
}
};
@ -359,7 +326,69 @@ angular.module('portainer.services', ['ngResource', 'ngSanitize'])
setActiveEndpoint: { method: 'POST', params: { id: '@id', action: 'active' } }
});
}])
.factory('EndpointService', ['$q', '$timeout', 'Endpoints', 'FileUploadService', function EndpointServiceFactory($q, $timeout, Endpoints, FileUploadService) {
.factory('LocalStorage', ['localStorageService', function LocalStorageFactory(localStorageService) {
'use strict';
return {
storeEndpointState: function(state) {
localStorageService.set('ENDPOINT_STATE', state);
},
getEndpointState: function() {
return localStorageService.get('ENDPOINT_STATE');
},
storeJWT: function(jwt) {
localStorageService.set('JWT', jwt);
},
getJWT: function() {
return localStorageService.get('JWT');
},
deleteJWT: function() {
localStorageService.remove('JWT');
}
};
}])
.factory('StateManager', ['$q', 'Info', 'InfoHelper', 'Version', 'LocalStorage', function StateManagerFactory($q, Info, InfoHelper, Version, LocalStorage) {
'use strict';
var state = {
loading: true,
application: {},
endpoint: {}
};
return {
init: function() {
var endpointState = LocalStorage.getEndpointState();
if (endpointState) {
state.endpoint = endpointState;
}
state.loading = false;
},
updateEndpointState: function(loading) {
var deferred = $q.defer();
if (loading) {
state.loading = true;
}
$q.all([Info.get({}).$promise, Version.get({}).$promise])
.then(function success(data) {
var endpointMode = InfoHelper.determineEndpointMode(data[0]);
var endpointAPIVersion = parseFloat(data[1].ApiVersion);
state.endpoint.mode = endpointMode;
state.endpoint.apiVersion = endpointAPIVersion;
LocalStorage.storeEndpointState(state.endpoint);
state.loading = false;
deferred.resolve();
}, function error(err) {
state.loading = false;
deferred.reject({msg: 'Unable to connect to the Docker endpoint', err: err});
});
return deferred.promise;
},
getState: function() {
return state;
}
};
}])
.factory('EndpointService', ['$q', 'Endpoints', 'FileUploadService', function EndpointServiceFactory($q, Endpoints, FileUploadService) {
'use strict';
return {
getActive: function() {

View File

@ -213,7 +213,6 @@ input[type="radio"] {
}
.page-wrapper {
margin-top: 25px;
height: 100%;
width: 100%;
display: flex;

View File

@ -398,14 +398,14 @@ module.exports = function (grunt) {
command: [
'docker stop portainer',
'docker rm portainer',
'docker run --privileged -d -p 9000:9000 -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock --name portainer portainer -d /data'
'docker run --privileged -d -p 9000:9000 -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock --name portainer portainer'
].join(';')
},
runSwarm: {
command: [
'docker stop portainer',
'docker rm portainer',
'docker run -d -p 9000:9000 -v /tmp/portainer:/data --name portainer portainer -H tcp://10.0.7.10:2375 -d /data'
'docker run -d -p 9000:9000 -v /tmp/portainer:/data --name portainer portainer -H tcp://10.0.7.10:2375'
].join(';')
},
runSwarmLocal: {
@ -419,7 +419,7 @@ module.exports = function (grunt) {
command: [
'docker stop portainer',
'docker rm portainer',
'docker run -d -p 9000:9000 -v /tmp/portainer:/data -v /tmp/docker-ssl:/certs --name portainer portainer -H tcp://10.0.7.10:2376 -d /data --tlsverify'
'docker run -d -p 9000:9000 -v /tmp/portainer:/data -v /tmp/docker-ssl:/certs --name portainer portainer -H tcp://10.0.7.10:2376 --tlsverify'
].join(';')
},
cleanImages: {

View File

@ -29,15 +29,36 @@
</head>
<body ng-controller="MainController">
<div id="page-wrapper" ng-class="{open: toggle && $state.current.name !== 'auth' && $state.current.name !== 'endpointInit', nopadding: $state.current.name === 'auth' || $state.current.name === 'endpointInit'}" ng-cloak>
<div id="page-wrapper" ng-class="{open: toggle && $state.current.name !== 'auth' && $state.current.name !== 'endpointInit' && $state.current.name !== 'init', nopadding: $state.current.name === 'auth' || $state.current.name === 'endpointInit' || $state.current.name === 'init' || applicationState.loading }" ng-cloak>
<div id="sideview" ui-view="sidebar"></div>
<div id="sideview" ui-view="sidebar" ng-if="!applicationState.loading"></div>
<div id="content-wrapper">
<div class="page-content">
<div class="page-wrapper" ng-if="applicationState.loading">
<!-- loading box -->
<div class="container simple-box">
<div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3">
<!-- loading box logo -->
<div class="row">
<img ng-if="logo" ng-src="{{ logo }}" class="simple-box-logo">
<img ng-if="!logo" src="images/logo_alt.png" class="simple-box-logo" alt="Portainer">
</div>
<!-- !loading box logo -->
<!-- panel -->
<div class="row" style="text-align: center">
Connecting to the Docker enpoint...
<i class="fa fa-cog fa-spin" style="margin-left: 5px"></i>
</div>
<!-- !panel -->
</div>
</div>
<!-- !loading box -->
</div>
<!-- Main Content -->
<div id="view" ui-view="content"></div>
<div id="view" ui-view="content" ng-if="!applicationState.loading"></div>
</div><!-- End Page Content -->
</div><!-- End Content Wrapper -->