PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,48 @@
<div class="capabilitiesEdit" ng-class="{ busy: $ctrl.isBusy }">
<div
class="chip"
ng-repeat="capability in $ctrl.availableCapabilities"
ng-if="$ctrl.capabilitiesSet[capability.id]"
>
<span
class="capability-name"
title="{{ capability.description }} {{ ($ctrl.isIncludedInRole(capability) ? ('<br/><br/>' + ('UsersManager_IncludedInUsersRole'|translate)): '') }}"
>
{{ capability.category }}: {{ capability.name }}
</span>
<span
class="icon-close"
ng-if="!$ctrl.isIncludedInRole(capability)"
ng-click="$ctrl.capabilityToAddOrRemove = capability; $ctrl.onToggleCapability(fakse)"
></span>
</div>
<div piwik-field uicontrol="expandable-select"
name="add_capability"
full-width="true"
ng-change="$ctrl.onToggleCapability(true)"
ng-model="$ctrl.capabilityToAddOrRemoveId"
options="$ctrl.availableCapabilitiesGrouped"
data-disabled="$ctrl.isBusy"
ng-if="$ctrl.availableCapabilitiesGrouped.length"
class="addCapability"
>
</div>
<div class="ui-confirm confirmCapabilityToggle modal">
<div class="modal-content">
<h2 ng-if="$ctrl.isAddingCapability" piwik-translate="UsersManager_AreYouSureAddCapability">
<strong>{{ $ctrl.userLogin }}</strong>::<strong>{{ $ctrl.capabilityToAddOrRemove.name }}</strong>::<strong>{{ $ctrl.siteName }}</strong>
</h2>
<h2 ng-if="!$ctrl.isAddingCapability" piwik-translate="UsersManager_AreYouSureRemoveCapability">
<strong>{{ $ctrl.capabilityToAddOrRemove.name }}</strong>::<strong>{{ $ctrl.userLogin }}</strong>::<strong>{{ $ctrl.siteName }}</strong>
</h2>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.toggleCapability()">{{:: 'General_Yes'|translate }}</a>
<a href="" class="modal-action modal-close modal-no" ng-click="$ctrl.capabilityToAddOrRemove = null; $ctrl.capabilityToAddOrRemoveId = null">
{{:: 'General_No'|translate }}
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,213 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <piwik-capabilities-edit>
*/
(function () {
angular.module('piwikApp').component('piwikCapabilitiesEdit', {
templateUrl: 'plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.html?cb=' + piwik.cacheBuster,
bindings: {
idsite: '<',
siteName: '<',
userLogin: '<',
userRole: '<',
capabilities: '<',
onCapabilitiesChange: '&',
},
controller: CapabilitiesEditController
});
CapabilitiesEditController.$inject = ['piwikApi', 'permissionsMetadataService', 'piwik', '$element'];
function CapabilitiesEditController(piwikApi, permissionsMetadataService, piwik, $element) {
var vm = this;
vm.isBusy = false;
vm.availableCapabilities = [];
vm.availableCapabilitiesGrouped = [];
vm.capabilitiesSet = {};
// intermediate state
vm.isAddingCapability = false;
vm.capabilityToAddOrRemoveId = null;
vm.capabilityToAddOrRemove = null;
vm.$onInit = $onInit;
vm.$onChanges = $onChanges;
vm.onToggleCapability = onToggleCapability;
vm.toggleCapability = toggleCapability;
vm.isIncludedInRole = isIncludedInRole;
function $onInit() {
fetchAvailableCapabilities();
if (typeof vm.capabilities === 'undefined') {
fetchCapabilities();
}
}
function $onChanges() {
setCapabilitiesSet();
}
function isIncludedInRole(capability) {
return capability.includedInRoles.indexOf(vm.userRole) !== -1;
}
function fetchAvailableCapabilities() {
permissionsMetadataService.getAllCapabilities()
.then(function (capabilities) {
vm.availableCapabilities = capabilities;
setCapabilitiesSet();
setAvailableCapabilitiesDropdown();
});
}
function fetchCapabilities() {
vm.isBusy = true;
piwikApi.fetch({
method: 'UsersManager.getUsersPlusRole',
limit: '1',
filter_search: vm.userLogin,
}).then(function (user) {
if (!user || !user.capabilities) {
return [];
}
return user.capabilities;
}).then(function (capabilities) {
vm.capabilities = capabilities;
setCapabilitiesSet();
setAvailableCapabilitiesDropdown();
})['finally'](function () {
vm.isBusy = false;
});
}
function setCapabilitiesSet() {
vm.capabilitiesSet = {};
(vm.capabilities || []).forEach(function (capability) {
vm.capabilitiesSet[capability] = true;
});
(vm.availableCapabilities || []).forEach(function (capability) {
if (vm.isIncludedInRole(capability)) {
vm.capabilitiesSet[capability.id] = true;
}
});
}
function setAvailableCapabilitiesDropdown() {
var availableCapabilitiesGrouped = [];
vm.availableCapabilities.forEach(function (capability) {
if (vm.capabilitiesSet[capability.id]) {
return;
}
availableCapabilitiesGrouped.push({
group: capability.category,
key: capability.id,
value: capability.name,
tooltip: capability.description,
});
});
vm.availableCapabilitiesGrouped = availableCapabilitiesGrouped;
vm.availableCapabilitiesGrouped.sort(function (lhs, rhs) {
if (lhs.group === rhs.group) {
if (lhs.value === rhs.value) {
return 0;
}
return lhs.value < rhs.value ? -1 : 1;
}
return lhs.group < rhs.group ? -1 : 1;
});
}
function onToggleCapability(isAdd) {
vm.isAddingCapability = isAdd;
vm.availableCapabilities.forEach(function (capability) {
if (capability.id === vm.capabilityToAddOrRemoveId) {
vm.capabilityToAddOrRemove = capability;
}
});
$element.find('.confirmCapabilityToggle').openModal({
dismissible: false,
yes: function () {
},
});
}
function toggleCapability() {
if (vm.isAddingCapability) {
addCapability(vm.capabilityToAddOrRemove);
} else {
removeCapability(vm.capabilityToAddOrRemove);
}
}
function addCapability(capability) {
vm.isBusy = true;
piwikApi.post({
method: 'UsersManager.addCapabilities',
}, {
userLogin: vm.userLogin,
capabilities: capability.id,
idSites: vm.idsite
}).then(function () {
vm.onCapabilitiesChange.call({
capabilities: getCapabilitiesList(),
});
setCapabilitiesSet();
setAvailableCapabilitiesDropdown();
})['finally'](function () {
vm.isBusy = false;
vm.capabilityToAddOrRemove = null;
vm.capabilityToAddOrRemoveId = null;
});
}
function removeCapability(capability) {
vm.isBusy = true;
piwikApi.post({
method: 'UsersManager.removeCapabilities',
}, {
userLogin: vm.userLogin,
capabilities: capability.id,
idSites: vm.idsite
}).then(function () {
vm.onCapabilitiesChange.call({
capabilities: getCapabilitiesList(),
});
setCapabilitiesSet();
setAvailableCapabilitiesDropdown();
})['finally'](function () {
vm.isBusy = false;
vm.capabilityToAddOrRemove = null;
vm.capabilityToAddOrRemoveId = null;
});
}
function getCapabilitiesList() {
var result = [];
vm.availableCapabilities.forEach(function (capability) {
if (vm.isIncludedInRole(capability)) {
return;
}
if (vm.capabilitiesSet[capability.id]) {
result.push(capability.id);
}
});
return result;
}
}
})();

View File

@ -0,0 +1,76 @@
.capabilitiesEdit {
display: inline-block;
margin-bottom: -8px;
> div.chip {
margin-right: 8px;
display: inline-block;
margin-bottom: 8px;
span.capability-name {
padding-right: .4rem;
}
> span.icon-close {
font-size: .6rem;
float: right;
margin-top: 1.4em;
cursor: pointer;
}
}
.addCapability {
display: inline-block;
margin-bottom: 8px;
vertical-align: bottom;
.input-field {
margin-top: 0;
.caret {
right: -25px;
top: 16px;
z-index: 9;
cursor: pointer;
}
.select-dropdown {
margin-top: 8px;
margin-bottom: 0;
border: 0;
background: #e4e4e4;
display: inline-block;
height: 32px;
font-size: 13px;
font-weight: 500;
color: rgba(0,0,0,0.6);
line-height: 32px;
border-radius: 3em;
padding-left: 12px;
padding-right: 30px;
}
}
.select-wrapper {
transform: scale(.89) translate(-.6rem);
margin-top: -0.55rem;
max-width: 160px;
input {
margin-bottom: 0;
height: 2rem;
line-height: 2rem;
}
}
}
&.busy {
opacity: 0.5;
}
.confirmCapabilityToggle {
.modal-no {
float: right;
margin-right: 1em;
margin-top: 1em;
}
}
}

View File

@ -0,0 +1,3 @@
<div class="capabilityEdit">
{{ capabilityEdit.myProperty }}
</div>

View File

@ -0,0 +1,3 @@
.capabilityEdit {
// ...
}

View File

@ -0,0 +1,206 @@
<div class="pagedUsersList" ng-class="{loading: $ctrl.isLoadingUsers}">
<div class="userListFilters row">
<div class="col s12 m12 l6">
<div class="input-field col s12 m4 l4">
<a
class='dropdown-trigger btn bulk-actions'
href=''
data-activates='user-list-bulk-actions'
piwik-dropdown-menu
ng-class="{ disabled: $ctrl.isBulkActionsDisabled }"
>
{{:: 'UsersManager_BulkActions'|translate }}
</a>
<ul id='user-list-bulk-actions' class='dropdown-content'>
<li>
<a
class='dropdown-trigger'
data-activates="bulk-set-access"
piwik-dropdown-menu
>
{{:: 'UsersManager_SetPermission'|translate }}
</a>
<ul id="bulk-set-access" class="dropdown-content">
<li ng-repeat="access in $ctrl.bulkActionAccessLevels">
<a
href=""
ng-click="$ctrl.userToChange = null; $ctrl.roleToChangeTo = access.key; $ctrl.showAccessChangeConfirm();"
>
{{ access.value }}
</a>
</li>
</ul>
</li>
<li>
<a
href=""
ng-click="$ctrl.userToChange = null; $ctrl.roleToChangeTo = 'noaccess'; $ctrl.showAccessChangeConfirm();"
>
{{:: 'UsersManager_RemovePermissions'|translate }}
</a>
</li>
<li ng-if="$ctrl.currentUserRole == 'superuser'">
<a href="" ng-click="$ctrl.showDeleteConfirm()">{{:: 'UsersManager_DeleteUsers'|translate }}</a>
</li>
</ul>
</div>
<div class="input-field col s12 m4 l4">
<div
piwik-field
name="user-text-filter"
class="permissions-for-selector"
uicontrol="text"
ng-model="$ctrl.userTextFilter"
ng-model-options="{debounce: 300}"
placeholder="{{:: 'UsersManager_UserSearch'|translate }}"
full-width="true"
ng-change="$ctrl.changeSearch({ filter_search: $ctrl.userTextFilter })"
></div>
</div>
<div class="input-field col s12 m4 l4">
<div
piwik-field
name="access-level-filter"
uicontrol="select"
ng-model="$ctrl.accessLevelFilter"
placeholder="{{:: 'UsersManager_FilterByAccess'|translate }}"
options="$ctrl.filterAccessLevels"
full-width="true"
ng-change="$ctrl.changeSearch({ filter_access: $ctrl.accessLevelFilter })"
></div>
</div>
</div>
<div class="input-field col s12 m12 l6 users-list-pagination-container" ng-if="$ctrl.totalEntries > $ctrl.searchParams.limit">
<div class="usersListPagination">
<a class="btn prev" ng-class="{ disabled: $ctrl.searchParams.offset <= 0 }" ng-click="$ctrl.gotoPreviousPage()">
<span class="pointer">« {{:: 'General_Previous'|translate }}</span>
</a>
<div class="counter">
<span>
{{ 'General_Pagination'|translate:$ctrl.getPaginationLowerBound():$ctrl.getPaginationUpperBound():$ctrl.totalEntries }}
</span>
<div piwik-activity-indicator ng-if="$ctrl.isLoadingUsers" loading="$ctrl.isLoadingUsers"></div>
</div>
<a class="btn next" ng-class="{ disabled: $ctrl.searchParams.offset + $ctrl.searchParams.limit >= $ctrl.totalEntries }" ng-click="$ctrl.gotoNextPage()">
<span class="pointer">{{:: 'General_Next'|translate }} »</span>
</a>
</div>
</div>
</div>
<div piwik-notification context="info" type="persistent" noclear="true" ng-if="$ctrl.isRoleHelpToggled" class="roles-help-notification">
<span piwik-translate="UsersManager_RolesHelp"><a href='https://matomo.org/faq/general/faq_70/' target='_blank' rel='noreferrer noopener'>::</a>::<a href='https://matomo.org/faq/general/faq_69/' target='_blank' rel='noreferrer noopener'>::</a></span>
</div>
<div piwik-content-block>
<table piwik-content-table id="manageUsersTable" ng-class="{ loading: $ctrl.isLoadingUsers }">
<thead>
<tr>
<th class="select-cell">
<span class="checkbox-container">
<input type="checkbox" id="paged_users_select_all" checked="checked" ng-model="$ctrl.isAllCheckboxSelected" ng-change="$ctrl.onAllCheckboxChange()" />
<label for="paged_users_select_all"></label>
</span>
</th>
<th class='first'>{{:: 'UsersManager_Username'|translate }}</th>
<th class="role_header">
<span>{{:: 'UsersManager_RoleFor'|translate }}</span>
<a href="" class="helpIcon" ng-click="$ctrl.isRoleHelpToggled = !$ctrl.isRoleHelpToggled" ng-class="{ sticky: $ctrl.isRoleHelpToggled }">
<span class="icon-help"></span>
</a>
<div
piwik-field
class="permissions-for-selector"
uicontrol="site"
ng-model="$ctrl.permissionsForSite"
ng-change="$ctrl.changeSearch({ idSite: $ctrl.permissionsForSite.id })"
ui-control-attributes="{ onlySitesWithAdminAccess: $ctrl.currentUserRole !== 'superuser' }"
></div>
</th>
<th ng-if="$ctrl.currentUserRole == 'superuser'">{{:: 'UsersManager_Email'|translate }}</th>
<th ng-if="$ctrl.currentUserRole == 'superuser'" title="{{'UsersManager_UsesTwoFactorAuthentication'|translate}}">{{:: 'UsersManager_2FA'|translate }}</th>
<th ng-if="$ctrl.currentUserRole == 'superuser'">{{:: 'UsersManager_LastSeen'|translate }}</th>
<th class="actions-cell-header"><div>{{:: 'General_Actions'|translate }}</div></th>
</tr>
</thead>
<tbody>
<tr class="select-all-row" ng-if="$ctrl.isAllCheckboxSelected && $ctrl.users.length && $ctrl.users.length < $ctrl.totalEntries">
<td colspan="8">
<div ng-if="!$ctrl.areAllResultsSelected">
<span piwik-translate="UsersManager_TheDisplayedUsersAreSelected"><strong>{{ $ctrl.users.length }}</strong></span>
<a class="toggle-select-all-in-search" href="#" ng-click="$ctrl.areAllResultsSelected = !$ctrl.areAllResultsSelected" piwik-translate="UsersManager_ClickToSelectAll"><strong>{{ $ctrl.totalEntries }}</strong></a>
</div>
<div ng-if="$ctrl.areAllResultsSelected">
<span piwik-translate="UsersManager_AllUsersAreSelected"><strong>{{ $ctrl.totalEntries }}</strong></span>
<a class="toggle-select-all-in-search" href="#" ng-click="$ctrl.areAllResultsSelected = !$ctrl.areAllResultsSelected" piwik-translate="UsersManager_ClickToSelectDisplayedUsers"><strong>{{ $ctrl.users.length }}</strong></a>
</div>
</td>
</tr>
<tr ng-repeat="user in $ctrl.users" ng-attr-id="row{{ $index }}">
<td class="select-cell">
<span class="checkbox-container">
<input type="checkbox" ng-attr-id="paged_users_select_row{{ $index }}" checked="checked" ng-model="$ctrl.selectedRows[$index]" ng-click="$ctrl.onRowSelected()" />
<label ng-attr-for="paged_users_select_row{{ $index }}"></label>
</span>
</td>
<td id="userLogin">{{ user.login }}</td>
<td class="access-cell">
<div
piwik-field
uicontrol="select"
ng-model="user.role"
options="user.login != 'anonymous' ? $ctrl.accessLevels : $ctrl.anonymousAccessLevels"
ng-change="$ctrl.userToChange = user; $ctrl.roleToChangeTo = user.role; $ctrl.previousRole = '{{ user.role }}'; $ctrl.showAccessChangeConfirm();"
data-disabled="user.role == 'superuser'"
></div>
</td>
<td id="email" ng-if="$ctrl.currentUserRole == 'superuser'">{{ user.email }}</td>
<td id="twofa" ng-if="$ctrl.currentUserRole == 'superuser'">
{{ user.uses_2fa ? ('✓') : '☓' }}
</td>
<td id="last_seen" ng-if="$ctrl.currentUserRole == 'superuser'">
{{ user.last_seen ? (user.last_seen + ' ago') : '-' }}
</td>
<td class="center actions-cell">
<button class="edituser table-action" title="Edit" ng-click="$ctrl.onEditUser({ user: user })" ng-if="user.login != 'anonymous'">
<span class="icon-edit"></span>
</button>
<button class="deleteuser table-action" title="Delete" ng-click="$ctrl.userToChange = user; $ctrl.showDeleteConfirm()" ng-if="$ctrl.currentUserRole == 'superuser' && user.login != 'anonymous'">
<span class="icon-delete"></span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="delete-user-confirm-modal modal">
<div class="modal-content">
<h3 ng-if="$ctrl.userToChange" piwik-translate="UsersManager_DeleteUserConfirmSingle"><strong>{{ $ctrl.userToChange.login }}</strong></h3>
<p ng-if="!$ctrl.userToChange" piwik-translate="UsersManager_DeleteUserConfirmMultiple"><strong>{{ $ctrl.getAffectedUsersCount() }}</strong></p>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.deleteRequestedUsers()">{{:: 'General_Yes'|translate }}</a>
<a href="" class="modal-action modal-close modal-no" ng-click="$ctrl.userToChange = null; $ctrl.roleToChangeTo = null;">{{:: 'General_No'|translate }}</a>
</div>
</div>
<div class="change-user-role-confirm-modal modal">
<div class="modal-content">
<h3 ng-if="$ctrl.userToChange" piwik-translate="UsersManager_DeleteUserPermConfirmSingle"><strong>{{ $ctrl.userToChange.login }}</strong>::<strong>{{ $ctrl.getRoleDisplay($ctrl.roleToChangeTo) }}</strong>::<strong>{{ $ctrl.permissionsForSite.name }}</strong></h3>
<h3 ng-if="$ctrl.userToChange && $ctrl.userToChange.login == 'anonymous' && $ctrl.roleToChangeTo == 'view'"><em>{{ 'General_Note'|translate }}: <span piwik-translate="UsersManager_AnonymousUserRoleChangeWarning">anonymous::{{ $ctrl.getRoleDisplay($ctrl.roleToChangeTo) }}</span></em></h3>
<p ng-if="!$ctrl.userToChange" piwik-translate="UsersManager_DeleteUserPermConfirmMultiple"><strong>{{ $ctrl.getAffectedUsersCount() }}</strong>::<strong>{{ $ctrl.getRoleDisplay($ctrl.roleToChangeTo) }}</strong>::<strong>{{ $ctrl.permissionsForSite.name }}</strong></p>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.changeUserRole()">{{:: 'General_Yes'|translate }}</a>
<a href="" class="modal-action modal-close modal-no" ng-click="$ctrl.userToChange.role = $ctrl.previousRole; $ctrl.userToChange = null; $ctrl.roleToChangeTo = null;">{{:: 'General_No'|translate }}</a>
</div>
</div>
</div>

View File

@ -0,0 +1,223 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <piwik-paged-users-list>
*/
(function () {
angular.module('piwikApp').component('piwikPagedUsersList', {
templateUrl: 'plugins/UsersManager/angularjs/paged-users-list/paged-users-list.component.html?cb=' + piwik.cacheBuster,
bindings: {
onEditUser: '&',
onChangeUserRole: '&',
onDeleteUser: '&',
onSearchChange: '&',
initialSiteId: '<',
initialSiteName: '<',
currentUserRole: '<',
isLoadingUsers: '<',
accessLevels: '<',
filterAccessLevels: '<',
totalEntries: '<',
users: '<',
searchParams: '<'
},
controller: PagedUsersListController
});
PagedUsersListController.$inject = ['$element'];
function PagedUsersListController($element) {
var vm = this;
// options for selects
vm.bulkActionAccessLevels = null;
// selection state
vm.areAllResultsSelected = false;
vm.selectedRows = {};
vm.isAllCheckboxSelected = false;
// intermediate state
vm.isBulkActionsDisabled = true;
vm.userToChange = null;
vm.roleToChangeTo = null;
vm.previousRole = null;
vm.accessLevelFilter = null;
// other state
vm.isRoleHelpToggled = false;
vm.$onInit = $onInit;
vm.$onChanges = $onChanges;
vm.onAllCheckboxChange = onAllCheckboxChange;
vm.changeUserRole = changeUserRole;
vm.onRowSelected = onRowSelected;
vm.deleteRequestedUsers = deleteRequestedUsers;
vm.getPaginationLowerBound = getPaginationLowerBound;
vm.getPaginationUpperBound = getPaginationUpperBound;
vm.showDeleteConfirm = showDeleteConfirm;
vm.getAffectedUsersCount = getAffectedUsersCount;
vm.showAccessChangeConfirm = showAccessChangeConfirm;
vm.getRoleDisplay = getRoleDisplay;
vm.changeSearch = changeSearch;
vm.gotoPreviousPage = gotoPreviousPage;
vm.gotoNextPage = gotoNextPage;
function changeSearch(changes) {
var newParams = $.extend({}, vm.searchParams, changes);
vm.onSearchChange({ params: newParams });
}
function $onInit() {
vm.permissionsForSite = {
id: vm.initialSiteId,
name: vm.initialSiteName
};
vm.bulkActionAccessLevels = [];
vm.anonymousAccessLevels = [];
vm.accessLevels.forEach(function (entry) {
if (entry.key !== 'noaccess' && entry.key !== 'superuser') {
vm.bulkActionAccessLevels.push(entry);
}
if (entry.key === 'noaccess' || entry.key === 'view') {
vm.anonymousAccessLevels.push(entry);
}
});
}
function $onChanges(changes) {
if (changes.users) {
clearSelection();
}
}
function onAllCheckboxChange() {
if (!vm.isAllCheckboxSelected) {
clearSelection();
} else {
for (var i = 0; i !== vm.users.length; ++i) {
vm.selectedRows[i] = true;
}
vm.isBulkActionsDisabled = false;
}
}
function clearSelection() {
vm.selectedRows = {};
vm.areAllResultsSelected = false;
vm.isBulkActionsDisabled = true;
vm.isAllCheckboxSelected = false;
vm.userToChange = null;
}
function changeUserRole() {
vm.onChangeUserRole({
users: getUserOperationSubject(),
role: vm.roleToChangeTo
});
}
function deleteRequestedUsers() {
vm.onDeleteUser({
users: getUserOperationSubject(),
});
}
function getUserOperationSubject() {
if (vm.userToChange) {
return [vm.userToChange];
} else if (vm.areAllResultsSelected) {
return 'all';
} else {
return getSelectedUsers();
}
}
function showAccessChangeConfirm() {
$element.find('.change-user-role-confirm-modal').openModal({ dismissible: false });
}
function getAffectedUsersCount() {
if (vm.areAllResultsSelected) {
return vm.totalEntries;
}
return getSelectedCount();
}
function onRowSelected() {
var selectedRowKeyCount = getSelectedCount();
vm.isBulkActionsDisabled = selectedRowKeyCount === 0;
vm.isAllCheckboxSelected = selectedRowKeyCount === vm.users.length;
}
function getSelectedCount() {
var selectedRowKeyCount = 0;
Object.keys(vm.selectedRows).forEach(function (key) {
if (vm.selectedRows[key]) {
++selectedRowKeyCount;
}
});
return selectedRowKeyCount;
}
function getSelectedUsers() {
var result = [];
Object.keys(vm.selectedRows).forEach(function (index) {
if (vm.selectedRows[index]
&& vm.users[index] // sanity check
) {
result.push(vm.users[index]);
}
});
return result;
}
function getPaginationLowerBound() {
return vm.searchParams.offset + 1;
}
function getPaginationUpperBound() {
return Math.min(vm.searchParams.offset + vm.searchParams.limit, vm.totalEntries);
}
function showDeleteConfirm() {
$element.find('.delete-user-confirm-modal').openModal({ dismissible: false });
}
function getRoleDisplay(role) {
var result = null;
vm.accessLevels.forEach(function (entry) {
if (entry.key === role) {
result = entry.value;
}
});
return result;
}
function gotoPreviousPage() {
changeSearch({
offset: Math.max(0, vm.searchParams.offset - vm.searchParams.limit)
});
}
function gotoNextPage() {
var newOffset = vm.searchParams.offset + vm.searchParams.limit;
if (newOffset >= vm.totalEntries) {
return;
}
changeSearch({
offset: newOffset,
});
}
}
})();

View File

@ -0,0 +1,203 @@
piwik-paged-users-list {
display: block;
position: relative;
[piwik-siteselector] {
display: inline-block;
margin-left: .3rem;
}
.access-display-control {
position: absolute;
margin-left: .1rem;
label {
color: @theme-color-text;
}
}
.card {
margin-top: 0;
margin-bottom: 20px;
}
.card .card-content {
padding-top: 0;
padding-bottom: 0;
}
table.entityTable tbody tr td {
vertical-align: middle !important;
}
table.entityTable tbody tr td.actions-cell {
width: 130px;
padding: 0;
text-align: center;
}
table.entityTable th.actions-cell-header > div {
text-align: center;
}
table#manageUsersTable {
[piwik-field] {
.form-group {
margin: 0;
}
.input-field {
margin-top: 0;
padding: 0;
}
}
.select-wrapper {
transform: scale(.8);
width: 100px;
input {
margin-bottom: 0;
padding-bottom: .3em;
height: 1em;
line-height: 1em;
}
span.caret {
top: 0;
}
}
th.role_header {
.helpIcon {
color: #9e9e9e;
font-size: .8rem;
margin-left: .1rem;
text-decoration: none;
&:hover,&.sticky {
opacity: 1;
}
}
}
}
tbody span.checkbox-container {
label {
transform: scale(.8);
height: 1em;
line-height: 1em;
}
}
.select-cell {
width: 32px;
}
table.entityTable tbody tr.select-all-row > td {
padding: 6px;
text-align: center;
}
.sites_autocomplete {
display: block;
margin-left: 0;
}
.userListFilters {
> .col > .input-field {
display: inline-block;
vertical-align: top;
padding: 0;
}
.form-group, [piwik-form-field] .input-field {
margin: 0;
}
[piwik-form-field] input {
margin-bottom: 0;
}
.input-field > .btn {
margin-top: .7rem;
}
&.row {
margin-bottom: 0;
margin-left: -0.75rem;
margin-right: -0.75rem;
}
}
.usersListPagination {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: .7rem;
float: right;
.pointer {
cursor: pointer;
}
div.counter {
display: inline-block;
line-height: 36px;
vertical-align: bottom;
flex: 1;
text-align: center;
margin-left: 10px;
margin-right: 10px;
}
}
.delete-user-confirm-modal,.change-user-role-confirm-modal {
.modal-no {
float: right;
margin-right: 1em;
margin-top: 1em;
}
}
.pagedUsersList.loading {
table {
opacity: 0.5;
}
a, input, select, button, label {
pointer-events: none;
}
div.counter {
position: relative;
> span {
opacity: 0;
}
}
div[piwik-activity-indicator] {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
span {
display: none;
}
}
}
.roles-help-notification {
margin-top: 1rem;
}
}
#root piwik-paged-users-list .siteSelector.borderedControl {
background-color: white;
width: 150px;
}
#content piwik-paged-users-list .sites_autocomplete > .siteSelector {
position: static;
}

View File

@ -0,0 +1,35 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <piwik-capabilities-edit>
*/
(function () {
angular.module('piwikApp').factory('permissionsMetadataService', PermissionsMetadataService);
PermissionsMetadataService.$inject = ['piwikApi', '$q'];
function PermissionsMetadataService(piwikApi, $q) {
var allCapabilities;
return {
getAllCapabilities: function () {
if (allCapabilities) {
return $q.when(allCapabilities);
}
return piwikApi.fetch({
method: 'UsersManager.getAvailableCapabilities',
}).then(function (capabilities) {
allCapabilities = capabilities;
return allCapabilities;
});
},
};
}
})();

View File

@ -0,0 +1,47 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp').controller('AnonymousSettingsController', AnonymousSettingsController);
AnonymousSettingsController.$inject = ['piwikApi'];
function AnonymousSettingsController(piwikApi) {
// remember to keep controller very simple. Create a service/factory (model) if needed
var self = this;
function updateSettings(postParams)
{
self.loading = true;
piwikApi.withTokenInUrl();
piwikApi.post({
module: 'UsersManager', action: 'recordAnonymousUserSettings', format: 'json'
}, postParams).then(function (success) {
var UI = require('piwik/UI');
var notification = new UI.Notification();
notification.show(_pk_translate('CoreAdminHome_SettingsSaveSuccess'), {
id: 'anonymousUserSettings', context: 'success'});
notification.scrollToNotification();
self.loading = false;
}, function (errorMessage) {
self.loading = false;
});
}
this.save = function () {
var postParams = {
anonymousDefaultReport: this.defaultReport == '1' ? this.defaultReportWebsite : this.defaultReport,
anonymousDefaultDate: this.defaultDate
};
updateSettings(postParams);
};
}
})();

View File

@ -0,0 +1,103 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function () {
angular.module('piwikApp').controller('PersonalSettingsController', PersonalSettingsController);
PersonalSettingsController.$inject = ['piwikApi', '$window', 'piwik'];
function PersonalSettingsController(piwikApi, $window, piwik) {
// remember to keep controller very simple. Create a service/factory (model) if needed
var self = this;
this.doesRequirePasswordConfirmation = false;
function updateSettings(postParams)
{
self.loading = true;
piwikApi.withTokenInUrl();
piwikApi.post({
module: 'UsersManager', action: 'recordUserSettings', format: 'json'
}, postParams).then(function (success) {
var UI = require('piwik/UI');
var notification = new UI.Notification();
notification.show(_pk_translate('CoreAdminHome_SettingsSaveSuccess'), {
id: 'PersonalSettingsSuccess', context: 'success'});
notification.scrollToNotification();
self.doesRequirePasswordConfirmation = !!self.password;
self.passwordCurrent = '';
self.loading = false;
}, function (errorMessage) {
self.loading = false;
self.passwordCurrent = '';
});
}
this.requirePasswordConfirmation = function () {
this.doesRequirePasswordConfirmation = true;
};
this.regenerateTokenAuth = function () {
var parameters = { userLogin: piwik.userLogin };
self.loading = true;
piwikHelper.modalConfirm('#confirmTokenRegenerate', {yes: function () {
piwikApi.withTokenInUrl();
piwikApi.post({
module: 'API',
method: 'UsersManager.regenerateTokenAuth'
}, parameters).then(function (success) {
$window.location.reload();
self.loading = false;
}, function (errorMessage) {
self.loading = false;
});
}});
};
this.cancelSave = function () {
this.passwordCurrent = '';
};
this.save = function () {
if (this.doesRequirePasswordConfirmation && !this.passwordCurrent) {
angular.element('#confirmChangesWithPassword').openModal({ dismissible: false, ready: function () {
$('.modal.open #currentPassword').focus();
}});
return;
}
angular.element('#confirmChangesWithPassword').closeModal();
var postParams = {
email: this.email,
defaultReport: this.defaultReport == 'MultiSites' ? this.defaultReport : this.site.id,
defaultDate: this.defaultDate,
language: this.language,
timeformat: this.timeformat,
};
if (this.password) {
postParams.password = this.password;
}
if (this.passwordBis) {
postParams.passwordBis = this.passwordBis;
}
if (this.passwordCurrent) {
postParams.passwordConfirmation = this.passwordCurrent;
}
updateSettings(postParams);
};
}
})();

View File

@ -0,0 +1,188 @@
<div
piwik-content-block
content-title="{{ $ctrl.getFormTitle() }}"
class="userEditForm"
ng-class="{ loading: $ctrl.isSavingUserInfo }"
>
<div class="row" piwik-form>
<div class="col m2 entityList" ng-if="!$ctrl.isAdd">
<ul class="listCircle">
<li ng-class="{active: $ctrl.activeTab === 'basic'}" class="menuBasicInfo">
<a href="" ng-click="$ctrl.activeTab = 'basic'">{{:: 'UsersManager_BasicInformation'|translate }}</a>
</li>
<li ng-class="{active: $ctrl.activeTab === 'permissions'}" class="menuPermissions">
<a href="" ng-click="$ctrl.activeTab = 'permissions'">
{{:: 'UsersManager_Permissions'|translate }}
</a>
<span class="icon-warning" ng-if="!$ctrl.userHasAccess && !$ctrl.user.superuser_access"></span>
</li>
<li ng-class="{active: $ctrl.activeTab === 'superuser'}" class="menuSuperuser" ng-if="$ctrl.currentUserRole == 'superuser'">
<a href="" ng-click="$ctrl.activeTab = 'superuser'">{{:: 'UsersManager_SuperUserAccess'|translate }}</a>
</li>
<li ng-class="{active: $ctrl.activeTab === '2fa'}" class="menuUserTwoFa" ng-if="$ctrl.currentUserRole == 'superuser' && $ctrl.user.uses_2fa && !$ctrl.isAdd">
<a href="" ng-click="$ctrl.activeTab = '2fa'">{{:: 'UsersManager_TwoFactorAuthentication'|translate }}</a>
</li>
</ul>
<div class="save-button-spacer hide-on-small-only">
</div>
<div class='entityCancel' ng-click="$ctrl.onDoneEditing({ isUserModified: $ctrl.isUserModified })">
<a href="" class="entityCancelLink">{{:: 'Mobile_NavigationBack'|translate }}</a>
</div>
</div>
<div class="visibleTab col m10">
<div ng-if="$ctrl.activeTab === 'basic'" class="basic-info-tab">
<div
piwik-field
uicontrol="text"
name="user_login"
ng-model="$ctrl.user.login"
title="Username"
maxlength="100"
data-disabled="$ctrl.isSavingUserInfo || !$ctrl.isAdd"
>
</div>
<div
piwik-field
uicontrol="password"
name="user_password"
ng-model="$ctrl.user.password"
title="Password"
data-disabled="$ctrl.isSavingUserInfo || ($ctrl.currentUserRole != 'superuser' && !$ctrl.isAdd)"
>
</div>
<div
piwik-field
uicontrol="text"
name="user_email"
ng-model="$ctrl.user.email"
title="Email"
maxlength="100"
data-disabled="$ctrl.isSavingUserInfo || ($ctrl.currentUserRole != 'superuser' && !$ctrl.isAdd)"
ng-if="$ctrl.currentUserRole == 'superuser' || $ctrl.isAdd"
>
</div>
<div
piwik-field
uicontrol="site"
name="user_site"
ng-model="$ctrl.firstSiteAccess"
title="First website permission"
data-disabled="$ctrl.isSavingUserInfo"
ng-if="$ctrl.isAdd"
ui-control-attributes="{ onlySitesWithAdminAccess: true }"
ng-attr-inline-help="{{:: 'UsersManager_FirstSiteInlineHelp'|translate }}"
>
</div>
<div piwik-save-button
saving="$ctrl.isSavingUserInfo"
onconfirm="$ctrl.saveUserInfo()"
ng-value="$ctrl.getSaveButtonLabel()"
data-disabled="$ctrl.isAdd && (!$ctrl.firstSiteAccess || !$ctrl.firstSiteAccess.id)"
ng-if="$ctrl.currentUserRole == 'superuser' || $ctrl.isAdd"
></div>
<div class='entityCancel' ng-if="$ctrl.isAdd">
<a href="" class="entityCancelLink" ng-click="$ctrl.onDoneEditing({ isUserModified: $ctrl.isUserModified })">{{:: 'General_Cancel'|translate }}</a>
</div>
</div>
<div ng-if="!$ctrl.isAdd" ng-show="$ctrl.activeTab === 'permissions'" class="user-permissions">
<piwik-user-permissions-edit
user-login="$ctrl.user.login"
ng-if="!$ctrl.user.superuser_access"
on-user-has-access-detected="$ctrl.userHasAccess = hasAccess"
on-access-change="$ctrl.isUserModified = true"
access-levels="$ctrl.accessLevels"
filter-access-levels="$ctrl.filterAccessLevels"
>
</piwik-user-permissions-edit>
<div ng-if="$ctrl.user.superuser_access" class="alert alert-info">
{{:: 'UsersManager_SuperUsersPermissionsNotice'|translate }}
</div>
</div>
<div ng-if="$ctrl.activeTab === 'superuser' && $ctrl.currentUserRole == 'superuser' && !$ctrl.isAdd" class="superuser-access">
<p>{{:: 'UsersManager_SuperUserIntro1'|translate }}</p>
<p><strong>{{:: 'UsersManager_SuperUserIntro2'|translate }}</strong></p>
<div
piwik-field
uicontrol="checkbox"
name="superuser_access"
ng-model="$ctrl.user.superuser_access"
ng-attr-title="{{:: 'UsersManager_HasSuperUserAccess'|translate }}"
ng-click="$ctrl.confirmSuperUserChange()"
data-disabled="$ctrl.isSavingUserInfo"
>
</div>
<div class="superuser-confirm-modal modal">
<div class="modal-content">
<h2>{{:: 'UsersManager_AreYouSure'|translate }}</h2>
<p ng-if="!$ctrl.user.superuser_access">
{{:: 'UsersManager_RemoveSuperuserAccessConfirm'|translate }}
</p>
<p ng-if="$ctrl.user.superuser_access">
{{:: 'UsersManager_AddSuperuserAccessConfirm'|translate }}
</p>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.toggleSuperuserAccess()">{{:: 'General_Yes'|translate }}</a>
<a href="" class="modal-action modal-close modal-no" ng-click="$ctrl.user.superuser_access = !$ctrl.user.superuser_access">{{:: 'General_No'|translate }}</a>
</div>
</div>
</div>
<div ng-if="$ctrl.activeTab === '2fa' && $ctrl.currentUserRole == 'superuser' && !$ctrl.isAdd" class="twofa-reset">
<p>{{:: 'UsersManager_ResetTwoFactorAuthenticationInfo'|translate }}</p>
<div piwik-save-button
class="resetTwoFa"
saving="$ctrl.isResetting2FA"
onconfirm="$ctrl.confirmReset2FA()"
value="{{ 'UsersManager_ResetTwoFactorAuthentication'|translate }}"
></div>
<div class="twofa-confirm-modal modal">
<div class="modal-content">
<h2>{{:: 'UsersManager_AreYouSure'|translate }}</h2>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.reset2FA()">{{:: 'General_Yes'|translate }}</a>
<a href="" class="modal-action modal-close modal-no">{{:: 'General_No'|translate }}</a>
</div>
</div>
</div>
</div>
</div>
<div class="change-password-modal modal">
<div class="modal-content">
<h2 piwik-translate="UsersManager_AreYouSureChangeDetails"><strong>{{ $ctrl.user.login }}</strong></h2>
<p>{{:: 'UsersManager_ConfirmWithPassword'|translate }}</p>
<div piwik-field uicontrol="password" name="currentUserPassword" autocomplete="off"
ng-model="$ctrl.passwordConfirmation"
full-width="true"
title="{{ 'UsersManager_YourCurrentPassword'|translate }}"
value="">
</div>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.updateUser()">{{:: 'General_Yes'|translate }}</a>
<a href="" class="modal-action modal-close modal-no">{{:: 'General_No'|translate }}</a>
</div>
</div>
</div>

View File

@ -0,0 +1,195 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <piwik-user-edit-form>
*/
(function () {
angular.module('piwikApp').component('piwikUserEditForm', {
templateUrl: 'plugins/UsersManager/angularjs/user-edit-form/user-edit-form.component.html?cb=' + piwik.cacheBuster,
bindings: {
user: '<',
onDoneEditing: '&',
currentUserRole: '<',
accessLevels: '<',
filterAccessLevels: '<',
initialSiteId: '<',
initialSiteName: '<'
},
controller: UserEditFormController
});
UserEditFormController.$inject = ['$element', 'piwikApi', '$q'];
function UserEditFormController($element, piwikApi, $q) {
var vm = this;
vm.activeTab = 'basic';
vm.permissionsForIdSite = 1;
vm.isSavingUserInfo = false;
vm.userHasAccess = true;
vm.firstSiteAccess = null;
vm.isUserModified = false;
vm.passwordConfirmation = '';
vm.$onInit = $onInit;
vm.$onChanges = $onChanges;
vm.confirmSuperUserChange = confirmSuperUserChange;
vm.confirmReset2FA = confirmReset2FA;
vm.getFormTitle = getFormTitle;
vm.getSaveButtonLabel = getSaveButtonLabel;
vm.toggleSuperuserAccess = toggleSuperuserAccess;
vm.saveUserInfo = saveUserInfo;
vm.reset2FA = reset2FA;
vm.updateUser = updateUser;
function $onInit() {
vm.firstSiteAccess = {
id: vm.initialSiteId,
name: vm.initialSiteName
};
}
function $onChanges() {
if (vm.user) {
vm.isAdd = false;
} else {
vm.isAdd = true;
vm.user = {};
}
if (!vm.isAdd) {
vm.user.password = 'XXXXXXXX'; // make sure password is not stored in the client after update/save
}
}
function getFormTitle() {
return vm.isAdd ? _pk_translate('UsersManager_AddNewUser') : _pk_translate('UsersManager_EditUser');
}
function getSaveButtonLabel() {
return vm.isAdd ? _pk_translate('UsersManager_CreateUser') : _pk_translate('UsersManager_SaveBasicInfo');
}
function confirmSuperUserChange() {
$element.find('.superuser-confirm-modal').openModal({ dismissible: false });
}
function confirmReset2FA() {
$element.find('.twofa-confirm-modal').openModal({ dismissible: false });
}
function confirmUserChange() {
vm.passwordConfirmation = '';
function onEnter(event){
var keycode = (event.keyCode ? event.keyCode : event.which);
if (keycode == '13'){
$element.find('.change-password-modal').closeModal();
vm.updateUser();
}
}
$element.find('.change-password-modal').openModal({ dismissible: false, ready: function () {
$('.modal.open #currentUserPassword').focus();
$('.modal.open #currentUserPassword').off('keypress').keypress(onEnter);
}});
}
function toggleSuperuserAccess() {
vm.isSavingUserInfo = true;
piwikApi.post({
method: 'UsersManager.setSuperUserAccess'
}, {
userLogin: vm.user.login,
hasSuperUserAccess: vm.user.superuser_access ? '1' : '0'
}).catch(function () {
// ignore error (still displayed to user)
}).then(function () {
vm.isSavingUserInfo = false;
vm.isUserModified = true;
});
}
function saveUserInfo() {
if (vm.isAdd) {
createUser();
} else {
confirmUserChange();
}
}
function reset2FA() {
vm.isResetting2FA = true;
return piwikApi.post({
method: 'TwoFactorAuth.resetTwoFactorAuth',
userLogin: vm.user.login
}).catch(function (e) {
vm.isResetting2FA = false;
throw e;
}).then(function () {
vm.isResetting2FA = false;
vm.user.uses_2fa = false;
vm.activeTab = 'basic';
showUserSavedNotification();
});
}
function showUserSavedNotification() {
var UI = require('piwik/UI');
var notification = new UI.Notification();
notification.show(_pk_translate('General_YourChangesHaveBeenSaved'), { context: 'success', type: 'toast' });
}
function createUser() {
vm.isSavingUserInfo = true;
return piwikApi.post({
method: 'UsersManager.addUser'
}, {
userLogin: vm.user.login,
password: vm.user.password,
email: vm.user.email,
alias: vm.user.alias,
initialIdSite: vm.firstSiteAccess ? vm.firstSiteAccess.id : undefined
}).catch(function (e) {
vm.isSavingUserInfo = false;
throw e;
}).then(function () {
vm.firstSiteAccess = null;
vm.isSavingUserInfo = false;
vm.isAdd = false;
vm.isEmailChanged = false;
vm.isUserModified = true;
showUserSavedNotification();
});
}
function updateUser() {
vm.isSavingUserInfo = true;
return piwikApi.post({
method: 'UsersManager.updateUser'
}, {
userLogin: vm.user.login,
password: vm.user.password ? vm.user.password : undefined,
passwordConfirmation: vm.passwordConfirmation ? vm.passwordConfirmation : undefined,
email: vm.user.email,
alias: vm.user.alias
}).catch(function (e) {
vm.isSavingUserInfo = false;
vm.passwordConfirmation = false;
throw e;
}).then(function () {
vm.isSavingUserInfo = false;
vm.passwordConfirmation = false;
vm.isUserModified = true;
showUserSavedNotification();
});
}
}
})();

View File

@ -0,0 +1,45 @@
.userEditForm {
.entityList ul li.active a {
font-weight: bold;
}
.entityList ul {
.icon-warning {
.alert-warning;
&:hover {
opacity: 1;
}
}
}
.user-permissions,.superuser-access {
margin-bottom: 32px;
}
.save-button-spacer {
height: 48px;
}
.twofa-confirm-modal, .superuser-confirm-modal,.change-password-modal {
.modal-no {
float: right;
margin-right: 1em;
margin-top: 1em;
}
}
.basic-info-tab {
.siteSelector {
width: calc(~'100% - 25px');
}
[piwik-siteselector] {
margin-bottom: 1rem;
.title {
&,span {
max-width: none;
}
}
}
}
}

View File

