1
0
Fork 0
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:
Anthony Lapenna 2017-05-23 20:56:10 +02:00 committed by GitHub
parent a380fd9adc
commit 5523fc9023
160 changed files with 7112 additions and 3166 deletions

View file

@ -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) {

View 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>

View file

@ -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();
}]);

View 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>

View file

@ -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();
}]);

View file

@ -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>

View file

@ -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');
});
};

View file

@ -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);

View file

@ -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);
});

View file

@ -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>

View file

@ -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 () {

View file

@ -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();
}]);

View file

@ -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>

View file

@ -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');
});
}

View file

@ -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();
}]);

View file

@ -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>

View file

@ -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();

View file

@ -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 -->

View file

@ -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');
});
}

View file

@ -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();
});
}]);

View file

@ -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');
});
}

View file

@ -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>

View file

@ -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();
}]);

View file

@ -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)

View file

@ -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() {

View file

@ -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');
});
}]);

View file

@ -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() {

View file

@ -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');
});
}
}

View file

@ -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 = [];
});
}

View file

@ -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>

View file

@ -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');
});
}
}

View file

@ -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>

View file

@ -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">

View file

@ -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();
}]);

View file

@ -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>

View file

@ -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();
}]);

View file

@ -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>

View file

@ -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);
}
});
};

View file

@ -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>

View file

@ -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();
}]);

View file

@ -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();
}]);

View file

@ -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>

View file

@ -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();
}]);

View 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>

View 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();
}]);

View 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>

View 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();
}]);

View file

@ -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;">

View file

@ -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);

View file

@ -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>

View file

@ -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();
}]);

View file

@ -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>

View file

@ -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();
}]);

View 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>

View 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();
}]);

View file

@ -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>

View file

@ -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();
}]);