feat(init-admin): allow to specify a username for the initial admin account (#1160)

pull/1169/head
Anthony Lapenna 2017-08-28 20:59:13 +02:00 committed by GitHub
parent 13b2fcffd2
commit e65d132b3d
12 changed files with 385 additions and 276 deletions

View File

@ -13,8 +13,10 @@ const (
const (
ErrUserNotFound = Error("User not found")
ErrUserAlreadyExists = Error("User already exists")
ErrInvalidUsername = Error("Invalid username. White spaces are not allowed.")
ErrAdminAlreadyInitialized = Error("Admin user already initialized")
ErrInvalidUsername = Error("Invalid username. White spaces are not allowed")
ErrAdminAlreadyInitialized = Error("An administrator user already exists")
ErrCannotRemoveAdmin = Error("Cannot remove the default administrator account")
ErrAdminCannotRemoveSelf = Error("Cannot remove your own user account. Contact another administrator")
)
// Team errors.

View File

@ -82,6 +82,7 @@ type (
}
postAdminInitRequest struct {
Username string `valid:"required"`
Password string `valid:"required"`
}
)
@ -358,10 +359,14 @@ func (handler *UserHandler) handlePostAdminInit(w http.ResponseWriter, r *http.R
return
}
user, err := handler.UserService.UserByUsername("admin")
if err == portainer.ErrUserNotFound {
users, err := handler.UserService.UsersByRole(portainer.AdministratorRole)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
if len(users) == 0 {
user := &portainer.User{
Username: "admin",
Username: req.Username,
Role: portainer.AdministratorRole,
}
user.Password, err = handler.CryptoService.Hash(req.Password)
@ -375,11 +380,7 @@ func (handler *UserHandler) handlePostAdminInit(w http.ResponseWriter, r *http.R
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
} else if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
if user != nil {
} else {
httperror.WriteErrorResponse(w, portainer.ErrAdminAlreadyInitialized, http.StatusConflict, handler.Logger)
return
}
@ -396,6 +397,22 @@ func (handler *UserHandler) handleDeleteUser(w http.ResponseWriter, r *http.Requ
return
}
if userID == 1 {
httperror.WriteErrorResponse(w, portainer.ErrCannotRemoveAdmin, http.StatusForbidden, handler.Logger)
return
}
tokenData, err := security.RetrieveTokenData(r)
if err != nil {
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
return
}
if tokenData.ID == portainer.UserID(userID) {
httperror.WriteErrorResponse(w, portainer.ErrAdminCannotRemoveSelf, http.StatusForbidden, handler.Logger)
return
}
_, err = handler.UserService.User(portainer.UserID(userID))
if err == portainer.ErrUserNotFound {

View File

@ -34,11 +34,12 @@ angular.module('portainer', [
'docker',
'endpoint',
'endpointAccess',
'endpointInit',
'endpoints',
'events',
'image',
'images',
'initAdmin',
'initEndpoint',
'main',
'network',
'networks',
@ -321,6 +322,33 @@ angular.module('portainer', [
}
}
})
.state('init', {
abstract: true,
url: '/init',
views: {
'content@': {
template: '<div ui-view="content@"></div>'
}
}
})
.state('init.endpoint', {
url: '/endpoint',
views: {
'content@': {
templateUrl: 'app/components/initEndpoint/initEndpoint.html',
controller: 'InitEndpointController'
}
}
})
.state('init.admin', {
url: '/admin',
views: {
'content@': {
templateUrl: 'app/components/initAdmin/initAdmin.html',
controller: 'InitAdminController'
}
}
})
.state('docker', {
url: '/docker/',
views: {
@ -373,15 +401,6 @@ angular.module('portainer', [
}
}
})
.state('endpointInit', {
url: '/init/endpoint',
views: {
'content@': {
templateUrl: 'app/components/endpointInit/endpointInit.html',
controller: 'EndpointInitController'
}
}
})
.state('events', {
url: '/events/',
views: {

View File

@ -1,92 +1,38 @@
<div class="page-wrapper">
<!-- login box -->
<div class="container simple-box">
<div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3">
<div class="col-sm-6 col-sm-offset-3">
<!-- login 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">
<img ng-if="logo" ng-src="{{ logo }}" class="simple-box-logo">
</div>
<!-- !login box logo -->
<!-- init password panel -->
<div class="panel panel-default" ng-if="initPassword">
<div class="panel-body">
<!-- init password form -->
<form class="login-form form-horizontal" enctype="multipart/form-data" method="POST">
<!-- comment -->
<div class="input-group">
<p style="margin: 5px;">
Please specify a password for the <b>admin</b> user account.
</p>
</div>
<!-- !comment input -->
<!-- comment -->
<div class="input-group">
<p style="margin: 5px;">
<i ng-class="{true: 'fa fa-check green-icon', false: 'fa fa-times red-icon'}[initPasswordData.password.length >= 8]" aria-hidden="true"></i>
Your password must be at least 8 characters long
</p>
</div>
<!-- !comment input -->
<!-- password input -->
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
<input id="admin_password" type="password" class="form-control" name="password" ng-model="initPasswordData.password" autofocus>
</div>
<!-- !password input -->
<!-- comment -->
<div class="input-group">
<p style="margin: 5px;">
<i ng-class="{true: 'fa fa-check green-icon', false: 'fa fa-times red-icon'}[initPasswordData.password !== '' && initPasswordData.password === initPasswordData.password_confirmation]" aria-hidden="true"></i>
Confirm your password
</p>
</div>
<!-- !comment input -->
<!-- password confirmation input -->
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
<input id="password_confirmation" type="password" class="form-control" name="password" ng-model="initPasswordData.password_confirmation">
</div>
<!-- !password confirmation input -->
<!-- validate button -->
<div class="form-group">
<div class="col-sm-12 controls">
<p class="pull-left text-danger" ng-if="initPasswordData.error" style="margin: 5px;">
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> Unable to create default user
</p>
<button type="submit" class="btn btn-primary pull-right" ng-disabled="initPasswordData.password.length < 8 || initPasswordData.password !== initPasswordData.password_confirmation" ng-click="createAdminUser()"><i class="fa fa-key" aria-hidden="true"></i> Validate</button>
</div>
</div>
<!-- !validate button -->
</form>
<!-- !init password form -->
</div>
</div>
<!-- !init password panel -->
<!-- login panel -->
<div class="panel panel-default" ng-if="!initPassword">
<div class="panel panel-default">
<div class="panel-body">
<!-- login form -->
<form class="login-form form-horizontal" enctype="multipart/form-data" method="POST">
<form class="simple-box-form form-horizontal">
<!-- username input -->
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user" aria-hidden="true"></i></span>
<input id="username" type="text" class="form-control" name="username" ng-model="authData.username" placeholder="Username">
<input id="username" type="text" class="form-control" name="username" ng-model="formValues.Username" placeholder="admin" autofocus>
</div>
<!-- !username input -->
<!-- password input -->
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
<input id="password" type="password" class="form-control" name="password" ng-model="authData.password" autofocus>
<input id="password" type="password" class="form-control" name="password" ng-model="formValues.Password">
</div>
<!-- !password input -->
<!-- login button -->
<div class="form-group">
<div class="col-sm-12 controls">
<p class="pull-left text-danger" ng-if="authData.error" style="margin: 5px;">
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ authData.error }}
</p>
<button type="submit" class="btn btn-primary pull-right" ng-click="authenticateUser()"><i class="fa fa-sign-in" aria-hidden="true"></i> Login</button>
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-sm pull-right" ng-click="authenticateUser()"><i class="fa fa-sign-in" aria-hidden="true"></i> Login</button>
<span class="pull-left" style="margin: 5px;" ng-if="state.AuthenticationError">
<i class="fa fa-exclamation-triangle red-icon" aria-hidden="true" style="margin-right: 2px;"></i>
<span class="small text-danger">{{ state.AuthenticationError }}</span>
</span>
</div>
</div>
<!-- !login button -->

View File

@ -1,123 +1,110 @@
angular.module('auth', [])
.controller('AuthenticationController', ['$scope', '$state', '$stateParams', '$window', '$timeout', '$sanitize', 'Authentication', 'Users', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'SettingsService',
function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Authentication, Users, EndpointService, StateManager, EndpointProvider, Notifications, SettingsService) {
$scope.authData = {
username: 'admin',
password: '',
error: ''
};
$scope.initPasswordData = {
password: '',
password_confirmation: '',
error: false
};
.controller('AuthenticationController', ['$scope', '$state', '$stateParams', '$window', '$timeout', '$sanitize', 'Authentication', 'Users', 'UserService', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'SettingsService',
function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Authentication, Users, UserService, EndpointService, StateManager, EndpointProvider, Notifications, SettingsService) {
$scope.logo = StateManager.getState().application.logo;
if (!$scope.applicationState.application.authentication) {
EndpointService.endpoints()
.then(function success(data) {
if (data.length > 0) {
endpointID = EndpointProvider.endpointID();
if (!endpointID) {
endpointID = data[0].Id;
EndpointProvider.setEndpointID(endpointID);
}
StateManager.updateEndpointState(true)
.then(function success() {
$state.go('dashboard');
}, function error(err) {
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
});
}
else {
$state.go('endpointInit');
}
}, function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve endpoints');
});
} else {
Users.checkAdminUser({}, function () {},
function (e) {
if (e.status === 404) {
$scope.initPassword = true;
} else {
Notifications.error('Failure', e, 'Unable to verify administrator account existence');
}
});
}
if ($stateParams.logout) {
Authentication.logout();
}
if ($stateParams.error) {
$scope.authData.error = $stateParams.error;
Authentication.logout();
}
if (Authentication.isAuthenticated()) {
$state.go('dashboard');
}
$scope.createAdminUser = function() {
var password = $sanitize($scope.initPasswordData.password);
Users.initAdminUser({password: password}, function (d) {
$scope.initPassword = false;
$timeout(function() {
var element = $window.document.getElementById('password');
if(element) {
element.focus();
}
});
}, function (e) {
$scope.initPassword.error = true;
});
$scope.formValues = {
Username: '',
Password: ''
};
$scope.state = {
AuthenticationError: ''
};
function setActiveEndpointAndRedirectToDashboard(endpoint) {
var endpointID = EndpointProvider.endpointID();
if (!endpointID) {
EndpointProvider.setEndpointID(endpoint.Id);
}
StateManager.updateEndpointState(true)
.then(function success(data) {
$state.go('dashboard');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
});
}
function unauthenticatedFlow() {
EndpointService.endpoints()
.then(function success(data) {
var endpoints = data;
if (endpoints.length > 0) {
setActiveEndpointAndRedirectToDashboard(endpoints[0]);
} else {
$state.go('init.endpoint');
}
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve endpoints');
});
}
function authenticatedFlow() {
UserService.administratorExists()
.then(function success(exists) {
if (!exists) {
$state.go('init.admin');
}
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to verify administrator account existence');
});
}
$scope.authenticateUser = function() {
$scope.authenticationError = false;
var username = $scope.formValues.Username;
var password = $scope.formValues.Password;
SettingsService.publicSettings()
.then(function success(data) {
var settings = data;
var username = $scope.authData.username;
var password = $scope.authData.password;
if (settings.AuthenticationMethod === 1) {
username = $sanitize($scope.authData.username);
password = $sanitize($scope.authData.password);
username = $sanitize(username);
password = $sanitize(password);
}
return Authentication.login(username, password);
})
.then(function success(data) {
.then(function success() {
return EndpointService.endpoints();
})
.then(function success(data) {
var endpoints = data;
var userDetails = Authentication.getUserDetails();
if (data.length > 0) {
endpointID = EndpointProvider.endpointID();
if (!endpointID) {
endpointID = data[0].Id;
EndpointProvider.setEndpointID(endpointID);
}
StateManager.updateEndpointState(true)
.then(function success() {
$state.go('dashboard');
}, function error(err) {
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
});
}
else if (data.length === 0 && userDetails.role === 1) {
$state.go('endpointInit');
} else if (data.length === 0 && userDetails.role === 2) {
if (endpoints.length > 0) {
setActiveEndpointAndRedirectToDashboard(endpoints[0]);
} else if (endpoints.length === 0 && userDetails.role === 1) {
$state.go('init.endpoint');
} else if (endpoints.length === 0 && userDetails.role === 2) {
Authentication.logout();
$scope.authData.error = 'User not allowed. Please contact your administrator.';
$scope.state.AuthenticationError = 'User not allowed. Please contact your administrator.';
}
})
.catch(function error(err) {
$scope.authData.error = 'Authentication error';
.catch(function error() {
$scope.state.AuthenticationError = 'Invalid credentials';
});
};
function initView() {
if ($stateParams.logout || $stateParams.error) {
Authentication.logout();
$scope.state.AuthenticationError = $stateParams.error;
return;
}
if (Authentication.isAuthenticated()) {
$state.go('dashboard');
}
var authenticationEnabled = $scope.applicationState.application.authentication;
if (!authenticationEnabled) {
unauthenticatedFlow();
} else {
authenticatedFlow();
}
}
initView();
}]);

View File

@ -0,0 +1,80 @@
<div class="page-wrapper">
<!-- simple box -->
<div class="container simple-box">
<div class="col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1">
<!-- simple 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>
<!-- !simple box logo -->
<!-- init password panel -->
<div class="panel panel-default">
<div class="panel-body">
<!-- init password form -->
<form class="simple-box-form form-horizontal">
<!-- note -->
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">
Please create the initial administrator user.
</span>
</div>
</div>
<!-- !note -->
<!-- username-input -->
<div class="form-group">
<label for="username" class="col-sm-4 control-label text-left">
Username
</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="username" ng-model="formValues.Username" placeholder="e.g. admin">
</div>
</div>
<!-- !username-input -->
<!-- new-password-input -->
<div class="form-group">
<label for="password" class="col-sm-4 control-label text-left">Password</label>
<div class="col-sm-8">
<input type="password" class="form-control" ng-model="formValues.Password" id="password">
</div>
</div>
<!-- !new-password-input -->
<!-- confirm-password-input -->
<div class="form-group">
<label for="confirm_password" class="col-sm-4 control-label text-left">Confirm password</label>
<div class="col-sm-8">
<div class="input-group">
<input type="password" class="form-control" ng-model="formValues.ConfirmPassword" id="confirm_password">
<span class="input-group-addon"><i ng-class="{true: 'fa fa-check green-icon', false: 'fa fa-times red-icon'}[formValues.Password !== '' && formValues.Password === formValues.ConfirmPassword]" aria-hidden="true"></i></span>
</div>
</div>
</div>
<!-- !confirm-password-input -->
<!-- note -->
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">
<i ng-class="{true: 'fa fa-check green-icon', false: 'fa fa-times red-icon'}[formValues.Password.length >= 8]" aria-hidden="true"></i>
The password must be at least 8 characters long
</span>
</div>
</div>
<!-- !note -->
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="formValues.Password.length < 8 || formValues.Password !== formValues.ConfirmPassword" ng-click="createAdminUser()"><i class="fa fa-user-plus" aria-hidden="true"></i> Create user</button>
<i id="createResourceSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
<!-- !actions -->
</form>
<!-- !init password form -->
</div>
</div>
<!-- !init password panel -->
</div>
</div>
<!-- !simple box -->
</div>

View File

@ -0,0 +1,33 @@
angular.module('initAdmin', [])
.controller('InitAdminController', ['$scope', '$state', '$sanitize', 'Notifications', 'Authentication', 'StateManager', 'UserService',
function ($scope, $state, $sanitize, Notifications, Authentication, StateManager, UserService) {
$scope.logo = StateManager.getState().application.logo;
$scope.formValues = {
Username: 'admin',
Password: '',
ConfirmPassword: ''
};
$scope.createAdminUser = function() {
$('#createResourceSpinner').show();
var username = $sanitize($scope.formValues.Username);
var password = $sanitize($scope.formValues.Password);
UserService.initAdministrator(username, password)
.then(function success() {
return Authentication.login(username, password);
})
.then(function success() {
$state.go('init.endpoint');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to create administrator user');
})
.finally(function final() {
$('#createResourceSpinner').hide();
});
};
}]);

View File

@ -12,51 +12,74 @@
<div class="panel panel-default">
<div class="panel-body">
<!-- init-endpoint form -->
<form class="form-horizontal" style="margin: 20px;" enctype="multipart/form-data" method="POST">
<!-- comment -->
<div class="form-group" style="text-align: center;">
<h4>Connect Portainer to a Docker engine or Swarm cluster endpoint</h4>
</div>
<!-- !comment input -->
<!-- endpoin-type radio -->
<form class="simple-box-form form-horizontal">
<!-- note -->
<div class="form-group">
<div class="radio">
<label><input type="radio" name="endpointType" value="local" ng-model="formValues.endpointType" ng-click="resetErrorMessage()">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" ng-click="resetErrorMessage()">Manage a remote Docker instance</label>
<div class="col-sm-12">
<span class="small text-muted">
Connect Portainer to the Docker environment you want to manage.
</span>
</div>
</div>
<!-- endpoint-type radio -->
<!-- local-endpoint -->
<div ng-if="formValues.endpointType === 'local'" style="margin-top: 25px;">
<div class="form-group">
<i class="fa fa-exclamation-triangle" aria-hidden="true" style="margin-right: 5px;"></i>
<span class="small text-primary">This feature is not yet available for native Docker Windows containers.</span>
<div class="small text-primary">On Linux and when using Docker for Mac or Docker for Windows or Docker Toolbox, ensure that you have started Portainer container with the following Docker flag <code>-v "/var/run/docker.sock:/var/run/docker.sock"</code></div>
<!-- !note -->
<!-- endpoint-type -->
<div class="form-group" style="margin-bottom: 0">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="local_endpoint" ng-model="formValues.EndpointType" value="local">
<label for="local_endpoint">
<div class="boxselector_header">
<i class="fa fa-bolt" aria-hidden="true" style="margin-right: 2px;"></i>
Local
</div>
<p>Manage the Docker environment where Portainer is running</p>
</label>
</div>
<div>
<input type="radio" id="remote_endpoint" ng-model="formValues.EndpointType" value="remote">
<label for="remote_endpoint">
<div class="boxselector_header">
<i class="fa fa-plug" aria-hidden="true" style="margin-right: 2px;"></i>
Remote
</div>
<p>Manage a remote Docker environment</p>
</label>
</div>
</div>
<!-- connect button -->
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-12 controls">
<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>
<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>
</div>
<!-- !endpoint-type -->
<!-- local-endpoint -->
<div ng-if="formValues.EndpointType === 'local'">
<div class="form-group">
<div class="col-sm-12">
<span class="small">
<p class="text-muted">
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This feature is not yet available for <u>native Docker Windows containers</u>.
</p>
<p class="text-primary">
Please ensure that you have started the Portainer container with the following Docker flag <code>-v "/var/run/docker.sock:/var/run/docker.sock"</code> in order to connect to the local Docker environment.
</p>
</span>
</div>
</div>
<!-- !connect button -->
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-sm" ng-click="createLocalEndpoint()"><i class="fa fa-bolt" aria-hidden="true"></i> Connect</button>
<i id="createResourceSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
<!-- !actions -->
</div>
<!-- !local-endpoint -->
<!-- remote-endpoint -->
<div ng-if="formValues.endpointType === 'remote'" style="margin-top: 25px;">
<div ng-if="formValues.EndpointType === 'remote'">
<!-- name-input -->
<div class="form-group">
<label for="container_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
<label for="endpoint_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
<div class="col-sm-8 col-lg-9">
<input type="text" class="form-control" id="container_name" ng-model="formValues.Name" placeholder="e.g. docker-prod01">
<input type="text" class="form-control" id="endpoint_name" ng-model="formValues.Name" placeholder="e.g. docker-prod01">
</div>
</div>
<!-- !name-input -->
@ -127,19 +150,14 @@
<!-- !key-input -->
</div>
<!-- !tls-certs -->
<!-- connect button -->
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-12 controls">
<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>
<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>
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-sm" 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>
<i id="createResourceSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
<!-- !connect button -->
<!-- !actions -->
</div>
<!-- !remote-endpoint -->
</form>

View File

@ -1,13 +1,15 @@
angular.module('endpointInit', [])
.controller('EndpointInitController', ['$scope', '$state', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications',
angular.module('initEndpoint', [])
.controller('InitEndpointController', ['$scope', '$state', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications',
function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notifications) {
$scope.logo = StateManager.getState().application.logo;
$scope.state = {
error: '',
uploadInProgress: false
};
$scope.formValues = {
endpointType: 'remote',
EndpointType: 'remote',
Name: '',
URL: '',
TLS: false,
@ -20,51 +22,31 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
$state.go('dashboard');
}
$scope.resetErrorMessage = function() {
$scope.state.error = '';
};
function showErrorMessage(message) {
$scope.state.uploadInProgress = false;
$scope.state.error = message;
}
$scope.createLocalEndpoint = function() {
$('#createResourceSpinner').show();
var name = 'local';
var URL = 'unix:///var/run/docker.sock';
function updateEndpointState(endpointID) {
EndpointProvider.setEndpointID(endpointID);
StateManager.updateEndpointState(false)
EndpointService.createLocalEndpoint(name, URL, false, true)
.then(function success(data) {
var endpointID = data.Id;
EndpointProvider.setEndpointID(endpointID);
return StateManager.updateEndpointState(false);
})
.then(function success(data) {
$state.go('dashboard');
})
.catch(function error(err) {
EndpointService.deleteEndpoint(endpointID)
.then(function success() {
showErrorMessage('Unable to connect to the Docker endpoint');
});
});
}
$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) {
var endpointID = data.Id;
updateEndpointState(data.Id);
}, function error() {
$scope.state.error = 'Unable to create endpoint';
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
})
.finally(function final() {
$('#initEndpointSpinner').hide();
$('#createResourceSpinner').hide();
});
};
$scope.createRemoteEndpoint = function() {
$('#initEndpointSpinner').show();
$scope.state.error = '';
$('#createResourceSpinner').show();
var name = $scope.formValues.Name;
var URL = $scope.formValues.URL;
var PublicURL = URL.split(':')[0];
@ -76,16 +58,17 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
EndpointService.createRemoteEndpoint(name, URL, PublicURL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile)
.then(function success(data) {
var endpointID = data.Id;
updateEndpointState(endpointID);
}, function error(err) {
showErrorMessage(err.msg);
}, function update(evt) {
if (evt.upload) {
$scope.state.uploadInProgress = evt.upload;
}
EndpointProvider.setEndpointID(endpointID);
return StateManager.updateEndpointState(false);
})
.then(function success(data) {
$state.go('dashboard');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
})
.finally(function final() {
$('#initEndpointSpinner').hide();
$('#createResourceSpinner').hide();
});
};
}]);

View File

@ -134,5 +134,26 @@ angular.module('portainer.services')
return deferred.promise;
};
service.initAdministrator = function(username, password) {
return Users.initAdminUser({ Username: username, Password: password }).$promise;
};
service.administratorExists = function() {
var deferred = $q.defer();
Users.checkAdminUser({}).$promise
.then(function success(data) {
deferred.resolve(true);
})
.catch(function error(err) {
if (err.status === 404) {
deferred.resolve(false);
}
deferred.reject({ msg: 'Unable to verify administrator account existence', err: err });
});
return deferred.promise;
};
return service;
}]);

View File

@ -254,11 +254,11 @@ a[ng-click]{
margin-bottom: 10px;
}
.login-form > div {
.simple-box-form > div {
margin-bottom: 25px;
}
.login-form > div:last-child {
.simple-box-form > div:last-child {
margin-top: 10px;
margin-bottom: 10px;
}

View File

@ -29,8 +29,11 @@
</head>
<body ng-controller="MainController">
<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="page-wrapper" ng-class="{
open: toggle && ['auth', 'init', 'init.endpoint', 'init.admin'].indexOf($state.current.name) === -1,
nopadding: ['auth', 'init', 'init.endpoint', 'init.admin'].indexOf($state.current.name) > -1 || applicationState.loading
}"
ng-cloak>
<div id="sideview" ui-view="sidebar" ng-if="!applicationState.loading"></div>
<div id="content-wrapper">