@ -0,0 +1,218 @@
<div class="userPermissionsEdit" ng-class="{ loading: $ctrl.isLoadingAccess }">
<div class="row" ng-if="!$ctrl.hasAccessToAtLeastOneSite">
<div piwik-notification context="warning" type="persistent" noclear="true">
<strong>{{:: 'General_Warning'|translate }}:</strong>
{{:: 'UsersManager_NoAccessWarning'|translate }}
</div>
</div>
<div class="row to-all-websites">
<div class="col s12">
<div>
<span>{{ 'UsersManager_GiveAccessToAll'|translate }}:</span>
<div
id="all-sites-access-select"
piwik-field
uicontrol="select"
ng-model="$ctrl.allWebsitesAccssLevelSet"
options="$ctrl.accessLevels"
full-width="true"
></div>
<a href="" class="btn" ng-class="{ disabled: $ctrl.isGivingAccessToAllSites }" ng-click="$ctrl.showChangeAccessAllSitesModal()">
{{:: 'General_Apply'|translate }}
</a>
</div>
<p>&nbsp;</p>
<p>{{ 'UsersManager_OrManageIndividually'|translate }}:</p>
</div>
</div>
<div class="filters row">
<div class="col s12 m12 l8">
<div class="input-field bulk-actions">
<a
class='dropdown-trigger btn'
href=''
data-activates='user-permissions-edit-bulk-actions'
piwik-dropdown-menu
ng-class="{ disabled: $ctrl.isBulkActionsDisabled }"
>
{{:: 'UsersManager_BulkActions'|translate }}
</a>
<ul id='user-permissions-edit-bulk-actions' class='dropdown-content'>
<li>
<a class='dropdown-trigger' data-activates="user-permissions-bulk-set-access" piwik-dropdown-menu>{{:: 'UsersManager_SetPermission'|translate }}</a>
<ul id="user-permissions-bulk-set-access" class="dropdown-content">
<li ng-repeat="access in $ctrl.accessLevels">
<a href="" ng-click="$ctrl.siteAccessToChange = null; $ctrl.roleToChangeTo = access.key; $ctrl.showChangeAccessConfirm();">{{ access.value }}</a>
</li>
</ul>
</li>
<li>
<a href="" ng-click="$ctrl.siteAccessToChange = null; $ctrl.roleToChangeTo = 'noaccess'; $ctrl.showRemoveAccessConfirm();">{{:: 'UsersManager_RemovePermissions'|translate }}</a>
</li>
</ul>
</div>
<div class="input-field site-filter">
<input
type="text"
placeholder="{{:: 'UsersManager_FilterByWebsite'|translate }}"
ng-model="$ctrl.siteNameFilter"
ng-model-options="{debounce: 300}"
ng-change="$ctrl.offset = 0; $ctrl.fetchAccess()"
/>
</div>
<div class="input-field access-filter">
<div
piwik-field
uicontrol="select"
ng-model="$ctrl.accessLevelFilter"
options="$ctrl.filterAccessLevels"
full-width="true"
ng-change="$ctrl.offset = 0; $ctrl.fetchAccess()"
placeholder="{{ 'UsersManager_FilterByAccess'|translate }}"
></div>
</div>
</div>
<div class="col s12 m12 l4 sites-for-permission-pagination-container" ng-if="$ctrl.totalEntries > $ctrl.limit">
<div class="sites-for-permission-pagination">
<a class="prev" ng-class="{ disabled: $ctrl.offset <= 0 }">
<span class="pointer" ng-click="$ctrl.gotoPreviousPage()">« {{:: 'General_Previous'|translate }}</span>
</a>
<span class="counter">
<span>
{{ 'General_Pagination'|translate:$ctrl.getPaginationLowerBound():$ctrl.getPaginationUpperBound():$ctrl.totalEntries }}
</span>
</span>
<a class="next" ng-class="{ disabled: $ctrl.offset + $ctrl.limit >= $ctrl.totalEntries }">
<span class="pointer" ng-click="$ctrl.gotoNextPage()">{{:: 'General_Next'|translate }} »</span>
</a>
</div>
</div>
</div>
<div piwik-notification context="info" type="persistent" noclear="true" ng-if="$ctrl.isRoleHelpToggled" class="roles-help-notification">
<span piwik-translate="UsersManager_RolesHelp">
<a href='https://matomo.org/faq/general/faq_70/' target='_blank' rel='noreferrer noopener'>::</a>::<a href='https://matomo.org/faq/general/faq_69/' target='_blank' rel='noreferrer noopener'>::</a>
</span>
</div>
<div piwik-notification context="info" type="persistent" noclear="true" ng-if="$ctrl.isCapabilitiesHelpToggled" class="capabilities-help-notification">
<span piwik-translate="UsersManager_CapabilitiesHelp">
TODO
</span>
</div>
<table piwik-content-table id="sitesForPermission">
<thead>
<tr>
<th class="select-cell">
<span class="checkbox-container">
<input type="checkbox" id="perm_edit_select_all" ng-model="$ctrl.isAllCheckboxSelected" ng-change="$ctrl.onAllCheckboxChange()" />
<label for="perm_edit_select_all"></label>
</span>
</th>
<th>{{:: 'General_Name'|translate }}</th>
<th class="role_header">
<span>{{:: 'UsersManager_Role'|translate }}</span>
<a href="" class="helpIcon" ng-click="$ctrl.isRoleHelpToggled = !$ctrl.isRoleHelpToggled" ng-class="{ sticky: $ctrl.isRoleHelpToggled }">
<span class="icon-help"></span>
</a>
</th>
<th class="capabilities_header">
<span>{{:: 'UsersManager_Capabilities'|translate }}</span>
<a href="" class="helpIcon" ng-click="$ctrl.isCapabilitiesHelpToggled = !$ctrl.isCapabilitiesHelpToggled" ng-class="{ sticky: $ctrl.isCapabilitiesHelpToggled }">
<span class="icon-help"></span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr class="select-all-row" ng-if="$ctrl.isAllCheckboxSelected && $ctrl.siteAccess.length < $ctrl.totalEntries">
<td colspan="4">
<div ng-if="!$ctrl.areAllResultsSelected">
<span piwik-translate="UsersManager_TheDisplayedWebsitesAreSelected"><strong>{{ $ctrl.siteAccess.length }}</strong></span>
<a href="#" ng-click="$ctrl.areAllResultsSelected = !$ctrl.areAllResultsSelected" piwik-translate="UsersManager_ClickToSelectAll"><strong>{{ $ctrl.totalEntries }}</strong></a>
</div>
<div ng-if="$ctrl.areAllResultsSelected">
<span piwik-translate="UsersManager_AllWebsitesAreSelected"><strong>{{ $ctrl.totalEntries }}</strong></span>
<a href="#" ng-click="$ctrl.areAllResultsSelected = !$ctrl.areAllResultsSelected" piwik-translate="UsersManager_ClickToSelectDisplayedWebsites"><strong>{{ $ctrl.siteAccess.length }}</strong></a>
</div>
</td>
</tr>
<tr ng-repeat="entry in $ctrl.siteAccess">
<td class="select-cell">
<span class="checkbox-container">
<input type="checkbox" ng-attr-id="perm_edit_select_row{{ $index }}" ng-model="$ctrl.selectedRows[$index]" ng-click="$ctrl.onRowSelected()" />
<label ng-attr-for="perm_edit_select_row{{ $index }}"></label>
</span>
</td>
<td>
<span>{{ entry.site_name }}</span>
</td>
<td>
<div
piwik-field
uicontrol="select"
ng-model="entry.role"
options="$ctrl.accessLevels"
ng-change="$ctrl.previousRole = '{{ entry.role }}'; $ctrl.roleToChangeTo = entry.role; $ctrl.siteAccessToChange = entry; $ctrl.showChangeAccessConfirm();"
full-width="true"
class="role-select"
></div>
</td>
<td>
<piwik-capabilities-edit
idsite="entry.idsite"
site-name="entry.site_name"
user-login="$ctrl.userLogin"
user-role="entry.role"
capabilities="entry.capabilities"
on-capabilities-change="$ctrl.fetchAccess()"
>
</piwik-capabilities-edit>
</td>
</tr>
</tbody>
</table>
<div class="delete-access-confirm-modal modal">
<div class="modal-content">
<h3 ng-if="$ctrl.siteAccessToChange" piwik-translate="UsersManager_DeletePermConfirmSingle"><strong>{{ $ctrl.userLogin }}</strong>::<strong>{{ $ctrl.siteAccessToChange.site_name }}</strong></h3>
<p ng-if="!$ctrl.siteAccessToChange" piwik-translate="UsersManager_DeletePermConfirmMultiple"><strong>{{ $ctrl.userLogin }}</strong>::<strong>{{ $ctrl.getAffectedSitesCount() }}</strong></p>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.changeUserRole()">{{:: 'General_Yes'|translate }}</a>
<a href="" class="modal-action modal-close modal-no" ng-click="$ctrl.siteAccessToChange = null; $ctrl.roleToChangeTo = null;">{{:: 'General_No'|translate }}</a>
</div>
</div>
<div class="change-access-confirm-modal modal">
<div class="modal-content">
<h3 ng-if="$ctrl.siteAccessToChange" piwik-translate="UsersManager_ChangePermToSiteConfirmSingle"><strong>{{ $ctrl.userLogin }}</strong>::<strong>{{ $ctrl.siteAccessToChange.site_name }}</strong>::<strong>{{ $ctrl.getRoleDisplay($ctrl.roleToChangeTo) }}</strong></h3>
<p ng-if="!$ctrl.siteAccessToChange" piwik-translate="UsersManager_ChangePermToSiteConfirmMultiple"><strong>{{ $ctrl.userLogin }}</strong>::<strong>{{ $ctrl.getAffectedSitesCount() }}</strong>::<strong>{{ $ctrl.getRoleDisplay($ctrl.roleToChangeTo) }}</strong></p>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.changeUserRole()">{{:: 'General_Yes'|translate }}</a>
<a href="" class="modal-action modal-close modal-no" ng-click="$ctrl.siteAccessToChange.role = $ctrl.previousRole; $ctrl.siteAccessToChange = null; $ctrl.roleToChangeTo = null;">{{:: 'General_No'|translate }}</a>
</div>
</div>
<div class="confirm-give-access-all-sites modal">
<div class="modal-content">
<h3 piwik-translate="UsersManager_ChangePermToAllSitesConfirm"><strong>{{ $ctrl.userLogin }}</strong>::<strong>{{ $ctrl.getRoleDisplay($ctrl.allWebsitesAccssLevelSet) }}</strong></h3>
<p>{{ 'UsersManager_ChangePermToAllSitesConfirm2'|translate }}</p>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.giveAccessToAllSites()">{{:: 'General_Yes'|translate }}</a>
<a href="" class="modal-action modal-close modal-no">{{:: 'General_No'|translate }}</a>
</div>
</div>
</div>

View File

@ -0,0 +1,298 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <piwik-user-permissions-edit>
*/
(function () {
angular.module('piwikApp').component('piwikUserPermissionsEdit', {
templateUrl: 'plugins/UsersManager/angularjs/user-permissions-edit/user-permissions-edit.component.html?cb=' + piwik.cacheBuster,
bindings: {
userLogin: '<',
limit: '<',
onUserHasAccessDetected: '&',
onAccessChange: '&',
accessLevels: '<',
filterAccessLevels: '<'
},
controller: UserPermissionsEditController
});
UserPermissionsEditController.$inject = ['piwikApi', '$element', '$q'];
function UserPermissionsEditController(piwikApi, $element, $q) {
var vm = this;
// search/pagination state
vm.siteAccess = [];
vm.offset = 0;
vm.totalEntries = null;
vm.accessLevelFilter = '';
vm.siteNameFilter = '';
vm.isLoadingAccess = false;
vm.allWebsitesAccssLevelSet = 'view';
// row selection state
vm.isAllCheckboxSelected = false;
vm.selectedRows = {};
vm.isBulkActionsDisabled = true;
vm.areAllResultsSelected = false;
vm.previousRole = null;
// other state
vm.hasAccessToAtLeastOneSite = true;
vm.isRoleHelpToggled = false;
vm.isCapabilitiesHelpToggled = false;
vm.isGivingAccessToAllSites = false;
// intermediate state
vm.roleToChangeTo = null;
vm.siteAccessToChange = null;
vm.$onInit = $onInit;
vm.$onChanges = $onChanges;
vm.onAllCheckboxChange = onAllCheckboxChange;
vm.onRowSelected = onRowSelected;
vm.getPaginationLowerBound = getPaginationLowerBound;
vm.getPaginationUpperBound = getPaginationUpperBound;
vm.fetchAccess = fetchAccess;
vm.gotoPreviousPage = gotoPreviousPage;
vm.gotoNextPage = gotoNextPage;
vm.showRemoveAccessConfirm = showRemoveAccessConfirm;
vm.getSelectedRowsCount = getSelectedRowsCount;
vm.getAffectedSitesCount = getAffectedSitesCount;
vm.changeUserRole = changeUserRole;
vm.showChangeAccessConfirm = showChangeAccessConfirm;
vm.getRoleDisplay = getRoleDisplay;
vm.showAddExistingUserModal = showAddExistingUserModal;
vm.giveAccessToAllSites = giveAccessToAllSites;
vm.showChangeAccessAllSitesModal = showChangeAccessAllSitesModal;
function giveAccessToAllSites() {
vm.isGivingAccessToAllSites = true;
piwikApi.fetch({
method: 'SitesManager.getSitesWithAdminAccess',
}).then(function (allSites) {
var idSites = allSites.map(function (s) { return s.idsite; });
return piwikApi.post({
method: 'UsersManager.setUserAccess'
}, {
userLogin: vm.userLogin,
access: vm.allWebsitesAccssLevelSet,
'idSites[]': idSites,
});
}).then(function () {
return vm.fetchAccess();
})['finally'](function () {
vm.isGivingAccessToAllSites = false;
});
}
function showChangeAccessAllSitesModal() {
$element.find('.confirm-give-access-all-sites').openModal({ dismissible: false });
}
function $onInit() {
vm.limit = vm.limit || 10;
resetSiteToAdd();
fetchAccess();
}
function $onChanges() {
vm.accessLevels = vm.accessLevels.filter(shouldShowAccessLevel);
vm.filterAccessLevels = vm.filterAccessLevels.filter(shouldShowAccessLevel);
if (vm.limit) {
fetchAccess();
}
function shouldShowAccessLevel(entry) {
return entry.key !== 'superuser';
}
}
function fetchAccess() {
vm.isLoadingAccess = true;
piwikApi.fetch({
method: 'UsersManager.getSitesAccessForUser',
limit: vm.limit,
offset: vm.offset,
filter_search: vm.siteNameFilter,
filter_access: vm.accessLevelFilter,
userLogin: vm.userLogin
}, { includeHeaders: true }).then(function (result) {
vm.isLoadingAccess = false;
vm.siteAccess = result.response;
vm.totalEntries = parseInt(result.headers('x-matomo-total-results')) || 0;
vm.hasAccessToAtLeastOneSite = !! result.headers('x-matomo-has-some');
if (vm.onUserHasAccessDetected) {
vm.onUserHasAccessDetected({ hasAccess: vm.hasAccessToAtLeastOneSite });
}
clearSelection();
}).catch(function () {
vm.isLoadingAccess = false;
clearSelection();
});
}
function getAllSitesInSearch() {
return piwikApi.fetch({
method: 'UsersManager.getSitesAccessForUser',
filter_search: vm.siteNameFilter,
filter_access: vm.accessLevelFilter,
userLogin: vm.userLogin,
filter_limit: '-1'
}).then(function (access) {
return access.map(function (a) { return a.idsite; });
});
}
function clearSelection() {
vm.selectedRows = {};
vm.areAllResultsSelected = false;
vm.isBulkActionsDisabled = true;
vm.isAllCheckboxSelected = false;
vm.siteAccessToChange = null;
}
function onAllCheckboxChange() {
if (!vm.isAllCheckboxSelected) {
clearSelection();
} else {
for (var i = 0; i !== vm.siteAccess.length; ++i) {
vm.selectedRows[i] = true;
}
vm.isBulkActionsDisabled = false;
}
}
function onRowSelected() {
var selectedRowKeyCount = getSelectedRowsCount();
vm.isBulkActionsDisabled = selectedRowKeyCount === 0;
vm.isAllCheckboxSelected = selectedRowKeyCount === vm.siteAccess.length;
}
function getPaginationLowerBound() {
return vm.offset + 1;
}
function getPaginationUpperBound() {
return Math.min(vm.offset + vm.limit, vm.totalEntries);
}
function resetSiteToAdd() {
vm.siteToAdd = {
id: null,
name: ''
};
}
function changeUserRole() {
vm.isLoadingAccess = true;
return $q.resolve().then(function () {
if (vm.siteAccessToChange) {
return [vm.siteAccessToChange.idsite];
}
if (vm.areAllResultsSelected) {
return getAllSitesInSearch();
}
return getSelectedSites();
}).then(function (idSites) {
return piwikApi.post({
method: 'UsersManager.setUserAccess'
}, {
userLogin: vm.userLogin,
access: vm.roleToChangeTo,
'idSites[]': idSites
});
}).catch(function () {
// ignore (errors will still be displayed to the user)
}).then(function () {
vm.onAccessChange();
return fetchAccess();
});
}
function getSelectedSites() {
var result = [];
Object.keys(vm.selectedRows).forEach(function (index) {
if (vm.selectedRows[index]
&& vm.siteAccess[index] // safety check
) {
result.push(vm.siteAccess[index].idsite);
}
});
return result;
}
function gotoPreviousPage() {
vm.offset = Math.max(0, vm.offset - vm.limit);
fetchAccess();
}
function gotoNextPage() {
var newOffset = vm.offset + vm.limit;
if (newOffset >= vm.totalEntries) {
return;
}
vm.offset = newOffset;
fetchAccess();
}
function showRemoveAccessConfirm() {
$element.find('.delete-access-confirm-modal').openModal({ dismissible: false });
}
function showChangeAccessConfirm() {
$element.find('.change-access-confirm-modal').openModal({ dismissible: false });
}
function showAddExistingUserModal() {
$element.find('.add-existing-user-modal').openModal({ dismissible: false });
}
function getSelectedRowsCount() {
var selectedRowKeyCount = 0;
Object.keys(vm.selectedRows).forEach(function (key) {
if (vm.selectedRows[key]) {
++selectedRowKeyCount;
}
});
return selectedRowKeyCount;
}
function getAffectedSitesCount() {
if (vm.areAllResultsSelected) {
return vm.totalEntries;
}
return getSelectedRowsCount();
}
function getRoleDisplay(role) {
var result = null;
vm.accessLevels.forEach(function (entry) {
if (entry.key === role) {
result = entry.value;
}
});
return result;
}
}
})();

View File

@ -0,0 +1,232 @@
.userPermissionsEdit {
&.loading {
.sites-for-permission-pagination, table {
opacity: .5;
}
}
.permission-select .select-wrapper {
display: inline-block;
transform: scale(.8);
margin-right: -10px;
margin-left: -10px;
z-index: 999;
input {
margin-bottom: 0;
height: 1.1em;
line-height: 1.1em;
}
.caret {
top: 0;
}
}
.add-site {
float: right;
[piwik-field] {
display:inline-block;
.input-field {
width: 180px;
}
}
[piwik-siteselector] {
display: inline-block;
a.title {
width: 180px;
}
.siteSelector {
position: static !important;
margin-top: 1px;
}
}
.btn-flat:hover {
background: none;
}
}
.filters {
margin-left: -0.75rem;
margin-right: -0.75rem;
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
> div:first-child {
flex: 1;
}
> div > .input-field {
display: inline-block;
vertical-align: top;
width: 180px;
}
.sites-for-permission-pagination {
display: inline-block;
vertical-align: top;
min-height: 2.5rem;
}
.form-group, .input-field, input {
margin: 0;
}
.add-site {
> div {
vertical-align: bottom;
margin-top: .8rem;
}
> a {
padding: 0 1rem 0 0;
}
}
}
.bulk-actions > a.dropdown-trigger {
margin-top: .8rem;
margin-right: 1rem;
}
#sitesForPermission {
margin-left: 0;
margin-right: 0;
width: calc(100%);
font-size: 100%;
td > span {
display:inline-block;
}
.select-cell {
width: 32px;
}
span.checkbox-container {
transform: scale(.8);
margin-top: -4px;
}
.role-select .select-wrapper {
transform: scale(.8) translate(-1.3rem);
margin-top: -0.5rem;
max-width: 160px;
span.caret {
top: 8px;
}
input {
margin-bottom: 0;
height: 2rem;
line-height: 2rem;
}
}
tr.select-all-row > td {
padding: 6px;
text-align: center;
}
.row.form-group {
margin: 0;
.col {
padding: 0;
}
}
tr .input-field {
margin-top: 0;
}
}
table.entityTable tbody tr td {
vertical-align: middle !important;
}
.add-permission {
float: right;
}
.sites-for-permission-pagination-container {
position: relative;
}
.sites-for-permission-pagination {
position: absolute;
bottom: 0;
width: calc(100%);
text-align: center;
a.disabled {
pointer-events: none;
color: #9e9e9e;
}
.counter {
margin-left: 8px;
margin-right: 8px;
}
}
.delete-site-permission {
float: right;
}
.delete-access-confirm-modal, .change-access-confirm-modal, .confirm-give-access-all-sites {
.modal-no {
float: right;
margin-right: 1em;
margin-top: 1em;
}
}
th.role_header, th.capabilities_header {
.helpIcon {
color: #9e9e9e;
font-size: .8rem;
margin-left: .1rem;
text-decoration: none;
&:hover,&.sticky {
opacity: 1;
}
}
}
.to-all-websites {
margin-left: -0.75em;
margin-right: -0.75em;
}
#all-sites-access-select {
display: inline-block;
vertical-align: bottom;
width: 150px;
.form-group {
margin: 0;
}
.input-field {
margin-top: 0;
}
.select-dropdown {
margin-bottom: 0;
}
}
}
.user-permission-toast .notification {
padding-left: 20px;
&::before {
display:none;
}
}

View File

@ -0,0 +1,80 @@
<div class="usersManager">
<div ng-show="!$ctrl.isEditing">
<div piwik-content-intro>
<h2
piwik-enriched-headline
help-url="https://matomo.org/docs/manage-users/"
feature-name="Users Management"
>
{{:: 'UsersManager_ManageUsers'|translate }}
</h2>
<p>
{{:: 'UsersManager_ManageUsersDesc'|translate }}
</p>
<div class="row add-user-container">
<div class="col s12">
<div class="input-field">
<a class="btn add-new-user" ng-click="$ctrl.isEditing = true; $ctrl.userBeingEdited = null;">
{{:: 'UsersManager_AddUser'|translate }}
</a>
</div>
<div class="input-field" ng-if="$ctrl.currentUserRole !== 'superuser'">
<a class="btn add-existing-user" ng-click="$ctrl.showAddExistingUserModal();">
{{:: 'UsersManager_AddExistingUser'|translate }}
</a>
</div>
</div>
</div>
<piwik-paged-users-list
on-edit-user="$ctrl.onEditUser(user)"
on-change-user-role="$ctrl.onChangeUserRole(users, role)"
on-delete-user="$ctrl.onDeleteUser(users)"
on-search-change="$ctrl.searchParams = params; $ctrl.fetchUsers();"
initial-site-id="$ctrl.initialSiteId"
initial-site-name="$ctrl.initialSiteName"
is-loading-users="$ctrl.isLoadingUsers"
current-user-role="$ctrl.currentUserRole"
access-levels="$ctrl.accessLevels"
filter-access-levels="$ctrl.filterAccessLevels"
search-params="$ctrl.searchParams"
users="$ctrl.users"
total-entries="$ctrl.totalEntries"
></piwik-paged-users-list>
</div>
</div>
<!-- TODO: whether a user is being edited should be part of the URL -->
<div ng-if="$ctrl.isEditing">
<piwik-user-edit-form
on-done-editing="$ctrl.onDoneEditing(isUserModified);"
user="$ctrl.userBeingEdited"
current-user-role="$ctrl.currentUserRole"
allow-superuser-edit="$ctrl.isCurrentUserSuperUser"
access-levels="$ctrl.accessLevels"
filter-access-levels="$ctrl.filterAccessLevels"
initial-site-id="$ctrl.initialSiteId"
initial-site-name="$ctrl.initialSiteName"
></piwik-user-edit-form>
</div>
<div class="add-existing-user-modal modal">
<div class="modal-content">
<h3>{{:: 'UsersManager_AddExistingUser'|translate }}</h3>
<p>{{:: 'UsersManager_EnterUsernameOrEmail'|translate }}:</p>
<div
piwik-field
name="add-existing-user-email"
uicontrol="text"
ng-model="$ctrl.addNewUserLoginEmail"
>
</div>
</div>
<div class="modal-footer">
<a href="" class="modal-action modal-close btn" ng-click="$ctrl.addExistingUser()">{{:: 'General_Add'|translate }}</a>
<a href="" class="modal-action modal-close modal-no" ng-click="$ctrl.addNewUserLoginEmail = null;">{{:: 'General_Cancel'|translate }}</a>
</div>
</div>
</div>

View File

@ -0,0 +1,220 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <piwik-users-manager>
*/
(function () {
angular.module('piwikApp').component('piwikUsersManager', {
templateUrl: 'plugins/UsersManager/angularjs/users-manager/users-manager.component.html?cb=' + piwik.cacheBuster,
bindings: {
currentUserRole: '<',
initialSiteName: '@',
initialSiteId: '@',
accessLevels: '<',
filterAccessLevels: '<'
},
controller: UsersManagerController
});
UsersManagerController.$inject = ['$element', 'piwik', 'piwikApi', '$q', '$timeout'];
function UsersManagerController($element, piwik, piwikApi, $q, $timeout) {
var vm = this;
vm.isEditing = false;
vm.isCurrentUserSuperUser = true;
// search state
vm.users = [];
vm.totalEntries = null;
vm.searchParams = {};
vm.isLoadingUsers = false;
vm.$onInit = $onInit;
vm.$onChanges = $onChanges;
vm.$onDestroy = $onDestroy;
vm.onEditUser = onEditUser;
vm.onDoneEditing = onDoneEditing;
vm.showAddExistingUserModal = showAddExistingUserModal;
vm.onChangeUserRole = onChangeUserRole;
vm.onDeleteUser = onDeleteUser;
vm.fetchUsers = fetchUsers;
vm.addExistingUser = addExistingUser;
function onChangeUserRole(users, role) {
vm.isLoadingUsers = true;
$q.resolve().then(function () {
if (users === 'all') {
return getAllUsersInSearch();
}
return users;
}).then(function (users) {
return users.filter(function (user) {
return user.role !== 'superuser';
}).map(function (user) {
return user.login;
});
}).then(function (userLogins) {
var requests = userLogins.map(function (login) {
return {
method: 'UsersManager.setUserAccess',
userLogin: login,
access: role,
idSites: vm.searchParams.idSite,
ignoreSuperusers: 1
};
});
return piwikApi.bulkFetch(requests, { createErrorNotification: true });
}).catch(function (e) {
// ignore (errors will still be displayed to the user)
}).then(function () {
return fetchUsers();
});
}
function onDeleteUser(users) {
vm.isLoadingUsers = true;
$q.resolve().then(function () {
if (users === 'all') {
return getAllUsersInSearch();
}
return users;
}).then(function (users) {
return users.map(function (user) { return user.login; });
}).then(function (userLogins) {
var requests = userLogins.map(function (login) {
return {
method: 'UsersManager.deleteUser',
userLogin: login
};
});
return piwikApi.bulkFetch(requests, { createErrorNotification: true });
}).catch(function () {
// ignore (errors will still be displayed to the user)
}).then(function () {
return fetchUsers();
});
}
function $onInit() {
// TODO: maybe this should go in another directive...
$element.tooltip({
track: true,
content: function() {
var title = $(this).attr('title');
return piwikHelper.escape(title.replace(/\n/g, '<br />'));
},
show: false,
hide: false
});
if (vm.currentUserRole === 'superuser') {
vm.filterAccessLevels.push({ key: 'superuser', value: 'Superuser' });
}
vm.searchParams = {
offset: 0,
limit: 20,
filter_search: '',
filter_access: '',
idSite: vm.initialSiteId
};
fetchUsers();
}
function $onChanges(changes) {
if (changes.limit) {
fetchUsers();
}
}
function $onDestroy() {
try {
$element.tooltip('destroy');
} catch (e) {
// empty
}
}
function fetchUsers() {
vm.isLoadingUsers = true;
return piwikApi.fetch($.extend({}, vm.searchParams, {
method: 'UsersManager.getUsersPlusRole'
}), { includeHeaders: true }).then(function (result) {
vm.totalEntries = parseInt(result.headers('x-matomo-total-results')) || 0;
vm.users = result.response;
vm.isLoadingUsers = false;
}).catch(function () {
vm.isLoadingUsers = false;
});
}
function getAllUsersInSearch() {
return piwikApi.fetch({
method: 'UsersManager.getUsersPlusRole',
filter_search: vm.searchParams.filter_search,
filter_access: vm.searchParams.filter_access,
idSite: vm.searchParams.idSite,
filter_limit: '-1'
});
}
function onEditUser(user) {
piwik.helper.lazyScrollToContent();
vm.isEditing = true;
vm.userBeingEdited = user;
}
function onDoneEditing(isUserModified) {
vm.isEditing = false;
if (isUserModified) { // if a user was modified, we must reload the users list
fetchUsers();
}
}
function showAddExistingUserModal() {
$element.find('.add-existing-user-modal').openModal({ dismissible: false });
}
function addExistingUser() {
vm.isLoadingUsers = true;
return piwikApi.fetch({
method: 'UsersManager.userExists',
userLogin: vm.addNewUserLoginEmail
}).then(function (response) {
if (response && response.value) {
return vm.addNewUserLoginEmail;
}
return piwikApi.fetch({
method: 'UsersManager.getUserLoginFromUserEmail',
userEmail: vm.addNewUserLoginEmail
}).then(function (response) {
return response.value;
});
}).then(function (login) {
return piwikApi.post({
method: 'UsersManager.setUserAccess'
}, {
userLogin: login,
access: 'view',
idSites: vm.searchParams.idSite
});
}).catch(function (error) {
vm.isLoadingUsers = false;
throw error;
}).then(function () {
return fetchUsers();
});
}
}
})();

View File

@ -0,0 +1,28 @@
.usersManager {
.card .card-content .card-title {
margin-bottom: 0;
}
.add-user-container {
&.row {
margin-left: -0.75rem;
margin-right: -0.75rem;
}
> .col > .input-field {
display: inline-block;
}
}
.add-existing-user-modal {
.form-group,.input-field,input.control_text {
margin: 0;
}
.modal-no {
float: right;
margin-right: 1em;
margin-top: 1em;
}
}
}