mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 13:55:21 +02:00
feat(global): introduce user teams and new UAC system (#868)
This commit is contained in:
parent
a380fd9adc
commit
5523fc9023
160 changed files with 7112 additions and 3166 deletions
|
@ -26,14 +26,14 @@ function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Config, Au
|
|||
.then(function success() {
|
||||
$state.go('dashboard');
|
||||
}, function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to connect to the Docker endpoint');
|
||||
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');
|
||||
Notifications.error('Failure', err, 'Unable to retrieve endpoints');
|
||||
});
|
||||
} else {
|
||||
Users.checkAdminUser({}, function () {},
|
||||
|
@ -41,7 +41,7 @@ function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Config, Au
|
|||
if (e.status === 404) {
|
||||
$scope.initPassword = true;
|
||||
} else {
|
||||
Notifications.error("Failure", e, 'Unable to verify administrator account existence');
|
||||
Notifications.error('Failure', e, 'Unable to verify administrator account existence');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ function ($scope, $state, $stateParams, $window, $timeout, $sanitize, Config, Au
|
|||
.then(function success() {
|
||||
$state.go('dashboard');
|
||||
}, function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to connect to the Docker endpoint');
|
||||
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
|
||||
});
|
||||
}
|
||||
else if (data.length === 0 && userDetails.role === 1) {
|
||||
|
|
126
app/components/common/accessControlForm/accessControlForm.html
Normal file
126
app/components/common/accessControlForm/accessControlForm.html
Normal file
|
@ -0,0 +1,126 @@
|
|||
<div ng-controller="AccessControlFormController">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Access control
|
||||
</div>
|
||||
<!-- access-control-switch -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="ownership" class="control-label text-left">
|
||||
Enable access control
|
||||
<portainer-tooltip position="bottom" message="When enabled, you can restrict the access and management of this resource."></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input name="ownership" type="checkbox" ng-model="formValues.enableAccessControl" ng-click="synchronizeFormData()"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !access-control-switch -->
|
||||
<!-- restricted-access -->
|
||||
<div class="form-group" ng-if="formValues.enableAccessControl" style="margin-bottom: 0">
|
||||
<div class="ownership_wrapper">
|
||||
<div ng-if="isAdmin">
|
||||
<input type="radio" id="access_administrators" ng-model="formValues.Ownership" ng-click="synchronizeFormData()" value="administrators">
|
||||
<label for="access_administrators">
|
||||
<div class="ownership_header">
|
||||
<i ng-class="'administrators' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Administrators
|
||||
</div>
|
||||
<p>I want to restrict the management of this resource to administrators only</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="isAdmin">
|
||||
<input type="radio" id="access_restricted" ng-model="formValues.Ownership" ng-click="synchronizeFormData()" value="restricted">
|
||||
<label for="access_restricted">
|
||||
<div class="ownership_header">
|
||||
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Restricted
|
||||
</div>
|
||||
<p>
|
||||
I want to restrict the management of this resource to a set of users and/or teams
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!isAdmin">
|
||||
<input type="radio" id="access_private" ng-model="formValues.Ownership" ng-click="synchronizeFormData()" value="private">
|
||||
<label for="access_private">
|
||||
<div class="ownership_header">
|
||||
<i ng-class="'private' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Private
|
||||
</div>
|
||||
<p>
|
||||
I want to this resource to be manageable by myself only
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!isAdmin && availableTeams.length > 0">
|
||||
<input type="radio" id="access_restricted" ng-model="formValues.Ownership" ng-click="synchronizeFormData()" value="restricted">
|
||||
<label for="access_restricted">
|
||||
<div class="ownership_header">
|
||||
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Restricted
|
||||
</div>
|
||||
<p ng-if="availableTeams.length === 1">
|
||||
I want any member of my team (<b>{{ availableTeams[0].Name }}</b>) to be able to manage this resource
|
||||
</p>
|
||||
<p ng-if="availableTeams.length > 1">
|
||||
I want to restrict the management of this resource to one or more of my teams
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- restricted-access -->
|
||||
<!-- authorized-teams -->
|
||||
<div class="form-group" ng-if="formValues.enableAccessControl && formValues.Ownership === 'restricted' && (isAdmin || (!isAdmin && availableTeams.length > 1))" >
|
||||
<div class="col-sm-12">
|
||||
<label for="group-access" class="control-label text-left">
|
||||
Authorized teams
|
||||
<portainer-tooltip ng-if="isAdmin && availableTeams.length > 0" position="bottom" message="You can select which teams(s) will be able to manage this resource."></portainer-tooltip>
|
||||
<portainer-tooltip ng-if="!isAdmin && availableTeams.length > 1" position="bottom" message="As you are a member of multiple teams, you can select which teams(s) will be able to manage this resource."></portainer-tooltip>
|
||||
</label>
|
||||
<span ng-if="isAdmin && availableTeams.length === 0" class="small text-muted" style="margin-left: 20px;">
|
||||
You have not yet created any team. Head over the <a ui-sref="teams">teams view</a> to manage user teams.</span>
|
||||
</span>
|
||||
<span isteven-multi-select
|
||||
ng-if="(isAdmin && availableTeams.length > 0) || (!isAdmin && availableTeams.length > 1)"
|
||||
input-model="availableTeams"
|
||||
output-model="formValues.Ownership_Teams"
|
||||
button-label="Name"
|
||||
item-label="Name"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
on-item-click="synchronizeFormData()"
|
||||
translation="{nothingSelected: 'Select one or more teams', search: 'Search...'}"
|
||||
style="margin-left: 20px;"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !authorized-teams -->
|
||||
<!-- authorized-users -->
|
||||
<div class="form-group" ng-if="formValues.enableAccessControl && formValues.Ownership === 'restricted' && isAdmin">
|
||||
<div class="col-sm-12">
|
||||
<label for="group-access" class="control-label text-left">
|
||||
Authorized users
|
||||
<portainer-tooltip ng-if="isAdmin && availableUsers.length > 0" position="bottom" message="You can select which user(s) will be able to manage this resource."></portainer-tooltip>
|
||||
</label>
|
||||
<span ng-if="availableUsers.length === 0" class="small text-muted" style="margin-left: 20px;">
|
||||
You have not yet created any user. Head over the <a ui-sref="users">users view</a> to manage users.</span>
|
||||
</span>
|
||||
<span isteven-multi-select
|
||||
ng-if="availableUsers.length > 0"
|
||||
input-model="availableUsers"
|
||||
output-model="formValues.Ownership_Users"
|
||||
button-label="Username"
|
||||
item-label="Username"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="Username"
|
||||
on-item-click="synchronizeFormData()"
|
||||
translation="{nothingSelected: 'Select one or more users', search: 'Search...'}"
|
||||
style="margin-left: 20px;"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !authorized-users -->
|
||||
</div>
|
|
@ -0,0 +1,55 @@
|
|||
angular.module('common.accesscontrol.form', [])
|
||||
.controller('AccessControlFormController', ['$q', '$scope', '$state', 'UserService', 'ResourceControlService', 'Notifications', 'Authentication', 'ModalService', 'ControllerDataPipeline',
|
||||
function ($q, $scope, $state, UserService, ResourceControlService, Notifications, Authentication, ModalService, ControllerDataPipeline) {
|
||||
|
||||
$scope.availableTeams = [];
|
||||
$scope.availableUsers = [];
|
||||
|
||||
$scope.formValues = {
|
||||
enableAccessControl: true,
|
||||
Ownership_Teams: [],
|
||||
Ownership_Users: [],
|
||||
Ownership: 'private'
|
||||
};
|
||||
|
||||
$scope.synchronizeFormData = function() {
|
||||
ControllerDataPipeline.setAccessControlFormData($scope.formValues.enableAccessControl,
|
||||
$scope.formValues.Ownership, $scope.formValues.Ownership_Users, $scope.formValues.Ownership_Teams);
|
||||
};
|
||||
|
||||
function initAccessControlForm() {
|
||||
$('#loadingViewSpinner').show();
|
||||
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true: false;
|
||||
$scope.isAdmin = isAdmin;
|
||||
|
||||
if (isAdmin) {
|
||||
$scope.formValues.Ownership = 'administrators';
|
||||
}
|
||||
|
||||
$q.all({
|
||||
availableTeams: UserService.userTeams(userDetails.ID),
|
||||
availableUsers: isAdmin ? UserService.users(false) : []
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.availableUsers = data.availableUsers;
|
||||
|
||||
var availableTeams = data.availableTeams;
|
||||
$scope.availableTeams = availableTeams;
|
||||
if (!isAdmin && availableTeams.length === 1) {
|
||||
$scope.formValues.Ownership_Teams = availableTeams;
|
||||
}
|
||||
|
||||
$scope.synchronizeFormData();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve access control information');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
initAccessControlForm();
|
||||
}]);
|
178
app/components/common/accessControlPanel/accessControlPanel.html
Normal file
178
app/components/common/accessControlPanel/accessControlPanel.html
Normal file
|
@ -0,0 +1,178 @@
|
|||
<div class="row" ng-controller="AccessControlPanelController">
|
||||
<div class="col-sm-12" ng-if="state.displayAccessControlPanel">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-eye" title="Access control"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<!-- ownership -->
|
||||
<tr>
|
||||
<td>Ownership</td>
|
||||
<td>
|
||||
<i ng-class="resourceControl.Ownership | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
<span ng-if="!resourceControl">
|
||||
public
|
||||
<portainer-tooltip message="This resource can be managed by any user with access to this endpoint." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
||||
</span>
|
||||
<span ng-if="resourceControl">
|
||||
{{ resourceControl.Ownership }}
|
||||
<portainer-tooltip ng-if="resourceControl.Ownership === 'administrators'" message="This resource can only be managed by administrators." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
||||
<portainer-tooltip ng-if="resourceControl.Ownership === 'private'" message="Management of this resource is restricted to a single user." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
||||
<portainer-tooltip ng-if="resourceControl.Ownership === 'restricted'" message="This resource can be managed by a restricted set of users and/or teams." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- !ownership -->
|
||||
<tr ng-if="resourceControl.Type === 2 && resourceType === 'container'">
|
||||
<td colspan="2">
|
||||
<i class="fa fa-info-circle" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Access control on this resource is inherited from the following service: <a ui-sref="service({ id: resourceControl.ResourceId })">{{ resourceControl.ResourceId | truncate }}</a>
|
||||
<portainer-tooltip message="Access control applied on a service is also applied on each container of that service." position="bottom" style="margin-left: 2px;"></portainer-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="resourceControl.Type === 1 && resourceType === 'volume'">
|
||||
<td colspan="2">
|
||||
<i class="fa fa-info-circle" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Access control on this resource is inherited from the following container: <a ui-sref="container({ id: resourceControl.ResourceId })">{{ resourceControl.ResourceId | truncate }}</a>
|
||||
<portainer-tooltip message="Access control applied on a container created using a template is also applied on each volume associated to the container." position="bottom" style="margin-left: 2px;"></portainer-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- authorized-users -->
|
||||
<tr ng-if="resourceControl.UserAccesses.length > 0">
|
||||
<td>Authorized users</td>
|
||||
<td>
|
||||
<span ng-repeat="user in authorizedUsers">{{user.Username}}{{$last ? '' : ', '}} </span>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- !authorized-users -->
|
||||
<!-- authorized-teams -->
|
||||
<tr ng-if="resourceControl.TeamAccesses.length > 0">
|
||||
<td>Authorized teams</td>
|
||||
<td>
|
||||
<span ng-repeat="team in authorizedTeams">{{team.Name}}{{$last ? '' : ', '}} </span>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- !authorized-teams -->
|
||||
<!-- edit-ownership -->
|
||||
<tr ng-if="!(resourceControl.Type === 1 && resourceType === 'volume') && !(resourceControl.Type === 2 && resourceType === 'container') && !state.editOwnership && (isAdmin || state.canEditOwnership)">
|
||||
<td colspan="2">
|
||||
<a class="btn-outline-secondary" ng-click="state.editOwnership = true"><i class="fa fa-edit space-right" aria-hidden="true"></i>Change ownership</a>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- !edit-ownership -->
|
||||
<!-- edit-ownership-choices -->
|
||||
<tr ng-if="state.editOwnership">
|
||||
<td colspan="2">
|
||||
<div class="ownership_wrapper">
|
||||
<div ng-if="isAdmin">
|
||||
<input type="radio" id="access_administrators" ng-model="formValues.Ownership" value="administrators">
|
||||
<label for="access_administrators">
|
||||
<div class="ownership_header">
|
||||
<i ng-class="'administrators' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Administrators
|
||||
</div>
|
||||
<p>I want to restrict the management of this resource to administrators only</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="isAdmin">
|
||||
<input type="radio" id="access_restricted" ng-model="formValues.Ownership" value="restricted">
|
||||
<label for="access_restricted">
|
||||
<div class="ownership_header">
|
||||
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Restricted
|
||||
</div>
|
||||
<p>
|
||||
I want to restrict the management of this resource to a set of users and/or teams
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-if="!isAdmin && state.canChangeOwnershipToTeam && availableTeams.length > 0">
|
||||
<input type="radio" id="access_restricted" ng-model="formValues.Ownership" value="restricted">
|
||||
<label for="access_restricted">
|
||||
<div class="ownership_header">
|
||||
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Restricted
|
||||
</div>
|
||||
<p ng-if="availableTeams.length === 1">
|
||||
I want any member of my team (<b>{{ availableTeams[0].Name }}</b>) to be able to manage this resource
|
||||
</p>
|
||||
<p ng-if="availableTeams.length > 1">
|
||||
I want to restrict the management of this resource to one or more of my teams
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="access_public" ng-model="formValues.Ownership" value="public">
|
||||
<label for="access_public">
|
||||
<div class="ownership_header">
|
||||
<i ng-class="'public' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Public
|
||||
</div>
|
||||
<p>I want any user with access to this endpoint to be able to manage this resource</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- edit-ownership-choices -->
|
||||
<!-- select-teams -->
|
||||
<tr ng-if="state.editOwnership && formValues.Ownership === 'restricted' && (isAdmin || !isAdmin && availableTeams.length > 1)">
|
||||
<td colspan="2">
|
||||
<span>Teams</span>
|
||||
<span ng-if="isAdmin && availableTeams.length === 0" class="small text-muted" style="margin-left: 10px;">
|
||||
You have not yet created any team. Head over the <a ui-sref="teams">teams view</a> to manage user teams.</span>
|
||||
</span>
|
||||
<span isteven-multi-select
|
||||
ng-if="(isAdmin && availableTeams.length > 0) || (!isAdmin && availableTeams.length > 1)"
|
||||
input-model="availableTeams"
|
||||
output-model="formValues.Ownership_Teams"
|
||||
button-label="Name"
|
||||
item-label="Name"
|
||||
tick-property="selected"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
max-labels="3"
|
||||
translation="{nothingSelected: 'Select one or more teams', search: 'Search...'}"
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- !select-teams -->
|
||||
<!-- select-users -->
|
||||
<tr ng-if="isAdmin && state.editOwnership && formValues.Ownership === 'restricted'">
|
||||
<td colspan="2">
|
||||
<span>Users</span>
|
||||
<span ng-if="availableUsers.length === 0" class="small text-muted" style="margin-left: 10px;">
|
||||
You have not yet created any user. Head over the <a ui-sref="users">users view</a> to manage users.</span>
|
||||
</span>
|
||||
<span isteven-multi-select
|
||||
ng-if="availableUsers.length > 0"
|
||||
input-model="availableUsers"
|
||||
output-model="formValues.Ownership_Users"
|
||||
button-label="Username"
|
||||
item-label="Username"
|
||||
tick-property="selected"
|
||||
helper-elements="filter"
|
||||
search-property="Username"
|
||||
max-labels="3"
|
||||
translation="{nothingSelected: 'Select one or more users', search: 'Search...'}"
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- !select-users -->
|
||||
<!-- ownership-actions -->
|
||||
<tr ng-if="state.editOwnership">
|
||||
<td colspan="2">
|
||||
<div>
|
||||
<a type="button" class="btn btn-default btn-sm" ng-click="state.editOwnership = false">Cancel</a>
|
||||
<a type="button" class="btn btn-primary btn-sm" ng-click="confirmUpdateOwnership()">Update ownership</a>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- !ownership-actions -->
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,158 @@
|
|||
angular.module('common.accesscontrol.panel', [])
|
||||
.controller('AccessControlPanelController', ['$q', '$scope', '$state', 'UserService', 'ResourceControlService', 'Notifications', 'Authentication', 'ModalService', 'ControllerDataPipeline', 'FormValidator',
|
||||
function ($q, $scope, $state, UserService, ResourceControlService, Notifications, Authentication, ModalService, ControllerDataPipeline, FormValidator) {
|
||||
|
||||
$scope.state = {
|
||||
displayAccessControlPanel: false,
|
||||
canEditOwnership: false,
|
||||
editOwnership: false,
|
||||
formValidationError: ''
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
Ownership: 'public',
|
||||
Ownership_Users: [],
|
||||
Ownership_Teams: []
|
||||
};
|
||||
|
||||
$scope.authorizedUsers = [];
|
||||
$scope.availableUsers = [];
|
||||
$scope.authorizedTeams = [];
|
||||
$scope.availableTeams = [];
|
||||
|
||||
$scope.confirmUpdateOwnership = function (force) {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
ModalService.confirmAccessControlUpdate(function (confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
updateOwnership();
|
||||
});
|
||||
};
|
||||
|
||||
function processOwnershipFormValues() {
|
||||
var userIds = [];
|
||||
angular.forEach($scope.formValues.Ownership_Users, function(user) {
|
||||
userIds.push(user.Id);
|
||||
});
|
||||
var teamIds = [];
|
||||
angular.forEach($scope.formValues.Ownership_Teams, function(team) {
|
||||
teamIds.push(team.Id);
|
||||
});
|
||||
var administratorsOnly = $scope.formValues.Ownership === 'administrators' ? true : false;
|
||||
|
||||
return {
|
||||
ownership: $scope.formValues.Ownership,
|
||||
authorizedUserIds: administratorsOnly ? [] : userIds,
|
||||
authorizedTeamIds: administratorsOnly ? [] : teamIds,
|
||||
administratorsOnly: administratorsOnly
|
||||
};
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
|
||||
var accessControlData = {
|
||||
ownership: $scope.formValues.Ownership,
|
||||
authorizedUsers: $scope.formValues.Ownership_Users,
|
||||
authorizedTeams: $scope.formValues.Ownership_Teams
|
||||
};
|
||||
var isAdmin = $scope.isAdmin;
|
||||
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
if (error) {
|
||||
$scope.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateOwnership() {
|
||||
$('#loadingViewSpinner').show();
|
||||
|
||||
var accessControlData = ControllerDataPipeline.getAccessControlData();
|
||||
var resourceId = accessControlData.resourceId;
|
||||
var ownershipParameters = processOwnershipFormValues();
|
||||
|
||||
ResourceControlService.applyResourceControlChange(accessControlData.resourceType, resourceId,
|
||||
$scope.resourceControl, ownershipParameters)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Access control successfully updated');
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update access control');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function initAccessControlPanel() {
|
||||
$('#loadingViewSpinner').show();
|
||||
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true: false;
|
||||
var userId = userDetails.ID;
|
||||
$scope.isAdmin = isAdmin;
|
||||
|
||||
var accessControlData = ControllerDataPipeline.getAccessControlData();
|
||||
var resourceControl = accessControlData.resourceControl;
|
||||
$scope.resourceType = accessControlData.resourceType;
|
||||
$scope.resourceControl = resourceControl;
|
||||
|
||||
if (isAdmin) {
|
||||
if (resourceControl) {
|
||||
$scope.formValues.Ownership = resourceControl.Ownership === 'private' ? 'restricted' : resourceControl.Ownership;
|
||||
} else {
|
||||
$scope.formValues.Ownership = 'public';
|
||||
}
|
||||
} else {
|
||||
$scope.formValues.Ownership = 'public';
|
||||
}
|
||||
|
||||
ResourceControlService.retrieveOwnershipDetails(resourceControl)
|
||||
.then(function success(data) {
|
||||
$scope.authorizedUsers = data.authorizedUsers;
|
||||
$scope.authorizedTeams = data.authorizedTeams;
|
||||
return ResourceControlService.retrieveUserPermissionsOnResource(userId, isAdmin, resourceControl);
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.state.canEditOwnership = data.isPartOfRestrictedUsers || data.isLeaderOfAnyRestrictedTeams;
|
||||
$scope.state.canChangeOwnershipToTeam = data.isPartOfRestrictedUsers;
|
||||
|
||||
return $q.all({
|
||||
availableUsers: isAdmin ? UserService.users(false) : [],
|
||||
availableTeams: isAdmin || data.isPartOfRestrictedUsers ? UserService.userTeams(userId) : []
|
||||
});
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.availableUsers = data.availableUsers;
|
||||
angular.forEach($scope.availableUsers, function(user) {
|
||||
var found = _.find($scope.authorizedUsers, { Id: user.Id });
|
||||
if (found) {
|
||||
user.selected = true;
|
||||
}
|
||||
});
|
||||
$scope.availableTeams = data.availableTeams;
|
||||
angular.forEach(data.availableTeams, function(team) {
|
||||
var found = _.find($scope.authorizedTeams, { Id: team.Id });
|
||||
if (found) {
|
||||
team.selected = true;
|
||||
}
|
||||
});
|
||||
if (data.availableTeams.length === 1) {
|
||||
$scope.formValues.Ownership_Teams.push(data.availableTeams[0]);
|
||||
}
|
||||
$scope.state.displayAccessControlPanel = true;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve access control information');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
initAccessControlPanel();
|
||||
}]);
|
|
@ -87,6 +87,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-include="'app/components/common/accessControlPanel/accessControlPanel.html'" ng-if="container && applicationState.application.authentication"></div>
|
||||
|
||||
<div ng-if="container.State.Health" class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('container', [])
|
||||
.controller('ContainerController', ['$scope', '$state','$stateParams', '$filter', 'Container', 'ContainerCommit', 'ImageHelper', 'Network', 'Notifications', 'Pagination', 'ModalService',
|
||||
function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, ImageHelper, Network, Notifications, Pagination, ModalService) {
|
||||
.controller('ContainerController', ['$scope', '$state','$stateParams', '$filter', 'Container', 'ContainerCommit', 'ContainerService', 'ImageHelper', 'Network', 'Notifications', 'Pagination', 'ModalService', 'ControllerDataPipeline',
|
||||
function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, ContainerService, ImageHelper, Network, Notifications, Pagination, ModalService, ControllerDataPipeline) {
|
||||
$scope.activityTime = 0;
|
||||
$scope.portBindings = [];
|
||||
$scope.config = {
|
||||
|
@ -17,25 +17,27 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
var update = function () {
|
||||
$('#loadingViewSpinner').show();
|
||||
Container.get({id: $stateParams.id}, function (d) {
|
||||
$scope.container = d;
|
||||
var container = new ContainerDetailsViewModel(d);
|
||||
$scope.container = container;
|
||||
ControllerDataPipeline.setAccessControlData('container', $stateParams.id, container.ResourceControl);
|
||||
$scope.container.edit = false;
|
||||
$scope.container.newContainerName = $filter('trimcontainername')(d.Name);
|
||||
$scope.container.newContainerName = $filter('trimcontainername')(container.Name);
|
||||
|
||||
if (d.State.Running) {
|
||||
$scope.activityTime = moment.duration(moment(d.State.StartedAt).utc().diff(moment().utc())).humanize();
|
||||
} else if (d.State.Status === "created") {
|
||||
$scope.activityTime = moment.duration(moment(d.Created).utc().diff(moment().utc())).humanize();
|
||||
if (container.State.Running) {
|
||||
$scope.activityTime = moment.duration(moment(container.State.StartedAt).utc().diff(moment().utc())).humanize();
|
||||
} else if (container.State.Status === 'created') {
|
||||
$scope.activityTime = moment.duration(moment(container.Created).utc().diff(moment().utc())).humanize();
|
||||
} else {
|
||||
$scope.activityTime = moment.duration(moment().utc().diff(moment(d.State.FinishedAt).utc())).humanize();
|
||||
$scope.activityTime = moment.duration(moment().utc().diff(moment(container.State.FinishedAt).utc())).humanize();
|
||||
}
|
||||
|
||||
$scope.portBindings = [];
|
||||
if (d.NetworkSettings.Ports) {
|
||||
angular.forEach(Object.keys(d.NetworkSettings.Ports), function(portMapping) {
|
||||
if (d.NetworkSettings.Ports[portMapping]) {
|
||||
if (container.NetworkSettings.Ports) {
|
||||
angular.forEach(Object.keys(container.NetworkSettings.Ports), function(portMapping) {
|
||||
if (container.NetworkSettings.Ports[portMapping]) {
|
||||
var mapping = {};
|
||||
mapping.container = portMapping;
|
||||
mapping.host = d.NetworkSettings.Ports[portMapping][0].HostIp + ':' + d.NetworkSettings.Ports[portMapping][0].HostPort;
|
||||
mapping.host = container.NetworkSettings.Ports[portMapping][0].HostIp + ':' + container.NetworkSettings.Ports[portMapping][0].HostPort;
|
||||
$scope.portBindings.push(mapping);
|
||||
}
|
||||
});
|
||||
|
@ -43,7 +45,7 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
$('#loadingViewSpinner').hide();
|
||||
}, function (e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to retrieve container info");
|
||||
Notifications.error('Failure', e, 'Unable to retrieve container info');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -51,10 +53,10 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
$('#loadingViewSpinner').show();
|
||||
Container.start({id: $scope.container.Id}, {}, function (d) {
|
||||
update();
|
||||
Notifications.success("Container started", $stateParams.id);
|
||||
Notifications.success('Container started', $stateParams.id);
|
||||
}, function (e) {
|
||||
update();
|
||||
Notifications.error("Failure", e, "Unable to start container");
|
||||
Notifications.error('Failure', e, 'Unable to start container');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -62,10 +64,10 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
$('#loadingViewSpinner').show();
|
||||
Container.stop({id: $stateParams.id}, function (d) {
|
||||
update();
|
||||
Notifications.success("Container stopped", $stateParams.id);
|
||||
Notifications.success('Container stopped', $stateParams.id);
|
||||
}, function (e) {
|
||||
update();
|
||||
Notifications.error("Failure", e, "Unable to stop container");
|
||||
Notifications.error('Failure', e, 'Unable to stop container');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -73,10 +75,10 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
$('#loadingViewSpinner').show();
|
||||
Container.kill({id: $stateParams.id}, function (d) {
|
||||
update();
|
||||
Notifications.success("Container killed", $stateParams.id);
|
||||
Notifications.success('Container killed', $stateParams.id);
|
||||
}, function (e) {
|
||||
update();
|
||||
Notifications.error("Failure", e, "Unable to kill container");
|
||||
Notifications.error('Failure', e, 'Unable to kill container');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -88,11 +90,11 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
ContainerCommit.commit({id: $stateParams.id, tag: imageConfig.tag, repo: imageConfig.repo}, function (d) {
|
||||
$('#createImageSpinner').hide();
|
||||
update();
|
||||
Notifications.success("Container commited", $stateParams.id);
|
||||
Notifications.success('Container commited', $stateParams.id);
|
||||
}, function (e) {
|
||||
$('#createImageSpinner').hide();
|
||||
update();
|
||||
Notifications.error("Failure", e, "Unable to commit container");
|
||||
Notifications.error('Failure', e, 'Unable to commit container');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -100,10 +102,10 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
$('#loadingViewSpinner').show();
|
||||
Container.pause({id: $stateParams.id}, function (d) {
|
||||
update();
|
||||
Notifications.success("Container paused", $stateParams.id);
|
||||
Notifications.success('Container paused', $stateParams.id);
|
||||
}, function (e) {
|
||||
update();
|
||||
Notifications.error("Failure", e, "Unable to pause container");
|
||||
Notifications.error('Failure', e, 'Unable to pause container');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -111,10 +113,10 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
$('#loadingViewSpinner').show();
|
||||
Container.unpause({id: $stateParams.id}, function (d) {
|
||||
update();
|
||||
Notifications.success("Container unpaused", $stateParams.id);
|
||||
Notifications.success('Container unpaused', $stateParams.id);
|
||||
}, function (e) {
|
||||
update();
|
||||
Notifications.error("Failure", e, "Unable to unpause container");
|
||||
Notifications.error('Failure', e, 'Unable to unpause container');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -138,18 +140,16 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
|
||||
$scope.remove = function(cleanAssociatedVolumes) {
|
||||
$('#loadingViewSpinner').show();
|
||||
Container.remove({id: $stateParams.id, v: (cleanAssociatedVolumes) ? 1 : 0, force: true}, function (d) {
|
||||
if (d.message) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", d, "Unable to remove container");
|
||||
}
|
||||
else {
|
||||
$state.go('containers', {}, {reload: true});
|
||||
Notifications.success("Container removed", $stateParams.id);
|
||||
}
|
||||
}, function (e) {
|
||||
update();
|
||||
Notifications.error("Failure", e, "Unable to remove container");
|
||||
ContainerService.remove($scope.container, cleanAssociatedVolumes)
|
||||
.then(function success() {
|
||||
Notifications.success('Container successfully removed');
|
||||
$state.go('containers', {}, {reload: true});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove container');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -157,24 +157,24 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
$('#loadingViewSpinner').show();
|
||||
Container.restart({id: $stateParams.id}, function (d) {
|
||||
update();
|
||||
Notifications.success("Container restarted", $stateParams.id);
|
||||
Notifications.success('Container restarted', $stateParams.id);
|
||||
}, function (e) {
|
||||
update();
|
||||
Notifications.error("Failure", e, "Unable to restart container");
|
||||
Notifications.error('Failure', e, 'Unable to restart container');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.renameContainer = function () {
|
||||
Container.rename({id: $stateParams.id, 'name': $scope.container.newContainerName}, function (d) {
|
||||
if (d.message) {
|
||||
if (container.message) {
|
||||
$scope.container.newContainerName = $scope.container.Name;
|
||||
Notifications.error("Unable to rename container", {}, d.message);
|
||||
Notifications.error('Unable to rename container', {}, container.message);
|
||||
} else {
|
||||
$scope.container.Name = $scope.container.newContainerName;
|
||||
Notifications.success("Container successfully renamed", d.name);
|
||||
Notifications.success('Container successfully renamed', container.name);
|
||||
}
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'Unable to rename container');
|
||||
Notifications.error('Failure', e, 'Unable to rename container');
|
||||
});
|
||||
$scope.container.edit = false;
|
||||
};
|
||||
|
@ -182,17 +182,17 @@ function ($scope, $state, $stateParams, $filter, Container, ContainerCommit, Ima
|
|||
$scope.containerLeaveNetwork = function containerLeaveNetwork(container, networkId) {
|
||||
$('#loadingViewSpinner').show();
|
||||
Network.disconnect({id: networkId}, { Container: $stateParams.id, Force: false }, function (d) {
|
||||
if (d.message) {
|
||||
if (container.message) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Error", d, "Unable to disconnect container from network");
|
||||
Notifications.error('Error', d, 'Unable to disconnect container from network');
|
||||
} else {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.success("Container left network", $stateParams.id);
|
||||
Notifications.success('Container left network', $stateParams.id);
|
||||
$state.go('container', {id: $stateParams.id}, {reload: true});
|
||||
}
|
||||
}, function (e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to disconnect container from network");
|
||||
Notifications.error('Failure', e, 'Unable to disconnect container from network');
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ function ($scope, $stateParams, Settings, Container, Image, Exec, $timeout, Endp
|
|||
Container.get({id: $stateParams.id}, function(d) {
|
||||
$scope.container = d;
|
||||
if (d.message) {
|
||||
Notifications.error("Error", d, 'Unable to retrieve container details');
|
||||
Notifications.error('Error', d, 'Unable to retrieve container details');
|
||||
$('#loadingViewSpinner').hide();
|
||||
} else {
|
||||
Image.get({id: d.Image}, function(imgData) {
|
||||
|
@ -26,12 +26,12 @@ function ($scope, $stateParams, Settings, Container, Image, Exec, $timeout, Endp
|
|||
$scope.state.loaded = true;
|
||||
$('#loadingViewSpinner').hide();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'Unable to retrieve image details');
|
||||
Notifications.error('Failure', e, 'Unable to retrieve image details');
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'Unable to retrieve container details');
|
||||
Notifications.error('Failure', e, 'Unable to retrieve container details');
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
|
||||
|
@ -45,13 +45,13 @@ function ($scope, $stateParams, Settings, Container, Image, Exec, $timeout, Endp
|
|||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Tty: true,
|
||||
Cmd: $scope.state.command.replace(" ", ",").split(",")
|
||||
Cmd: $scope.state.command.replace(' ', ',').split(',')
|
||||
};
|
||||
|
||||
Container.exec(execConfig, function(d) {
|
||||
if (d.message) {
|
||||
$('#loadConsoleSpinner').hide();
|
||||
Notifications.error("Error", {}, d.message);
|
||||
Notifications.error('Error', {}, d.message);
|
||||
} else {
|
||||
var execId = d.Id;
|
||||
resizeTTY(execId, termHeight, termWidth);
|
||||
|
@ -65,7 +65,7 @@ function ($scope, $stateParams, Settings, Container, Image, Exec, $timeout, Endp
|
|||
}
|
||||
}, function (e) {
|
||||
$('#loadConsoleSpinner').hide();
|
||||
Notifications.error("Failure", e, 'Unable to start an exec instance');
|
||||
Notifications.error('Failure', e, 'Unable to start an exec instance');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -86,7 +86,7 @@ function ($scope, $stateParams, Settings, Container, Image, Exec, $timeout, Endp
|
|||
Notifications.error('Error', {}, 'Unable to resize TTY');
|
||||
}
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", {}, 'Unable to resize TTY');
|
||||
Notifications.error('Failure', {}, 'Unable to resize TTY');
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ function ($scope, $stateParams, $anchorScroll, ContainerLogs, Container) {
|
|||
$('#loadingViewSpinner').hide();
|
||||
}, function (e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to retrieve container info");
|
||||
Notifications.error('Failure', e, 'Unable to retrieve container info');
|
||||
});
|
||||
|
||||
function getLogs() {
|
||||
|
@ -60,7 +60,7 @@ function ($scope, $stateParams, $anchorScroll, ContainerLogs, Container) {
|
|||
getLogs();
|
||||
var logIntervalId = window.setInterval(getLogs, 5000);
|
||||
|
||||
$scope.$on("$destroy", function () {
|
||||
$scope.$on('$destroy', function () {
|
||||
// clearing interval when view changes
|
||||
clearInterval(logIntervalId);
|
||||
});
|
||||
|
|
|
@ -91,10 +91,10 @@
|
|||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ui-sref="containers" ng-click="order('Metadata.ResourceControl.OwnerId')">
|
||||
<a ui-sref="containers" ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'Metadata.ResourceControl.OwnerId' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Metadata.ResourceControl.OwnerId' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
|
@ -118,34 +118,9 @@
|
|||
<span ng-if="container.Ports.length == 0" >-</span>
|
||||
</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span ng-if="!container.Metadata.ResourceControl">
|
||||
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||
<span ng-if="container.Labels['com.docker.swarm.service.id']">
|
||||
Public service
|
||||
</span>
|
||||
<span ng-if="!container.Labels['com.docker.swarm.service.id']">
|
||||
Public
|
||||
</span>
|
||||
</span>
|
||||
<span ng-if="container.Metadata.ResourceControl.OwnerId === user.ID">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
<span ng-if="container.Labels['com.docker.swarm.service.id']">
|
||||
Private service
|
||||
</span>
|
||||
<span ng-if="!container.Labels['com.docker.swarm.service.id']">
|
||||
Private
|
||||
<a ng-click="switchOwnership(container)" class="interactive"><i class="fa fa-eye" aria-hidden="true" style="margin-left: 7px;"></i> Switch to public</a>
|
||||
</span>
|
||||
</span>
|
||||
<span ng-if="container.Metadata.ResourceControl && container.Metadata.ResourceControl.OwnerId !== user.ID">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
<span ng-if="container.Labels['com.docker.swarm.service.id']">
|
||||
Private service <span ng-if="container.Owner">(owner: {{ container.Owner }})</span>
|
||||
</span>
|
||||
<span ng-if="!container.Labels['com.docker.swarm.service.id']">
|
||||
Private <span ng-if="container.Owner">(owner: {{ container.Owner }})</span>
|
||||
<a ng-click="switchOwnership(container)" class="interactive"><i class="fa fa-eye" aria-hidden="true" style="margin-left: 7px;"></i> Switch to public</a>
|
||||
</span>
|
||||
<span>
|
||||
<i ng-class="container.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ container.ResourceControl.Ownership ? container.ResourceControl.Ownership : container.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('containers', [])
|
||||
.controller('ContainersController', ['$q', '$scope', '$filter', 'Container', 'ContainerHelper', 'Info', 'Settings', 'Notifications', 'Config', 'Pagination', 'EntityListService', 'ModalService', 'Authentication', 'ResourceControlService', 'UserService', 'EndpointProvider',
|
||||
function ($q, $scope, $filter, Container, ContainerHelper, Info, Settings, Notifications, Config, Pagination, EntityListService, ModalService, Authentication, ResourceControlService, UserService, EndpointProvider) {
|
||||
.controller('ContainersController', ['$q', '$scope', '$filter', 'Container', 'ContainerService', 'ContainerHelper', 'Info', 'Settings', 'Notifications', 'Config', 'Pagination', 'EntityListService', 'ModalService', 'ResourceControlService', 'EndpointProvider',
|
||||
function ($q, $scope, $filter, Container, ContainerService, ContainerHelper, Info, Settings, Notifications, Config, Pagination, EntityListService, ModalService, ResourceControlService, EndpointProvider) {
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('containers');
|
||||
$scope.state.displayAll = Settings.displayAll;
|
||||
|
@ -20,51 +20,8 @@ angular.module('containers', [])
|
|||
|
||||
$scope.cleanAssociatedVolumes = false;
|
||||
|
||||
function removeContainerResourceControl(container) {
|
||||
volumeResourceControlQueries = [];
|
||||
angular.forEach(container.Mounts, function (volume) {
|
||||
volumeResourceControlQueries.push(ResourceControlService.removeVolumeResourceControl(container.Metadata.ResourceControl.OwnerId, volume.Name));
|
||||
});
|
||||
|
||||
$q.all(volumeResourceControlQueries)
|
||||
.then(function success() {
|
||||
return ResourceControlService.removeContainerResourceControl(container.Metadata.ResourceControl.OwnerId, container.Id);
|
||||
})
|
||||
.then(function success() {
|
||||
delete container.Metadata.ResourceControl;
|
||||
Notifications.success('Ownership changed to public', container.Id);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to change container ownership");
|
||||
});
|
||||
}
|
||||
|
||||
$scope.switchOwnership = function(container) {
|
||||
ModalService.confirmContainerOwnershipChange(function (confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
removeContainerResourceControl(container);
|
||||
});
|
||||
};
|
||||
|
||||
function mapUsersToContainers(users) {
|
||||
angular.forEach($scope.containers, function (container) {
|
||||
if (container.Metadata) {
|
||||
var containerRC = container.Metadata.ResourceControl;
|
||||
if (containerRC && containerRC.OwnerId !== $scope.user.ID) {
|
||||
angular.forEach(users, function (user) {
|
||||
if (containerRC.OwnerId === user.Id) {
|
||||
container.Owner = user.Username;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var update = function (data) {
|
||||
$('#loadContainersSpinner').show();
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
$scope.user = userDetails;
|
||||
$scope.state.selectedItemCount = 0;
|
||||
Container.query(data, function (d) {
|
||||
var containers = d;
|
||||
|
@ -87,23 +44,10 @@ angular.module('containers', [])
|
|||
}
|
||||
return model;
|
||||
});
|
||||
if (userDetails.role === 1) {
|
||||
UserService.users()
|
||||
.then(function success(data) {
|
||||
mapUsersToContainers(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to retrieve users");
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadContainersSpinner').hide();
|
||||
});
|
||||
} else {
|
||||
$('#loadContainersSpinner').hide();
|
||||
}
|
||||
$('#loadContainersSpinner').hide();
|
||||
}, function (e) {
|
||||
$('#loadContainersSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to retrieve containers");
|
||||
Notifications.error('Failure', e, 'Unable to retrieve containers');
|
||||
$scope.containers = [];
|
||||
});
|
||||
};
|
||||
|
@ -123,56 +67,44 @@ angular.module('containers', [])
|
|||
counter = counter + 1;
|
||||
if (action === Container.start) {
|
||||
action({id: c.Id}, {}, function (d) {
|
||||
Notifications.success("Container " + msg, c.Id);
|
||||
Notifications.success('Container ' + msg, c.Id);
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to start container");
|
||||
Notifications.error('Failure', e, 'Unable to start container');
|
||||
complete();
|
||||
});
|
||||
}
|
||||
else if (action === Container.remove) {
|
||||
action({id: c.Id, v: ($scope.cleanAssociatedVolumes) ? 1 : 0, force: true}, function (d) {
|
||||
if (d.message) {
|
||||
Notifications.error("Error", d, "Unable to remove container");
|
||||
}
|
||||
else {
|
||||
if (c.Metadata && c.Metadata.ResourceControl) {
|
||||
ResourceControlService.removeContainerResourceControl(c.Metadata.ResourceControl.OwnerId, c.Id)
|
||||
.then(function success() {
|
||||
Notifications.success("Container " + msg, c.Id);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to remove container ownership");
|
||||
});
|
||||
} else {
|
||||
Notifications.success("Container " + msg, c.Id);
|
||||
}
|
||||
}
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'Unable to remove container');
|
||||
ContainerService.remove(c, $scope.cleanAssociatedVolumes)
|
||||
.then(function success() {
|
||||
Notifications.success('Container successfully removed');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove container');
|
||||
})
|
||||
.finally(function final() {
|
||||
complete();
|
||||
});
|
||||
}
|
||||
else if (action === Container.pause) {
|
||||
action({id: c.Id}, function (d) {
|
||||
if (d.message) {
|
||||
Notifications.success("Container is already paused", c.Id);
|
||||
Notifications.success('Container is already paused', c.Id);
|
||||
} else {
|
||||
Notifications.success("Container " + msg, c.Id);
|
||||
Notifications.success('Container ' + msg, c.Id);
|
||||
}
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'Unable to pause container');
|
||||
Notifications.error('Failure', e, 'Unable to pause container');
|
||||
complete();
|
||||
});
|
||||
}
|
||||
else {
|
||||
action({id: c.Id}, function (d) {
|
||||
Notifications.success("Container " + msg, c.Id);
|
||||
Notifications.success('Container ' + msg, c.Id);
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'An error occured');
|
||||
Notifications.error('Failure', e, 'An error occured');
|
||||
complete();
|
||||
});
|
||||
|
||||
|
@ -207,31 +139,31 @@ angular.module('containers', [])
|
|||
};
|
||||
|
||||
$scope.startAction = function () {
|
||||
batch($scope.containers, Container.start, "Started");
|
||||
batch($scope.containers, Container.start, 'Started');
|
||||
};
|
||||
|
||||
$scope.stopAction = function () {
|
||||
batch($scope.containers, Container.stop, "Stopped");
|
||||
batch($scope.containers, Container.stop, 'Stopped');
|
||||
};
|
||||
|
||||
$scope.restartAction = function () {
|
||||
batch($scope.containers, Container.restart, "Restarted");
|
||||
batch($scope.containers, Container.restart, 'Restarted');
|
||||
};
|
||||
|
||||
$scope.killAction = function () {
|
||||
batch($scope.containers, Container.kill, "Killed");
|
||||
batch($scope.containers, Container.kill, 'Killed');
|
||||
};
|
||||
|
||||
$scope.pauseAction = function () {
|
||||
batch($scope.containers, Container.pause, "Paused");
|
||||
batch($scope.containers, Container.pause, 'Paused');
|
||||
};
|
||||
|
||||
$scope.unpauseAction = function () {
|
||||
batch($scope.containers, Container.unpause, "Unpaused");
|
||||
batch($scope.containers, Container.unpause, 'Unpaused');
|
||||
};
|
||||
|
||||
$scope.removeAction = function () {
|
||||
batch($scope.containers, Container.remove, "Removed");
|
||||
batch($scope.containers, Container.remove, 'Removed');
|
||||
};
|
||||
|
||||
$scope.confirmRemoveAction = function () {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
||||
// See app/components/templates/templatesController.js as a reference.
|
||||
angular.module('createContainer', [])
|
||||
.controller('CreateContainerController', ['$scope', '$state', '$stateParams', '$filter', 'Config', 'Info', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'Network', 'ResourceControlService', 'Authentication', 'Notifications',
|
||||
function ($scope, $state, $stateParams, $filter, Config, Info, Container, ContainerHelper, Image, ImageHelper, Volume, Network, ResourceControlService, Authentication, Notifications) {
|
||||
.controller('CreateContainerController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'Config', 'Info', 'Container', 'ContainerHelper', 'Image', 'ImageHelper', 'Volume', 'Network', 'ResourceControlService', 'Authentication', 'Notifications', 'ContainerService', 'ImageService', 'ControllerDataPipeline', 'FormValidator',
|
||||
function ($q, $scope, $state, $stateParams, $filter, Config, Info, Container, ContainerHelper, Image, ImageHelper, Volume, Network, ResourceControlService, Authentication, Notifications, ContainerService, ImageService, ControllerDataPipeline, FormValidator) {
|
||||
|
||||
$scope.formValues = {
|
||||
Ownership: $scope.applicationState.application.authentication ? 'private' : '',
|
||||
alwaysPull: true,
|
||||
Console: 'none',
|
||||
Volumes: [],
|
||||
|
@ -17,7 +16,9 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
|
|||
IPv6: ''
|
||||
};
|
||||
|
||||
$scope.imageConfig = {};
|
||||
$scope.state = {
|
||||
formValidationError: ''
|
||||
};
|
||||
|
||||
$scope.config = {
|
||||
Image: '',
|
||||
|
@ -81,7 +82,7 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
|
|||
$scope.removeExtraHost = function(index) {
|
||||
$scope.formValues.ExtraHosts.splice(index, 1);
|
||||
};
|
||||
|
||||
|
||||
$scope.addDevice = function() {
|
||||
$scope.config.HostConfig.Devices.push({ pathOnHost: '', pathInContainer: '' });
|
||||
};
|
||||
|
@ -90,98 +91,6 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
|
|||
$scope.config.HostConfig.Devices.splice(index, 1);
|
||||
};
|
||||
|
||||
Config.$promise.then(function (c) {
|
||||
var containersToHideLabels = c.hiddenLabels;
|
||||
|
||||
Volume.query({}, function (d) {
|
||||
$scope.availableVolumes = d.Volumes;
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to retrieve volumes");
|
||||
});
|
||||
|
||||
Network.query({}, function (d) {
|
||||
var networks = d;
|
||||
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;
|
||||
}
|
||||
});
|
||||
$scope.globalNetworkCount = networks.length;
|
||||
networks.push({Name: "bridge"});
|
||||
networks.push({Name: "host"});
|
||||
networks.push({Name: "none"});
|
||||
}
|
||||
networks.push({Name: "container"});
|
||||
$scope.availableNetworks = networks;
|
||||
if (!_.find(networks, {'Name': 'bridge'})) {
|
||||
$scope.config.HostConfig.NetworkMode = 'nat';
|
||||
}
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to retrieve networks");
|
||||
});
|
||||
|
||||
Container.query({}, function (d) {
|
||||
var containers = d;
|
||||
if (containersToHideLabels) {
|
||||
containers = ContainerHelper.hideContainers(d, containersToHideLabels);
|
||||
}
|
||||
$scope.runningContainers = containers;
|
||||
}, function(e) {
|
||||
Notifications.error("Failure", e, "Unable to retrieve running containers");
|
||||
});
|
||||
});
|
||||
|
||||
function startContainer(containerID) {
|
||||
Container.start({id: containerID}, {}, function (cd) {
|
||||
if (cd.message) {
|
||||
$('#createContainerSpinner').hide();
|
||||
Notifications.error('Error', {}, cd.message);
|
||||
} else {
|
||||
$('#createContainerSpinner').hide();
|
||||
Notifications.success('Container Started', containerID);
|
||||
$state.go('containers', {}, {reload: true});
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createContainerSpinner').hide();
|
||||
Notifications.error("Failure", e, 'Unable to start container');
|
||||
});
|
||||
}
|
||||
|
||||
function createContainer(config) {
|
||||
Container.create(config, function (d) {
|
||||
if (d.message) {
|
||||
$('#createContainerSpinner').hide();
|
||||
Notifications.error('Error', {}, d.message);
|
||||
} else {
|
||||
if ($scope.formValues.Ownership === 'private') {
|
||||
ResourceControlService.setContainerResourceControl(Authentication.getUserDetails().ID, d.Id)
|
||||
.then(function success() {
|
||||
startContainer(d.Id);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$('#createContainerSpinner').hide();
|
||||
Notifications.error("Failure", err, 'Unable to apply resource control on container');
|
||||
});
|
||||
} else {
|
||||
startContainer(d.Id);
|
||||
}
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createContainerSpinner').hide();
|
||||
Notifications.error("Failure", e, 'Unable to create container');
|
||||
});
|
||||
}
|
||||
|
||||
function pullImageAndCreateContainer(config) {
|
||||
Image.create($scope.imageConfig, function (data) {
|
||||
createContainer(config);
|
||||
}, function (e) {
|
||||
$('#createContainerSpinner').hide();
|
||||
Notifications.error('Failure', e, 'Unable to pull image');
|
||||
});
|
||||
}
|
||||
|
||||
function prepareImageConfig(config) {
|
||||
var image = config.Image;
|
||||
var registry = $scope.formValues.Registry;
|
||||
|
@ -194,7 +103,7 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
|
|||
var bindings = {};
|
||||
config.HostConfig.PortBindings.forEach(function (portBinding) {
|
||||
if (portBinding.containerPort) {
|
||||
var key = portBinding.containerPort + "/" + portBinding.protocol;
|
||||
var key = portBinding.containerPort + '/' + portBinding.protocol;
|
||||
var binding = {};
|
||||
if (portBinding.hostPort && portBinding.hostPort.indexOf(':') > -1) {
|
||||
var hostAndPort = portBinding.hostPort.split(':');
|
||||
|
@ -230,7 +139,7 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
|
|||
var env = [];
|
||||
config.Env.forEach(function (v) {
|
||||
if (v.name && v.value) {
|
||||
env.push(v.name + "=" + v.value);
|
||||
env.push(v.name + '=' + v.value);
|
||||
}
|
||||
});
|
||||
config.Env = env;
|
||||
|
@ -295,7 +204,7 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
|
|||
});
|
||||
config.Labels = labels;
|
||||
}
|
||||
|
||||
|
||||
function prepareDevices(config) {
|
||||
var path = [];
|
||||
config.HostConfig.Devices.forEach(function (p) {
|
||||
|
@ -303,10 +212,10 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
|
|||
if(p.pathInContainer === '') {
|
||||
p.pathInContainer = p.pathOnHost;
|
||||
}
|
||||
path.push({PathOnHost:p.pathOnHost,PathInContainer:p.pathInContainer,CgroupPermissions:'rwm'});
|
||||
path.push({PathOnHost:p.pathOnHost,PathInContainer:p.pathInContainer,CgroupPermissions:'rwm'});
|
||||
}
|
||||
});
|
||||
config.HostConfig.Devices = path;
|
||||
config.HostConfig.Devices = path;
|
||||
}
|
||||
|
||||
function prepareConfiguration() {
|
||||
|
@ -323,13 +232,100 @@ function ($scope, $state, $stateParams, $filter, Config, Info, Container, Contai
|
|||
return config;
|
||||
}
|
||||
|
||||
$scope.create = function () {
|
||||
var config = prepareConfiguration();
|
||||
$('#createContainerSpinner').show();
|
||||
if ($scope.formValues.alwaysPull) {
|
||||
pullImageAndCreateContainer(config);
|
||||
} else {
|
||||
createContainer(config);
|
||||
function initView() {
|
||||
Config.$promise.then(function (c) {
|
||||
var containersToHideLabels = c.hiddenLabels;
|
||||
|
||||
Volume.query({}, function (d) {
|
||||
$scope.availableVolumes = d.Volumes;
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to retrieve volumes');
|
||||
});
|
||||
|
||||
Network.query({}, function (d) {
|
||||
var networks = d;
|
||||
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;
|
||||
}
|
||||
});
|
||||
$scope.globalNetworkCount = networks.length;
|
||||
networks.push({Name: 'bridge'});
|
||||
networks.push({Name: 'host'});
|
||||
networks.push({Name: 'none'});
|
||||
}
|
||||
networks.push({Name: 'container'});
|
||||
$scope.availableNetworks = networks;
|
||||
if (!_.find(networks, {'Name': 'bridge'})) {
|
||||
$scope.config.HostConfig.NetworkMode = 'nat';
|
||||
}
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to retrieve networks');
|
||||
});
|
||||
|
||||
Container.query({}, function (d) {
|
||||
var containers = d;
|
||||
if (containersToHideLabels) {
|
||||
containers = ContainerHelper.hideContainers(d, containersToHideLabels);
|
||||
}
|
||||
$scope.runningContainers = containers;
|
||||
}, function(e) {
|
||||
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
|
||||
if (error) {
|
||||
$scope.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$scope.create = function () {
|
||||
$('#createContainerSpinner').show();
|
||||
|
||||
var accessControlData = ControllerDataPipeline.getAccessControlFormData();
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
$('#createContainerSpinner').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var config = prepareConfiguration();
|
||||
createContainer(config, accessControlData);
|
||||
};
|
||||
|
||||
function createContainer(config, accessControlData) {
|
||||
$q.when($scope.formValues.alwaysPull ? ImageService.pullImage($scope.config.Image, $scope.formValues.Registry) : null)
|
||||
.then(function success() {
|
||||
return ContainerService.createAndStartContainer(config);
|
||||
})
|
||||
.then(function success(data) {
|
||||
var containerIdentifier = data.Id;
|
||||
var userId = Authentication.getUserDetails().ID;
|
||||
return ResourceControlService.applyResourceControl('container', containerIdentifier, userId, accessControlData, []);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Container successfully created');
|
||||
$state.go('containers', {}, {reload: true});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create container');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#createContainerSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
}]);
|
||||
|
|
|
@ -107,29 +107,9 @@
|
|||
<!-- !port-mapping-input-list -->
|
||||
</div>
|
||||
<!-- !port-mapping -->
|
||||
<div class="col-sm-12 form-section-title" ng-if="applicationState.application.authentication">
|
||||
Access control
|
||||
</div>
|
||||
<!-- ownership -->
|
||||
<div class="form-group" ng-if="applicationState.application.authentication">
|
||||
<div class="col-sm-12">
|
||||
<label for="ownership" class="control-label text-left">
|
||||
Ownership
|
||||
<portainer-tooltip position="bottom" message="When setting the ownership value to private, only you and the administrators will be able to see and manage this object. When choosing public, everybody will be able to access it."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="btn-group btn-group-sm" style="margin-left: 20px;">
|
||||
<label class="btn btn-primary" ng-model="formValues.Ownership" uib-btn-radio="'private'">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
Private
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.Ownership" uib-btn-radio="'public'">
|
||||
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||
Public
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !ownership -->
|
||||
<!-- access-control -->
|
||||
<div ng-include="'app/components/common/accessControlForm/accessControlForm.html'" ng-if="applicationState.application.authentication"></div>
|
||||
<!-- !access-control -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
|
@ -139,6 +119,7 @@
|
|||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.Image" ng-click="create()">Start container</button>
|
||||
<a type="button" class="btn btn-default btn-sm" ui-sref="containers">Cancel</a>
|
||||
<i id="createContainerSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
|
@ -532,7 +513,7 @@
|
|||
</form>
|
||||
|
||||
</div>
|
||||
<!-- !tab-runtime -->
|
||||
<!-- !tab-runtime -->
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
|
|
|
@ -44,13 +44,13 @@ function ($scope, $state, Notifications, Network) {
|
|||
$('#createNetworkSpinner').hide();
|
||||
Notifications.error('Unable to create network', {}, d.message);
|
||||
} else {
|
||||
Notifications.success("Network created", d.Id);
|
||||
Notifications.success('Network created', d.Id);
|
||||
$('#createNetworkSpinner').hide();
|
||||
$state.go('networks', {}, {reload: true});
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Notifications.error("Failure", e, 'Unable to create network');
|
||||
Notifications.error('Failure', e, 'Unable to create network');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
||||
// See app/components/templates/templatesController.js as a reference.
|
||||
angular.module('createService', [])
|
||||
.controller('CreateServiceController', ['$scope', '$state', 'Service', 'ServiceHelper', 'Volume', 'Network', 'ImageHelper', 'Authentication', 'ResourceControlService', 'Notifications',
|
||||
function ($scope, $state, Service, ServiceHelper, Volume, Network, ImageHelper, Authentication, ResourceControlService, Notifications) {
|
||||
.controller('CreateServiceController', ['$scope', '$state', 'Service', 'ServiceHelper', 'Volume', 'Network', 'ImageHelper', 'Authentication', 'ResourceControlService', 'Notifications', 'ControllerDataPipeline', 'FormValidator',
|
||||
function ($scope, $state, Service, ServiceHelper, Volume, Network, ImageHelper, Authentication, ResourceControlService, Notifications, ControllerDataPipeline, FormValidator) {
|
||||
|
||||
$scope.formValues = {
|
||||
Ownership: $scope.applicationState.application.authentication ? 'private' : '',
|
||||
Name: '',
|
||||
Image: '',
|
||||
Registry: '',
|
||||
|
@ -28,6 +27,10 @@ function ($scope, $state, Service, ServiceHelper, Volume, Network, ImageHelper,
|
|||
FailureAction: 'pause'
|
||||
};
|
||||
|
||||
$scope.state = {
|
||||
formValidationError: ''
|
||||
};
|
||||
|
||||
$scope.addPortBinding = function() {
|
||||
$scope.formValues.Ports.push({ PublishedPort: '', TargetPort: '', Protocol: 'tcp', PublishMode: 'ingress' });
|
||||
};
|
||||
|
@ -121,7 +124,7 @@ function ($scope, $state, Service, ServiceHelper, Volume, Network, ImageHelper,
|
|||
}
|
||||
|
||||
function commandToArray(cmd) {
|
||||
var tokens = [].concat.apply([], cmd.split('"').map(function(v,i) {
|
||||
var tokens = [].concat.apply([], cmd.split('\'').map(function(v,i) {
|
||||
return i%2 ? v : v.split(' ');
|
||||
})).filter(Boolean);
|
||||
return tokens;
|
||||
|
@ -146,7 +149,7 @@ function ($scope, $state, Service, ServiceHelper, Volume, Network, ImageHelper,
|
|||
var env = [];
|
||||
input.Env.forEach(function (v) {
|
||||
if (v.name) {
|
||||
env.push(v.name + "=" + v.value);
|
||||
env.push(v.name + '=' + v.value);
|
||||
}
|
||||
});
|
||||
config.TaskTemplate.ContainerSpec.Env = env;
|
||||
|
@ -231,49 +234,70 @@ function ($scope, $state, Service, ServiceHelper, Volume, Network, ImageHelper,
|
|||
return config;
|
||||
}
|
||||
|
||||
function createNewService(config) {
|
||||
Service.create(config, function (d) {
|
||||
if ($scope.formValues.Ownership === 'private') {
|
||||
ResourceControlService.setServiceResourceControl(Authentication.getUserDetails().ID, d.ID)
|
||||
.then(function success() {
|
||||
$('#createServiceSpinner').hide();
|
||||
Notifications.success('Service created', d.ID);
|
||||
$state.go('services', {}, {reload: true});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$('#createContainerSpinner').hide();
|
||||
Notifications.error("Failure", err, 'Unable to apply resource control on service');
|
||||
});
|
||||
} else {
|
||||
$('#createServiceSpinner').hide();
|
||||
Notifications.success('Service created', d.ID);
|
||||
$state.go('services', {}, {reload: true});
|
||||
}
|
||||
}, function (e) {
|
||||
function createNewService(config, accessControlData) {
|
||||
Service.create(config).$promise
|
||||
.then(function success(data) {
|
||||
var serviceIdentifier = data.ID;
|
||||
var userId = Authentication.getUserDetails().ID;
|
||||
return ResourceControlService.applyResourceControl('service', serviceIdentifier, userId, accessControlData, []);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Service successfully created');
|
||||
$state.go('services', {}, {reload: true});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create service');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#createServiceSpinner').hide();
|
||||
Notifications.error("Failure", e, 'Unable to create service');
|
||||
});
|
||||
}
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
|
||||
if (error) {
|
||||
$scope.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$scope.create = function createService() {
|
||||
$('#createServiceSpinner').show();
|
||||
|
||||
var accessControlData = ControllerDataPipeline.getAccessControlFormData();
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
$('#createServiceSpinner').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var config = prepareConfiguration();
|
||||
createNewService(config);
|
||||
createNewService(config, accessControlData);
|
||||
};
|
||||
|
||||
Volume.query({}, function (d) {
|
||||
$scope.availableVolumes = d.Volumes;
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to retrieve volumes");
|
||||
});
|
||||
|
||||
Network.query({}, function (d) {
|
||||
$scope.availableNetworks = d.filter(function (network) {
|
||||
if (network.Scope === 'swarm') {
|
||||
return network;
|
||||
}
|
||||
function initView() {
|
||||
Volume.query({}, function (d) {
|
||||
$scope.availableVolumes = d.Volumes;
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to retrieve volumes');
|
||||
});
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to retrieve networks");
|
||||
});
|
||||
|
||||
Network.query({}, function (d) {
|
||||
$scope.availableNetworks = d.filter(function (network) {
|
||||
if (network.Scope === 'swarm') {
|
||||
return network;
|
||||
}
|
||||
});
|
||||
}, function (e) {
|
||||
Notifications.error('Failure', e, 'Unable to retrieve networks');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -108,29 +108,9 @@
|
|||
<!-- !port-mapping-input-list -->
|
||||
</div>
|
||||
<!-- !port-mapping -->
|
||||
<div class="col-sm-12 form-section-title" ng-if="applicationState.application.authentication">
|
||||
Access control
|
||||
</div>
|
||||
<!-- ownership -->
|
||||
<div class="form-group" ng-if="applicationState.application.authentication">
|
||||
<div class="col-sm-12">
|
||||
<label for="ownership" class="control-label text-left">
|
||||
Ownership
|
||||
<portainer-tooltip position="bottom" message="When setting the ownership value to private, only you and the administrators will be able to see and manage this object. When choosing public, everybody will be able to access it."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="btn-group btn-group-sm" style="margin-left: 20px;">
|
||||
<label class="btn btn-primary" ng-model="formValues.Ownership" uib-btn-radio="'private'">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
Private
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.Ownership" uib-btn-radio="'public'">
|
||||
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||
Public
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !ownership -->
|
||||
<!-- access-control -->
|
||||
<div ng-include="'app/components/common/accessControlForm/accessControlForm.html'" ng-if="applicationState.application.authentication"></div>
|
||||
<!-- !access-control -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
|
@ -140,6 +120,7 @@
|
|||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!formValues.Image" ng-click="create()">Create service</button>
|
||||
<a type="button" class="btn btn-default btn-sm" ui-sref="services">Cancel</a>
|
||||
<i id="createServiceSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
|
@ -251,7 +232,7 @@
|
|||
<div class="input-group col-sm-5" style="margin-left: 5px;">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<label class="btn btn-primary" ng-model="volume.Type" uib-btn-radio="'volume'" ng-click="volume.name = ''">Volume</label>
|
||||
<label class="btn btn-primary" ng-model="volume.Type" uib-btn-radio="'bind'" ng-click="volume.Name = ''">Bind</label>
|
||||
<label class="btn btn-primary" ng-model="volume.Type" uib-btn-radio="'bind'" ng-click="volume.Id = ''">Bind</label>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeVolume($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
angular.module('createVolume', [])
|
||||
.controller('CreateVolumeController', ['$scope', '$state', 'VolumeService', 'InfoService', 'ResourceControlService', 'Authentication', 'Notifications',
|
||||
function ($scope, $state, VolumeService, InfoService, ResourceControlService, Authentication, Notifications) {
|
||||
.controller('CreateVolumeController', ['$scope', '$state', 'VolumeService', 'InfoService', 'ResourceControlService', 'Authentication', 'Notifications', 'ControllerDataPipeline', 'FormValidator',
|
||||
function ($scope, $state, VolumeService, InfoService, ResourceControlService, Authentication, Notifications, ControllerDataPipeline, FormValidator) {
|
||||
|
||||
$scope.formValues = {
|
||||
Ownership: $scope.applicationState.application.authentication ? 'private' : '',
|
||||
Driver: 'local',
|
||||
DriverOptions: []
|
||||
};
|
||||
|
||||
$scope.state = {
|
||||
formValidationError: ''
|
||||
};
|
||||
|
||||
$scope.availableVolumeDrivers = [];
|
||||
|
||||
$scope.addDriverOption = function() {
|
||||
|
@ -17,6 +21,18 @@ function ($scope, $state, VolumeService, InfoService, ResourceControlService, Au
|
|||
$scope.formValues.DriverOptions.splice(index, 1);
|
||||
};
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
|
||||
if (error) {
|
||||
$scope.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$scope.create = function () {
|
||||
$('#createVolumeSpinner').show();
|
||||
|
||||
|
@ -24,25 +40,27 @@ function ($scope, $state, VolumeService, InfoService, ResourceControlService, Au
|
|||
var driver = $scope.formValues.Driver;
|
||||
var driverOptions = $scope.formValues.DriverOptions;
|
||||
var volumeConfiguration = VolumeService.createVolumeConfiguration(name, driver, driverOptions);
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var accessControlData = ControllerDataPipeline.getAccessControlFormData();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
$('#createVolumeSpinner').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
VolumeService.createVolume(volumeConfiguration)
|
||||
.then(function success(data) {
|
||||
if ($scope.formValues.Ownership === 'private') {
|
||||
ResourceControlService.setVolumeResourceControl(Authentication.getUserDetails().ID, data.Name)
|
||||
.then(function success() {
|
||||
Notifications.success("Volume created", data.Name);
|
||||
$state.go('volumes', {}, {reload: true});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to apply resource control on volume');
|
||||
});
|
||||
} else {
|
||||
Notifications.success("Volume created", data.Name);
|
||||
$state.go('volumes', {}, {reload: true});
|
||||
}
|
||||
var volumeIdentifier = data.Id;
|
||||
var userId = userDetails.ID;
|
||||
return ResourceControlService.applyResourceControl('volume', volumeIdentifier, userId, accessControlData, []);
|
||||
})
|
||||
.then(function success(data) {
|
||||
Notifications.success('Volume successfully created');
|
||||
$state.go('volumes', {}, {reload: true});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create volume');
|
||||
Notifications.error('Failure', err, 'An error occured during volume creation');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#createVolumeSpinner').hide();
|
||||
|
@ -56,7 +74,7 @@ function ($scope, $state, VolumeService, InfoService, ResourceControlService, Au
|
|||
$scope.availableVolumeDrivers = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to retrieve volume plugin information');
|
||||
Notifications.error('Failure', err, 'Unable to retrieve volume drivers');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
|
|
|
@ -64,29 +64,9 @@
|
|||
<!-- !driver-options-input-list -->
|
||||
</div>
|
||||
<!-- !driver-options -->
|
||||
<div class="col-sm-12 form-section-title" ng-if="applicationState.application.authentication">
|
||||
Access control
|
||||
</div>
|
||||
<!-- ownership -->
|
||||
<div class="form-group" ng-if="applicationState.application.authentication">
|
||||
<div class="col-sm-12">
|
||||
<label for="ownership" class="control-label text-left">
|
||||
Ownership
|
||||
<portainer-tooltip position="bottom" message="When setting the ownership value to private, only you and the administrators will be able to see and manage this object. When choosing public, everybody will be able to access it."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="btn-group btn-group-sm" style="margin-left: 20px;">
|
||||
<label class="btn btn-primary" ng-model="formValues.Ownership" uib-btn-radio="'private'">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
Private
|
||||
</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.Ownership" uib-btn-radio="'public'">
|
||||
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||
Public
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !ownership -->
|
||||
<!-- access-control -->
|
||||
<div ng-include="'app/components/common/accessControlForm/accessControlForm.html'" ng-if="applicationState.application.authentication"></div>
|
||||
<!-- !access-control -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Actions
|
||||
|
@ -96,6 +76,7 @@
|
|||
<button type="button" class="btn btn-primary btn-sm" ng-click="create()">Create volume</button>
|
||||
<a type="button" class="btn btn-default btn-sm" ui-sref="volumes">Cancel</a>
|
||||
<i id="createVolumeSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
|
|
|
@ -82,7 +82,7 @@ function ($scope, $q, Config, Container, ContainerHelper, Image, Network, Volume
|
|||
$('#loadingViewSpinner').hide();
|
||||
}, function(e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to load dashboard data");
|
||||
Notifications.error('Failure', e, 'Unable to load dashboard data');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -14,11 +14,11 @@ function ($scope, Info, Version, Notifications) {
|
|||
$scope.state.loaded = true;
|
||||
$('#loadingViewSpinner').hide();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'Unable to retrieve engine details');
|
||||
Notifications.error('Failure', e, 'Unable to retrieve engine details');
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'Unable to retrieve engine information');
|
||||
Notifications.error('Failure', e, 'Unable to retrieve engine information');
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -32,7 +32,7 @@ function ($scope, $state, $stateParams, $filter, EndpointService, Notifications)
|
|||
|
||||
EndpointService.updateEndpoint(ID, endpointParams)
|
||||
.then(function success(data) {
|
||||
Notifications.success("Endpoint updated", $scope.endpoint.Name);
|
||||
Notifications.success('Endpoint updated', $scope.endpoint.Name);
|
||||
$state.go('endpoints');
|
||||
}, function error(err) {
|
||||
$scope.state.error = err.msg;
|
||||
|
@ -48,7 +48,7 @@ function ($scope, $state, $stateParams, $filter, EndpointService, Notifications)
|
|||
EndpointService.endpoint($stateParams.id).then(function success(data) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
$scope.endpoint = data;
|
||||
if (data.URL.indexOf("unix://") === 0) {
|
||||
if (data.URL.indexOf('unix://') === 0) {
|
||||
$scope.endpointType = 'local';
|
||||
} else {
|
||||
$scope.endpointType = 'remote';
|
||||
|
@ -59,7 +59,7 @@ function ($scope, $state, $stateParams, $filter, EndpointService, Notifications)
|
|||
$scope.formValues.TLSKey = data.TLSKey;
|
||||
}, function error(err) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", err, "Unable to retrieve endpoint details");
|
||||
Notifications.error('Failure', err, 'Unable to retrieve endpoint details');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
<tr>
|
||||
<td colspan="2">
|
||||
<span class="small text-muted">
|
||||
You can select which user can access this endpoint by moving them to the authorized users table. Simply click
|
||||
on a user entry to move it from one table to the other.
|
||||
You can select which user or team can access this endpoint by moving them to the authorized accesses table. Simply click
|
||||
on a user or team entry to move it from one table to the other.
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -44,10 +44,10 @@
|
|||
<div class="row" ng-if="endpoint">
|
||||
<div class="col-sm-6">
|
||||
<rd-widget>
|
||||
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Users">
|
||||
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Users and groups">
|
||||
<div class="pull-md-right pull-lg-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count_users" ng-change="changePaginationCountUsers()">
|
||||
<select ng-model="state.pagination_count_accesses" ng-change="changePaginationCountAccesses()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
|
@ -58,7 +58,7 @@
|
|||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-sm-12 nopadding">
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<button class="btn btn-primary btn-sm" ng-click="authorizeAllUsers()" ng-disabled="users.length === 0 || filteredUsers.length === 0"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Authorize all users</button>
|
||||
<button class="btn btn-primary btn-sm" ng-click="authorizeAllAccesses()" ng-disabled="accesses.length === 0 || filteredUsers.length === 0"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Authorize all</button>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<input type="text" id="filter" ng-model="state.filterUsers" placeholder="Filter..." class="form-control input-sm" />
|
||||
|
@ -70,38 +70,38 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderUsers('Username')">
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAccesses('Name')">
|
||||
Name
|
||||
<span ng-show="sortTypeUsers == 'Username' && !sortReverseUsers" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeUsers == 'Username' && sortReverseUsers" class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span ng-show="sortTypeAccesses == 'Name' && !sortReverseAccesses" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeAccesses == 'Name' && sortReverseAccesses" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderUsers('Role')">
|
||||
Role
|
||||
<span ng-show="sortTypeUsers == 'Role' && !sortReverseUsers" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeUsers == 'Role' && sortReverseUsers" class="glyphicon glyphicon-chevron-up"></span>
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAccesses('Type')">
|
||||
Type
|
||||
<span ng-show="sortTypeAccesses == 'Type' && !sortReverseAccesses" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeAccesses == 'Type' && sortReverseAccesses" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-click="authorizeUser(user)" class="interactive" dir-paginate="user in (state.filteredUsers = (users | filter:state.filterUsers | orderBy:sortTypeUsers:sortReverseUsers | itemsPerPage: state.pagination_count_users))">
|
||||
<td>{{ user.Username }}</td>
|
||||
<tr ng-click="authorizeAccess(user)" class="interactive" dir-paginate="user in accesses | filter:state.filterUsers | orderBy:sortTypeAccesses:sortReverseAccesses | itemsPerPage: state.pagination_count_accesses">
|
||||
<td>{{ user.Name }}</td>
|
||||
<td>
|
||||
{{ user.RoleName }}
|
||||
<i class="fa" ng-class="user.RoleId === 1 ? 'fa-user-circle-o' : 'fa-user'" aria-hidden="true" style="margin-left: 2px;"></i>
|
||||
<i class="fa" ng-class="user.Type === 'user' ? 'fa-user' : 'fa-users'" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ user.Type }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!users">
|
||||
<tr ng-if="!accesses">
|
||||
<td colspan="2" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="users.length === 0 || state.filteredUsers.length === 0">
|
||||
<td colspan="2" class="text-center text-muted">No users.</td>
|
||||
<tr ng-if="accesses.length === 0 || (accesses | filter:state.filterUsers | orderBy:sortTypeAccesses:sortReverseAccesses | itemsPerPage: state.pagination_count_accesses).length === 0">
|
||||
<td colspan="2" class="text-center text-muted">No user or team available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="users" class="pull-left pagination-controls">
|
||||
<div ng-if="accesses" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -110,10 +110,10 @@
|
|||
</div>
|
||||
<div class="col-sm-6">
|
||||
<rd-widget>
|
||||
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Authorized users">
|
||||
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Authorized users and groups">
|
||||
<div class="pull-md-right pull-lg-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count_authorizedUsers" ng-change="changePaginationCountAuthorizedUsers()">
|
||||
<select ng-model="state.pagination_count_authorizedAccesses" ng-change="changePaginationCountAuthorizedAccesses()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
|
@ -124,7 +124,7 @@
|
|||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-sm-12 nopadding">
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<button class="btn btn-primary btn-sm" ng-click="unauthorizeAllUsers()" ng-disabled="authorizedUsers.length === 0 || filteredAuthorizedUsers.length === 0"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Deny all users</button>
|
||||
<button class="btn btn-primary btn-sm" ng-click="unauthorizeAllAccesses()" ng-disabled="authorizedAccesses.length === 0 || filteredAuthorizedUsers.length === 0"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Deny all</button>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<input type="text" id="filter" ng-model="state.filterAuthorizedUsers" placeholder="Filter..." class="form-control input-sm" />
|
||||
|
@ -136,39 +136,39 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAuthorizedUsers('Username')">
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAuthorizedAccesses('Name')">
|
||||
Name
|
||||
<span ng-show="sortTypeAuthorizedUsers == 'Username' && !sortReverseAuthorizedUsers" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeAuthorizedUsers == 'Username' && sortReverseAuthorizedUsers" class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span ng-show="sortTypeAuthorizedAccesses == 'Name' && !sortReverseAuthorizedAccesses" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeAuthorizedAccesses == 'Name' && sortReverseAuthorizedAccesses" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAuthorizedUsers('Role')">
|
||||
Role
|
||||
<span ng-show="sortTypeAuthorizedUsers == 'Role' && !sortReverseAuthorizedUsers" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeAuthorizedUsers == 'Role' && sortReverseAuthorizedUsers" class="glyphicon glyphicon-chevron-up"></span>
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderAuthorizedAccesses('Type')">
|
||||
Type
|
||||
<span ng-show="sortTypeAuthorizedAccesses == 'Type' && !sortReverseAuthorizedAccesses" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeAuthorizedAccesses == 'Type' && sortReverseAuthorizedAccesses" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-click="unauthorizeUser(user)" class="interactive" dir-paginate="user in (state.filteredAuthorizedUsers = (authorizedUsers | filter:state.filterAuthorizedUsers | orderBy:sortTypeAuthorizedUsers:sortReverseAuthorizedUsers | itemsPerPage: state.pagination_count_authorizedUsers))">
|
||||
<td>{{ user.Username }}</td>
|
||||
<tr ng-click="unauthorizeAccess(user)" class="interactive" pagination-id="table_authaccess" dir-paginate="user in authorizedAccesses | filter:state.filterAuthorizedUsers | orderBy:sortTypeAuthorizedAccesses:sortReverseAuthorizedAccesses | itemsPerPage: state.pagination_count_authorizedAccesses">
|
||||
<td>{{ user.Name }}</td>
|
||||
<td>
|
||||
{{ user.RoleName }}
|
||||
<i class="fa" ng-class="user.RoleId === 1 ? 'fa-user-circle-o' : 'fa-user'" aria-hidden="true" style="margin-left: 2px;"></i>
|
||||
<i class="fa" ng-class="user.Type === 'user' ? 'fa-user' : 'fa-users'" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ user.Type }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!authorizedUsers">
|
||||
<tr ng-if="!authorizedAccesses">
|
||||
<td colspan="2" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="authorizedUsers.length === 0 || state.filteredAuthorizedUsers.length === 0">
|
||||
<td colspan="2" class="text-center text-muted">No authorized users.</td>
|
||||
<tr ng-if="authorizedAccesses.length === 0 || (authorizedAccesses | filter:state.filterAuthorizedUsers | orderBy:sortTypeAuthorizedAccesses:sortReverseAuthorizedAccesses | itemsPerPage: state.pagination_count_authorizedAccesses).length === 0">
|
||||
<td colspan="2" class="text-center text-muted">No authorized user or team.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="authorizedUsers" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
<div ng-if="authorizedAccesses" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls pagination-id="table_authaccess"></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
|
|
|
@ -1,148 +1,192 @@
|
|||
angular.module('endpointAccess', [])
|
||||
.controller('EndpointAccessController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'EndpointService', 'UserService', 'Pagination', 'Notifications',
|
||||
function ($q, $scope, $state, $stateParams, $filter, EndpointService, UserService, Pagination, Notifications) {
|
||||
.controller('EndpointAccessController', ['$q', '$scope', '$state', '$stateParams', '$filter', 'EndpointService', 'UserService', 'TeamService', 'Pagination', 'Notifications',
|
||||
function ($q, $scope, $state, $stateParams, $filter, EndpointService, UserService, TeamService, Pagination, Notifications) {
|
||||
|
||||
$scope.state = {
|
||||
pagination_count_users: Pagination.getPaginationCount('endpoint_access_users'),
|
||||
pagination_count_authorizedUsers: Pagination.getPaginationCount('endpoint_access_authorizedUsers')
|
||||
pagination_count_accesses: Pagination.getPaginationCount('endpoint_access_accesses'),
|
||||
pagination_count_authorizedAccesses: Pagination.getPaginationCount('endpoint_access_authorizedAccesses')
|
||||
};
|
||||
|
||||
$scope.sortTypeUsers = 'Username';
|
||||
$scope.sortReverseUsers = true;
|
||||
$scope.sortTypeAccesses = 'Type';
|
||||
$scope.sortReverseAccesses = false;
|
||||
|
||||
$scope.orderUsers = function(sortType) {
|
||||
$scope.sortReverseUsers = ($scope.sortTypeUsers === sortType) ? !$scope.sortReverseUsers : false;
|
||||
$scope.sortTypeUsers = sortType;
|
||||
$scope.orderAccesses = function(sortType) {
|
||||
$scope.sortReverseAccesses = ($scope.sortTypeAccesses === sortType) ? !$scope.sortReverseAccesses : false;
|
||||
$scope.sortTypeAccesses = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCountUsers = function() {
|
||||
Pagination.setPaginationCount('endpoint_access_users', $scope.state.pagination_count_users);
|
||||
$scope.changePaginationCountAccesses = function() {
|
||||
Pagination.setPaginationCount('endpoint_access_accesses', $scope.state.pagination_count_accesses);
|
||||
};
|
||||
|
||||
$scope.sortTypeAuthorizedUsers = 'Username';
|
||||
$scope.sortReverseAuthorizedUsers = true;
|
||||
$scope.sortTypeAuthorizedAccesses = 'Type';
|
||||
$scope.sortReverseAuthorizedAccesses = false;
|
||||
|
||||
$scope.orderAuthorizedUsers = function(sortType) {
|
||||
$scope.sortReverseAuthorizedUsers = ($scope.sortTypeAuthorizedUsers === sortType) ? !$scope.sortReverseAuthorizedUsers : false;
|
||||
$scope.sortTypeAuthorizedUsers = sortType;
|
||||
$scope.orderAuthorizedAccesses = function(sortType) {
|
||||
$scope.sortReverseAuthorizedAccesses = ($scope.sortTypeAuthorizedAccesses === sortType) ? !$scope.sortReverseAuthorizedAccesses : false;
|
||||
$scope.sortTypeAuthorizedAccesses = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCountAuthorizedUsers = function() {
|
||||
Pagination.setPaginationCount('endpoint_access_authorizedUsers', $scope.state.pagination_count_authorizedUsers);
|
||||
$scope.changePaginationCountAuthorizedAccesses = function() {
|
||||
Pagination.setPaginationCount('endpoint_access_authorizedAccesses', $scope.state.pagination_count_authorizedAccesses);
|
||||
};
|
||||
|
||||
$scope.authorizeAllUsers = function() {
|
||||
var authorizedUserIDs = [];
|
||||
angular.forEach($scope.authorizedUsers, function (user) {
|
||||
authorizedUserIDs.push(user.Id);
|
||||
});
|
||||
angular.forEach($scope.users, function (user) {
|
||||
authorizedUserIDs.push(user.Id);
|
||||
});
|
||||
EndpointService.updateAuthorizedUsers($stateParams.id, authorizedUserIDs)
|
||||
.then(function success(data) {
|
||||
$scope.authorizedUsers = $scope.authorizedUsers.concat($scope.users);
|
||||
$scope.users = [];
|
||||
Notifications.success('Access granted for all users');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to update endpoint permissions");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.unauthorizeAllUsers = function() {
|
||||
EndpointService.updateAuthorizedUsers($stateParams.id, [])
|
||||
.then(function success(data) {
|
||||
$scope.users = $scope.users.concat($scope.authorizedUsers);
|
||||
$scope.authorizedUsers = [];
|
||||
Notifications.success('Access removed for all users');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to update endpoint permissions");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.authorizeUser = function(user) {
|
||||
var authorizedUserIDs = [];
|
||||
angular.forEach($scope.authorizedUsers, function (u) {
|
||||
authorizedUserIDs.push(u.Id);
|
||||
});
|
||||
authorizedUserIDs.push(user.Id);
|
||||
EndpointService.updateAuthorizedUsers($stateParams.id, authorizedUserIDs)
|
||||
.then(function success(data) {
|
||||
removeUserFromArray(user.Id, $scope.users);
|
||||
$scope.authorizedUsers.push(user);
|
||||
Notifications.success('Access granted for user', user.Username);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to update endpoint permissions");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.unauthorizeUser = function(user) {
|
||||
var authorizedUserIDs = $scope.authorizedUsers.filter(function (u) {
|
||||
if (u.Id !== user.Id) {
|
||||
return u;
|
||||
$scope.authorizeAllAccesses = function() {
|
||||
var authorizedUsers = [];
|
||||
var authorizedTeams = [];
|
||||
angular.forEach($scope.authorizedAccesses, function (a) {
|
||||
if (a.Type === 'user') {
|
||||
authorizedUsers.push(a.Id);
|
||||
} else if (a.Type === 'team') {
|
||||
authorizedTeams.push(a.Id);
|
||||
}
|
||||
}).map(function (u) {
|
||||
return u.Id;
|
||||
});
|
||||
EndpointService.updateAuthorizedUsers($stateParams.id, authorizedUserIDs)
|
||||
angular.forEach($scope.accesses, function (a) {
|
||||
if (a.Type === 'user') {
|
||||
authorizedUsers.push(a.Id);
|
||||
} else if (a.Type === 'team') {
|
||||
authorizedTeams.push(a.Id);
|
||||
}
|
||||
});
|
||||
|
||||
EndpointService.updateAccess($stateParams.id, authorizedUsers, authorizedTeams)
|
||||
.then(function success(data) {
|
||||
removeUserFromArray(user.Id, $scope.authorizedUsers);
|
||||
$scope.users.push(user);
|
||||
Notifications.success('Access removed for user', user.Username);
|
||||
$scope.authorizedAccesses = $scope.authorizedAccesses.concat($scope.accesses);
|
||||
$scope.accesses = [];
|
||||
Notifications.success('Endpoint accesses successfully updated');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to update endpoint permissions");
|
||||
Notifications.error('Failure', err, 'Unable to update endpoint accesses');
|
||||
});
|
||||
};
|
||||
|
||||
function getEndpointAndUsers(endpointID) {
|
||||
$scope.unauthorizeAllAccesses = function() {
|
||||
EndpointService.updateAccess($stateParams.id, [], [])
|
||||
.then(function success(data) {
|
||||
$scope.accesses = $scope.accesses.concat($scope.authorizedAccesses);
|
||||
$scope.authorizedAccesses = [];
|
||||
Notifications.success('Endpoint accesses successfully updated');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update endpoint accesses');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.authorizeAccess = function(access) {
|
||||
var authorizedUsers = [];
|
||||
var authorizedTeams = [];
|
||||
angular.forEach($scope.authorizedAccesses, function (a) {
|
||||
if (a.Type === 'user') {
|
||||
authorizedUsers.push(a.Id);
|
||||
} else if (a.Type === 'team') {
|
||||
authorizedTeams.push(a.Id);
|
||||
}
|
||||
});
|
||||
|
||||
if (access.Type === 'user') {
|
||||
authorizedUsers.push(access.Id);
|
||||
} else if (access.Type === 'team') {
|
||||
authorizedTeams.push(access.Id);
|
||||
}
|
||||
|
||||
EndpointService.updateAccess($stateParams.id, authorizedUsers, authorizedTeams)
|
||||
.then(function success(data) {
|
||||
removeAccessFromArray(access, $scope.accesses);
|
||||
$scope.authorizedAccesses.push(access);
|
||||
Notifications.success('Endpoint accesses successfully updated', access.Name);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update endpoint accesses');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.unauthorizeAccess = function(access) {
|
||||
var authorizedUsers = [];
|
||||
var authorizedTeams = [];
|
||||
angular.forEach($scope.authorizedAccesses, function (a) {
|
||||
if (a.Type === 'user') {
|
||||
authorizedUsers.push(a.Id);
|
||||
} else if (a.Type === 'team') {
|
||||
authorizedTeams.push(a.Id);
|
||||
}
|
||||
});
|
||||
|
||||
if (access.Type === 'user') {
|
||||
_.remove(authorizedUsers, function(n) {
|
||||
return n === access.Id;
|
||||
});
|
||||
} else if (access.Type === 'team') {
|
||||
_.remove(authorizedTeams, function(n) {
|
||||
return n === access.Id;
|
||||
});
|
||||
}
|
||||
|
||||
EndpointService.updateAccess($stateParams.id, authorizedUsers, authorizedTeams)
|
||||
.then(function success(data) {
|
||||
removeAccessFromArray(access, $scope.authorizedAccesses);
|
||||
$scope.accesses.push(access);
|
||||
Notifications.success('Endpoint accesses successfully updated', access.Name);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update endpoint accesses');
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
$q.all({
|
||||
endpoint: EndpointService.endpoint($stateParams.id),
|
||||
users: UserService.users(),
|
||||
users: UserService.users(false),
|
||||
teams: TeamService.teams()
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.endpoint = data.endpoint;
|
||||
$scope.users = data.users.filter(function (user) {
|
||||
if (user.Role !== 1) {
|
||||
return user;
|
||||
}
|
||||
}).map(function (user) {
|
||||
return new UserViewModel(user);
|
||||
$scope.accesses = [];
|
||||
var users = data.users.map(function (user) {
|
||||
return new EndpointAccessUserViewModel(user);
|
||||
});
|
||||
$scope.authorizedUsers = [];
|
||||
var teams = data.teams.map(function (team) {
|
||||
return new EndpointAccessTeamViewModel(team);
|
||||
});
|
||||
$scope.accesses = $scope.accesses.concat(users, teams);
|
||||
$scope.authorizedAccesses = [];
|
||||
angular.forEach($scope.endpoint.AuthorizedUsers, function(userID) {
|
||||
for (var i = 0, l = $scope.users.length; i < l; i++) {
|
||||
if ($scope.users[i].Id === userID) {
|
||||
$scope.authorizedUsers.push($scope.users[i]);
|
||||
$scope.users.splice(i, 1);
|
||||
for (var i = 0, l = $scope.accesses.length; i < l; i++) {
|
||||
if ($scope.accesses[i].Type === 'user' && $scope.accesses[i].Id === userID) {
|
||||
$scope.authorizedAccesses.push($scope.accesses[i]);
|
||||
$scope.accesses.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
angular.forEach($scope.endpoint.AuthorizedTeams, function(teamID) {
|
||||
for (var i = 0, l = $scope.accesses.length; i < l; i++) {
|
||||
if ($scope.accesses[i].Type === 'team' && $scope.accesses[i].Id === teamID) {
|
||||
$scope.authorizedAccesses.push($scope.accesses[i]);
|
||||
$scope.accesses.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.templates = [];
|
||||
$scope.users = [];
|
||||
$scope.authorizedUsers = [];
|
||||
Notifications.error("Failure", err, "Unable to retrieve endpoint details");
|
||||
$scope.accesses = [];
|
||||
$scope.authorizedAccesses = [];
|
||||
Notifications.error('Failure', err, 'Unable to retrieve endpoint details');
|
||||
})
|
||||
.finally(function final(){
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function removeUserFromArray(id, users) {
|
||||
for (var i = 0, l = users.length; i < l; i++) {
|
||||
if (users[i].Id === id) {
|
||||
users.splice(i, 1);
|
||||
function removeAccessFromArray(access, accesses) {
|
||||
for (var i = 0, l = accesses.length; i < l; i++) {
|
||||
if (access.Type === accesses[i].Type && access.Id === accesses[i].Id) {
|
||||
accesses.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getEndpointAndUsers($stateParams.id);
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -7,7 +7,7 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
|||
};
|
||||
|
||||
$scope.formValues = {
|
||||
endpointType: "remote",
|
||||
endpointType: 'remote',
|
||||
Name: '',
|
||||
URL: '',
|
||||
TLS: false,
|
||||
|
@ -46,8 +46,8 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
|||
$scope.createLocalEndpoint = function() {
|
||||
$('#initEndpointSpinner').show();
|
||||
$scope.state.error = '';
|
||||
var name = "local";
|
||||
var URL = "unix:///var/run/docker.sock";
|
||||
var name = 'local';
|
||||
var URL = 'unix:///var/run/docker.sock';
|
||||
var TLS = false;
|
||||
|
||||
EndpointService.createLocalEndpoint(name, URL, TLS, true)
|
||||
|
|
|
@ -59,7 +59,7 @@ function ($scope, $state, EndpointService, EndpointProvider, Notifications, Pagi
|
|||
var TLSCertFile = $scope.formValues.TLSCert;
|
||||
var TLSKeyFile = $scope.formValues.TLSKey;
|
||||
EndpointService.createRemoteEndpoint(name, URL, PublicURL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, false).then(function success(data) {
|
||||
Notifications.success("Endpoint created", name);
|
||||
Notifications.success('Endpoint created', name);
|
||||
$state.reload();
|
||||
}, function error(err) {
|
||||
$scope.state.uploadInProgress = false;
|
||||
|
@ -84,12 +84,12 @@ function ($scope, $state, EndpointService, EndpointProvider, Notifications, Pagi
|
|||
if (endpoint.Checked) {
|
||||
counter = counter + 1;
|
||||
EndpointService.deleteEndpoint(endpoint.Id).then(function success(data) {
|
||||
Notifications.success("Endpoint deleted", endpoint.Name);
|
||||
Notifications.success('Endpoint deleted', endpoint.Name);
|
||||
var index = $scope.endpoints.indexOf(endpoint);
|
||||
$scope.endpoints.splice(index, 1);
|
||||
complete();
|
||||
}, function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to remove endpoint');
|
||||
Notifications.error('Failure', err, 'Unable to remove endpoint');
|
||||
complete();
|
||||
});
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ function ($scope, $state, EndpointService, EndpointProvider, Notifications, Pagi
|
|||
$scope.activeEndpointID = EndpointProvider.endpointID();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to retrieve endpoints");
|
||||
Notifications.error('Failure', err, 'Unable to retrieve endpoints');
|
||||
$scope.endpoints = [];
|
||||
})
|
||||
.finally(function final() {
|
||||
|
|
|
@ -27,6 +27,6 @@ function ($scope, Notifications, Events, Pagination) {
|
|||
},
|
||||
function (e) {
|
||||
$('#loadEventsSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to load events");
|
||||
Notifications.error('Failure', e, 'Unable to load events');
|
||||
});
|
||||
}]);
|
||||
|
|
|
@ -47,7 +47,7 @@ function ($scope, $state, Config, ImageService, Notifications, Pagination, Modal
|
|||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to pull image");
|
||||
Notifications.error('Failure', err, 'Unable to pull image');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#pullImageSpinner').hide();
|
||||
|
@ -76,12 +76,12 @@ function ($scope, $state, Config, ImageService, Notifications, Pagination, Modal
|
|||
counter = counter + 1;
|
||||
ImageService.deleteImage(i.Id, force)
|
||||
.then(function success(data) {
|
||||
Notifications.success("Image deleted", i.Id);
|
||||
Notifications.success('Image deleted', i.Id);
|
||||
var index = $scope.images.indexOf(i);
|
||||
$scope.images.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to remove image');
|
||||
Notifications.error('Failure', err, 'Unable to remove image');
|
||||
})
|
||||
.finally(function final() {
|
||||
complete();
|
||||
|
@ -97,7 +97,7 @@ function ($scope, $state, Config, ImageService, Notifications, Pagination, Modal
|
|||
$scope.images = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to retrieve images");
|
||||
Notifications.error('Failure', err, 'Unable to retrieve images');
|
||||
$scope.images = [];
|
||||
})
|
||||
.finally(function final() {
|
||||
|
|
|
@ -7,15 +7,15 @@ function ($scope, $state, $stateParams, $filter, Config, Network, Container, Con
|
|||
Network.remove({id: $stateParams.id}, function (d) {
|
||||
if (d.message) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Error", d, "Unable to remove network");
|
||||
Notifications.error('Error', d, 'Unable to remove network');
|
||||
} else {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.success("Network removed", $stateParams.id);
|
||||
Notifications.success('Network removed', $stateParams.id);
|
||||
$state.go('networks', {});
|
||||
}
|
||||
}, function (e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to remove network");
|
||||
Notifications.error('Failure', e, 'Unable to remove network');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -24,15 +24,15 @@ function ($scope, $state, $stateParams, $filter, Config, Network, Container, Con
|
|||
Network.disconnect({id: $stateParams.id}, { Container: containerId, Force: false }, function (d) {
|
||||
if (d.message) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Error", d, "Unable to disconnect container from network");
|
||||
Notifications.error('Error', d, 'Unable to disconnect container from network');
|
||||
} else {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.success("Container left network", $stateParams.id);
|
||||
Notifications.success('Container left network', $stateParams.id);
|
||||
$state.go('network', {id: network.Id}, {reload: true});
|
||||
}
|
||||
}, function (e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to disconnect container from network");
|
||||
Notifications.error('Failure', e, 'Unable to disconnect container from network');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -43,7 +43,7 @@ function ($scope, $state, $stateParams, $filter, Config, Network, Container, Con
|
|||
getContainersInNetwork(data);
|
||||
}, function error(err) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", err, "Unable to retrieve network info");
|
||||
Notifications.error('Failure', err, 'Unable to retrieve network info');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ function ($scope, $state, $stateParams, $filter, Config, Network, Container, Con
|
|||
$('#loadingViewSpinner').hide();
|
||||
}, function error(err) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", err, "Unable to retrieve containers in network");
|
||||
Notifications.error('Failure', err, 'Unable to retrieve containers in network');
|
||||
});
|
||||
} else {
|
||||
Container.query({
|
||||
|
@ -87,7 +87,7 @@ function ($scope, $state, $stateParams, $filter, Config, Network, Container, Con
|
|||
$('#loadingViewSpinner').hide();
|
||||
}, function error(err) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", err, "Unable to retrieve containers in network");
|
||||
Notifications.error('Failure', err, 'Unable to retrieve containers in network');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,13 +36,13 @@ function ($scope, $state, Network, Config, Notifications, Pagination) {
|
|||
$('#createNetworkSpinner').hide();
|
||||
Notifications.error('Unable to create network', {}, d.message);
|
||||
} else {
|
||||
Notifications.success("Network created", d.Id);
|
||||
Notifications.success('Network created', d.Id);
|
||||
$('#createNetworkSpinner').hide();
|
||||
$state.reload();
|
||||
}
|
||||
}, function (e) {
|
||||
$('#createNetworkSpinner').hide();
|
||||
Notifications.error("Failure", e, 'Unable to create network');
|
||||
Notifications.error('Failure', e, 'Unable to create network');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -82,15 +82,15 @@ function ($scope, $state, Network, Config, Notifications, Pagination) {
|
|||
counter = counter + 1;
|
||||
Network.remove({id: network.Id}, function (d) {
|
||||
if (d.message) {
|
||||
Notifications.error("Error", d, "Unable to remove network");
|
||||
Notifications.error('Error', d, 'Unable to remove network');
|
||||
} else {
|
||||
Notifications.success("Network removed", network.Id);
|
||||
Notifications.success('Network removed', network.Id);
|
||||
var index = $scope.networks.indexOf(network);
|
||||
$scope.networks.splice(index, 1);
|
||||
}
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'Unable to remove network');
|
||||
Notifications.error('Failure', e, 'Unable to remove network');
|
||||
complete();
|
||||
});
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ function ($scope, $state, Network, Config, Notifications, Pagination) {
|
|||
$('#loadNetworksSpinner').hide();
|
||||
}, function (e) {
|
||||
$('#loadNetworksSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to retrieve networks");
|
||||
Notifications.error('Failure', e, 'Unable to retrieve networks');
|
||||
$scope.networks = [];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -239,10 +239,10 @@
|
|||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="node" ng-click="order('Image')">
|
||||
<a ui-sref="node" ng-click="order('Spec.ContainerSpec.Image')">
|
||||
Image
|
||||
<span ng-show="sortType == 'Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span ng-show="sortType == 'Spec.ContainerSpec.Image' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Spec.ContainerSpec.Image' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
|
@ -257,10 +257,10 @@
|
|||
<tbody>
|
||||
<tr dir-paginate="task in (filteredTasks = ( tasks | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><a ui-sref="task({ id: task.Id })">{{ task.Id }}</a></td>
|
||||
<td><span class="label label-{{ task.Status|taskstatusbadge }}">{{ task.Status }}</span></td>
|
||||
<td>{{ task.Slot }}</td>
|
||||
<td>{{ task.Image }}</td>
|
||||
<td>{{ task.Updated|getisodate }}</td>
|
||||
<td><span class="label label-{{ task.Status.State|taskstatusbadge }}">{{ task.Status.State }}</span></td>
|
||||
<td>{{ task.Slot ? task.Slot : '-' }}</td>
|
||||
<td>{{ task.Spec.ContainerSpec.Image | hideshasum }}</td>
|
||||
<td>{{ task.Updated | getisodate }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// @@OLD_SERVICE_CONTROLLER: this service should be rewritten to use services.
|
||||
// See app/components/templates/templatesController.js as a reference.
|
||||
angular.module('node', [])
|
||||
.controller('NodeController', ['$scope', '$state', '$stateParams', 'LabelHelper', 'Node', 'NodeHelper', 'Task', 'Pagination', 'Notifications',
|
||||
function ($scope, $state, $stateParams, LabelHelper, Node, NodeHelper, Task, Pagination, Notifications) {
|
||||
|
@ -6,7 +8,6 @@ function ($scope, $state, $stateParams, LabelHelper, Node, NodeHelper, Task, Pag
|
|||
$scope.state.pagination_count = Pagination.getPaginationCount('node_tasks');
|
||||
$scope.loading = true;
|
||||
$scope.tasks = [];
|
||||
$scope.displayNode = false;
|
||||
$scope.sortType = 'Status';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
|
@ -68,11 +69,11 @@ function ($scope, $state, $stateParams, LabelHelper, Node, NodeHelper, Task, Pag
|
|||
|
||||
Node.update({ id: node.Id, version: node.Version }, config, function (data) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
Notifications.success("Node successfully updated", "Node updated");
|
||||
Notifications.success('Node successfully updated', 'Node updated');
|
||||
$state.go('node', {id: node.Id}, {reload: true});
|
||||
}, function (e) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
Notifications.error("Failure", e, "Failed to update node");
|
||||
Notifications.error('Failure', e, 'Failed to update node');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -81,7 +82,7 @@ function ($scope, $state, $stateParams, LabelHelper, Node, NodeHelper, Task, Pag
|
|||
if ($scope.applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE') {
|
||||
Node.get({ id: $stateParams.id}, function(d) {
|
||||
if (d.message) {
|
||||
Notifications.error("Failure", e, "Unable to inspect the node");
|
||||
Notifications.error('Failure', e, 'Unable to inspect the node');
|
||||
} else {
|
||||
var node = new NodeViewModel(d);
|
||||
originalNode = angular.copy(node);
|
||||
|
@ -99,10 +100,10 @@ function ($scope, $state, $stateParams, LabelHelper, Node, NodeHelper, Task, Pag
|
|||
if (node) {
|
||||
Task.query({filters: {node: [node.ID]}}, function (tasks) {
|
||||
$scope.tasks = tasks.map(function (task) {
|
||||
return new TaskViewModel(task, [node]);
|
||||
return new TaskViewModel(task);
|
||||
});
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to retrieve tasks associated to the node");
|
||||
Notifications.error('Failure', e, 'Unable to retrieve tasks associated to the node');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div ng-if="tasks.length > 0">
|
||||
<div ng-if="tasks.length > 0 && nodes">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tasks" title="Associated tasks">
|
||||
<div class="pull-right">
|
||||
|
@ -24,14 +24,14 @@
|
|||
<span ng-show="sortType == 'Status' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<th ng-if="service.Mode !== 'global'">
|
||||
<a ui-sref="service" ng-click="order('Slot')">
|
||||
Slot
|
||||
<span ng-show="sortType == 'Slot' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Slot' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="displayNode">
|
||||
<th>
|
||||
<a ui-sref="service" ng-click="order('Node')">
|
||||
Node
|
||||
<span ng-show="sortType == 'Node' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
|
@ -50,10 +50,10 @@
|
|||
<tbody>
|
||||
<tr dir-paginate="task in (filteredTasks = ( tasks | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><a ui-sref="task({ id: task.Id })">{{ task.Id }}</a></td>
|
||||
<td><span class="label label-{{ task.Status|taskstatusbadge }}">{{ task.Status }}</span></td>
|
||||
<td>{{ task.Slot }}</td>
|
||||
<td ng-if="displayNode">{{ task.Node }}</td>
|
||||
<td>{{ task.Updated|getisodate }}</td>
|
||||
<td><span class="label label-{{ task.Status.State|taskstatusbadge }}">{{ task.Status.State }}</span></td>
|
||||
<td ng-if="service.Mode !== 'global'">{{ task.Slot }}</td>
|
||||
<td>{{ task.NodeId | tasknodename: nodes }}</td>
|
||||
<td>{{ task.Updated | getisodate }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -116,6 +116,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-include="'app/components/common/accessControlPanel/accessControlPanel.html'" ng-if="service && applicationState.application.authentication"></div>
|
||||
|
||||
<div class="row">
|
||||
<hr>
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
angular.module('service', [])
|
||||
.controller('ServiceController', ['$scope', '$stateParams', '$state', '$location', '$anchorScroll', 'Service', 'ServiceHelper', 'Task', 'Node', 'Notifications', 'Pagination', 'ModalService',
|
||||
function ($scope, $stateParams, $state, $location, $anchorScroll, Service, ServiceHelper, Task, Node, Notifications, Pagination, ModalService) {
|
||||
.controller('ServiceController', ['$q', '$scope', '$stateParams', '$state', '$location', '$anchorScroll', 'ServiceService', 'Service', 'ServiceHelper', 'TaskService', 'NodeService', 'Notifications', 'Pagination', 'ModalService', 'ControllerDataPipeline',
|
||||
function ($q, $scope, $stateParams, $state, $location, $anchorScroll, ServiceService, Service, ServiceHelper, TaskService, NodeService, Notifications, Pagination, ModalService, ControllerDataPipeline) {
|
||||
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('service_tasks');
|
||||
$scope.service = {};
|
||||
$scope.tasks = [];
|
||||
$scope.displayNode = false;
|
||||
$scope.sortType = 'Status';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
|
@ -213,12 +211,12 @@ function ($scope, $stateParams, $state, $location, $anchorScroll, Service, Servi
|
|||
|
||||
Service.update({ id: service.Id, version: service.Version }, config, function (data) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.success("Service successfully updated", "Service updated");
|
||||
Notifications.success('Service successfully updated', 'Service updated');
|
||||
$scope.cancelChanges({});
|
||||
fetchServiceDetails();
|
||||
initView();
|
||||
}, function (e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to update service");
|
||||
Notifications.error('Failure', e, 'Unable to update service');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -234,18 +232,16 @@ function ($scope, $stateParams, $state, $location, $anchorScroll, Service, Servi
|
|||
|
||||
function removeService() {
|
||||
$('#loadingViewSpinner').show();
|
||||
Service.remove({id: $stateParams.id}, function (d) {
|
||||
if (d.message) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Error", d, "Unable to remove service");
|
||||
} else {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.success("Service removed", $stateParams.id);
|
||||
$state.go('services', {});
|
||||
}
|
||||
}, function (e) {
|
||||
ServiceService.remove($scope.service)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully deleted');
|
||||
$state.go('services', {});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove service');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to remove service");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -258,10 +254,12 @@ function ($scope, $stateParams, $state, $location, $anchorScroll, Service, Servi
|
|||
service.ServiceConstraints = translateConstraintsToKeyValue(service.Constraints);
|
||||
}
|
||||
|
||||
function fetchServiceDetails() {
|
||||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
Service.get({id: $stateParams.id}, function (d) {
|
||||
var service = new ServiceViewModel(d);
|
||||
|
||||
ServiceService.service($stateParams.id)
|
||||
.then(function success(data) {
|
||||
var service = data;
|
||||
$scope.isUpdating = $scope.lastVersion >= service.Version;
|
||||
if (!$scope.isUpdating) {
|
||||
$scope.lastVersion = service.Version;
|
||||
|
@ -269,29 +267,23 @@ function ($scope, $stateParams, $state, $location, $anchorScroll, Service, Servi
|
|||
|
||||
translateServiceArrays(service);
|
||||
$scope.service = service;
|
||||
ControllerDataPipeline.setAccessControlData('service', $stateParams.id, service.ResourceControl);
|
||||
originalService = angular.copy(service);
|
||||
|
||||
Task.query({filters: {service: [service.Name]}}, function (tasks) {
|
||||
Node.query({}, function (nodes) {
|
||||
$scope.displayNode = true;
|
||||
$scope.tasks = tasks.map(function (task) {
|
||||
return new TaskViewModel(task, nodes);
|
||||
});
|
||||
$('#loadingViewSpinner').hide();
|
||||
}, function (e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
$scope.tasks = tasks.map(function (task) {
|
||||
return new TaskViewModel(task, null);
|
||||
});
|
||||
Notifications.error("Failure", e, "Unable to retrieve node information");
|
||||
});
|
||||
}, function (e) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to retrieve tasks associated to the service");
|
||||
return $q.all({
|
||||
tasks: TaskService.serviceTasks(service.Name),
|
||||
nodes: NodeService.nodes()
|
||||
});
|
||||
}, function (e) {
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.tasks = data.tasks;
|
||||
$scope.nodes = data.nodes;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve service details');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to retrieve service details");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -382,5 +374,5 @@ function ($scope, $stateParams, $state, $location, $anchorScroll, Service, Servi
|
|||
return [];
|
||||
}
|
||||
|
||||
fetchServiceDetails();
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -73,10 +73,10 @@
|
|||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ui-sref="services" ng-click="order('Metadata.ResourceControl.OwnerId')">
|
||||
<a ui-sref="services" ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'Metadata.ResourceControl.OwnerId' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Metadata.ResourceControl.OwnerId' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</thead>
|
||||
|
@ -109,24 +109,9 @@
|
|||
{{ service.UpdatedAt|getisodate }}
|
||||
</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span ng-if="user.role === 1 && service.Metadata.ResourceControl">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
<span ng-if="service.Metadata.ResourceControl.OwnerId === user.ID">
|
||||
Private
|
||||
</span>
|
||||
<span ng-if="service.Metadata.ResourceControl.OwnerId !== user.ID">
|
||||
Private <span ng-if="service.Owner">(owner: {{ service.Owner }})</span>
|
||||
</span>
|
||||
<a ng-click="switchOwnership(service)" class="interactive"><i class="fa fa-eye" aria-hidden="true" style="margin-left: 7px;"></i> Switch to public</a>
|
||||
</span>
|
||||
<span ng-if="user.role !== 1 && service.Metadata.ResourceControl.OwnerId === user.ID">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
Private
|
||||
<a ng-click="switchOwnership(service)" class="interactive"><i class="fa fa-eye" aria-hidden="true" style="margin-left: 7px;"></i> Switch to public</a>
|
||||
</span>
|
||||
<span ng-if="!service.Metadata.ResourceControl">
|
||||
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||
Public
|
||||
<span>
|
||||
<i ng-class="service.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ service.ResourceControl.Ownership ? service.ResourceControl.Ownership : service.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,40 +1,12 @@
|
|||
angular.module('services', [])
|
||||
.controller('ServicesController', ['$q', '$scope', '$stateParams', '$state', 'Service', 'ServiceHelper', 'Notifications', 'Pagination', 'Task', 'Node', 'NodeHelper', 'Authentication', 'UserService', 'ModalService', 'ResourceControlService',
|
||||
function ($q, $scope, $stateParams, $state, Service, ServiceHelper, Notifications, Pagination, Task, Node, NodeHelper, Authentication, UserService, ModalService, ResourceControlService) {
|
||||
.controller('ServicesController', ['$q', '$scope', '$stateParams', '$state', 'Service', 'ServiceService', 'ServiceHelper', 'Notifications', 'Pagination', 'Task', 'Node', 'NodeHelper', 'ModalService', 'ResourceControlService',
|
||||
function ($q, $scope, $stateParams, $state, Service, ServiceService, ServiceHelper, Notifications, Pagination, Task, Node, NodeHelper, ModalService, ResourceControlService) {
|
||||
$scope.state = {};
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('services');
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
function removeServiceResourceControl(service) {
|
||||
volumeResourceControlQueries = [];
|
||||
angular.forEach(service.Mounts, function (mount) {
|
||||
if (mount.Type === 'volume') {
|
||||
volumeResourceControlQueries.push(ResourceControlService.removeVolumeResourceControl(service.Metadata.ResourceControl.OwnerId, mount.Source));
|
||||
}
|
||||
});
|
||||
|
||||
$q.all(volumeResourceControlQueries)
|
||||
.then(function success() {
|
||||
return ResourceControlService.removeServiceResourceControl(service.Metadata.ResourceControl.OwnerId, service.Id);
|
||||
})
|
||||
.then(function success() {
|
||||
delete service.Metadata.ResourceControl;
|
||||
Notifications.success('Ownership changed to public', service.Id);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to change service ownership");
|
||||
});
|
||||
}
|
||||
|
||||
$scope.switchOwnership = function(volume) {
|
||||
ModalService.confirmServiceOwnershipChange(function (confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
removeServiceResourceControl(volume);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('services', $scope.state.pagination_count);
|
||||
};
|
||||
|
@ -58,13 +30,13 @@ function ($q, $scope, $stateParams, $state, Service, ServiceHelper, Notification
|
|||
config.Mode.Replicated.Replicas = service.Replicas;
|
||||
Service.update({ id: service.Id, version: service.Version }, config, function (data) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
Notifications.success("Service successfully scaled", "New replica count: " + service.Replicas);
|
||||
Notifications.success('Service successfully scaled', 'New replica count: ' + service.Replicas);
|
||||
$state.reload();
|
||||
}, function (e) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
service.Scale = false;
|
||||
service.Replicas = service.ReplicaCount;
|
||||
Notifications.error("Failure", e, "Unable to scale service");
|
||||
Notifications.error('Failure', e, 'Unable to scale service');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -90,40 +62,22 @@ function ($q, $scope, $stateParams, $state, Service, ServiceHelper, Notification
|
|||
angular.forEach($scope.services, function (service) {
|
||||
if (service.Checked) {
|
||||
counter = counter + 1;
|
||||
Service.remove({id: service.Id}, function (d) {
|
||||
if (d.message) {
|
||||
$('#loadServicesSpinner').hide();
|
||||
Notifications.error("Unable to remove service", {}, d[0].message);
|
||||
} else {
|
||||
if (service.Metadata && service.Metadata.ResourceControl) {
|
||||
ResourceControlService.removeServiceResourceControl(service.Metadata.ResourceControl.OwnerId, service.Id)
|
||||
.then(function success() {
|
||||
Notifications.success("Service deleted", service.Id);
|
||||
var index = $scope.services.indexOf(service);
|
||||
$scope.services.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to remove service ownership");
|
||||
});
|
||||
} else {
|
||||
Notifications.success("Service deleted", service.Id);
|
||||
var index = $scope.services.indexOf(service);
|
||||
$scope.services.splice(index, 1);
|
||||
}
|
||||
}
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, 'Unable to remove service');
|
||||
ServiceService.remove(service)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Service successfully deleted');
|
||||
var index = $scope.services.indexOf(service);
|
||||
$scope.services.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove service');
|
||||
})
|
||||
.finally(function final() {
|
||||
complete();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// $scope.removeAction = function () {
|
||||
//
|
||||
// };
|
||||
|
||||
function mapUsersToServices(users) {
|
||||
angular.forEach($scope.services, function (service) {
|
||||
if (service.Metadata) {
|
||||
|
@ -139,46 +93,33 @@ function ($q, $scope, $stateParams, $state, Service, ServiceHelper, Notification
|
|||
});
|
||||
}
|
||||
|
||||
function fetchServices() {
|
||||
function initView() {
|
||||
$('#loadServicesSpinner').show();
|
||||
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
$scope.user = userDetails;
|
||||
|
||||
$q.all({
|
||||
services: Service.query({}).$promise,
|
||||
tasks: Task.query({filters: {'desired-state': ['running']}}).$promise,
|
||||
nodes: Node.query({}).$promise,
|
||||
nodes: Node.query({}).$promise
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.swarmManagerIP = NodeHelper.getManagerIP(data.nodes);
|
||||
$scope.services = data.services.map(function (service) {
|
||||
var serviceTasks = data.tasks.filter(function (task) {
|
||||
return task.ServiceID === service.ID && task.Status.State === "running";
|
||||
return task.ServiceID === service.ID && task.Status.State === 'running';
|
||||
});
|
||||
var taskNodes = data.nodes.filter(function (node) {
|
||||
return node.Spec.Availability === 'active' && node.Status.State === 'ready';
|
||||
});
|
||||
return new ServiceViewModel(service, serviceTasks, taskNodes);
|
||||
});
|
||||
if (userDetails.role === 1) {
|
||||
UserService.users()
|
||||
.then(function success(data) {
|
||||
mapUsersToServices(data);
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadServicesSpinner').hide();
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.services = [];
|
||||
Notifications.error("Failure", err, "Unable to retrieve services");
|
||||
Notifications.error('Failure', err, 'Unable to retrieve services');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadServicesSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
fetchServices();
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -21,11 +21,11 @@
|
|||
</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 class="form-group" ng-if="invalidPassword">
|
||||
<div class="col-sm-12">
|
||||
<i class="fa fa-times red-icon" aria-hidden="true"></i>
|
||||
<span class="small text-muted">Current password is not valid</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- new-password-input -->
|
||||
<div class="form-group">
|
||||
|
@ -38,6 +38,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- !new-password-input -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<i ng-class="{true: 'fa fa-check green-icon', false: 'fa fa-times red-icon'}[formValues.newPassword.length >= 8]" aria-hidden="true"></i>
|
||||
<span class="small text-muted">Your new password must be at least 8 characters long</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- confirm-password-input -->
|
||||
<div class="form-group">
|
||||
<label for="confirm_password" class="col-sm-2 control-label text-left">Confirm password</label>
|
||||
|
@ -51,14 +57,9 @@
|
|||
</div>
|
||||
<!-- !confirm-password-input -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2">
|
||||
<div class="col-sm-12">
|
||||
<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>
|
||||
|
|
|
@ -15,14 +15,14 @@ function ($scope, $state, $sanitize, Authentication, UserService, Notifications)
|
|||
|
||||
UserService.updateUserPassword(userID, currentPassword, newPassword)
|
||||
.then(function success() {
|
||||
Notifications.success("Success", "Password successfully updated");
|
||||
Notifications.success('Success', 'Password successfully updated');
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
if (err.invalidPassword) {
|
||||
$scope.invalidPassword = true;
|
||||
} else {
|
||||
Notifications.error("Failure", err, err.msg);
|
||||
Notifications.error('Failure', err, err.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -49,14 +49,16 @@
|
|||
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE'">
|
||||
<a ui-sref="docker" ui-sref-active="active">Docker <span class="menu-icon fa fa-th"></span></a>
|
||||
</li>
|
||||
<li class="sidebar-title"><span>Portainer settings</span></li>
|
||||
<li class="sidebar-list" ng-if="applicationState.application.authentication">
|
||||
<a ui-sref="settings" ui-sref-active="active">Password <span class="menu-icon fa fa-lock"></span></a>
|
||||
<li class="sidebar-title" ng-if="isAdmin || isTeamLeader">
|
||||
<span>Portainer settings</span>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="applicationState.application.authentication && userRole === 1">
|
||||
<a ui-sref="users" ui-sref-active="active">Users <span class="menu-icon fa fa-user"></span></a>
|
||||
<li class="sidebar-list" ng-if="applicationState.application.authentication && (isAdmin || isTeamLeader)">
|
||||
<a ui-sref="users" ui-sref-active="active">User management <span class="menu-icon fa fa-users"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'users' || $state.current.name === 'user' || $state.current.name === 'teams' || $state.current.name === 'team')">
|
||||
<a ui-sref="teams" ui-sref-active="active">Teams</span></a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || userRole === 1">
|
||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
|
||||
<a ui-sref="endpoints" ui-sref-active="active">Endpoints <span class="menu-icon fa fa-plug"></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
angular.module('sidebar', [])
|
||||
.controller('SidebarController', ['$scope', '$state', 'Settings', 'Config', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication',
|
||||
function ($scope, $state, Settings, Config, EndpointService, StateManager, EndpointProvider, Notifications, Authentication) {
|
||||
.controller('SidebarController', ['$q', '$scope', '$state', 'Settings', 'Config', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications', 'Authentication', 'UserService',
|
||||
function ($q, $scope, $state, Settings, Config, EndpointService, StateManager, EndpointProvider, Notifications, Authentication, UserService) {
|
||||
|
||||
Config.$promise.then(function (c) {
|
||||
$scope.logo = c.logo;
|
||||
});
|
||||
|
||||
$scope.uiVersion = Settings.uiVersion;
|
||||
$scope.userRole = Authentication.getUserDetails().role;
|
||||
$scope.endpoints = [];
|
||||
|
||||
$scope.switchEndpoint = function(endpoint) {
|
||||
var activeEndpointID = EndpointProvider.endpointID();
|
||||
|
@ -27,22 +27,47 @@ function ($scope, $state, Settings, Config, EndpointService, StateManager, Endpo
|
|||
});
|
||||
};
|
||||
|
||||
function fetchEndpoints() {
|
||||
EndpointService.endpoints()
|
||||
.then(function success(data) {
|
||||
$scope.endpoints = data;
|
||||
var activeEndpointID = EndpointProvider.endpointID();
|
||||
angular.forEach($scope.endpoints, function (endpoint) {
|
||||
if (endpoint.Id === activeEndpointID) {
|
||||
$scope.activeEndpoint = endpoint;
|
||||
EndpointProvider.setEndpointPublicURL(endpoint.PublicURL);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.endpoints = [];
|
||||
function setActiveEndpoint(endpoints) {
|
||||
var activeEndpointID = EndpointProvider.endpointID();
|
||||
angular.forEach(endpoints, function (endpoint) {
|
||||
if (endpoint.Id === activeEndpointID) {
|
||||
$scope.activeEndpoint = endpoint;
|
||||
EndpointProvider.setEndpointPublicURL(endpoint.PublicURL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fetchEndpoints();
|
||||
function checkPermissions(memberships) {
|
||||
var isLeader = false;
|
||||
angular.forEach(memberships, function(membership) {
|
||||
if (membership.Role === 1) {
|
||||
isLeader = true;
|
||||
}
|
||||
});
|
||||
$scope.isTeamLeader = isLeader;
|
||||
}
|
||||
|
||||
function initView() {
|
||||
EndpointService.endpoints()
|
||||
.then(function success(data) {
|
||||
var endpoints = data;
|
||||
$scope.endpoints = endpoints;
|
||||
setActiveEndpoint(endpoints);
|
||||
|
||||
if (StateManager.getState().application.authentication) {
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true: false;
|
||||
$scope.isAdmin = isAdmin;
|
||||
return $q.when(!isAdmin ? UserService.userMemberships(userDetails.ID) : []);
|
||||
}
|
||||
})
|
||||
.then(function success(data) {
|
||||
checkPermissions(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve endpoints');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -42,33 +42,33 @@ function (Pagination, $scope, Notifications, $timeout, Container, ContainerTop,
|
|||
networkRxData.push(0);
|
||||
}
|
||||
var cpuDataset = { // CPU Usage
|
||||
fillColor: "rgba(151,187,205,0.5)",
|
||||
strokeColor: "rgba(151,187,205,1)",
|
||||
pointColor: "rgba(151,187,205,1)",
|
||||
pointStrokeColor: "#fff",
|
||||
fillColor: 'rgba(151,187,205,0.5)',
|
||||
strokeColor: 'rgba(151,187,205,1)',
|
||||
pointColor: 'rgba(151,187,205,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
data: cpuData
|
||||
};
|
||||
var memoryDataset = {
|
||||
fillColor: "rgba(151,187,205,0.5)",
|
||||
strokeColor: "rgba(151,187,205,1)",
|
||||
pointColor: "rgba(151,187,205,1)",
|
||||
pointStrokeColor: "#fff",
|
||||
fillColor: 'rgba(151,187,205,0.5)',
|
||||
strokeColor: 'rgba(151,187,205,1)',
|
||||
pointColor: 'rgba(151,187,205,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
data: memoryData
|
||||
};
|
||||
var networkRxDataset = {
|
||||
label: "Rx Bytes",
|
||||
fillColor: "rgba(151,187,205,0.5)",
|
||||
strokeColor: "rgba(151,187,205,1)",
|
||||
pointColor: "rgba(151,187,205,1)",
|
||||
pointStrokeColor: "#fff",
|
||||
label: 'Rx Bytes',
|
||||
fillColor: 'rgba(151,187,205,0.5)',
|
||||
strokeColor: 'rgba(151,187,205,1)',
|
||||
pointColor: 'rgba(151,187,205,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
data: networkRxData
|
||||
};
|
||||
var networkTxDataset = {
|
||||
label: "Tx Bytes",
|
||||
fillColor: "rgba(255,180,174,0.5)",
|
||||
strokeColor: "rgba(255,180,174,1)",
|
||||
pointColor: "rgba(255,180,174,1)",
|
||||
pointStrokeColor: "#fff",
|
||||
label: 'Tx Bytes',
|
||||
fillColor: 'rgba(255,180,174,0.5)',
|
||||
strokeColor: 'rgba(255,180,174,1)',
|
||||
pointColor: 'rgba(255,180,174,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
data: networkTxData
|
||||
};
|
||||
var networkLegendData = [
|
||||
|
@ -87,7 +87,7 @@ function (Pagination, $scope, Notifications, $timeout, Container, ContainerTop,
|
|||
legend($('#network-legend').get(0), networkLegendData);
|
||||
|
||||
Chart.defaults.global.animationSteps = 30; // Lower from 60 to ease CPU load.
|
||||
var cpuChart = new Chart($('#cpu-stats-chart').get(0).getContext("2d")).Line({
|
||||
var cpuChart = new Chart($('#cpu-stats-chart').get(0).getContext('2d')).Line({
|
||||
labels: cpuLabels,
|
||||
datasets: [cpuDataset]
|
||||
}, {
|
||||
|
@ -108,7 +108,7 @@ function (Pagination, $scope, Notifications, $timeout, Container, ContainerTop,
|
|||
//scaleStepWidth: Math.ceil(initialStats.memory_stats.limit / 10),
|
||||
//scaleStartValue: 0
|
||||
});
|
||||
var networkChart = new Chart($('#network-stats-chart').get(0).getContext("2d")).Line({
|
||||
var networkChart = new Chart($('#network-stats-chart').get(0).getContext('2d')).Line({
|
||||
labels: networkLabels,
|
||||
datasets: [networkRxDataset, networkTxDataset]
|
||||
}, {
|
||||
|
@ -211,7 +211,7 @@ function (Pagination, $scope, Notifications, $timeout, Container, ContainerTop,
|
|||
Container.get({id: $stateParams.id}, function (d) {
|
||||
$scope.container = d;
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to retrieve container info");
|
||||
Notifications.error('Failure', e, 'Unable to retrieve container info');
|
||||
});
|
||||
$scope.getTop();
|
||||
}]);
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
<rd-header-title title="Task details">
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="services">Services</a> > <a ui-sref="service({id: task.ServiceID})">{{ serviceName }}</a> > {{ task.ID }}
|
||||
<rd-header-content ng-if="task && service">
|
||||
<a ui-sref="services">Services</a> > <a ui-sref="service({id: service.Id })">{{ service.Name }}</a> > {{ task.Id }}
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="row" ng-if="task && service">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-tasks" title="Task status"></rd-widget-header>
|
||||
|
@ -16,7 +16,7 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>{{ task.ID }}</td>
|
||||
<td>{{ task.Id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>State</td>
|
||||
|
@ -28,15 +28,15 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Image</td>
|
||||
<td>{{ task.Spec.ContainerSpec.Image }}</td>
|
||||
<td>{{ task.Spec.ContainerSpec.Image | hideshasum }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr ng-if="service.Mode !== 'global'">
|
||||
<td>Slot</td>
|
||||
<td>{{ task.Slot }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created</td>
|
||||
<td>{{ task.CreatedAt|getisodate }}</td>
|
||||
<td>{{ task.Created|getisodate }}</td>
|
||||
</tr>
|
||||
<tr ng-if="task.Status.ContainerStatus.ContainerID">
|
||||
<td>Container ID</td>
|
||||
|
|
|
@ -1,29 +1,26 @@
|
|||
angular.module('task', [])
|
||||
.controller('TaskController', ['$scope', '$stateParams', '$state', 'Task', 'Service', 'Notifications',
|
||||
function ($scope, $stateParams, $state, Task, Service, Notifications) {
|
||||
.controller('TaskController', ['$scope', '$stateParams', 'TaskService', 'Service', 'Notifications',
|
||||
function ($scope, $stateParams, TaskService, Service, Notifications) {
|
||||
|
||||
$scope.task = {};
|
||||
$scope.serviceName = 'service';
|
||||
$scope.isTaskRunning = false;
|
||||
|
||||
function fetchTaskDetails() {
|
||||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
Task.get({id: $stateParams.id}, function (d) {
|
||||
$scope.task = d;
|
||||
fetchAssociatedServiceDetails(d.ServiceID);
|
||||
TaskService.task($stateParams.id)
|
||||
.then(function success(data) {
|
||||
var task = data;
|
||||
$scope.task = task;
|
||||
return Service.get({ id: task.ServiceId }).$promise;
|
||||
})
|
||||
.then(function success(data) {
|
||||
var service = new ServiceViewModel(data);
|
||||
$scope.service = service;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve task details');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to retrieve task details");
|
||||
});
|
||||
}
|
||||
|
||||
function fetchAssociatedServiceDetails(serviceId) {
|
||||
Service.get({id: serviceId}, function (d) {
|
||||
$scope.serviceName = d.Spec.Name;
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to retrieve associated service details");
|
||||
});
|
||||
}
|
||||
|
||||
fetchTaskDetails();
|
||||
initView();
|
||||
}]);
|
||||
|
|
176
app/components/team/team.html
Normal file
176
app/components/team/team.html
Normal file
|
@ -0,0 +1,176 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Team details">
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="teams">Teams</a> > <a ui-sref="team({id: team.Id})">{{ team.Name }}</a>
|
||||
</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-users" title="Team details"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
{{ team.Name }}
|
||||
<button class="btn btn-xs btn-danger" ng-if="isAdmin" ng-click="deleteTeam()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete this team</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Leaders</td>
|
||||
<td>{{ leaderCount }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total users in team</td>
|
||||
<td>{{ teamMembers.length }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="team">
|
||||
<div class="col-sm-6">
|
||||
<rd-widget>
|
||||
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Users">
|
||||
<div class="pull-md-right pull-lg-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count_users" ng-change="changePaginationCountUsers()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-sm-12 nopadding">
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<button class="btn btn-primary btn-sm" ng-click="addAllUsers()" ng-if="isAdmin" ng-disabled="users.length === 0 || filteredUsers.length === 0"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Add all users</button>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<input type="text" id="filter" ng-model="state.filterUsers" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ui-sref="endpoint.access({id: endpoint.Id})" ng-click="orderUsers('Username')">
|
||||
Name
|
||||
<span ng-show="sortTypeUsers == 'Username' && !sortReverseUsers" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeUsers == 'Username' && sortReverseUsers" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr pagination-id="table1" dir-paginate="user in users | filter:state.filterUsers | orderBy:sortTypeUsers:sortReverseUsers | itemsPerPage: state.pagination_count_users">
|
||||
<td>
|
||||
{{ user.Username }}
|
||||
<span style="margin-left: 5px;">
|
||||
<a class="btn-outline-secondary" ng-click="addUser(user)"><i class="fa fa-plus-circle space-right" aria-hidden="true"></i>Add</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!users">
|
||||
<td colspan="2" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="users.length === 0 || (users | filter:state.filterUsers).length === 0">
|
||||
<td colspan="2" class="text-center text-muted">No users.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="users" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls pagination-id="table1"></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<rd-widget>
|
||||
<rd-widget-header classes="col-sm-12 col-md-6 nopadding" icon="fa-users" title="Team members">
|
||||
<div class="pull-md-right pull-lg-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count_members" ng-change="changePaginationCountGroupMembers()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-sm-12 nopadding">
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<button class="btn btn-primary btn-sm" ng-click="removeAllUsers()" ng-if="isAdmin" ng-disabled="teamMembers.length === 0 || filteredGroupMembers.length === 0"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Remove all users</button>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-6 nopadding">
|
||||
<input type="text" id="filter" ng-model="state.filterGroupMembers" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a ui-sref="team({id: team.Id})" ng-click="orderGroupMembers('Username')">
|
||||
Name
|
||||
<span ng-show="sortTypeGroupMembers == 'Username' && !sortReverseGroupMembers" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeGroupMembers == 'Username' && sortReverseGroupMembers" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="team({id: team.Id})" ng-click="orderGroupMembers('TeamRole')">
|
||||
Team Role
|
||||
<span ng-show="sortTypeGroupMembers == 'TeamRole' && !sortReverseGroupMembers" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortTypeGroupMembers == 'TeamRole' && sortReverseGroupMembers" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr pagination-id="table2" dir-paginate="user in teamMembers | filter:state.filterGroupMembers | orderBy:sortTypeGroupMembers:sortReverseGroupMembers | itemsPerPage: state.pagination_count_groupMembers">
|
||||
<td>
|
||||
{{ user.Username }}
|
||||
<span style="margin-left: 5px;" ng-if="isAdmin || user.TeamRole === 'Member'")>
|
||||
<a class="btn-outline-secondary" ng-click="removeUser(user)"><i class="fa fa-minus-circle space-right" aria-hidden="true"></i>Remove</a>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<i ng-if="user.TeamRole === 'Leader'" class="fa fa-user-plus" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
<i ng-if="user.TeamRole === 'Member'" class="fa fa-user" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ user.TeamRole }}
|
||||
<span style="margin-left: 5px;" ng-if="isAdmin">
|
||||
<a class="btn-outline-secondary" style="margin-left: 5px;" ng-click="promoteToLeader(user)" ng-if="user.TeamRole === 'Member'"><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Leader</a>
|
||||
<a class="btn-outline-secondary" style="margin-left: 5px;" ng-click="demoteToMember(user)" ng-if="user.TeamRole === 'Leader'"><i class="fa fa-user-times space-right" aria-hidden="true"></i>Member</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!teamMembers">
|
||||
<td colspan="2" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="teamMembers.length === 0 || (teamMembers | filter:state.filterGroupMembers).length === 0">
|
||||
<td colspan="2" class="text-center text-muted">No team members.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="teamMembers" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls pagination-id="table2"></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
229
app/components/team/teamController.js
Normal file
229
app/components/team/teamController.js
Normal file
|
@ -0,0 +1,229 @@
|
|||
angular.module('team', [])
|
||||
.controller('TeamController', ['$q', '$scope', '$state', '$stateParams', 'TeamService', 'UserService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication',
|
||||
function ($q, $scope, $state, $stateParams, TeamService, UserService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication) {
|
||||
|
||||
$scope.state = {
|
||||
pagination_count_users: Pagination.getPaginationCount('team_available_users'),
|
||||
pagination_count_members: Pagination.getPaginationCount('team_members')
|
||||
};
|
||||
$scope.sortTypeUsers = 'Username';
|
||||
$scope.sortReverseUsers = true;
|
||||
$scope.users = [];
|
||||
$scope.teamMembers = [];
|
||||
$scope.leaderCount = 0;
|
||||
|
||||
$scope.orderUsers = function(sortType) {
|
||||
$scope.sortReverseUsers = ($scope.sortTypeUsers === sortType) ? !$scope.sortReverseUsers : false;
|
||||
$scope.sortTypeUsers = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCountUsers = function() {
|
||||
Pagination.setPaginationCount('team_available_users', $scope.state.pagination_count_users);
|
||||
};
|
||||
|
||||
$scope.sortTypeGroupMembers = 'TeamRole';
|
||||
$scope.sortReverseGroupMembers = false;
|
||||
|
||||
$scope.orderGroupMembers = function(sortType) {
|
||||
$scope.sortReverseGroupMembers = ($scope.sortTypeGroupMembers === sortType) ? !$scope.sortReverseGroupMembers : false;
|
||||
$scope.sortTypeGroupMembers = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCountGroupMembers = function() {
|
||||
Pagination.setPaginationCount('team_members', $scope.state.pagination_count_members);
|
||||
};
|
||||
|
||||
$scope.deleteTeam = function() {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to delete this team? Users in this team will not be deleted.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
deleteTeam();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.promoteToLeader = function(user) {
|
||||
$('#loadingViewSpinner').show();
|
||||
TeamMembershipService.updateMembership(user.MembershipId, user.Id, $scope.team.Id, 1)
|
||||
.then(function success(data) {
|
||||
$scope.leaderCount++;
|
||||
user.TeamRole = 'Leader';
|
||||
Notifications.success('User is now team leader', user.Username);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update user role');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.demoteToMember = function(user) {
|
||||
$('#loadingViewSpinner').show();
|
||||
TeamMembershipService.updateMembership(user.MembershipId, user.Id, $scope.team.Id, 2)
|
||||
.then(function success(data) {
|
||||
user.TeamRole = 'Member';
|
||||
$scope.leaderCount--;
|
||||
Notifications.success('User is now team member', user.Username);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update user role');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addAllUsers = function() {
|
||||
$('#loadingViewSpinner').show();
|
||||
var teamMembershipQueries = [];
|
||||
angular.forEach($scope.users, function (user) {
|
||||
teamMembershipQueries.push(TeamMembershipService.createMembership(user.Id, $scope.team.Id, 2));
|
||||
});
|
||||
$q.all(teamMembershipQueries)
|
||||
.then(function success(data) {
|
||||
var users = $scope.users;
|
||||
for (var i = 0; i < users.length; i++) {
|
||||
var user = users[i];
|
||||
user.MembershipId = data[i].Id;
|
||||
user.TeamRole = 'Member';
|
||||
}
|
||||
$scope.teamMembers = $scope.teamMembers.concat(users);
|
||||
$scope.users = [];
|
||||
Notifications.success('All users successfully added');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update team members');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addUser = function(user) {
|
||||
$('#loadingViewSpinner').show();
|
||||
TeamMembershipService.createMembership(user.Id, $scope.team.Id, 2)
|
||||
.then(function success(data) {
|
||||
removeUserFromArray(user.Id, $scope.users);
|
||||
user.TeamRole = 'Member';
|
||||
user.MembershipId = data.Id;
|
||||
$scope.teamMembers.push(user);
|
||||
Notifications.success('User added to team', user.Username);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update team members');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeAllUsers = function() {
|
||||
$('#loadingViewSpinner').show();
|
||||
var teamMembershipQueries = [];
|
||||
angular.forEach($scope.teamMembers, function (user) {
|
||||
teamMembershipQueries.push(TeamMembershipService.deleteMembership(user.MembershipId));
|
||||
});
|
||||
$q.all(teamMembershipQueries)
|
||||
.then(function success(data) {
|
||||
$scope.users = $scope.users.concat($scope.teamMembers);
|
||||
$scope.teamMembers = [];
|
||||
Notifications.success('All users successfully removed');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update team members');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeUser = function(user) {
|
||||
$('#loadingViewSpinner').show();
|
||||
TeamMembershipService.deleteMembership(user.MembershipId)
|
||||
.then(function success() {
|
||||
removeUserFromArray(user.Id, $scope.teamMembers);
|
||||
$scope.users.push(user);
|
||||
Notifications.success('User removed from team', user.Username);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update team members');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
function deleteTeam() {
|
||||
$('#loadingViewSpinner').show();
|
||||
TeamService.deleteTeam($scope.team.Id)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Team successfully deleted', $scope.team.Name);
|
||||
$state.go('teams');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove team');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function removeUserFromArray(id, users) {
|
||||
for (var i = 0, l = users.length; i < l; i++) {
|
||||
if (users[i].Id === id) {
|
||||
users.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assignUsersAndMembers(users, memberships) {
|
||||
for (var i = 0; i < users.length; i++) {
|
||||
var user = users[i];
|
||||
var member = false;
|
||||
for (var j = 0; j < memberships.length; j++) {
|
||||
var membership = memberships[j];
|
||||
if (user.Id === membership.UserId) {
|
||||
member = true;
|
||||
if (membership.Role === 1) {
|
||||
user.TeamRole = 'Leader';
|
||||
$scope.leaderCount++;
|
||||
} else {
|
||||
user.TeamRole = 'Member';
|
||||
}
|
||||
user.MembershipId = membership.Id;
|
||||
$scope.teamMembers.push(user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!member) {
|
||||
$scope.users.push(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
$scope.isAdmin = Authentication.getUserDetails().role === 1 ? true: false;
|
||||
$q.all({
|
||||
team: TeamService.team($stateParams.id),
|
||||
users: UserService.users(false),
|
||||
memberships: TeamService.userMemberships($stateParams.id)
|
||||
})
|
||||
.then(function success(data) {
|
||||
var users = data.users;
|
||||
$scope.team = data.team;
|
||||
assignUsersAndMembers(users, data.memberships);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve team details');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
130
app/components/teams/teams.html
Normal file
130
app/components/teams/teams.html
Normal file
|
@ -0,0 +1,130 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Teams">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="teams" ui-sref-opts="{reload: true}">
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||
</a>
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px;"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>Teams management</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="isAdmin">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-plus" title="Add a new team">
|
||||
</rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="teamname" class="col-sm-1 control-label text-left">Name</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="teamname" ng-model="formValues.Name" ng-change="checkNameValidity()" placeholder="e.g. development">
|
||||
<span class="input-group-addon"><i ng-class="{true: 'fa fa-check green-icon', false: 'fa fa-times red-icon'}[state.validName]" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- team-leaders -->
|
||||
<div class="form-group" ng-if="users.length > 0">
|
||||
<div class="col-sm-12" >
|
||||
<label class="control-label text-left">
|
||||
Select team leader(s)
|
||||
<portainer-tooltip position="bottom" message="You can assign one or more leaders to this team. Team leaders can manage their teams users and resources."></portainer-tooltip>
|
||||
</label>
|
||||
<span isteven-multi-select
|
||||
ng-if="users.length > 0"
|
||||
input-model="users"
|
||||
output-model="formValues.Leaders"
|
||||
button-label="Username"
|
||||
item-label="Username"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="Username"
|
||||
translation="{nothingSelected: 'Select one or more team leaders', search: 'Search...'}"
|
||||
style="margin-left: 20px;"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !team-leaders -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!state.validName || formValues.Name === ''" ng-click="addTeam()"><i class="fa fa-plus" aria-hidden="true"></i> Add team</button>
|
||||
<i id="createTeamSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
<span class="text-danger" ng-if="state.teamCreationError" style="margin: 5px;">
|
||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ state.teamCreationError }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-users" title="Teams">
|
||||
<div class="pull-right">
|
||||
Items per page:
|
||||
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left" ng-if="isAdmin">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
|
||||
</div>
|
||||
</rd-widget-taskbar>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-if="isAdmin">
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="users" ng-click="order('Name')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="team in (state.filteredTeams = (teams | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td ng-if="isAdmin"><input type="checkbox" ng-model="team.Checked" ng-change="selectItem(team)" /></td>
|
||||
<td>{{ team.Name }}</td>
|
||||
<td>
|
||||
<a ui-sref="team({id: team.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!teams">
|
||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="teams.length == 0">
|
||||
<td colspan="3" class="text-center text-muted">No teams available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div ng-if="teams" class="pull-left pagination-controls">
|
||||
<dir-pagination-controls></dir-pagination-controls>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
<rd-widget>
|
||||
</div>
|
||||
</div>
|
140
app/components/teams/teamsController.js
Normal file
140
app/components/teams/teamsController.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
angular.module('teams', [])
|
||||
.controller('TeamsController', ['$q', '$scope', '$state', 'TeamService', 'UserService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication',
|
||||
function ($q, $scope, $state, TeamService, UserService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication) {
|
||||
$scope.state = {
|
||||
userGroupGroupCreationError: '',
|
||||
selectedItemCount: 0,
|
||||
validName: false,
|
||||
pagination_count: Pagination.getPaginationCount('teams')
|
||||
};
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
$scope.formValues = {
|
||||
Name: '',
|
||||
Leaders: []
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
$scope.sortReverse = ($scope.sortType === sortType) ? !$scope.sortReverse : false;
|
||||
$scope.sortType = sortType;
|
||||
};
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('teams', $scope.state.pagination_count);
|
||||
};
|
||||
|
||||
$scope.selectItems = function (allSelected) {
|
||||
angular.forEach($scope.state.filteredTeams, function (team) {
|
||||
if (team.Checked !== allSelected) {
|
||||
team.Checked = allSelected;
|
||||
$scope.selectItem(team);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.selectItem = function (item) {
|
||||
if (item.Checked) {
|
||||
$scope.state.selectedItemCount++;
|
||||
} else {
|
||||
$scope.state.selectedItemCount--;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.checkNameValidity = function() {
|
||||
var valid = true;
|
||||
for (var i = 0; i < $scope.teams.length; i++) {
|
||||
if ($scope.formValues.Name === $scope.teams[i].Name) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$scope.state.validName = valid;
|
||||
$scope.state.teamCreationError = valid ? '' : 'Team name already existing';
|
||||
};
|
||||
|
||||
$scope.addTeam = function() {
|
||||
$('#createTeamSpinner').show();
|
||||
$scope.state.teamCreationError = '';
|
||||
var teamName = $scope.formValues.Name;
|
||||
var leaderIds = [];
|
||||
angular.forEach($scope.formValues.Leaders, function(user) {
|
||||
leaderIds.push(user.Id);
|
||||
});
|
||||
|
||||
TeamService.createTeam(teamName, leaderIds)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Team successfully created', teamName);
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create team');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#createTeamSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
function deleteSelectedTeams() {
|
||||
$('#loadingViewSpinner').show();
|
||||
var counter = 0;
|
||||
var complete = function () {
|
||||
counter = counter - 1;
|
||||
if (counter === 0) {
|
||||
$('#loadingViewSpinner').hide();
|
||||
}
|
||||
};
|
||||
angular.forEach($scope.teams, function (team) {
|
||||
if (team.Checked) {
|
||||
counter = counter + 1;
|
||||
TeamService.deleteTeam(team.Id)
|
||||
.then(function success(data) {
|
||||
var index = $scope.teams.indexOf(team);
|
||||
$scope.teams.splice(index, 1);
|
||||
Notifications.success('Team successfully deleted', team.Name);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove team');
|
||||
})
|
||||
.finally(function final() {
|
||||
complete();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.removeAction = function () {
|
||||
ModalService.confirmDeletion(
|
||||
'Do you want to delete the selected team(s)? Users in the team(s) will not be deleted.',
|
||||
function onConfirm(confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
deleteSelectedTeams();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true: false;
|
||||
$scope.isAdmin = isAdmin;
|
||||
$q.all({
|
||||
users: UserService.users(false),
|
||||
teams: isAdmin ? TeamService.teams() : UserService.userLeadingTeams(userDetails.ID)
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.teams = data.teams;
|
||||
$scope.users = data.users;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.teams = [];
|
||||
$scope.users = [];
|
||||
Notifications.error('Failure', err, 'Unable to retrieve teams');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
|
@ -67,21 +67,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- !env -->
|
||||
<!-- ownership -->
|
||||
<div class="form-group" ng-if="applicationState.application.authentication">
|
||||
<div class="col-sm-12">
|
||||
<label for="ownership" class="control-label text-left">
|
||||
Ownership
|
||||
<portainer-tooltip position="bottom" message="When setting the ownership value to private, only you and the administrators will be able to see and manage this object. When choosing public, everybody will be able to access it."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="btn-group btn-group-sm" style="margin-left: 20px;">
|
||||
<label class="btn btn-primary" ng-model="formValues.Ownership" uib-btn-radio="'private'">Private</label>
|
||||
<label class="btn btn-primary" ng-model="formValues.Ownership" uib-btn-radio="'public'">Public</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !ownership -->
|
||||
<!-- advanced-options -->
|
||||
<!-- access-control -->
|
||||
<div ng-include="'app/components/common/accessControlForm/accessControlForm.html'" ng-if="applicationState.application.authentication"></div>
|
||||
<!-- !access-control -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<a class="small interactive" ng-if="!state.showAdvancedOptions" ng-click="state.showAdvancedOptions = true;">
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
angular.module('templates', [])
|
||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$stateParams', '$anchorScroll', '$filter', 'Config', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'Pagination', 'ResourceControlService', 'Authentication',
|
||||
function ($scope, $q, $state, $stateParams, $anchorScroll, $filter, Config, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, Pagination, ResourceControlService, Authentication) {
|
||||
.controller('TemplatesController', ['$scope', '$q', '$state', '$stateParams', '$anchorScroll', '$filter', 'Config', 'ContainerService', 'ContainerHelper', 'ImageService', 'NetworkService', 'TemplateService', 'TemplateHelper', 'VolumeService', 'Notifications', 'Pagination', 'ResourceControlService', 'Authentication', 'ControllerDataPipeline', 'FormValidator',
|
||||
function ($scope, $q, $state, $stateParams, $anchorScroll, $filter, Config, ContainerService, ContainerHelper, ImageService, NetworkService, TemplateService, TemplateHelper, VolumeService, Notifications, Pagination, ResourceControlService, Authentication, ControllerDataPipeline, FormValidator) {
|
||||
$scope.state = {
|
||||
selectedTemplate: null,
|
||||
showAdvancedOptions: false,
|
||||
hideDescriptions: $stateParams.hide_descriptions,
|
||||
pagination_count: Pagination.getPaginationCount('templates'),
|
||||
formValidationError: '',
|
||||
filters: {
|
||||
Categories: '!',
|
||||
Platform: '!'
|
||||
}
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
Ownership: $scope.applicationState.application.authentication ? 'private' : '',
|
||||
network: '',
|
||||
name: ''
|
||||
};
|
||||
|
@ -37,38 +38,55 @@ function ($scope, $q, $state, $stateParams, $anchorScroll, $filter, Config, Cont
|
|||
$scope.state.selectedTemplate.Ports.splice(index, 1);
|
||||
};
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
$scope.state.formValidationError = '';
|
||||
var error = '';
|
||||
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
|
||||
|
||||
if (error) {
|
||||
$scope.state.formValidationError = error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$scope.createTemplate = function() {
|
||||
$('#createContainerSpinner').show();
|
||||
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var accessControlData = ControllerDataPipeline.getAccessControlFormData();
|
||||
var isAdmin = userDetails.role === 1 ? true : false;
|
||||
|
||||
if (!validateForm(accessControlData, isAdmin)) {
|
||||
$('#createContainerSpinner').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var template = $scope.state.selectedTemplate;
|
||||
var templateConfiguration = createTemplateConfiguration(template);
|
||||
var generatedVolumeCount = TemplateHelper.determineRequiredGeneratedVolumeCount(template.Volumes);
|
||||
var generatedVolumeIds = [];
|
||||
VolumeService.createXAutoGeneratedLocalVolumes(generatedVolumeCount)
|
||||
.then(function success(data) {
|
||||
var volumeResourceControlQueries = [];
|
||||
if ($scope.formValues.Ownership === 'private') {
|
||||
angular.forEach(data, function (volume) {
|
||||
volumeResourceControlQueries.push(ResourceControlService.setVolumeResourceControl(Authentication.getUserDetails().ID, volume.Name));
|
||||
});
|
||||
}
|
||||
TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data);
|
||||
return $q.all(volumeResourceControlQueries)
|
||||
.then(function success() {
|
||||
return ImageService.pullImage(template.Image, template.Registry);
|
||||
angular.forEach(data, function (volume) {
|
||||
var volumeId = volume.Id;
|
||||
generatedVolumeIds.push(volumeId);
|
||||
});
|
||||
TemplateService.updateContainerConfigurationWithVolumes(templateConfiguration, template, data);
|
||||
return ImageService.pullImage(template.Image, template.Registry);
|
||||
})
|
||||
.then(function success(data) {
|
||||
return ContainerService.createAndStartContainer(templateConfiguration);
|
||||
})
|
||||
.then(function success(data) {
|
||||
Notifications.success('Container started', data.Id);
|
||||
if ($scope.formValues.Ownership === 'private') {
|
||||
ResourceControlService.setContainerResourceControl(Authentication.getUserDetails().ID, data.Id)
|
||||
.then(function success(data) {
|
||||
$state.go('containers', {}, {reload: true});
|
||||
});
|
||||
} else {
|
||||
$state.go('containers', {}, {reload: true});
|
||||
}
|
||||
var containerIdentifier = data.Id;
|
||||
var userId = userDetails.ID;
|
||||
return ResourceControlService.applyResourceControl('container', containerIdentifier, userId, accessControlData, generatedVolumeIds);
|
||||
})
|
||||
.then(function success() {
|
||||
Notifications.success('Container successfully created');
|
||||
$state.go('containers', {}, {reload: true});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, err.msg);
|
||||
|
|
|
@ -15,12 +15,13 @@
|
|||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td><label>Name</label></td>
|
||||
<td>
|
||||
{{ user.Username }}
|
||||
<button class="btn btn-xs btn-danger" ng-click="deleteUser()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Delete this user</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<label for="permissions" class="control-label text-left">
|
||||
Administrator
|
||||
|
@ -31,6 +32,29 @@
|
|||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- <tr ng-if="!formValues.Administrator">
|
||||
<td colspan="2">
|
||||
<label for="teams" class="control-label text-left">
|
||||
Teams
|
||||
</label>
|
||||
<span class="small text-muted" style="margin-left: 20px;" ng-if="teams.length === 0">
|
||||
You have not yet created any team. Head over the <a ui-sref="teams">teams view</a> to manage user teams.</span>
|
||||
</span>
|
||||
<span isteven-multi-select
|
||||
ng-if="teams.length > 0"
|
||||
input-model="teams"
|
||||
output-model="formValues.Teams"
|
||||
button-label="Name"
|
||||
item-label="Name"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
translation="{nothingSelected: 'Select one or more teams', search: 'Search...'}"
|
||||
style="margin-left: 20px;"
|
||||
on-item-click="onTeamClick(data)"
|
||||
</span>
|
||||
</td>
|
||||
</tr> -->
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
|
@ -71,11 +95,6 @@
|
|||
<div class="col-sm-2">
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="formValues.newPassword === '' || formValues.newPassword !== formValues.confirmPassword" ng-click="updatePassword()">Update password</button>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<p class="pull-left text-danger" ng-if="state.updatePasswordError" style="margin: 5px;">
|
||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ state.updatePasswordError }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
angular.module('user', [])
|
||||
.controller('UserController', ['$scope', '$state', '$stateParams', 'UserService', 'ModalService', 'Notifications',
|
||||
function ($scope, $state, $stateParams, UserService, ModalService, Notifications) {
|
||||
.controller('UserController', ['$q', '$scope', '$state', '$stateParams', 'UserService', 'ModalService', 'Notifications',
|
||||
function ($q, $scope, $state, $stateParams, UserService, ModalService, Notifications) {
|
||||
|
||||
$scope.state = {
|
||||
updatePasswordError: '',
|
||||
updatePasswordError: ''
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
Administrator: false,
|
||||
Administrator: false
|
||||
};
|
||||
|
||||
$scope.deleteUser = function() {
|
||||
|
@ -32,7 +32,7 @@ function ($scope, $state, $stateParams, UserService, ModalService, Notifications
|
|||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to update user permissions');
|
||||
Notifications.error('Failure', err, 'Unable to update user permissions');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
|
@ -47,7 +47,7 @@ function ($scope, $state, $stateParams, UserService, ModalService, Notifications
|
|||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.state.updatePasswordError = 'Unable to update password';
|
||||
Notifications.error('Failure', err, 'Unable to update user password');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
|
@ -62,28 +62,30 @@ function ($scope, $state, $stateParams, UserService, ModalService, Notifications
|
|||
$state.go('users');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to remove user');
|
||||
Notifications.error('Failure', err, 'Unable to remove user');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function getUser() {
|
||||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
UserService.user($stateParams.id)
|
||||
$q.all({
|
||||
user: UserService.user($stateParams.id)
|
||||
})
|
||||
.then(function success(data) {
|
||||
var user = new UserViewModel(data);
|
||||
var user = data.user;
|
||||
$scope.user = user;
|
||||
$scope.formValues.Administrator = user.RoleId === 1 ? true : false;
|
||||
$scope.formValues.Administrator = user.Role === 1 ? true : false;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to retrieve user information');
|
||||
Notifications.error('Failure', err, 'Unable to retrieve user information');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
getUser();
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -49,8 +49,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- !confirm-password-input -->
|
||||
<!-- role-checkbox -->
|
||||
<div class="form-group">
|
||||
<!-- admin-checkbox -->
|
||||
<div class="form-group" ng-if="isAdmin">
|
||||
<div class="col-sm-12">
|
||||
<label for="permissions" class="control-label text-left">
|
||||
Administrator
|
||||
|
@ -61,10 +61,36 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !role-checkbox -->
|
||||
<div class="form-group">
|
||||
<!-- !admin-checkbox -->
|
||||
<!-- teams -->
|
||||
<div class="form-group" ng-if="!formValues.Administrator">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted">Note: non-administrator users do not have access to any endpoint by default. Head over the <a ui-sref="endpoints">endpoints view</a> to manage their accesses.</span>
|
||||
<label class="control-label text-left">
|
||||
Add to team(s)
|
||||
</label>
|
||||
<span class="small text-muted" style="margin-left: 20px;" ng-if="teams.length === 0">
|
||||
You have not yet created any team. Head over the <a ui-sref="teams">teams view</a> to manage user teams.</span>
|
||||
</span>
|
||||
<span isteven-multi-select
|
||||
ng-if="teams.length > 0"
|
||||
input-model="teams"
|
||||
output-model="formValues.Teams"
|
||||
button-label="Name"
|
||||
item-label="Name"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
translation="{nothingSelected: 'Select one or more teams', search: 'Search...'}"
|
||||
style="margin-left: 20px;"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !teams -->
|
||||
<div class="form-group" ng-if="isAdmin && !formValues.Administrator && formValues.Teams.length === 0">
|
||||
<div class="col-sm-12">
|
||||
<span class="small text-muted">
|
||||
Note: non-administrator users with no team do not have access to any endpoint by default. Head over the <a ui-sref="endpoints">endpoints view</a> to manage their accesses.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -98,7 +124,7 @@
|
|||
</div>
|
||||
</rd-widget-header>
|
||||
<rd-widget-taskbar classes="col-lg-12">
|
||||
<div class="pull-left">
|
||||
<div class="pull-left" ng-if="isAdmin">
|
||||
<button type="button" class="btn btn-danger" ng-click="removeAction()" ng-disabled="!state.selectedItemCount"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
|
@ -110,7 +136,7 @@
|
|||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<th ng-if="isAdmin">
|
||||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
|
@ -127,18 +153,20 @@
|
|||
<span ng-show="sortType == 'RoleName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th></th>
|
||||
<th ng-if="isAdmin"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="user in (state.filteredUsers = (users | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><input type="checkbox" ng-model="user.Checked" ng-change="selectItem(user)" /></td>
|
||||
<td ng-if="isAdmin"><input type="checkbox" ng-model="user.Checked" ng-change="selectItem(user)" /></td>
|
||||
<td>{{ user.Username }}</td>
|
||||
<td>
|
||||
<i ng-if="user.Role === 1" class="fa fa-user-circle-o" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
<i ng-if="user.Role !== 1 && !user.isTeamLeader" class="fa fa-user" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
<i ng-if="user.isTeamLeader" class="fa fa-user-plus" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ user.RoleName }}
|
||||
<i class="fa" ng-class="user.RoleId === 1 ? 'fa-user-circle-o' : 'fa-user'" aria-hidden="true" style="margin-left: 2px;"></i>
|
||||
</td>
|
||||
<td>
|
||||
<td ng-if="isAdmin">
|
||||
<a ui-sref="user({id: user.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('users', [])
|
||||
.controller('UsersController', ['$scope', '$state', 'UserService', 'ModalService', 'Notifications', 'Pagination',
|
||||
function ($scope, $state, UserService, ModalService, Notifications, Pagination) {
|
||||
.controller('UsersController', ['$q', '$scope', '$state', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication',
|
||||
function ($q, $scope, $state, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication) {
|
||||
$scope.state = {
|
||||
userCreationError: '',
|
||||
selectedItemCount: 0,
|
||||
|
@ -15,6 +15,7 @@ function ($scope, $state, UserService, ModalService, Notifications, Pagination)
|
|||
Password: '',
|
||||
ConfirmPassword: '',
|
||||
Administrator: false,
|
||||
Teams: []
|
||||
};
|
||||
|
||||
$scope.order = function(sortType) {
|
||||
|
@ -56,20 +57,25 @@ function ($scope, $state, UserService, ModalService, Notifications, Pagination)
|
|||
};
|
||||
|
||||
$scope.addUser = function() {
|
||||
$('#createUserSpinner').show();
|
||||
$scope.state.userCreationError = '';
|
||||
var username = $scope.formValues.Username;
|
||||
var password = $scope.formValues.Password;
|
||||
var role = $scope.formValues.Administrator ? 1 : 2;
|
||||
UserService.createUser(username, password, role)
|
||||
var teamIds = [];
|
||||
angular.forEach($scope.formValues.Teams, function(team) {
|
||||
teamIds.push(team.Id);
|
||||
});
|
||||
UserService.createUser(username, password, role, teamIds)
|
||||
.then(function success(data) {
|
||||
Notifications.success("User created", username);
|
||||
Notifications.success('User successfully created', username);
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.state.userCreationError = err.msg;
|
||||
Notifications.error('Failure', err, 'Unable to create user');
|
||||
})
|
||||
.finally(function final() {
|
||||
|
||||
$('#createUserSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -92,7 +98,7 @@ function ($scope, $state, UserService, ModalService, Notifications, Pagination)
|
|||
Notifications.success('User successfully deleted', user.Username);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, 'Unable to remove user');
|
||||
Notifications.error('Failure', err, 'Unable to remove user');
|
||||
})
|
||||
.finally(function final() {
|
||||
complete();
|
||||
|
@ -111,22 +117,46 @@ function ($scope, $state, UserService, ModalService, Notifications, Pagination)
|
|||
);
|
||||
};
|
||||
|
||||
function fetchUsers() {
|
||||
function assignTeamLeaders(users, memberships) {
|
||||
for (var i = 0; i < users.length; i++) {
|
||||
var user = users[i];
|
||||
user.isTeamLeader = false;
|
||||
for (var j = 0; j < memberships.length; j++) {
|
||||
var membership = memberships[j];
|
||||
if (user.Id === membership.UserId && membership.Role === 1) {
|
||||
user.isTeamLeader = true;
|
||||
user.RoleName = 'team leader';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initView() {
|
||||
$('#loadUsersSpinner').show();
|
||||
UserService.users()
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
var isAdmin = userDetails.role === 1 ? true: false;
|
||||
$scope.isAdmin = isAdmin;
|
||||
$q.all({
|
||||
users: UserService.users(true),
|
||||
teams: isAdmin ? TeamService.teams() : UserService.userLeadingTeams(userDetails.ID),
|
||||
memberships: TeamMembershipService.memberships()
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.users = data.map(function(user) {
|
||||
return new UserViewModel(user);
|
||||
});
|
||||
var users = data.users;
|
||||
assignTeamLeaders(users, data.memberships);
|
||||
$scope.users = users;
|
||||
$scope.teams = data.teams;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to retrieve users");
|
||||
Notifications.error('Failure', err, 'Unable to retrieve users and teams');
|
||||
$scope.users = [];
|
||||
$scope.teams = [];
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadUsersSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
fetchUsers();
|
||||
initView();
|
||||
}]);
|
||||
|
|
68
app/components/volume/volume.html
Normal file
68
app/components/volume/volume.html
Normal file
|
@ -0,0 +1,68 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Volume details">
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="volumes">Volumes</a> > <a ui-sref="volume({id: volume.Id})">{{ volume.Id }}</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row" ng-if="volume">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-cube" title="Volume details"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>
|
||||
{{ volume.Id }}
|
||||
<button class="btn btn-xs btn-danger" ng-click="removeVolume()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove this volume</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mount path</td>
|
||||
<td>{{ volume.Mountpoint }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Driver</td>
|
||||
<td>{{ volume.Driver }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!(volume.Labels | emptyobject)">
|
||||
<td>Labels</td>
|
||||
<td>
|
||||
<table class="table table-bordered table-condensed">
|
||||
<tr ng-repeat="(k, v) in volume.Labels">
|
||||
<td>{{ k }}</td>
|
||||
<td>{{ v }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-include="'app/components/common/accessControlPanel/accessControlPanel.html'" ng-if="volume && applicationState.application.authentication"></div>
|
||||
|
||||
<div class="row" ng-if="!(volume.Options | emptyobject)">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-cogs" title="Volume options"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr ng-repeat="(key, value) in volume.Options">
|
||||
<td>{{ key }}</td>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
37
app/components/volume/volumeController.js
Normal file
37
app/components/volume/volumeController.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
angular.module('volume', [])
|
||||
.controller('VolumeController', ['$scope', '$state', '$stateParams', 'VolumeService', 'Notifications', 'ControllerDataPipeline',
|
||||
function ($scope, $state, $stateParams, VolumeService, Notifications, ControllerDataPipeline) {
|
||||
|
||||
$scope.removeVolume = function removeVolume() {
|
||||
$('#loadingViewSpinner').show();
|
||||
VolumeService.remove($scope.volume)
|
||||
.then(function success(data) {
|
||||
Notifications.success('Volume successfully removed', $stateParams.id);
|
||||
$state.go('volumes', {});
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove volume');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
VolumeService.volume($stateParams.id)
|
||||
.then(function success(data) {
|
||||
var volume = data;
|
||||
ControllerDataPipeline.setAccessControlData('volume', volume.Id, volume.ResourceControl);
|
||||
$scope.volume = volume;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve volume details');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
|
@ -40,10 +40,10 @@
|
|||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="volumes" ng-click="order('Name')">
|
||||
<a ui-sref="volumes" ng-click="order('Id')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Name' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Name' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span ng-show="sortType == 'Id' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Id' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
|
@ -53,18 +53,11 @@
|
|||
<span ng-show="sortType == 'Driver' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="volumes" ng-click="order('Mountpoint')">
|
||||
Mountpoint
|
||||
<span ng-show="sortType == 'Mountpoint' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Mountpoint' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="applicationState.application.authentication">
|
||||
<a ui-sref="volumes" ng-click="order('Metadata.ResourceControl.OwnerId')">
|
||||
<a ui-sref="volumes" ng-click="order('ResourceControl.Ownership')">
|
||||
Ownership
|
||||
<span ng-show="sortType == 'Metadata.ResourceControl.OwnerId' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Metadata.ResourceControl.OwnerId' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'ResourceControl.Ownership' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
|
@ -72,28 +65,12 @@
|
|||
<tbody>
|
||||
<tr dir-paginate="volume in (state.filteredVolumes = (volumes | filter:state.filter | orderBy:sortType:sortReverse | itemsPerPage: state.pagination_count))">
|
||||
<td><input type="checkbox" ng-model="volume.Checked" ng-change="selectItem(volume)"/></td>
|
||||
<td>{{ volume.Name|truncate:50 }}</td>
|
||||
<td><a ui-sref="volume({id: volume.Id})">{{ volume.Id|truncate:50 }}</a></td>
|
||||
<td>{{ volume.Driver }}</td>
|
||||
<td>{{ volume.Mountpoint }}</td>
|
||||
<td ng-if="applicationState.application.authentication">
|
||||
<span ng-if="user.role === 1 && volume.Metadata.ResourceControl">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
<span ng-if="volume.Metadata.ResourceControl.OwnerId === user.ID">
|
||||
Private
|
||||
</span>
|
||||
<span ng-if="volume.Metadata.ResourceControl.OwnerId !== user.ID">
|
||||
Private <span ng-if="volume.Owner">(owner: {{ volume.Owner }})</span>
|
||||
</span>
|
||||
<a ng-click="switchOwnership(volume)" class="interactive"><i class="fa fa-eye" aria-hidden="true" style="margin-left: 7px;"></i> Switch to public</a>
|
||||
</span>
|
||||
<span ng-if="user.role !== 1 && volume.Metadata.ResourceControl.OwnerId === user.ID">
|
||||
<i class="fa fa-eye-slash" aria-hidden="true"></i>
|
||||
Private
|
||||
<a ng-click="switchOwnership(volume)" class="interactive"><i class="fa fa-eye" aria-hidden="true" style="margin-left: 7px;"></i> Switch to public</a>
|
||||
</span>
|
||||
<span ng-if="!volume.Metadata.ResourceControl">
|
||||
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||
Public
|
||||
<span>
|
||||
<i ng-class="volume.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||
{{ volume.ResourceControl.Ownership ? volume.ResourceControl.Ownership : volume.ResourceControl.Ownership = 'public' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,32 +1,11 @@
|
|||
angular.module('volumes', [])
|
||||
.controller('VolumesController', ['$scope', '$state', 'Volume', 'Notifications', 'Pagination', 'ModalService', 'Authentication', 'ResourceControlService', 'UserService',
|
||||
function ($scope, $state, Volume, Notifications, Pagination, ModalService, Authentication, ResourceControlService, UserService) {
|
||||
.controller('VolumesController', ['$q', '$scope', 'VolumeService', 'Notifications', 'Pagination',
|
||||
function ($q, $scope, VolumeService, Notifications, Pagination) {
|
||||
$scope.state = {};
|
||||
$scope.state.pagination_count = Pagination.getPaginationCount('volumes');
|
||||
$scope.state.selectedItemCount = 0;
|
||||
$scope.sortType = 'Name';
|
||||
$scope.sortReverse = true;
|
||||
$scope.config = {
|
||||
Name: ''
|
||||
};
|
||||
|
||||
function removeVolumeResourceControl(volume) {
|
||||
ResourceControlService.removeVolumeResourceControl(volume.Metadata.ResourceControl.OwnerId, volume.Name)
|
||||
.then(function success() {
|
||||
delete volume.Metadata.ResourceControl;
|
||||
Notifications.success('Ownership changed to public', volume.Name);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to change volume ownership");
|
||||
});
|
||||
}
|
||||
|
||||
$scope.switchOwnership = function(volume) {
|
||||
ModalService.confirmVolumeOwnershipChange(function (confirmed) {
|
||||
if(!confirmed) { return; }
|
||||
removeVolumeResourceControl(volume);
|
||||
});
|
||||
};
|
||||
$scope.sortType = 'Id';
|
||||
$scope.sortReverse = false;
|
||||
|
||||
$scope.changePaginationCount = function() {
|
||||
Pagination.setPaginationCount('volumes', $scope.state.pagination_count);
|
||||
|
@ -57,88 +36,46 @@ function ($scope, $state, Volume, Notifications, Pagination, ModalService, Authe
|
|||
$scope.removeAction = function () {
|
||||
$('#loadVolumesSpinner').show();
|
||||
var counter = 0;
|
||||
|
||||
var complete = function () {
|
||||
counter = counter - 1;
|
||||
if (counter === 0) {
|
||||
$('#loadVolumesSpinner').hide();
|
||||
}
|
||||
};
|
||||
|
||||
angular.forEach($scope.volumes, function (volume) {
|
||||
if (volume.Checked) {
|
||||
counter = counter + 1;
|
||||
Volume.remove({name: volume.Name}, function (d) {
|
||||
if (d.message) {
|
||||
Notifications.error("Unable to remove volume", {}, d.message);
|
||||
} else {
|
||||
if (volume.Metadata && volume.Metadata.ResourceControl) {
|
||||
ResourceControlService.removeVolumeResourceControl(volume.Metadata.ResourceControl.OwnerId, volume.Name)
|
||||
.then(function success() {
|
||||
Notifications.success("Volume deleted", volume.Name);
|
||||
var index = $scope.volumes.indexOf(volume);
|
||||
$scope.volumes.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to remove volume ownership");
|
||||
});
|
||||
} else {
|
||||
Notifications.success("Volume deleted", volume.Name);
|
||||
var index = $scope.volumes.indexOf(volume);
|
||||
$scope.volumes.splice(index, 1);
|
||||
}
|
||||
}
|
||||
complete();
|
||||
}, function (e) {
|
||||
Notifications.error("Failure", e, "Unable to remove volume");
|
||||
VolumeService.remove(volume)
|
||||
.then(function success() {
|
||||
Notifications.success('Volume deleted', volume.Id);
|
||||
var index = $scope.volumes.indexOf(volume);
|
||||
$scope.volumes.splice(index, 1);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to remove volume');
|
||||
})
|
||||
.finally(function final() {
|
||||
complete();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function mapUsersToVolumes(users) {
|
||||
angular.forEach($scope.volumes, function (volume) {
|
||||
if (volume.Metadata) {
|
||||
var volumeRC = volume.Metadata.ResourceControl;
|
||||
if (volumeRC && volumeRC.OwnerId !== $scope.user.ID) {
|
||||
angular.forEach(users, function (user) {
|
||||
if (volumeRC.OwnerId === user.Id) {
|
||||
volume.Owner = user.Username;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fetchVolumes() {
|
||||
function initView() {
|
||||
$('#loadVolumesSpinner').show();
|
||||
var userDetails = Authentication.getUserDetails();
|
||||
$scope.user = userDetails;
|
||||
|
||||
Volume.query({}, function (d) {
|
||||
var volumes = d.Volumes || [];
|
||||
$scope.volumes = volumes.map(function (v) {
|
||||
return new VolumeViewModel(v);
|
||||
});
|
||||
if (userDetails.role === 1) {
|
||||
UserService.users()
|
||||
.then(function success(data) {
|
||||
mapUsersToVolumes(data);
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error("Failure", err, "Unable to retrieve users");
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadVolumesSpinner').hide();
|
||||
});
|
||||
} else {
|
||||
$('#loadVolumesSpinner').hide();
|
||||
}
|
||||
}, function (e) {
|
||||
$('#loadVolumesSpinner').hide();
|
||||
Notifications.error("Failure", e, "Unable to retrieve volumes");
|
||||
VolumeService.volumes()
|
||||
.then(function success(data) {
|
||||
$scope.volumes = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve volumes');
|
||||
$scope.volumes = [];
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadVolumesSpinner').hide();
|
||||
});
|
||||
}
|
||||
fetchVolumes();
|
||||
initView();
|
||||
}]);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue