mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(global): add authentication support with single admin account
This commit is contained in:
parent
1e5207517d
commit
4e77c72fa2
35 changed files with 1475 additions and 220 deletions
425
app/app.js
425
app/app.js
|
@ -6,9 +6,12 @@ angular.module('portainer', [
|
|||
'ngCookies',
|
||||
'ngSanitize',
|
||||
'angularUtils.directives.dirPagination',
|
||||
'LocalStorageModule',
|
||||
'angular-jwt',
|
||||
'portainer.services',
|
||||
'portainer.helpers',
|
||||
'portainer.filters',
|
||||
'auth',
|
||||
'dashboard',
|
||||
'container',
|
||||
'containerConsole',
|
||||
|
@ -19,8 +22,11 @@ angular.module('portainer', [
|
|||
'events',
|
||||
'images',
|
||||
'image',
|
||||
'main',
|
||||
'service',
|
||||
'services',
|
||||
'settings',
|
||||
'sidebar',
|
||||
'createService',
|
||||
'stats',
|
||||
'swarm',
|
||||
|
@ -31,131 +37,430 @@ angular.module('portainer', [
|
|||
'templates',
|
||||
'volumes',
|
||||
'createVolume'])
|
||||
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', function ($stateProvider, $urlRouterProvider, $httpProvider) {
|
||||
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'localStorageServiceProvider', 'jwtOptionsProvider', function ($stateProvider, $urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider) {
|
||||
'use strict';
|
||||
|
||||
$urlRouterProvider.otherwise('/');
|
||||
localStorageServiceProvider
|
||||
.setStorageType('sessionStorage')
|
||||
.setPrefix('portainer');
|
||||
|
||||
jwtOptionsProvider.config({
|
||||
tokenGetter: ['localStorageService', function(localStorageService) {
|
||||
return localStorageService.get('JWT');
|
||||
}],
|
||||
unauthenticatedRedirector: ['$state', function($state) {
|
||||
$state.go('auth', {error: 'Your session has expired'});
|
||||
}]
|
||||
});
|
||||
$httpProvider.interceptors.push('jwtInterceptor');
|
||||
|
||||
$urlRouterProvider.otherwise('/auth');
|
||||
|
||||
$stateProvider
|
||||
.state('index', {
|
||||
url: '/',
|
||||
templateUrl: 'app/components/dashboard/dashboard.html',
|
||||
controller: 'DashboardController'
|
||||
.state('auth', {
|
||||
url: '/auth',
|
||||
params: {
|
||||
logout: false,
|
||||
error: ''
|
||||
},
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/auth/auth.html',
|
||||
controller: 'AuthenticationController'
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('containers', {
|
||||
url: '/containers/',
|
||||
templateUrl: 'app/components/containers/containers.html',
|
||||
controller: 'ContainersController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/containers/containers.html',
|
||||
controller: 'ContainersController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('container', {
|
||||
url: "^/containers/:id",
|
||||
templateUrl: 'app/components/container/container.html',
|
||||
controller: 'ContainerController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/container/container.html',
|
||||
controller: 'ContainerController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('stats', {
|
||||
url: "^/containers/:id/stats",
|
||||
templateUrl: 'app/components/stats/stats.html',
|
||||
controller: 'StatsController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/stats/stats.html',
|
||||
controller: 'StatsController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('logs', {
|
||||
url: "^/containers/:id/logs",
|
||||
templateUrl: 'app/components/containerLogs/containerlogs.html',
|
||||
controller: 'ContainerLogsController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/containerLogs/containerlogs.html',
|
||||
controller: 'ContainerLogsController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('console', {
|
||||
url: "^/containers/:id/console",
|
||||
templateUrl: 'app/components/containerConsole/containerConsole.html',
|
||||
controller: 'ContainerConsoleController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/containerConsole/containerConsole.html',
|
||||
controller: 'ContainerConsoleController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('dashboard', {
|
||||
url: '/dashboard',
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/dashboard/dashboard.html',
|
||||
controller: 'DashboardController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('actions', {
|
||||
abstract: true,
|
||||
url: "/actions",
|
||||
template: '<ui-view/>'
|
||||
views: {
|
||||
"content": {
|
||||
template: '<div ui-view="content"></div>'
|
||||
},
|
||||
"sidebar": {
|
||||
template: '<div ui-view="sidebar"></div>'
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('actions.create', {
|
||||
abstract: true,
|
||||
url: "/create",
|
||||
template: '<ui-view/>'
|
||||
views: {
|
||||
"content": {
|
||||
template: '<div ui-view="content"></div>'
|
||||
},
|
||||
"sidebar": {
|
||||
template: '<div ui-view="sidebar"></div>'
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('actions.create.container', {
|
||||
url: "/container",
|
||||
templateUrl: 'app/components/createContainer/createcontainer.html',
|
||||
controller: 'CreateContainerController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/createContainer/createcontainer.html',
|
||||
controller: 'CreateContainerController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('actions.create.network', {
|
||||
url: "/network",
|
||||
templateUrl: 'app/components/createNetwork/createnetwork.html',
|
||||
controller: 'CreateNetworkController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/createNetwork/createnetwork.html',
|
||||
controller: 'CreateNetworkController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('actions.create.service', {
|
||||
url: "/service",
|
||||
templateUrl: 'app/components/createService/createservice.html',
|
||||
controller: 'CreateServiceController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/createService/createservice.html',
|
||||
controller: 'CreateServiceController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('actions.create.volume', {
|
||||
url: "/volume",
|
||||
templateUrl: 'app/components/createVolume/createvolume.html',
|
||||
controller: 'CreateVolumeController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/createVolume/createvolume.html',
|
||||
controller: 'CreateVolumeController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('docker', {
|
||||
url: '/docker/',
|
||||
templateUrl: 'app/components/docker/docker.html',
|
||||
controller: 'DockerController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/docker/docker.html',
|
||||
controller: 'DockerController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('events', {
|
||||
url: '/events/',
|
||||
templateUrl: 'app/components/events/events.html',
|
||||
controller: 'EventsController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/events/events.html',
|
||||
controller: 'EventsController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('images', {
|
||||
url: '/images/',
|
||||
templateUrl: 'app/components/images/images.html',
|
||||
controller: 'ImagesController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/images/images.html',
|
||||
controller: 'ImagesController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('image', {
|
||||
url: '^/images/:id/',
|
||||
templateUrl: 'app/components/image/image.html',
|
||||
controller: 'ImageController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/image/image.html',
|
||||
controller: 'ImageController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('networks', {
|
||||
url: '/networks/',
|
||||
templateUrl: 'app/components/networks/networks.html',
|
||||
controller: 'NetworksController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/networks/networks.html',
|
||||
controller: 'NetworksController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('network', {
|
||||
url: '^/networks/:id/',
|
||||
templateUrl: 'app/components/network/network.html',
|
||||
controller: 'NetworkController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/network/network.html',
|
||||
controller: 'NetworkController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('services', {
|
||||
url: '/services/',
|
||||
templateUrl: 'app/components/services/services.html',
|
||||
controller: 'ServicesController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/services/services.html',
|
||||
controller: 'ServicesController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('service', {
|
||||
url: '^/service/:id/',
|
||||
templateUrl: 'app/components/service/service.html',
|
||||
controller: 'ServiceController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/service/service.html',
|
||||
controller: 'ServiceController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('settings', {
|
||||
url: '/settings/',
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/settings/settings.html',
|
||||
controller: 'SettingsController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('task', {
|
||||
url: '^/task/:id',
|
||||
templateUrl: 'app/components/task/task.html',
|
||||
controller: 'TaskController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/task/task.html',
|
||||
controller: 'TaskController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('templates', {
|
||||
url: '/templates/',
|
||||
templateUrl: 'app/components/templates/templates.html',
|
||||
controller: 'TemplatesController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/templates/templates.html',
|
||||
controller: 'TemplatesController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('volumes', {
|
||||
url: '/volumes/',
|
||||
templateUrl: 'app/components/volumes/volumes.html',
|
||||
controller: 'VolumesController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/volumes/volumes.html',
|
||||
controller: 'VolumesController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
})
|
||||
.state('swarm', {
|
||||
url: '/swarm/',
|
||||
templateUrl: 'app/components/swarm/swarm.html',
|
||||
controller: 'SwarmController'
|
||||
views: {
|
||||
"content": {
|
||||
templateUrl: 'app/components/swarm/swarm.html',
|
||||
controller: 'SwarmController'
|
||||
},
|
||||
"sidebar": {
|
||||
templateUrl: 'app/components/sidebar/sidebar.html',
|
||||
controller: 'SidebarController'
|
||||
}
|
||||
},
|
||||
data: {
|
||||
requiresLogin: true
|
||||
}
|
||||
});
|
||||
|
||||
// The Docker API likes to return plaintext errors, this catches them and disp
|
||||
|
@ -165,7 +470,7 @@ angular.module('portainer', [
|
|||
return {
|
||||
'response': function(response) {
|
||||
if (typeof(response.data) === 'string' &&
|
||||
(response.data.startsWith('Conflict.') || response.data.startsWith('conflict:'))) {
|
||||
(response.data.startsWith('Conflict.') || response.data.startsWith('conflict:'))) {
|
||||
$.gritter.add({
|
||||
title: 'Error',
|
||||
text: $('<div>').text(response.data).html(),
|
||||
|
@ -182,12 +487,28 @@ angular.module('portainer', [
|
|||
};
|
||||
});
|
||||
}])
|
||||
.run(['$rootScope', '$state', 'Authentication', 'authManager', 'EndpointMode', function ($rootScope, $state, Authentication, authManager, EndpointMode) {
|
||||
authManager.checkAuthOnRefresh();
|
||||
authManager.redirectWhenUnauthenticated();
|
||||
Authentication.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 ((fromState.name === 'auth' || fromState.name === '') && 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
|
||||
.constant('DOCKER_ENDPOINT', 'dockerapi')
|
||||
.constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is requred. If you have a port, prefix it with a ':' i.e. :4243
|
||||
.constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is required. If you have a port, prefix it with a ':' i.e. :4243
|
||||
.constant('CONFIG_ENDPOINT', 'settings')
|
||||
.constant('AUTH_ENDPOINT', 'auth')
|
||||
.constant('TEMPLATES_ENDPOINT', 'templates')
|
||||
.constant('PAGINATION_MAX_ITEMS', 10)
|
||||
.constant('UI_VERSION', 'v1.10.2');
|
||||
|
|
101
app/components/auth/auth.html
Normal file
101
app/components/auth/auth.html
Normal file
|
@ -0,0 +1,101 @@
|
|||
<div class="login-wrapper">
|
||||
<!-- login box -->
|
||||
<div class="container login-box">
|
||||
<div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3">
|
||||
<!-- login box logo -->
|
||||
<div class="row">
|
||||
<img ng-if="logo" ng-src="{{ logo }}" class="login-logo">
|
||||
<img ng-if="!logo" src="images/logo_alt.png" class="login-logo" alt="Portainer">
|
||||
</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-body">
|
||||
<!-- login form -->
|
||||
<form class="login-form form-horizontal" enctype="multipart/form-data" method="POST">
|
||||
<!-- 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">
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
<!-- !login button -->
|
||||
</form>
|
||||
<!-- !login form -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- !login panel -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- !login box -->
|
||||
</div>
|
68
app/components/auth/authController.js
Normal file
68
app/components/auth/authController.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
angular.module('auth', [])
|
||||
.controller('AuthenticationController', ['$scope', '$state', '$stateParams', '$window', '$timeout', '$sanitize', 'Config', 'Authentication', 'Users', 'Messages',
|
||||
function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Config, Authentication, Users, Messages) {
|
||||
|
||||
$scope.authData = {
|
||||
username: 'admin',
|
||||
password: '',
|
||||
error: ''
|
||||
};
|
||||
$scope.initPasswordData = {
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
error: false
|
||||
};
|
||||
|
||||
if ($stateParams.logout) {
|
||||
Authentication.logout();
|
||||
}
|
||||
|
||||
if ($stateParams.error) {
|
||||
$scope.authData.error = $stateParams.error;
|
||||
Authentication.logout();
|
||||
}
|
||||
|
||||
if (Authentication.isAuthenticated()) {
|
||||
$state.go('dashboard');
|
||||
}
|
||||
|
||||
Config.$promise.then(function (c) {
|
||||
$scope.logo = c.logo;
|
||||
});
|
||||
|
||||
Users.checkAdminUser({}, function (d) {},
|
||||
function (e) {
|
||||
if (e.status === 404) {
|
||||
$scope.initPassword = true;
|
||||
} else {
|
||||
Messages.error("Failure", e, 'Unable to verify administrator account existence');
|
||||
}
|
||||
});
|
||||
|
||||
$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.authenticateUser = function() {
|
||||
$scope.authenticationError = false;
|
||||
var username = $sanitize($scope.authData.username);
|
||||
var password = $sanitize($scope.authData.password);
|
||||
Authentication.login(username, password)
|
||||
.then(function() {
|
||||
$state.go('dashboard');
|
||||
}, function() {
|
||||
$scope.authData.error = 'Invalid credentials';
|
||||
});
|
||||
};
|
||||
}]);
|
|
@ -66,7 +66,7 @@
|
|||
<span ng-show="sortType == 'IP' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="swarm && !swarm_mode">
|
||||
<th ng-if="endpointMode.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>
|
||||
|
@ -86,11 +86,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|containerstatus }}</span></td>
|
||||
<td ng-if="swarm && !swarm_mode"><a ui-sref="container({id: container.Id})">{{ container|swarmcontainername}}</a></td>
|
||||
<td ng-if="!swarm || swarm_mode"><a ui-sref="container({id: container.Id})">{{ container|containername}}</a></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><a ui-sref="image({id: container.Image})">{{ container.Image }}</a></td>
|
||||
<td ng-if="state.displayIP">{{ container.IP ? container.IP : '-' }}</td>
|
||||
<td ng-if="swarm && !swarm_mode">{{ container.hostIP }}</td>
|
||||
<td ng-if="endpointMode.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 }}
|
||||
|
|
|
@ -7,9 +7,7 @@ function ($scope, Container, ContainerHelper, Info, Settings, Messages, Config)
|
|||
$scope.sortType = 'State';
|
||||
$scope.sortReverse = false;
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.swarm_mode = false;
|
||||
$scope.pagination_count = Settings.pagination_count;
|
||||
|
||||
$scope.order = function (sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
|
@ -28,7 +26,7 @@ function ($scope, Container, ContainerHelper, Info, Settings, Messages, Config)
|
|||
if (model.IP) {
|
||||
$scope.state.displayIP = true;
|
||||
}
|
||||
if ($scope.swarm && !$scope.swarm_mode) {
|
||||
if ($scope.endpointMode.provider === 'DOCKER_SWARM') {
|
||||
model.hostIP = $scope.swarm_hosts[_.split(container.Names[0], '/')[1]];
|
||||
}
|
||||
return model;
|
||||
|
@ -150,17 +148,11 @@ function ($scope, Container, ContainerHelper, Info, Settings, Messages, Config)
|
|||
return swarm_hosts;
|
||||
}
|
||||
|
||||
$scope.swarm = false;
|
||||
Config.$promise.then(function (c) {
|
||||
$scope.containersToHideLabels = c.hiddenLabels;
|
||||
$scope.swarm = c.swarm;
|
||||
if (c.swarm) {
|
||||
if (c.swarm && $scope.endpointMode.provider === 'DOCKER_SWARM') {
|
||||
Info.get({}, function (d) {
|
||||
if (!_.startsWith(d.ServerVersion, 'swarm')) {
|
||||
$scope.swarm_mode = true;
|
||||
} else {
|
||||
$scope.swarm_hosts = retrieveSwarmHostsInfo(d);
|
||||
}
|
||||
$scope.swarm_hosts = retrieveSwarmHostsInfo(d);
|
||||
update({all: Settings.displayAll ? 1 : 0});
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -53,11 +53,6 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
|
|||
|
||||
Config.$promise.then(function (c) {
|
||||
$scope.swarm = c.swarm;
|
||||
Info.get({}, function(info) {
|
||||
if ($scope.swarm && !_.startsWith(info.ServerVersion, 'swarm')) {
|
||||
$scope.swarm_mode = true;
|
||||
}
|
||||
});
|
||||
var containersToHideLabels = c.hiddenLabels;
|
||||
|
||||
Volume.query({}, function (d) {
|
||||
|
@ -216,7 +211,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.swarm && !$scope.swarm_mode) {
|
||||
if ($scope.swarm && $scope.endpointMode.provider === 'DOCKER_SWARM') {
|
||||
containerName = $filter('swarmcontainername')(container);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -258,7 +258,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 && !swarm_mode">
|
||||
<div class="form-group" ng-if="globalNetworkCount === 0 && endpointMode.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>
|
||||
|
@ -278,10 +278,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="(!swarm || swarm && swarm_mode)" ng-options="container|containername for container in runningContainers" class="selectpicker form-control" ng-model="formValues.NetworkContainer">
|
||||
<select ng-if="endpointMode.provider !== 'DOCKER_SWARM'" ng-options="container|containername for container in runningContainers" class="selectpicker form-control" ng-model="formValues.NetworkContainer">
|
||||
<option selected disabled hidden value="">Select a container</option>
|
||||
</select>
|
||||
<select ng-if="swarm && !swarm_mode" ng-options="container|swarmcontainername for container in runningContainers" class="selectpicker form-control" ng-model="formValues.NetworkContainer">
|
||||
<select ng-if="endpointMode.provider === 'DOCKER_SWARM'" ng-options="container|swarmcontainername for container in runningContainers" class="selectpicker form-control" ng-model="formValues.NetworkContainer">
|
||||
<option selected disabled hidden value="">Select a container</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="swarm_mode || !swarm">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="endpointMode.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="swarm && !swarm_mode">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="endpointMode.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="swarm && swarm_mode">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tachometer" title="Swarm info"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
|
|
|
@ -14,7 +14,6 @@ function ($scope, $q, Config, Container, ContainerHelper, Image, Network, Volume
|
|||
$scope.volumeData = {
|
||||
total: 0
|
||||
};
|
||||
$scope.swarm_mode = false;
|
||||
|
||||
function prepareContainerData(d, containersToHideLabels) {
|
||||
var running = 0;
|
||||
|
@ -64,9 +63,6 @@ function ($scope, $q, Config, Container, ContainerHelper, Image, Network, Volume
|
|||
function prepareInfoData(d) {
|
||||
var info = d;
|
||||
$scope.infoData = info;
|
||||
if ($scope.swarm && !_.startsWith(info.ServerVersion, 'swarm')) {
|
||||
$scope.swarm_mode = true;
|
||||
}
|
||||
}
|
||||
|
||||
function fetchDashboardData(containersToHideLabels) {
|
||||
|
@ -84,6 +80,9 @@ function ($scope, $q, Config, Container, ContainerHelper, Image, Network, Volume
|
|||
prepareNetworkData(d[3]);
|
||||
prepareInfoData(d[4]);
|
||||
$('#loadingViewSpinner').hide();
|
||||
}, function(e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Messages.error("Failure", e, "Unable to load dashboard data");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,15 @@
|
|||
angular.module('dashboard')
|
||||
.controller('MasterCtrl', ['$scope', '$cookieStore', 'Settings', 'Config', 'Info',
|
||||
function ($scope, $cookieStore, Settings, Config, Info) {
|
||||
angular.module('main', [])
|
||||
.controller('MainController', ['$scope', '$cookieStore',
|
||||
function ($scope, $cookieStore) {
|
||||
|
||||
/**
|
||||
* Sidebar Toggle & Cookie Control
|
||||
*/
|
||||
var mobileView = 992;
|
||||
|
||||
$scope.getWidth = function() {
|
||||
return window.innerWidth;
|
||||
};
|
||||
|
||||
$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')) {
|
||||
$scope.swarm_mode = true;
|
||||
$scope.swarm_manager = false;
|
||||
if (d.Swarm.ControlAvailable) {
|
||||
$scope.swarm_manager = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$watch($scope.getWidth, function(newValue, oldValue) {
|
||||
if (newValue >= mobileView) {
|
||||
if (angular.isDefined($cookieStore.get('toggle'))) {
|
||||
|
@ -47,6 +31,4 @@ function ($scope, $cookieStore, Settings, Config, Info) {
|
|||
window.onresize = function() {
|
||||
$scope.$apply();
|
||||
};
|
||||
|
||||
$scope.uiVersion = Settings.uiVersion;
|
||||
}]);
|
|
@ -23,12 +23,12 @@
|
|||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- tag-note -->
|
||||
<div class="form-group" ng-if="swarm">
|
||||
<div class="form-group" ng-if="endpointMode.provider === 'DOCKER_SWARM' || endpointMode.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="!swarm">
|
||||
<div class="form-group" ng-if="endpointMode.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>
|
||||
|
|
67
app/components/settings/settings.html
Normal file
67
app/components/settings/settings.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Settings">
|
||||
</rd-header-title>
|
||||
<rd-header-content>Settings</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-lock" title="Change user password"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<!-- current-password-input -->
|
||||
<div class="form-group">
|
||||
<label for="current_password" class="col-sm-2 control-label text-left">Current password</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
|
||||
<input type="password" class="form-control" ng-model="formValues.currentPassword" id="current_password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !current-password-input -->
|
||||
<div class="form-group" style="margin-left: 5px;">
|
||||
<p>
|
||||
<i ng-class="{true: 'fa fa-check green-icon', false: 'fa fa-times red-icon'}[formValues.newPassword.length >= 8]" aria-hidden="true"></i>
|
||||
Your new password must be at least 8 characters long
|
||||
</p>
|
||||
</div>
|
||||
<!-- new-password-input -->
|
||||
<div class="form-group">
|
||||
<label for="new_password" class="col-sm-2 control-label text-left">New password</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
|
||||
<input type="password" class="form-control" ng-model="formValues.newPassword" id="new_password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !new-password-input -->
|
||||
<!-- confirm-password-input -->
|
||||
<div class="form-group">
|
||||
<label for="confirm_password" class="col-sm-2 control-label text-left">Confirm password</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
|
||||
<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.newPassword !== '' && formValues.newPassword === formValues.confirmPassword]" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !confirm-password-input -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2">
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="!formValues.currentPassword || formValues.newPassword.length < 8 || formValues.newPassword !== formValues.confirmPassword" ng-click="updatePassword()">Update password</button>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<p class="pull-left text-danger" ng-if="invalidPassword" style="margin: 5px;">
|
||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> Current password is not valid
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
30
app/components/settings/settingsController.js
Normal file
30
app/components/settings/settingsController.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
angular.module('settings', [])
|
||||
.controller('SettingsController', ['$scope', '$state', '$sanitize', 'Users', 'Messages',
|
||||
function ($scope, $state, $sanitize, Users, Messages) {
|
||||
$scope.formValues = {
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
};
|
||||
|
||||
$scope.updatePassword = function() {
|
||||
$scope.invalidPassword = false;
|
||||
$scope.error = false;
|
||||
var currentPassword = $sanitize($scope.formValues.currentPassword);
|
||||
Users.checkPassword({ username: $scope.username, password: currentPassword }, function (d) {
|
||||
if (d.valid) {
|
||||
var newPassword = $sanitize($scope.formValues.newPassword);
|
||||
Users.update({ username: $scope.username, password: newPassword }, function (d) {
|
||||
Messages.send("Success", "Password successfully updated");
|
||||
$state.go('settings', {}, {reload: true});
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e, "Unable to update password");
|
||||
});
|
||||
} else {
|
||||
$scope.invalidPassword = true;
|
||||
}
|
||||
}, function (e) {
|
||||
Messages.error("Failure", e, "Unable to check password validity");
|
||||
});
|
||||
};
|
||||
}]);
|
55
app/components/sidebar/sidebar.html
Normal file
55
app/components/sidebar/sidebar.html
Normal file
|
@ -0,0 +1,55 @@
|
|||
<!-- Sidebar -->
|
||||
<div id="sidebar-wrapper">
|
||||
<ul class="sidebar">
|
||||
<li class="sidebar-main">
|
||||
<a ng-click="toggleSidebar()" class="interactive">
|
||||
<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>
|
||||
<li class="sidebar-title"><span>NAVIGATION</span></li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="dashboard">Dashboard <span class="menu-icon fa fa-tachometer"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="templates">App Templates <span class="menu-icon fa fa-rocket"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">
|
||||
<a ui-sref="services">Services <span class="menu-icon fa fa-list-alt"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="containers">Containers <span class="menu-icon fa fa-server"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="images">Images <span class="menu-icon fa fa-clone"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="networks">Networks <span class="menu-icon fa fa-sitemap"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="volumes">Volumes <span class="menu-icon fa fa-cubes"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="endpointMode.provider === 'DOCKER_STANDALONE'">
|
||||
<a ui-sref="events">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')">
|
||||
<a ui-sref="swarm">Swarm <span class="menu-icon fa fa-object-group"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="endpointMode.provider === 'DOCKER_STANDALONE'">
|
||||
<a ui-sref="docker">Docker <span class="menu-icon fa fa-th"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-list">
|
||||
<a ui-sref="settings">Settings <span class="menu-icon fa fa-wrench"></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<div class="col-xs-12">
|
||||
<a href="https://github.com/portainer/portainer" target="_blank">
|
||||
<i class="fa fa-github" aria-hidden="true"></i>
|
||||
Portainer {{ uiVersion }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Sidebar -->
|
10
app/components/sidebar/sidebarController.js
Normal file
10
app/components/sidebar/sidebarController.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
angular.module('sidebar', [])
|
||||
.controller('SidebarController', ['$scope', 'Settings', 'Config', 'Info',
|
||||
function ($scope, Settings, Config, Info) {
|
||||
|
||||
Config.$promise.then(function (c) {
|
||||
$scope.logo = c.logo;
|
||||
});
|
||||
|
||||
$scope.uiVersion = Settings.uiVersion;
|
||||
}]);
|
|
@ -16,14 +16,14 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>Nodes</td>
|
||||
<td ng-if="!swarm_mode">{{ swarm.Nodes }}</td>
|
||||
<td ng-if="swarm_mode">{{ info.Swarm.Nodes }}</td>
|
||||
<td ng-if="endpointMode.provider === 'DOCKER_SWARM'">{{ swarm.Nodes }}</td>
|
||||
<td ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">{{ info.Swarm.Nodes }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!swarm_mode">
|
||||
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
|
||||
<td>Images</td>
|
||||
<td>{{ info.Images }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!swarm_mode">
|
||||
<tr ng-if="endpointMode.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="!swarm_mode">
|
||||
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
|
||||
<td>Strategy</td>
|
||||
<td>{{ swarm.Strategy }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total CPU</td>
|
||||
<td ng-if="!swarm_mode">{{ info.NCPU }}</td>
|
||||
<td ng-if="swarm_mode">{{ totalCPU }}</td>
|
||||
<td ng-if="endpointMode.provider === 'DOCKER_SWARM'">{{ info.NCPU }}</td>
|
||||
<td ng-if="endpointMode.provider === 'DOCKER_SWARM_MODE'">{{ totalCPU }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total memory</td>
|
||||
<td ng-if="!swarm_mode">{{ info.MemTotal|humansize: 2 }}</td>
|
||||
<td ng-if="swarm_mode">{{ totalMemory|humansize: 2 }}</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>
|
||||
</tr>
|
||||
<tr ng-if="!swarm_mode">
|
||||
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
|
||||
<td>Operating system</td>
|
||||
<td>{{ info.OperatingSystem }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!swarm_mode">
|
||||
<tr ng-if="endpointMode.provider === 'DOCKER_SWARM'">
|
||||
<td>Kernel version</td>
|
||||
<td>{{ info.KernelVersion }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!swarm_mode">
|
||||
<tr ng-if="endpointMode.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="!swarm_mode">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="endpointMode.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="swarm_mode">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" ng-if="endpointMode.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">
|
||||
|
|
|
@ -7,7 +7,6 @@ function ($scope, Info, Version, Node, Settings) {
|
|||
$scope.info = {};
|
||||
$scope.docker = {};
|
||||
$scope.swarm = {};
|
||||
$scope.swarm_mode = false;
|
||||
$scope.totalCPU = 0;
|
||||
$scope.totalMemory = 0;
|
||||
$scope.pagination_count = Settings.pagination_count;
|
||||
|
@ -23,8 +22,7 @@ function ($scope, Info, Version, Node, Settings) {
|
|||
|
||||
Info.get({}, function (d) {
|
||||
$scope.info = d;
|
||||
if (!_.startsWith(d.ServerVersion, 'swarm')) {
|
||||
$scope.swarm_mode = true;
|
||||
if ($scope.endpointMode.provider === 'DOCKER_SWARM_MODE') {
|
||||
Node.query({}, function(d) {
|
||||
$scope.nodes = d;
|
||||
var CPU = 0, memory = 0;
|
||||
|
|
|
@ -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 && !swarm_mode">
|
||||
<div class="form-group" ng-if="globalNetworkCount === 0 && endpointMode.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="swarm_mode">
|
||||
<div class="form-group" ng-if="endpointMode.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="(!swarm || swarm && swarm_mode) && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="selectpicker form-control" ng-model="var.value">
|
||||
<select ng-if="endpointMode.provider !== 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="selectpicker form-control" ng-model="var.value">
|
||||
<option selected disabled hidden value="">Select a container</option>
|
||||
</select>
|
||||
<select ng-if="swarm && !swarm_mode && var.type === 'container'" ng-options="container|swarmcontainername for container in runningContainers" class="selectpicker form-control" ng-model="var.value">
|
||||
<select ng-if="endpointMode.provider === 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|swarmcontainername for container in runningContainers" class="selectpicker 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 }}">
|
||||
|
|
|
@ -204,11 +204,6 @@ function ($scope, $q, $state, $filter, $anchorScroll, Config, Info, Container, C
|
|||
|
||||
Config.$promise.then(function (c) {
|
||||
$scope.swarm = c.swarm;
|
||||
Info.get({}, function(info) {
|
||||
if ($scope.swarm && !_.startsWith(info.ServerVersion, 'swarm')) {
|
||||
$scope.swarm_mode = true;
|
||||
}
|
||||
});
|
||||
var containersToHideLabels = c.hiddenLabels;
|
||||
Network.query({}, function (d) {
|
||||
var networks = d;
|
||||
|
|
|
@ -4,7 +4,7 @@ angular
|
|||
var directive = {
|
||||
requires: '^rdHeader',
|
||||
transclude: true,
|
||||
template: '<div class="breadcrumb-links" ng-transclude></div>',
|
||||
template: '<div class="breadcrumb-links"><div class="pull-left" ng-transclude></div><div class="pull-right"><a ui-sref="auth({logout: true})" class="text-danger" style="margin-right: 25px;"><u>log out <i class="fa fa-sign-out" aria-hidden="true"></i></u></a></div></div>',
|
||||
restrict: 'E'
|
||||
};
|
||||
return directive;
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
angular
|
||||
.module('portainer')
|
||||
.directive('rdHeaderTitle', function rdHeaderTitle() {
|
||||
.directive('rdHeaderTitle', ['$rootScope', function rdHeaderTitle($rootScope) {
|
||||
var directive = {
|
||||
requires: '^rdHeader',
|
||||
scope: {
|
||||
title: '@'
|
||||
},
|
||||
link: function (scope, iElement, iAttrs) {
|
||||
scope.username = $rootScope.username;
|
||||
},
|
||||
transclude: true,
|
||||
template: '<div class="page">{{title}}<span class="header_title_content" ng-transclude><span></div>',
|
||||
template: '<div class="page white-space-normal">{{title}}<span class="header_title_content" ng-transclude></span><span class="pull-right user-box"><i class="fa fa-user-circle-o" aria-hidden="true"></i> {{username}}</span></div>',
|
||||
restrict: 'E'
|
||||
};
|
||||
return directive;
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -166,14 +166,6 @@ angular.module('portainer.services', ['ngResource', 'ngSanitize'])
|
|||
get: {method: 'GET'}
|
||||
});
|
||||
}])
|
||||
.factory('Auth', ['$resource', 'Settings', function AuthFactory($resource, Settings) {
|
||||
'use strict';
|
||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#check-auth-configuration
|
||||
return $resource(Settings.url + '/auth', {}, {
|
||||
get: {method: 'GET'},
|
||||
update: {method: 'POST'}
|
||||
});
|
||||
}])
|
||||
.factory('Info', ['$resource', 'Settings', function InfoFactory($resource, Settings) {
|
||||
'use strict';
|
||||
// http://docs.docker.com/reference/api/docker_remote_api_<%= remoteApiVersion %>/#display-system-wide-information
|
||||
|
@ -229,6 +221,89 @@ angular.module('portainer.services', ['ngResource', 'ngSanitize'])
|
|||
pagination_count: PAGINATION_MAX_ITEMS
|
||||
};
|
||||
}])
|
||||
.factory('Auth', ['$resource', 'AUTH_ENDPOINT', function AuthFactory($resource, AUTH_ENDPOINT) {
|
||||
'use strict';
|
||||
return $resource(AUTH_ENDPOINT, {}, {
|
||||
login: {
|
||||
method: 'POST'
|
||||
}
|
||||
});
|
||||
}])
|
||||
.factory('Users', ['$resource', function UsersFactory($resource) {
|
||||
'use strict';
|
||||
return $resource('/users/:username/:action', {}, {
|
||||
create: { method: 'POST' },
|
||||
get: {method: 'GET', params: { username: '@username' } },
|
||||
update: { method: 'PUT', params: { username: '@username' } },
|
||||
checkPassword: { method: 'POST', params: { username: '@username', action: 'passwd' } },
|
||||
checkAdminUser: {method: 'GET', params: { username: 'admin', action: 'check' }},
|
||||
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) {
|
||||
'use strict';
|
||||
return {
|
||||
init: function() {
|
||||
var jwt = localStorageService.get('JWT');
|
||||
if (jwt) {
|
||||
var tokenPayload = jwtHelper.decodeToken(jwt);
|
||||
$rootScope.username = tokenPayload.username;
|
||||
}
|
||||
},
|
||||
login: function(username, password) {
|
||||
return $q(function (resolve, reject) {
|
||||
Auth.login({username: username, password: password}).$promise
|
||||
.then(function(data) {
|
||||
localStorageService.set('JWT', data.jwt);
|
||||
$rootScope.username = username;
|
||||
resolve();
|
||||
}, function() {
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
logout: function() {
|
||||
localStorageService.remove('JWT');
|
||||
},
|
||||
isAuthenticated: function() {
|
||||
var jwt = localStorageService.get('JWT');
|
||||
return jwt && !jwtHelper.isTokenExpired(jwt);
|
||||
}
|
||||
};
|
||||
}])
|
||||
.factory('Messages', ['$rootScope', '$sanitize', function MessagesFactory($rootScope, $sanitize) {
|
||||
'use strict';
|
||||
return {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue