Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordizzy <diosmosis@users.noreply.github.com>2022-02-07 18:49:41 +0300
committerGitHub <noreply@github.com>2022-02-07 18:49:41 +0300
commit1dd8569ba0279843b9e7b96f5712d34605530fbe (patch)
treed1b6750479c7a6a066af5d1d10a8aaadaa24b695
parent80a47f8d658d20e5db41b22fc82c4931911b35dd (diff)
[Vue] migrate capabilities edit component (#18566)
* Merge branch 'vue-period-selector-regression' into vue-reporting-menu * rebuild vue * use correct variable * rebuild vue * fix widget url logic * segment parameter can be undefined now for some reason * fix ngmodel binding in siteselector adapter (for last time hopefully) * the original site selector only set the first site to the first site in the initial sites query if there was only one site in the entire matomo instance * fix sitesmanager ui test failure * fix usersettings test failure * rebuild vue * more siteselector tweaks. * build CoreHome * more siteselector tweaks. * another siteselector issue * update screenshots * update screenshot and try to fix random failure * fix some issues in widget.vue when containerid is specified * fix couple tests * fix several test failures * fix string concat * fix test failure * extra change * fix last change and random failure * styling fix * fix last fix * real fix this time * fix stray request * proper fix * update build files * try to fix random failure * do not submit form * check for api errors in promise chain in ajaxhelper.ts * force a digest after a location change * use proper abortcontroller method instead of promise hack, have to add new polyfill + try to fix random test failure * some UI test fixes * fix some report export issues * several save button fixes + make replace approximation in createAngularJsAdapter better * fix sparkline * apply after manual click triggering in savebutton * css fixes and piwik-content-table was never applied by angularjs in installtion * rebuild vue * add names to divs so they can still be queried as they were in angularjs * rebuild vue * rebuild vue * now that format_metrics checkbox works, need to check it * small delay before processing first popover * fix dropdown class/directive name * fix overlay test * remove unintended changes * remove unintended changes * migrate archiving controller * do not do a passthrough transpile of vue typescript, and fix many typescript errors in existing code * more typescript fixes * even more fixes * workarounds to fix recursive typing issues * get corehome to build w/ full typescript build and output type definitions to local dir * get outputted typings to be used when compiling other plugins and fix typescript issues in CorePluginsAdmin * readd corehome umd * fix typescript errors in ExampleVue plugin * fix feedback typescript errors * rebuild * migrate branding controller and get to build * fix issues and get to work * rebuild * fix notification scroll * migrate smtp settings controller in coreadminhome * get to work * migrate js tracking code generator and get to build * migrate image tracking code generator and get to build * get to work in UI * get UI tests to pass locally * forgot to add files + rebuild vue * update screenshots * Show a summary of new features (#18065) * Added "What is new" notification display, populated by a new event * Removed test example event hook * Added support for applying a link attribute to menu items, fixes layout issue for mobile with html menu items * Updated UI test screenshots * Revert accidental edit * Hide the "What's new" icon if there are no new features to show * Changed to use changes.json, track user last viewed, added ui test * Fix UserManager unit tests broken by new ts_changes_viewed user field * Moved getChanges to separate helper class, added unit test, added user view access check * Updated to add new changes table and populate only on plugin update/install * Added missing fixture class, updated UI screenshots * Updated matomo font to add ringing bell and new releases icons * Fix for integration test * Reworked class structure, removed unnecessary angular directive, merged templates, other tidy ups * built vue files * built vue files * Added null user check, missing table exception handling, show plugin name in change title, better handling of missing change fields * Added sample changes file, moved UserChanges db code to changes model, added return type hints, better db error code handling, various other improvements * Revert accidental UI screenshot commit * Fix for incorrect link name parameter in sample changes, switched back to using $db->query for INSERT IGNORE * Integration test fix, UI screenshot updates * Test fix * Added link styling, show CoreHome changes without plugin prefix in title * Update UI test screenshot * Added styles to the popover, added event for filtering changes * Test fix * UI test screenshot updates Co-authored-by: sgiehl <stefan@matomo.org> Co-authored-by: bx80 <bx80@users.noreply.github.com> * Update test translation (#18531) update a test failed XML * updates all submodules (#18541) Co-authored-by: diosmosis <diosmosis@users.noreply.github.com> * Translations update from Hosted Weblate (#18529) * Translated using Weblate (Greek) Currently translated at 100.0% (162 of 162 strings) Translation: Matomo/Plugin CoreAdminHome Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/el/ [ci skip] Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Vasilis Lourdas <dev@lourdas.eu> * Translated using Weblate (Chinese (Simplified)) Currently translated at 83.9% (136 of 162 strings) Translation: Matomo/Plugin CoreAdminHome Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/zh_Hans/ [ci skip] Translated using Weblate (Chinese (Simplified)) Currently translated at 99.6% (620 of 622 strings) Translation: Matomo/Matomo Base Translate-URL: https://hosted.weblate.org/projects/matomo/matomo-base/zh_Hans/ [ci skip] Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: 刘韬 <lyuutau@outlook.com> * Update translation files Updated by "Squash Git commits" hook in Weblate. Translation: Matomo/Plugin CoreAdminHome Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/ [ci skip] Co-authored-by: Vasilis Lourdas <dev@lourdas.eu> Co-authored-by: 刘韬 <lyuutau@outlook.com> * [Vue] migrate report export directive and popover (#18440) * update files * sidenav start * make getRef a utility method * tweak * add return type * finish converting side-nav directive * starting on reporting menu conversion * remove unused properties * convert reporting pages service * migrate report metadata store * remove angularjs files * migrating reporting pages store * make store adapters more immutable * get service adapters to work * fix a UI test * another html fix * migrate most of reporting menu directive and model * Use themed font family for input forms to override materialize.css styling * rebuild vue * add a missing div * ui test fixes * update styling * get to build * get to load in the UI w/o error * clone result of functions * fix compile issue * migrate widget loader and get to load in UI * rebuild vue * migrate widgetcontainer * migrate widget bydimension container * migrate widget + add tooltips directive * quick fix * Updating version to 4.6.0 * loading in page * update expected screenshot * add wait just in case travis is slow * fix ordering bug * add another wait * rebuild vue * css tweak * fix some bugs and tests * undo screenshot changes * Menus test passing locally * [Vue] date picker viewDate property is not kept up to date (#18385) * viewDate ref is not kept up to date * rebuild corehome * reporting menu subcategory items are meant to be normal links * update some screenshots * use innerText instead of text() since angularjs maintains newlines in HTML that vue does not add * trigger angularjs digest after ajaxhelper request * rebuild vue * update screenshots, fix bug in link generation in reporting menu and allow syncing multiple screenshot regexes at a time * undo box-shadow change for UI tests * fix more issues & update more tests * update some screenshots * fix some tests * rebuild CoreHome * quick fix * built vue files * fix angularjs issue * add comment * update umd files * 4.6.1-rc1 * 4.6.1 * fix field array title * apply some pr feedback * apply more pr feedback * another fix * tweak * fix ng-change not executed before ng-model * fix another set of issues * fix another issue * rebuild vue * better ng-change/ng-model fix * update some screenshots * rebuild vue * remove some TODOs * initiate initial ng-change ONLY for site selectors where this behavior applies * emit/broadcast on correct scope in wrapper * rebuild vue * fix some issues * couple more fixes * fix another title issue * rebuild vue * do not report on ajax errors in notifications if not logged in * migrate reporting page and model * rebuild vue * create sites selector model adapter * fix siteselector vue bug, initial site is only set if there is just one site available * rebuild vue * migrate plugin settings directive * remove TODO * migrate plugin filter directive * migrate two more plugins directives * migrate save button * fix a bunch of bugs * fix another widget bug * allow change event name between angularjs and vue * rebuild vue * migrate plugin form directive * get to work * migrate select-on-focus directive and start migrating report-export directive * finish migrating report export directive & popover component + create reusable function to create vue app and add globals to it * rebuild vue * remove angularjs files and move less contents to vue dir * built vue files * fix function signature * fix vue warning * fix ajax request race condition * rebuild vue * add new notification type "help" so the help notification is not cleared when clearing transient notifications * fix some bugs and tests * update screenshot * update screenshot & fix a test * allow using unminified jquery ui + fix bug in last fix * fix error when enrichedheadline is used in modal * add polyfill min.js * remove two todos * fix widget url logic * update some screenshots and fix sanitization/escape issue * update screenshots * rebuild vue * fix url location updating regression in MatomoUrl.updateLocation use * submodule * update screenshots and fix possible error in json parse * built vue files * Merge branch 'vue-period-selector-regression' into vue-reporting-menu * rebuild vue * use correct variable * rebuild vue * fix widget url logic * segment parameter can be undefined now for some reason * fix ngmodel binding in siteselector adapter (for last time hopefully) * the original site selector only set the first site to the first site in the initial sites query if there was only one site in the entire matomo instance * fix sitesmanager ui test failure * fix usersettings test failure * rebuild vue * more siteselector tweaks. * build CoreHome * more siteselector tweaks. * another siteselector issue * update screenshots * update screenshot and try to fix random failure * fix some issues in widget.vue when containerid is specified * fix couple tests * fix several test failures * fix string concat * fix test failure * extra change * fix last change and random failure * styling fix * fix last fix * real fix this time * fix stray request * proper fix * update build files * try to fix random failure * do not submit form * check for api errors in promise chain in ajaxhelper.ts * force a digest after a location change * use proper abortcontroller method instead of promise hack, have to add new polyfill + try to fix random test failure * some UI test fixes * fix some report export issues * several save button fixes + make replace approximation in createAngularJsAdapter better * apply after manual click triggering in savebutton * add names to divs so they can still be queried as they were in angularjs * rebuild vue * now that format_metrics checkbox works, need to check it * fix unintended changes * updated expected screenshots * update two more * go back to previous format_metrics behavior in popover Co-authored-by: Justin Velluppillai <justin@innocraft.com> Co-authored-by: justinvelluppillai <justinvelluppillai@users.noreply.github.com> Co-authored-by: Matthieu Aubry <mattab@users.noreply.github.com> * [Vue] remove support in vue for FormField.allSettings (#18542) * deprecate support in vue for FormField.allSettings since deep watching the property doesnt quite work * built vue files * update screenshots * update screenshot * Show a summary of new features (#18065) * Added "What is new" notification display, populated by a new event * Removed test example event hook * Added support for applying a link attribute to menu items, fixes layout issue for mobile with html menu items * Updated UI test screenshots * Revert accidental edit * Hide the "What's new" icon if there are no new features to show * Changed to use changes.json, track user last viewed, added ui test * Fix UserManager unit tests broken by new ts_changes_viewed user field * Moved getChanges to separate helper class, added unit test, added user view access check * Updated to add new changes table and populate only on plugin update/install * Added missing fixture class, updated UI screenshots * Updated matomo font to add ringing bell and new releases icons * Fix for integration test * Reworked class structure, removed unnecessary angular directive, merged templates, other tidy ups * built vue files * built vue files * Added null user check, missing table exception handling, show plugin name in change title, better handling of missing change fields * Added sample changes file, moved UserChanges db code to changes model, added return type hints, better db error code handling, various other improvements * Revert accidental UI screenshot commit * Fix for incorrect link name parameter in sample changes, switched back to using $db->query for INSERT IGNORE * Integration test fix, UI screenshot updates * Test fix * Added link styling, show CoreHome changes without plugin prefix in title * Update UI test screenshot * Added styles to the popover, added event for filtering changes * Test fix * UI test screenshot updates Co-authored-by: sgiehl <stefan@matomo.org> Co-authored-by: bx80 <bx80@users.noreply.github.com> * Update test translation (#18531) update a test failed XML * updates all submodules (#18541) Co-authored-by: diosmosis <diosmosis@users.noreply.github.com> * Translations update from Hosted Weblate (#18529) * Translated using Weblate (Greek) Currently translated at 100.0% (162 of 162 strings) Translation: Matomo/Plugin CoreAdminHome Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/el/ [ci skip] Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Vasilis Lourdas <dev@lourdas.eu> * Translated using Weblate (Chinese (Simplified)) Currently translated at 83.9% (136 of 162 strings) Translation: Matomo/Plugin CoreAdminHome Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/zh_Hans/ [ci skip] Translated using Weblate (Chinese (Simplified)) Currently translated at 99.6% (620 of 622 strings) Translation: Matomo/Matomo Base Translate-URL: https://hosted.weblate.org/projects/matomo/matomo-base/zh_Hans/ [ci skip] Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: 刘韬 <lyuutau@outlook.com> * Update translation files Updated by "Squash Git commits" hook in Weblate. Translation: Matomo/Plugin CoreAdminHome Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/ [ci skip] Co-authored-by: Vasilis Lourdas <dev@lourdas.eu> Co-authored-by: 刘韬 <lyuutau@outlook.com> * [Vue] migrate report export directive and popover (#18440) * update files * sidenav start * make getRef a utility method * tweak * add return type * finish converting side-nav directive * starting on reporting menu conversion * remove unused properties * convert reporting pages service * migrate report metadata store * remove angularjs files * migrating reporting pages store * make store adapters more immutable * get service adapters to work * fix a UI test * another html fix * migrate most of reporting menu directive and model * Use themed font family for input forms to override materialize.css styling * rebuild vue * add a missing div * ui test fixes * update styling * get to build * get to load in the UI w/o error * clone result of functions * fix compile issue * migrate widget loader and get to load in UI * rebuild vue * migrate widgetcontainer * migrate widget bydimension container * migrate widget + add tooltips directive * quick fix * Updating version to 4.6.0 * loading in page * update expected screenshot * add wait just in case travis is slow * fix ordering bug * add another wait * rebuild vue * css tweak * fix some bugs and tests * undo screenshot changes * Menus test passing locally * [Vue] date picker viewDate property is not kept up to date (#18385) * viewDate ref is not kept up to date * rebuild corehome * reporting menu subcategory items are meant to be normal links * update some screenshots * use innerText instead of text() since angularjs maintains newlines in HTML that vue does not add * trigger angularjs digest after ajaxhelper request * rebuild vue * update screenshots, fix bug in link generation in reporting menu and allow syncing multiple screenshot regexes at a time * undo box-shadow change for UI tests * fix more issues & update more tests * update some screenshots * fix some tests * rebuild CoreHome * quick fix * built vue files * fix angularjs issue * add comment * update umd files * 4.6.1-rc1 * 4.6.1 * fix field array title * apply some pr feedback * apply more pr feedback * another fix * tweak * fix ng-change not executed before ng-model * fix another set of issues * fix another issue * rebuild vue * better ng-change/ng-model fix * update some screenshots * rebuild vue * remove some TODOs * initiate initial ng-change ONLY for site selectors where this behavior applies * emit/broadcast on correct scope in wrapper * rebuild vue * fix some issues * couple more fixes * fix another title issue * rebuild vue * do not report on ajax errors in notifications if not logged in * migrate reporting page and model * rebuild vue * create sites selector model adapter * fix siteselector vue bug, initial site is only set if there is just one site available * rebuild vue * migrate plugin settings directive * remove TODO * migrate plugin filter directive * migrate two more plugins directives * migrate save button * fix a bunch of bugs * fix another widget bug * allow change event name between angularjs and vue * rebuild vue * migrate plugin form directive * get to work * migrate select-on-focus directive and start migrating report-export directive * finish migrating report export directive & popover component + create reusable function to create vue app and add globals to it * rebuild vue * remove angularjs files and move less contents to vue dir * built vue files * fix function signature * fix vue warning * fix ajax request race condition * rebuild vue * add new notification type "help" so the help notification is not cleared when clearing transient notifications * fix some bugs and tests * update screenshot * update screenshot & fix a test * allow using unminified jquery ui + fix bug in last fix * fix error when enrichedheadline is used in modal * add polyfill min.js * remove two todos * fix widget url logic * update some screenshots and fix sanitization/escape issue * update screenshots * rebuild vue * fix url location updating regression in MatomoUrl.updateLocation use * submodule * update screenshots and fix possible error in json parse * built vue files * Merge branch 'vue-period-selector-regression' into vue-reporting-menu * rebuild vue * use correct variable * rebuild vue * fix widget url logic * segment parameter can be undefined now for some reason * fix ngmodel binding in siteselector adapter (for last time hopefully) * the original site selector only set the first site to the first site in the initial sites query if there was only one site in the entire matomo instance * fix sitesmanager ui test failure * fix usersettings test failure * rebuild vue * more siteselector tweaks. * build CoreHome * more siteselector tweaks. * another siteselector issue * update screenshots * update screenshot and try to fix random failure * fix some issues in widget.vue when containerid is specified * fix couple tests * fix several test failures * fix string concat * fix test failure * extra change * fix last change and random failure * styling fix * fix last fix * real fix this time * fix stray request * proper fix * update build files * try to fix random failure * do not submit form * check for api errors in promise chain in ajaxhelper.ts * force a digest after a location change * use proper abortcontroller method instead of promise hack, have to add new polyfill + try to fix random test failure * some UI test fixes * fix some report export issues * several save button fixes + make replace approximation in createAngularJsAdapter better * apply after manual click triggering in savebutton * add names to divs so they can still be queried as they were in angularjs * rebuild vue * now that format_metrics checkbox works, need to check it * fix unintended changes * updated expected screenshots * update two more * go back to previous format_metrics behavior in popover Co-authored-by: Justin Velluppillai <justin@innocraft.com> Co-authored-by: justinvelluppillai <justinvelluppillai@users.noreply.github.com> Co-authored-by: Matthieu Aubry <mattab@users.noreply.github.com> * [Vue] remove support in vue for FormField.allSettings (#18542) * deprecate support in vue for FormField.allSettings since deep watching the property doesnt quite work * built vue files * update screenshots * update screenshot * fix tests * rebuild * rebuild * order plugins by dependencies in vue:build and fix warning in corehome build * built vue files * built vue files * remove unused imports * built vue files * remove multilinefield component, fieldtextareaarray does the same thing * edit-trigger is not used anywhere * migrate sitetypes model to store * do not load nonexistant files * remove reference nonexistant files * start converting sitefields component * more work on sitefields component * undo submodule change * rebuild * get sitesmanager to build * get SiteFields component to work in UI * datepicker does not format times * export other stores * fix some typing issues and rebuild * start on site management conversion * add more comma delimited props to list + remove controller JS * rebuild * convert sites manager controller to sitesmanagement component * remove TODOs * finish migrating sitesmanager * remove some TODO * get to build * fixes from testing * rebuild * rebuild and fix issue w/ globalsettings hash detection * migrate capabilities-edit component. * some fixes and get to build * get to work * built vue files * more fixes * update file * fix bug and rebuild * fix bug and rebuild * fix issue * fix issues and rebuild * fix ui test * fix UI test failure * fixing some issues * complete fixes * fix some more issues * fix ui test failures * another fix * several more fixes * fix delete dialog * more fixes * fix styling issue * more fixes * fix another ui test + update other UI tests * fixing edisiteid handling * update screenshots * fix UI tests somre more * fix random failure * in groupedsetting handle templateFile property for angularjs BC * rebuild vue * fix view tracking code link * Update screenshot. * update screenshot Co-authored-by: Ben Burgess <88810029+bx80@users.noreply.github.com> Co-authored-by: sgiehl <stefan@matomo.org> Co-authored-by: bx80 <bx80@users.noreply.github.com> Co-authored-by: Peter Zhang <peter@innocraft.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Weblate (bot) <hosted@weblate.org> Co-authored-by: Vasilis Lourdas <dev@lourdas.eu> Co-authored-by: 刘韬 <lyuutau@outlook.com> Co-authored-by: Justin Velluppillai <justin@innocraft.com> Co-authored-by: justinvelluppillai <justinvelluppillai@users.noreply.github.com> Co-authored-by: Matthieu Aubry <mattab@users.noreply.github.com>
-rw-r--r--plugins/UsersManager/UsersManager.php4
-rw-r--r--plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.html48
-rw-r--r--plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.js214
-rw-r--r--plugins/UsersManager/angularjs/capability-edit/capability-edit.component.html3
-rw-r--r--plugins/UsersManager/angularjs/capability-edit/capability-edit.component.less3
-rw-r--r--plugins/UsersManager/angularjs/permissions-metadata/permissions-metadata.service.js35
-rw-r--r--plugins/UsersManager/tests/UI/UsersManager_spec.js1
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_all_sites_access.png4
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_bulk_access_set_all.png4
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_capability_single_site.png4
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_next.png4
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_remove_single.png4
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_select_multiple.png4
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_single_site_access.png4
-rw-r--r--plugins/UsersManager/vue/dist/UsersManager.umd.js596
-rw-r--r--plugins/UsersManager/vue/dist/UsersManager.umd.min.js14
-rw-r--r--plugins/UsersManager/vue/dist/umd.metadata.json6
-rw-r--r--plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.adapter.ts50
-rw-r--r--plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.less (renamed from plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.less)4
-rw-r--r--plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.vue311
-rw-r--r--plugins/UsersManager/vue/src/CapabilitiesStore/CapabilitiesStore.adapter.ts23
-rw-r--r--plugins/UsersManager/vue/src/CapabilitiesStore/CapabilitiesStore.ts57
-rw-r--r--plugins/UsersManager/vue/src/CapabilitiesStore/Capability.ts17
-rw-r--r--plugins/UsersManager/vue/src/index.ts10
24 files changed, 1102 insertions, 322 deletions
diff --git a/plugins/UsersManager/UsersManager.php b/plugins/UsersManager/UsersManager.php
index e5620260ef..d45083b1f2 100644
--- a/plugins/UsersManager/UsersManager.php
+++ b/plugins/UsersManager/UsersManager.php
@@ -134,8 +134,6 @@ class UsersManager extends \Piwik\Plugin
$jsFiles[] = "plugins/UsersManager/angularjs/user-permissions-edit/user-permissions-edit.component.js";
$jsFiles[] = "plugins/UsersManager/angularjs/personal-settings/personal-settings.controller.js";
$jsFiles[] = "plugins/UsersManager/angularjs/personal-settings/anonymous-settings.controller.js";
- $jsFiles[] = "plugins/UsersManager/angularjs/permissions-metadata/permissions-metadata.service.js";
- $jsFiles[] = "plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.js";
}
/**
@@ -149,7 +147,7 @@ class UsersManager extends \Piwik\Plugin
$stylesheets[] = "plugins/UsersManager/angularjs/paged-users-list/paged-users-list.component.less";
$stylesheets[] = "plugins/UsersManager/angularjs/user-edit-form/user-edit-form.component.less";
$stylesheets[] = "plugins/UsersManager/angularjs/user-permissions-edit/user-permissions-edit.component.less";
- $stylesheets[] = "plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.less";
+ $stylesheets[] = "plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.less";
}
/**
diff --git a/plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.html b/plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.html
deleted file mode 100644
index 4eee7fe20e..0000000000
--- a/plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<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.capabilityToAddOrRemoveId = capability.id; $ctrl.onToggleCapability(false)"
- ></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>
diff --git a/plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.js b/plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.js
deleted file mode 100644
index 6efe3129a0..0000000000
--- a/plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/*!
- * Matomo - free/libre analytics platform
- *
- * @link https://matomo.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.capabilityToAddOrRemove = null;
- vm.availableCapabilities.forEach(function (capability) {
- if (capability.id === vm.capabilityToAddOrRemoveId) {
- vm.capabilityToAddOrRemove = capability;
- }
- });
-
- $element.find('.confirmCapabilityToggle').modal({
- dismissible: false,
- yes: function () {
- },
- }).modal('open');
- }
-
- 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;
- }
- }
-})();
diff --git a/plugins/UsersManager/angularjs/capability-edit/capability-edit.component.html b/plugins/UsersManager/angularjs/capability-edit/capability-edit.component.html
deleted file mode 100644
index e20609a83b..0000000000
--- a/plugins/UsersManager/angularjs/capability-edit/capability-edit.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<div class="capabilityEdit">
- {{ capabilityEdit.myProperty }}
-</div> \ No newline at end of file
diff --git a/plugins/UsersManager/angularjs/capability-edit/capability-edit.component.less b/plugins/UsersManager/angularjs/capability-edit/capability-edit.component.less
deleted file mode 100644
index a59690b170..0000000000
--- a/plugins/UsersManager/angularjs/capability-edit/capability-edit.component.less
+++ /dev/null
@@ -1,3 +0,0 @@
-.capabilityEdit {
- // ...
-} \ No newline at end of file
diff --git a/plugins/UsersManager/angularjs/permissions-metadata/permissions-metadata.service.js b/plugins/UsersManager/angularjs/permissions-metadata/permissions-metadata.service.js
deleted file mode 100644
index 1728041412..0000000000
--- a/plugins/UsersManager/angularjs/permissions-metadata/permissions-metadata.service.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/*!
- * Matomo - free/libre analytics platform
- *
- * @link https://matomo.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;
- });
- },
- };
- }
-})();
diff --git a/plugins/UsersManager/tests/UI/UsersManager_spec.js b/plugins/UsersManager/tests/UI/UsersManager_spec.js
index e53e654cd3..475bba3d8c 100644
--- a/plugins/UsersManager/tests/UI/UsersManager_spec.js
+++ b/plugins/UsersManager/tests/UI/UsersManager_spec.js
@@ -332,6 +332,7 @@ describe("UsersManager", function () {
it('should select all displayed rows when the select all checkbox is clicked', async function () {
await page.click('.userPermissionsEdit th.select-cell input + span');
await page.waitForTimeout(250); // for checkbox animations
+ await page.mouse.move(-10, -10);
expect(await page.screenshotSelector('.usersManager')).to.matchImage({
imageName: 'permissions_select_all',
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_all_sites_access.png b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_all_sites_access.png
index ca5768d6ef..3ce1307b10 100644
--- a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_all_sites_access.png
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_all_sites_access.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d508ef919076c7de2aca6ef361ca5d8c508d340b1bc6d2a3e60e3ca6314bb34e
-size 102083
+oid sha256:1187cff49198d1ec7784bcf8ef9c57813b4325c0e6778117dd32b30d777c4594
+size 100480
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_bulk_access_set_all.png b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_bulk_access_set_all.png
index 19208deb3b..f6531a6194 100644
--- a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_bulk_access_set_all.png
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_bulk_access_set_all.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:990108c86e38681e565ccd517fb02369ad98f43a80e7892fe33a940781176596
-size 80978
+oid sha256:aaffd292e9b45fe77a6985984a20507d4ac198078074139c4b83eeab1fc0b2b7
+size 80848
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_capability_single_site.png b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_capability_single_site.png
index 50f12fb58a..9365527f47 100644
--- a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_capability_single_site.png
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_capability_single_site.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9484fcaf803922946c6cdbddfcfce0908fed6e77619655022d2454ed595f3f4d
-size 92451
+oid sha256:7245e5f4269836c8e2cde0c44de8ddb12423eb63f2e36d1ddb72c2926225ea5d
+size 92457
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_next.png b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_next.png
index b655bd93ca..f22b6b6284 100644
--- a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_next.png
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_next.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:846991e7dfd320e07e82212bc46bd7bef663289c17863a81f6881aef5df58698
-size 92113
+oid sha256:2ec58a4d3d51a8b13bad20f858453cba578e68b5a946ecede4d2b0174a76d83f
+size 92466
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_remove_single.png b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_remove_single.png
index efeb7da210..367f2c5b10 100644
--- a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_remove_single.png
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_remove_single.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5b1ac3c0fba9928c19cb39b56cc13ce064f9a8359e2fe91ee887269efd7b64e1
-size 90338
+oid sha256:86efc969c58f87ddfe9df3451d585cfe3710d851354e71f4d45b52225b03c320
+size 90565
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_select_multiple.png b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_select_multiple.png
index 0436cb04b7..69215c0e74 100644
--- a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_select_multiple.png
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_select_multiple.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4b68f0989997324e8575cfaa9ad1ba67bdc62aad3673626a1af495f74749bcba
-size 92270
+oid sha256:bb5d14dcd6075e80cdbf488ad9e03dc6a830dbfb8b31b3f0a172c9396697c82b
+size 92579
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_single_site_access.png b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_single_site_access.png
index 52d4c428fb..ab419ee5f9 100644
--- a/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_single_site_access.png
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UsersManager_permissions_single_site_access.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c69e778f61c9697067eea1cf4da7293161b3e19fd5d20d8523a7cb87cc36a0e2
-size 89089
+oid sha256:cec452b68a9801e37be4050f2454d5656844c5ccdd31af643d1e0583dbf8df08
+size 89100
diff --git a/plugins/UsersManager/vue/dist/UsersManager.umd.js b/plugins/UsersManager/vue/dist/UsersManager.umd.js
new file mode 100644
index 0000000000..ae145d094b
--- /dev/null
+++ b/plugins/UsersManager/vue/dist/UsersManager.umd.js
@@ -0,0 +1,596 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory(require("CoreHome"), require("vue"), require("CorePluginsAdmin"));
+ else if(typeof define === 'function' && define.amd)
+ define(["CoreHome", , "CorePluginsAdmin"], factory);
+ else if(typeof exports === 'object')
+ exports["UsersManager"] = factory(require("CoreHome"), require("vue"), require("CorePluginsAdmin"));
+ else
+ root["UsersManager"] = factory(root["CoreHome"], root["Vue"], root["CorePluginsAdmin"]);
+})((typeof self !== 'undefined' ? self : this), function(__WEBPACK_EXTERNAL_MODULE__19dc__, __WEBPACK_EXTERNAL_MODULE__8bbf__, __WEBPACK_EXTERNAL_MODULE_a5a2__) {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId]) {
+/******/ return installedModules[moduleId].exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ }
+/******/ };
+/******/
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = function(exports) {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/
+/******/ // create a fake namespace object
+/******/ // mode & 1: value is a module id, require it
+/******/ // mode & 2: merge all properties of value into the ns
+/******/ // mode & 4: return value when already ns object
+/******/ // mode & 8|1: behave like require
+/******/ __webpack_require__.t = function(value, mode) {
+/******/ if(mode & 1) value = __webpack_require__(value);
+/******/ if(mode & 8) return value;
+/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ var ns = Object.create(null);
+/******/ __webpack_require__.r(ns);
+/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ return ns;
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "plugins/UsersManager/vue/dist/";
+/******/
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = "fae3");
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ "19dc":
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE__19dc__;
+
+/***/ }),
+
+/***/ "8bbf":
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE__8bbf__;
+
+/***/ }),
+
+/***/ "a5a2":
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE_a5a2__;
+
+/***/ }),
+
+/***/ "fae3":
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+// ESM COMPAT FLAG
+__webpack_require__.r(__webpack_exports__);
+
+// EXPORTS
+__webpack_require__.d(__webpack_exports__, "CapabilitiesEdit", function() { return /* reexport */ CapabilitiesEdit; });
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js
+// This file is imported into lib/wc client bundles.
+
+if (typeof window !== 'undefined') {
+ var currentScript = window.document.currentScript
+ if (false) { var getCurrentScript; }
+
+ var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/)
+ if (src) {
+ __webpack_require__.p = src[1] // eslint-disable-line
+ }
+}
+
+// Indicate to webpack that this file can be concatenated
+/* harmony default export */ var setPublicPath = (null);
+
+// EXTERNAL MODULE: external "CoreHome"
+var external_CoreHome_ = __webpack_require__("19dc");
+
+// EXTERNAL MODULE: external {"commonjs":"vue","commonjs2":"vue","root":"Vue"}
+var external_commonjs_vue_commonjs2_vue_root_Vue_ = __webpack_require__("8bbf");
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.vue?vue&type=template&id=259d8ccf
+
+var _hoisted_1 = ["title"];
+var _hoisted_2 = ["onClick"];
+var _hoisted_3 = {
+ key: 0,
+ class: "addCapability"
+};
+var _hoisted_4 = {
+ class: "ui-confirm confirmCapabilityToggle modal",
+ ref: "confirmCapabilityToggleModal"
+};
+var _hoisted_5 = {
+ class: "modal-content"
+};
+var _hoisted_6 = ["innerHTML"];
+var _hoisted_7 = ["innerHTML"];
+var _hoisted_8 = {
+ class: "modal-footer"
+};
+function render(_ctx, _cache, $props, $setup, $data, $options) {
+ var _component_Field = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("Field");
+
+ return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", {
+ class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["capabilitiesEdit", {
+ busy: _ctx.isBusy
+ }])
+ }, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.actualCapabilities, function (capability) {
+ return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", {
+ key: capability.id,
+ class: "chip"
+ }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", {
+ class: "capability-name",
+ title: "".concat(capability.description, " ").concat(_ctx.isIncludedInRole(capability) ? "<br/><br/>".concat(_ctx.translate('UsersManager_IncludedInUsersRole')) : '')
+ }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(capability.category) + ": " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(capability.name), 9, _hoisted_1), !_ctx.isIncludedInRole(capability) ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", {
+ key: 0,
+ class: "icon-close",
+ onClick: function onClick($event) {
+ _ctx.capabilityToRemoveId = capability.id;
+
+ _ctx.onToggleCapability(false);
+ }
+ }, null, 8, _hoisted_2)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)]);
+ }), 128)), _ctx.availableCapabilitiesGrouped.length ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", _hoisted_3, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, {
+ "model-value": _ctx.capabilityToAddId,
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = function ($event) {
+ _ctx.capabilityToAddId = $event;
+
+ _ctx.onToggleCapability(true);
+ }),
+ disabled: _ctx.isBusy,
+ uicontrol: "expandable-select",
+ name: "add_capability",
+ "full-width": true,
+ options: _ctx.availableCapabilitiesGrouped
+ }, null, 8, ["model-value", "disabled", "options"])])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_4, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_5, [_ctx.isAddingCapability ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("h2", {
+ key: 0,
+ innerHTML: _ctx.$sanitize(_ctx.confirmAddCapabilityToggleContent)
+ }, null, 8, _hoisted_6)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), !_ctx.isAddingCapability ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("h2", {
+ key: 1,
+ innerHTML: _ctx.$sanitize(_ctx.confirmCapabilityToggleContent)
+ }, null, 8, _hoisted_7)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_8, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", {
+ href: "",
+ class: "modal-action modal-close btn",
+ onClick: _cache[1] || (_cache[1] = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withModifiers"])(function ($event) {
+ return _ctx.toggleCapability();
+ }, ["prevent"]))
+ }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Yes')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", {
+ href: "",
+ class: "modal-action modal-close modal-no",
+ onClick: _cache[2] || (_cache[2] = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withModifiers"])(function ($event) {
+ _ctx.capabilityToAddOrRemove = null;
+ _ctx.capabilityToAddId = null;
+ _ctx.capabilityToRemoveId = null;
+ }, ["prevent"]))
+ }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_No')), 1)])], 512)], 2);
+}
+// CONCATENATED MODULE: ./plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.vue?vue&type=template&id=259d8ccf
+
+// EXTERNAL MODULE: external "CorePluginsAdmin"
+var external_CorePluginsAdmin_ = __webpack_require__("a5a2");
+
+// CONCATENATED MODULE: ./plugins/UsersManager/vue/src/CapabilitiesStore/CapabilitiesStore.ts
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+
+
+var CapabilitiesStore_CapabilitiesStore = /*#__PURE__*/function () {
+ function CapabilitiesStore() {
+ var _this = this;
+
+ _classCallCheck(this, CapabilitiesStore);
+
+ _defineProperty(this, "privateState", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({
+ isLoading: false,
+ capabilities: []
+ }));
+
+ _defineProperty(this, "state", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(function () {
+ return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(_this.privateState);
+ }));
+
+ _defineProperty(this, "capabilities", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(function () {
+ return _this.state.value.capabilities;
+ }));
+
+ _defineProperty(this, "isLoading", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(function () {
+ return _this.state.value.isLoading;
+ }));
+
+ _defineProperty(this, "fetchPromise", void 0);
+
+ this.fetchCapabilities();
+ }
+
+ _createClass(CapabilitiesStore, [{
+ key: "fetchCapabilities",
+ value: function fetchCapabilities() {
+ var _this2 = this;
+
+ if (!this.fetchPromise) {
+ this.privateState.isLoading = true;
+ this.fetchPromise = external_CoreHome_["AjaxHelper"].fetch({
+ method: 'UsersManager.getAvailableCapabilities'
+ }).then(function (capabilities) {
+ _this2.privateState.capabilities = capabilities;
+ return _this2.capabilities.value;
+ }).finally(function () {
+ _this2.privateState.isLoading = false;
+ });
+ }
+
+ return this.fetchPromise;
+ }
+ }]);
+
+ return CapabilitiesStore;
+}();
+
+/* harmony default export */ var src_CapabilitiesStore_CapabilitiesStore = (Object(external_CoreHome_["lazyInitSingleton"])(CapabilitiesStore_CapabilitiesStore));
+// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--14-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--14-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.vue?vue&type=script&lang=ts
+
+
+
+
+var _window = window,
+ $ = _window.$;
+/* harmony default export */ var CapabilitiesEditvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({
+ props: {
+ idsite: [String, Number],
+ siteName: {
+ type: String,
+ required: true
+ },
+ userLogin: {
+ type: String,
+ required: true
+ },
+ userRole: {
+ type: String,
+ required: true
+ },
+ capabilities: Array
+ },
+ components: {
+ Field: external_CorePluginsAdmin_["Field"]
+ },
+ data: function data() {
+ return {
+ theCapabilities: this.capabilities || [],
+ isBusy: false,
+ isAddingCapability: false,
+ capabilityToAddId: null,
+ capabilityToRemoveId: null,
+ capabilityToAddOrRemove: null
+ };
+ },
+ emits: ['change'],
+ watch: {
+ capabilities: function capabilities(newValue) {
+ if (newValue) {
+ this.theCapabilities = newValue;
+ }
+ }
+ },
+ created: function created() {
+ var _this = this;
+
+ if (!this.capabilities) {
+ this.isBusy = true;
+ external_CoreHome_["AjaxHelper"].fetch({
+ method: 'UsersManager.getUsersPlusRole',
+ limit: '1',
+ filter_search: this.userLogin
+ }).then(function (user) {
+ if (!user || !user.capabilities) {
+ return [];
+ }
+
+ return user.capabilities;
+ }).then(function (capabilities) {
+ _this.theCapabilities = capabilities;
+ }).finally(function () {
+ _this.isBusy = false;
+ });
+ } else {
+ this.theCapabilities = this.capabilities;
+ }
+ },
+ methods: {
+ onToggleCapability: function onToggleCapability(isAdd) {
+ var _this2 = this;
+
+ this.isAddingCapability = isAdd;
+ var capabilityToAddOrRemoveId = isAdd ? this.capabilityToAddId : this.capabilityToRemoveId;
+ this.capabilityToAddOrRemove = null;
+ this.availableCapabilities.forEach(function (capability) {
+ if (capability.id === capabilityToAddOrRemoveId) {
+ _this2.capabilityToAddOrRemove = capability;
+ }
+ });
+
+ if (this.$refs.confirmCapabilityToggleModal) {
+ $(this.$refs.confirmCapabilityToggleModal).modal({
+ dismissible: false,
+ yes: function yes() {
+ return null;
+ }
+ }).modal('open');
+ }
+ },
+ toggleCapability: function toggleCapability() {
+ if (this.isAddingCapability) {
+ this.addCapability(this.capabilityToAddOrRemove);
+ } else {
+ this.removeCapability(this.capabilityToAddOrRemove);
+ }
+ },
+ isIncludedInRole: function isIncludedInRole(capability) {
+ return (capability.includedInRoles || []).indexOf(this.userRole) !== -1;
+ },
+ getCapabilitiesList: function getCapabilitiesList() {
+ var _this3 = this;
+
+ var result = [];
+ this.availableCapabilities.forEach(function (capability) {
+ if (_this3.isIncludedInRole(capability)) {
+ return;
+ }
+
+ if (_this3.capabilitiesSet[capability.id]) {
+ result.push(capability.id);
+ }
+ });
+ return result;
+ },
+ addCapability: function addCapability(capability) {
+ var _this4 = this;
+
+ this.isBusy = true;
+ external_CoreHome_["AjaxHelper"].post({
+ method: 'UsersManager.addCapabilities'
+ }, {
+ userLogin: this.userLogin,
+ capabilities: capability.id,
+ idSites: this.idsite
+ }).then(function () {
+ _this4.$emit('change', _this4.getCapabilitiesList());
+ }).finally(function () {
+ _this4.isBusy = false;
+ _this4.capabilityToAddOrRemove = null;
+ _this4.capabilityToAddId = null;
+ _this4.capabilityToRemoveId = null;
+ });
+ },
+ removeCapability: function removeCapability(capability) {
+ var _this5 = this;
+
+ this.isBusy = true;
+ external_CoreHome_["AjaxHelper"].post({
+ method: 'UsersManager.removeCapabilities'
+ }, {
+ userLogin: this.userLogin,
+ capabilities: capability.id,
+ idSites: this.idsite
+ }).then(function () {
+ _this5.$emit('change', _this5.getCapabilitiesList());
+ }).finally(function () {
+ _this5.isBusy = false;
+ _this5.capabilityToAddOrRemove = null;
+ _this5.capabilityToAddId = null;
+ _this5.capabilityToRemoveId = null;
+ });
+ }
+ },
+ computed: {
+ availableCapabilities: function availableCapabilities() {
+ return src_CapabilitiesStore_CapabilitiesStore.capabilities.value;
+ },
+ confirmAddCapabilityToggleContent: function confirmAddCapabilityToggleContent() {
+ return Object(external_CoreHome_["translate"])('UsersManager_AreYouSureAddCapability', "<strong>".concat(this.userLogin, "</strong>"), "<strong>".concat(this.capabilityToAddOrRemove ? this.capabilityToAddOrRemove.name : '', "</strong>"), "<strong>".concat(this.siteNameText, "</strong>"));
+ },
+ confirmCapabilityToggleContent: function confirmCapabilityToggleContent() {
+ return Object(external_CoreHome_["translate"])('UsersManager_AreYouSureRemoveCapability', "<strong>".concat(this.capabilityToAddOrRemove ? this.capabilityToAddOrRemove.name : '', "</strong>"), "<strong>".concat(this.userLogin, "</strong>"), "<strong>".concat(this.siteNameText, "</strong>"));
+ },
+ siteNameText: function siteNameText() {
+ return external_CoreHome_["Matomo"].helper.htmlEntities(this.siteName);
+ },
+ availableCapabilitiesGrouped: function availableCapabilitiesGrouped() {
+ var _this6 = this;
+
+ var availableCapabilitiesGrouped = this.availableCapabilities.filter(function (c) {
+ return !_this6.capabilitiesSet[c.id];
+ }).map(function (c) {
+ return {
+ group: c.category,
+ key: c.id,
+ value: c.name,
+ tooltip: c.description
+ };
+ });
+ 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;
+ });
+ return availableCapabilitiesGrouped;
+ },
+ capabilitiesSet: function capabilitiesSet() {
+ var _this7 = this;
+
+ var capabilitiesSet = {};
+ var capabilities = this.theCapabilities;
+ (capabilities || []).forEach(function (capability) {
+ capabilitiesSet[capability] = true;
+ });
+ (this.availableCapabilities || []).forEach(function (capability) {
+ if (_this7.isIncludedInRole(capability)) {
+ capabilitiesSet[capability.id] = true;
+ }
+ });
+ return capabilitiesSet;
+ },
+ actualCapabilities: function actualCapabilities() {
+ var capabilitiesSet = this.capabilitiesSet;
+ return this.availableCapabilities.filter(function (c) {
+ return !!capabilitiesSet[c.id];
+ });
+ }
+ }
+}));
+// CONCATENATED MODULE: ./plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.vue?vue&type=script&lang=ts
+
+// CONCATENATED MODULE: ./plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.vue
+
+
+
+CapabilitiesEditvue_type_script_lang_ts.render = render
+
+/* harmony default export */ var CapabilitiesEdit = (CapabilitiesEditvue_type_script_lang_ts);
+// CONCATENATED MODULE: ./plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.adapter.ts
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+
+/* harmony default export */ var CapabilitiesEdit_adapter = (Object(external_CoreHome_["createAngularJsAdapter"])({
+ component: CapabilitiesEdit,
+ scope: {
+ idsite: {
+ angularJsBind: '<'
+ },
+ siteName: {
+ angularJsBind: '<'
+ },
+ userLogin: {
+ angularJsBind: '<'
+ },
+ userRole: {
+ angularJsBind: '<'
+ },
+ capabilities: {
+ angularJsBind: '<'
+ },
+ onCapabilitiesChange: {
+ angularJsBind: '&',
+ vue: 'change'
+ }
+ },
+ directiveName: 'piwikCapabilitiesEdit',
+ restrict: 'E',
+ $inject: ['$timeout'],
+ events: {
+ change: function change(caps, vm, scope, element, attrs, controller, $timeout) {
+ $timeout(function () {
+ if (scope.onCapabilitiesChange) {
+ scope.onCapabilitiesChange.call({
+ capabilities: caps
+ });
+ }
+ });
+ }
+ }
+}));
+// CONCATENATED MODULE: ./plugins/UsersManager/vue/src/index.ts
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/entry-lib-no-default.js
+
+
+
+
+/***/ })
+
+/******/ });
+});
+//# sourceMappingURL=UsersManager.umd.js.map \ No newline at end of file
diff --git a/plugins/UsersManager/vue/dist/UsersManager.umd.min.js b/plugins/UsersManager/vue/dist/UsersManager.umd.min.js
new file mode 100644
index 0000000000..2a1f272e0c
--- /dev/null
+++ b/plugins/UsersManager/vue/dist/UsersManager.umd.min.js
@@ -0,0 +1,14 @@
+(function(e,i){"object"===typeof exports&&"object"===typeof module?module.exports=i(require("CoreHome"),require("vue"),require("CorePluginsAdmin")):"function"===typeof define&&define.amd?define(["CoreHome",,"CorePluginsAdmin"],i):"object"===typeof exports?exports["UsersManager"]=i(require("CoreHome"),require("vue"),require("CorePluginsAdmin")):e["UsersManager"]=i(e["CoreHome"],e["Vue"],e["CorePluginsAdmin"])})("undefined"!==typeof self?self:this,(function(e,i,t){return function(e){var i={};function t(a){if(i[a])return i[a].exports;var n=i[a]={i:a,l:!1,exports:{}};return e[a].call(n.exports,n,n.exports,t),n.l=!0,n.exports}return t.m=e,t.c=i,t.d=function(e,i,a){t.o(e,i)||Object.defineProperty(e,i,{enumerable:!0,get:a})},t.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,i){if(1&i&&(e=t(e)),8&i)return e;if(4&i&&"object"===typeof e&&e&&e.__esModule)return e;var a=Object.create(null);if(t.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:e}),2&i&&"string"!=typeof e)for(var n in e)t.d(a,n,function(i){return e[i]}.bind(null,n));return a},t.n=function(e){var i=e&&e.__esModule?function(){return e["default"]}:function(){return e};return t.d(i,"a",i),i},t.o=function(e,i){return Object.prototype.hasOwnProperty.call(e,i)},t.p="plugins/UsersManager/vue/dist/",t(t.s="fae3")}({"19dc":function(i,t){i.exports=e},"8bbf":function(e,t){e.exports=i},a5a2:function(e,i){e.exports=t},fae3:function(e,i,t){"use strict";if(t.r(i),t.d(i,"CapabilitiesEdit",(function(){return B})),"undefined"!==typeof window){var a=window.document.currentScript,n=a&&a.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);n&&(t.p=n[1])}var o=t("19dc"),l=t("8bbf"),r=["title"],s=["onClick"],c={key:0,class:"addCapability"},u={class:"ui-confirm confirmCapabilityToggle modal",ref:"confirmCapabilityToggleModal"},d={class:"modal-content"},p=["innerHTML"],b=["innerHTML"],f={class:"modal-footer"};function m(e,i,t,a,n,o){var m=Object(l["resolveComponent"])("Field");return Object(l["openBlock"])(),Object(l["createElementBlock"])("div",{class:Object(l["normalizeClass"])(["capabilitiesEdit",{busy:e.isBusy}])},[(Object(l["openBlock"])(!0),Object(l["createElementBlock"])(l["Fragment"],null,Object(l["renderList"])(e.actualCapabilities,(function(i){return Object(l["openBlock"])(),Object(l["createElementBlock"])("div",{key:i.id,class:"chip"},[Object(l["createElementVNode"])("span",{class:"capability-name",title:"".concat(i.description," ").concat(e.isIncludedInRole(i)?"<br/><br/>".concat(e.translate("UsersManager_IncludedInUsersRole")):"")},Object(l["toDisplayString"])(i.category)+": "+Object(l["toDisplayString"])(i.name),9,r),e.isIncludedInRole(i)?Object(l["createCommentVNode"])("",!0):(Object(l["openBlock"])(),Object(l["createElementBlock"])("span",{key:0,class:"icon-close",onClick:function(t){e.capabilityToRemoveId=i.id,e.onToggleCapability(!1)}},null,8,s))])})),128)),e.availableCapabilitiesGrouped.length?(Object(l["openBlock"])(),Object(l["createElementBlock"])("div",c,[Object(l["createVNode"])(m,{"model-value":e.capabilityToAddId,"onUpdate:modelValue":i[0]||(i[0]=function(i){e.capabilityToAddId=i,e.onToggleCapability(!0)}),disabled:e.isBusy,uicontrol:"expandable-select",name:"add_capability","full-width":!0,options:e.availableCapabilitiesGrouped},null,8,["model-value","disabled","options"])])):Object(l["createCommentVNode"])("",!0),Object(l["createElementVNode"])("div",u,[Object(l["createElementVNode"])("div",d,[e.isAddingCapability?(Object(l["openBlock"])(),Object(l["createElementBlock"])("h2",{key:0,innerHTML:e.$sanitize(e.confirmAddCapabilityToggleContent)},null,8,p)):Object(l["createCommentVNode"])("",!0),e.isAddingCapability?Object(l["createCommentVNode"])("",!0):(Object(l["openBlock"])(),Object(l["createElementBlock"])("h2",{key:1,innerHTML:e.$sanitize(e.confirmCapabilityToggleContent)},null,8,b))]),Object(l["createElementVNode"])("div",f,[Object(l["createElementVNode"])("a",{href:"",class:"modal-action modal-close btn",onClick:i[1]||(i[1]=Object(l["withModifiers"])((function(i){return e.toggleCapability()}),["prevent"]))},Object(l["toDisplayString"])(e.translate("General_Yes")),1),Object(l["createElementVNode"])("a",{href:"",class:"modal-action modal-close modal-no",onClick:i[2]||(i[2]=Object(l["withModifiers"])((function(i){e.capabilityToAddOrRemove=null,e.capabilityToAddId=null,e.capabilityToRemoveId=null}),["prevent"]))},Object(l["toDisplayString"])(e.translate("General_No")),1)])],512)],2)}var g=t("a5a2");function h(e,i){if(!(e instanceof i))throw new TypeError("Cannot call a class as a function")}function y(e,i){for(var t=0;t<i.length;t++){var a=i[t];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}function v(e,i,t){return i&&y(e.prototype,i),t&&y(e,t),e}function C(e,i,t){return i in e?Object.defineProperty(e,i,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[i]=t,e}
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */var O=function(){function e(){var i=this;h(this,e),C(this,"privateState",Object(l["reactive"])({isLoading:!1,capabilities:[]})),C(this,"state",Object(l["computed"])((function(){return Object(l["readonly"])(i.privateState)}))),C(this,"capabilities",Object(l["computed"])((function(){return i.state.value.capabilities}))),C(this,"isLoading",Object(l["computed"])((function(){return i.state.value.isLoading}))),C(this,"fetchPromise",void 0),this.fetchCapabilities()}return v(e,[{key:"fetchCapabilities",value:function(){var e=this;return this.fetchPromise||(this.privateState.isLoading=!0,this.fetchPromise=o["AjaxHelper"].fetch({method:"UsersManager.getAvailableCapabilities"}).then((function(i){return e.privateState.capabilities=i,e.capabilities.value})).finally((function(){e.privateState.isLoading=!1}))),this.fetchPromise}}]),e}(),j=Object(o["lazyInitSingleton"])(O),T=window,A=T.$,R=Object(l["defineComponent"])({props:{idsite:[String,Number],siteName:{type:String,required:!0},userLogin:{type:String,required:!0},userRole:{type:String,required:!0},capabilities:Array},components:{Field:g["Field"]},data:function(){return{theCapabilities:this.capabilities||[],isBusy:!1,isAddingCapability:!1,capabilityToAddId:null,capabilityToRemoveId:null,capabilityToAddOrRemove:null}},emits:["change"],watch:{capabilities:function(e){e&&(this.theCapabilities=e)}},created:function(){var e=this;this.capabilities?this.theCapabilities=this.capabilities:(this.isBusy=!0,o["AjaxHelper"].fetch({method:"UsersManager.getUsersPlusRole",limit:"1",filter_search:this.userLogin}).then((function(e){return e&&e.capabilities?e.capabilities:[]})).then((function(i){e.theCapabilities=i})).finally((function(){e.isBusy=!1})))},methods:{onToggleCapability:function(e){var i=this;this.isAddingCapability=e;var t=e?this.capabilityToAddId:this.capabilityToRemoveId;this.capabilityToAddOrRemove=null,this.availableCapabilities.forEach((function(e){e.id===t&&(i.capabilityToAddOrRemove=e)})),this.$refs.confirmCapabilityToggleModal&&A(this.$refs.confirmCapabilityToggleModal).modal({dismissible:!1,yes:function(){return null}}).modal("open")},toggleCapability:function(){this.isAddingCapability?this.addCapability(this.capabilityToAddOrRemove):this.removeCapability(this.capabilityToAddOrRemove)},isIncludedInRole:function(e){return-1!==(e.includedInRoles||[]).indexOf(this.userRole)},getCapabilitiesList:function(){var e=this,i=[];return this.availableCapabilities.forEach((function(t){e.isIncludedInRole(t)||e.capabilitiesSet[t.id]&&i.push(t.id)})),i},addCapability:function(e){var i=this;this.isBusy=!0,o["AjaxHelper"].post({method:"UsersManager.addCapabilities"},{userLogin:this.userLogin,capabilities:e.id,idSites:this.idsite}).then((function(){i.$emit("change",i.getCapabilitiesList())})).finally((function(){i.isBusy=!1,i.capabilityToAddOrRemove=null,i.capabilityToAddId=null,i.capabilityToRemoveId=null}))},removeCapability:function(e){var i=this;this.isBusy=!0,o["AjaxHelper"].post({method:"UsersManager.removeCapabilities"},{userLogin:this.userLogin,capabilities:e.id,idSites:this.idsite}).then((function(){i.$emit("change",i.getCapabilitiesList())})).finally((function(){i.isBusy=!1,i.capabilityToAddOrRemove=null,i.capabilityToAddId=null,i.capabilityToRemoveId=null}))}},computed:{availableCapabilities:function(){return j.capabilities.value},confirmAddCapabilityToggleContent:function(){return Object(o["translate"])("UsersManager_AreYouSureAddCapability","<strong>".concat(this.userLogin,"</strong>"),"<strong>".concat(this.capabilityToAddOrRemove?this.capabilityToAddOrRemove.name:"","</strong>"),"<strong>".concat(this.siteNameText,"</strong>"))},confirmCapabilityToggleContent:function(){return Object(o["translate"])("UsersManager_AreYouSureRemoveCapability","<strong>".concat(this.capabilityToAddOrRemove?this.capabilityToAddOrRemove.name:"","</strong>"),"<strong>".concat(this.userLogin,"</strong>"),"<strong>".concat(this.siteNameText,"</strong>"))},siteNameText:function(){return o["Matomo"].helper.htmlEntities(this.siteName)},availableCapabilitiesGrouped:function(){var e=this,i=this.availableCapabilities.filter((function(i){return!e.capabilitiesSet[i.id]})).map((function(e){return{group:e.category,key:e.id,value:e.name,tooltip:e.description}}));return i.sort((function(e,i){return e.group===i.group?e.value===i.value?0:e.value<i.value?-1:1:e.group<i.group?-1:1})),i},capabilitiesSet:function(){var e=this,i={},t=this.theCapabilities;return(t||[]).forEach((function(e){i[e]=!0})),(this.availableCapabilities||[]).forEach((function(t){e.isIncludedInRole(t)&&(i[t.id]=!0)})),i},actualCapabilities:function(){var e=this.capabilitiesSet;return this.availableCapabilities.filter((function(i){return!!e[i.id]}))}}});R.render=m;var B=R;
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */Object(o["createAngularJsAdapter"])({component:B,scope:{idsite:{angularJsBind:"<"},siteName:{angularJsBind:"<"},userLogin:{angularJsBind:"<"},userRole:{angularJsBind:"<"},capabilities:{angularJsBind:"<"},onCapabilitiesChange:{angularJsBind:"&",vue:"change"}},directiveName:"piwikCapabilitiesEdit",restrict:"E",$inject:["$timeout"],events:{change:function(e,i,t,a,n,o,l){l((function(){t.onCapabilitiesChange&&t.onCapabilitiesChange.call({capabilities:e})}))}}})}})}));
+//# sourceMappingURL=UsersManager.umd.min.js.map \ No newline at end of file
diff --git a/plugins/UsersManager/vue/dist/umd.metadata.json b/plugins/UsersManager/vue/dist/umd.metadata.json
new file mode 100644
index 0000000000..dce4477a3c
--- /dev/null
+++ b/plugins/UsersManager/vue/dist/umd.metadata.json
@@ -0,0 +1,6 @@
+{
+ "dependsOn": [
+ "CoreHome",
+ "CorePluginsAdmin"
+ ]
+} \ No newline at end of file
diff --git a/plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.adapter.ts b/plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.adapter.ts
new file mode 100644
index 0000000000..ca522d10db
--- /dev/null
+++ b/plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.adapter.ts
@@ -0,0 +1,50 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+import { ITimeoutService } from 'angular';
+import { createAngularJsAdapter } from 'CoreHome';
+import CapabilitiesEdit from './CapabilitiesEdit.vue';
+import Capability from '../CapabilitiesStore/Capability';
+
+export default createAngularJsAdapter<[ITimeoutService]>({
+ component: CapabilitiesEdit,
+ scope: {
+ idsite: {
+ angularJsBind: '<',
+ },
+ siteName: {
+ angularJsBind: '<',
+ },
+ userLogin: {
+ angularJsBind: '<',
+ },
+ userRole: {
+ angularJsBind: '<',
+ },
+ capabilities: {
+ angularJsBind: '<',
+ },
+ onCapabilitiesChange: {
+ angularJsBind: '&',
+ vue: 'change',
+ },
+ },
+ directiveName: 'piwikCapabilitiesEdit',
+ restrict: 'E',
+ $inject: ['$timeout'],
+ events: {
+ change(caps: Capability[], vm, scope, element, attrs, controller, $timeout) {
+ $timeout(() => {
+ if (scope.onCapabilitiesChange) {
+ scope.onCapabilitiesChange.call({
+ capabilities: caps,
+ });
+ }
+ });
+ },
+ },
+});
diff --git a/plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.less b/plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.less
index 8a0305bfcd..3d4cf06b95 100644
--- a/plugins/UsersManager/angularjs/capabilities-edit/capabilities-edit.component.less
+++ b/plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.less
@@ -2,7 +2,7 @@
display: inline-block;
margin-bottom: -8px;
- > div.chip {
+ div.chip {
margin-right: 8px;
display: inline-block;
margin-bottom: 8px;
@@ -11,7 +11,7 @@
padding-right: .4rem;
}
- > span.icon-close {
+ span.icon-close {
font-size: .6rem;
float: right;
margin-top: 1.4em;
diff --git a/plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.vue b/plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.vue
new file mode 100644
index 0000000000..e1c226b56e
--- /dev/null
+++ b/plugins/UsersManager/vue/src/CapabilitiesEdit/CapabilitiesEdit.vue
@@ -0,0 +1,311 @@
+<!--
+ Matomo - free/libre analytics platform
+ @link https://matomo.org
+ @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+-->
+
+<template>
+ <div
+ class="capabilitiesEdit"
+ :class="{busy: isBusy}"
+ >
+ <div
+ v-for="capability in actualCapabilities"
+ :key="capability.id"
+ class="chip"
+ >
+ <span
+ class="capability-name"
+ :title="`${capability.description} ${
+ isIncludedInRole(capability)
+ ? `<br/><br/>${translate('UsersManager_IncludedInUsersRole')}`
+ : ''
+ }`"
+ >
+ {{ capability.category }}: {{ capability.name }}
+ </span>
+ <span
+ class="icon-close"
+ v-if="!isIncludedInRole(capability)"
+ @click="capabilityToRemoveId = capability.id; onToggleCapability(false)"
+ />
+ </div>
+ <div
+ class="addCapability"
+ v-if="availableCapabilitiesGrouped.length"
+ >
+ <Field
+ :model-value="capabilityToAddId"
+ @update:model-value="capabilityToAddId = $event; onToggleCapability(true)"
+ :disabled="isBusy"
+ uicontrol="expandable-select"
+ name="add_capability"
+ :full-width="true"
+ :options="availableCapabilitiesGrouped"
+ >
+ </Field>
+ </div>
+ <div class="ui-confirm confirmCapabilityToggle modal" ref="confirmCapabilityToggleModal">
+ <div class="modal-content">
+ <h2
+ v-if="isAddingCapability"
+ v-html="$sanitize(confirmAddCapabilityToggleContent)"
+ ></h2>
+ <h2
+ v-if="!isAddingCapability"
+ v-html="$sanitize(confirmCapabilityToggleContent)"
+ ></h2>
+ </div>
+ <div class="modal-footer">
+ <a
+ href=""
+ class="modal-action modal-close btn"
+ @click.prevent="toggleCapability()"
+ >{{ translate('General_Yes') }}</a>
+ <a
+ href=""
+ class="modal-action modal-close modal-no"
+ @click.prevent="
+ capabilityToAddOrRemove = null;
+ capabilityToAddId = null;
+ capabilityToRemoveId = null;"
+ >
+ {{ translate('General_No') }}
+ </a>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, DeepReadonly } from 'vue';
+import { translate, AjaxHelper, Matomo } from 'CoreHome';
+import { Field } from 'CorePluginsAdmin';
+import CapabilitiesStore from '../CapabilitiesStore/CapabilitiesStore';
+import Capability from '../CapabilitiesStore/Capability';
+import ModalOptions = M.ModalOptions;
+
+interface CapabilitiesEditState {
+ isBusy: boolean;
+ theCapabilities: string[];
+ isAddingCapability: boolean;
+ capabilityToAddId: string|null;
+ capabilityToRemoveId: string|null;
+ capabilityToAddOrRemove: DeepReadonly<Capability>|null;
+}
+
+const { $ } = window;
+
+export default defineComponent({
+ props: {
+ idsite: [String, Number],
+ siteName: {
+ type: String,
+ required: true,
+ },
+ userLogin: {
+ type: String,
+ required: true,
+ },
+ userRole: {
+ type: String,
+ required: true,
+ },
+ capabilities: Array,
+ },
+ components: {
+ Field,
+ },
+ data(): CapabilitiesEditState {
+ return {
+ theCapabilities: (this.capabilities as string[]) || [],
+ isBusy: false,
+ isAddingCapability: false,
+ capabilityToAddId: null,
+ capabilityToRemoveId: null,
+ capabilityToAddOrRemove: null,
+ };
+ },
+ emits: ['change'],
+ watch: {
+ capabilities(newValue) {
+ if (newValue) {
+ this.theCapabilities = newValue as string[];
+ }
+ },
+ },
+ created() {
+ if (!this.capabilities) {
+ this.isBusy = true;
+
+ AjaxHelper.fetch<{ capabilities: string[] }>({
+ method: 'UsersManager.getUsersPlusRole',
+ limit: '1',
+ filter_search: this.userLogin,
+ }).then((user) => {
+ if (!user || !user.capabilities) {
+ return [];
+ }
+
+ return user.capabilities;
+ }).then((capabilities) => {
+ this.theCapabilities = capabilities;
+ }).finally(() => {
+ this.isBusy = false;
+ });
+ } else {
+ this.theCapabilities = this.capabilities as string[];
+ }
+ },
+ methods: {
+ onToggleCapability(isAdd: boolean) {
+ this.isAddingCapability = isAdd;
+
+ const capabilityToAddOrRemoveId = isAdd ? this.capabilityToAddId : this.capabilityToRemoveId;
+
+ this.capabilityToAddOrRemove = null;
+ this.availableCapabilities.forEach((capability) => {
+ if (capability.id === capabilityToAddOrRemoveId) {
+ this.capabilityToAddOrRemove = capability;
+ }
+ });
+
+ if (this.$refs.confirmCapabilityToggleModal) {
+ $(this.$refs.confirmCapabilityToggleModal as HTMLElement).modal({
+ dismissible: false,
+ yes: () => null,
+ } as unknown as ModalOptions).modal('open');
+ }
+ },
+ toggleCapability() {
+ if (this.isAddingCapability) {
+ this.addCapability(this.capabilityToAddOrRemove!);
+ } else {
+ this.removeCapability(this.capabilityToAddOrRemove!);
+ }
+ },
+ isIncludedInRole(capability: DeepReadonly<Capability>) {
+ return (capability.includedInRoles || []).indexOf(this.userRole) !== -1;
+ },
+ getCapabilitiesList() {
+ const result: string[] = [];
+ this.availableCapabilities.forEach((capability) => {
+ if (this.isIncludedInRole(capability)) {
+ return;
+ }
+
+ if (this.capabilitiesSet[capability.id]) {
+ result.push(capability.id);
+ }
+ });
+ return result;
+ },
+ addCapability(capability: DeepReadonly<Capability>) {
+ this.isBusy = true;
+ AjaxHelper.post(
+ {
+ method: 'UsersManager.addCapabilities',
+ },
+ {
+ userLogin: this.userLogin,
+ capabilities: capability.id,
+ idSites: this.idsite,
+ },
+ ).then(() => {
+ this.$emit('change', this.getCapabilitiesList());
+ }).finally(() => {
+ this.isBusy = false;
+ this.capabilityToAddOrRemove = null;
+ this.capabilityToAddId = null;
+ this.capabilityToRemoveId = null;
+ });
+ },
+ removeCapability(capability: DeepReadonly<Capability>) {
+ this.isBusy = true;
+ AjaxHelper.post(
+ {
+ method: 'UsersManager.removeCapabilities',
+ },
+ {
+ userLogin: this.userLogin,
+ capabilities: capability.id,
+ idSites: this.idsite,
+ },
+ ).then(() => {
+ this.$emit('change', this.getCapabilitiesList());
+ }).finally(() => {
+ this.isBusy = false;
+ this.capabilityToAddOrRemove = null;
+ this.capabilityToAddId = null;
+ this.capabilityToRemoveId = null;
+ });
+ },
+ },
+ computed: {
+ availableCapabilities() {
+ return CapabilitiesStore.capabilities.value;
+ },
+ confirmAddCapabilityToggleContent() {
+ return translate(
+ 'UsersManager_AreYouSureAddCapability',
+ `<strong>${this.userLogin}</strong>`,
+ `<strong>${this.capabilityToAddOrRemove ? this.capabilityToAddOrRemove.name : ''}</strong>`,
+ `<strong>${this.siteNameText}</strong>`,
+ );
+ },
+ confirmCapabilityToggleContent() {
+ return translate(
+ 'UsersManager_AreYouSureRemoveCapability',
+ `<strong>${this.capabilityToAddOrRemove ? this.capabilityToAddOrRemove.name : ''}</strong>`,
+ `<strong>${this.userLogin}</strong>`,
+ `<strong>${this.siteNameText}</strong>`,
+ );
+ },
+ siteNameText() {
+ return Matomo.helper.htmlEntities(this.siteName);
+ },
+ availableCapabilitiesGrouped() {
+ const availableCapabilitiesGrouped = this.availableCapabilities.filter(
+ (c) => !this.capabilitiesSet[c.id],
+ ).map((c) => ({
+ group: c.category,
+ key: c.id,
+ value: c.name,
+ tooltip: c.description,
+ }));
+
+ availableCapabilitiesGrouped.sort((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;
+ });
+
+ return availableCapabilitiesGrouped;
+ },
+ capabilitiesSet() {
+ const capabilitiesSet: Record<string, boolean> = {};
+ const capabilities = this.theCapabilities as string[];
+
+ (capabilities || []).forEach((capability) => {
+ capabilitiesSet[capability] = true;
+ });
+
+ (this.availableCapabilities || []).forEach((capability) => {
+ if (this.isIncludedInRole(capability)) {
+ capabilitiesSet[capability.id] = true;
+ }
+ });
+
+ return capabilitiesSet;
+ },
+ actualCapabilities() {
+ const { capabilitiesSet } = this;
+ return this.availableCapabilities.filter((c) => !!capabilitiesSet[c.id]);
+ },
+ },
+});
+</script>
diff --git a/plugins/UsersManager/vue/src/CapabilitiesStore/CapabilitiesStore.adapter.ts b/plugins/UsersManager/vue/src/CapabilitiesStore/CapabilitiesStore.adapter.ts
new file mode 100644
index 0000000000..dc3f08419f
--- /dev/null
+++ b/plugins/UsersManager/vue/src/CapabilitiesStore/CapabilitiesStore.adapter.ts
@@ -0,0 +1,23 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+import { DeepReadonly } from 'vue';
+import Capability from './Capability';
+import CapabilitiesStore from "./CapabilitiesStore";
+
+function permissionsMetadataServiceAdapter() {
+ return {
+ getAllCapabilities(): Promise<DeepReadonly<Capability[]>> {
+ return CapabilitiesStore.fetchCapabilities();
+ },
+ };
+}
+
+window.angular.module('piwikApp.service').factory(
+ 'permissionsMetadataService',
+ permissionsMetadataServiceAdapter,
+);
diff --git a/plugins/UsersManager/vue/src/CapabilitiesStore/CapabilitiesStore.ts b/plugins/UsersManager/vue/src/CapabilitiesStore/CapabilitiesStore.ts
new file mode 100644
index 0000000000..36b3c8113f
--- /dev/null
+++ b/plugins/UsersManager/vue/src/CapabilitiesStore/CapabilitiesStore.ts
@@ -0,0 +1,57 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+import {
+ reactive,
+ readonly,
+ computed,
+ DeepReadonly,
+} from 'vue';
+import { AjaxHelper, lazyInitSingleton } from 'CoreHome';
+import Capability from './Capability';
+
+interface CapabilitiesStoreState {
+ isLoading: boolean;
+ capabilities: Capability[];
+}
+
+class CapabilitiesStore {
+ private privateState = reactive<CapabilitiesStoreState>({
+ isLoading: false,
+ capabilities: [],
+ });
+
+ private readonly state = computed(() => readonly(this.privateState));
+
+ readonly capabilities = computed(() => this.state.value.capabilities);
+
+ readonly isLoading = computed(() => this.state.value.isLoading);
+
+ private fetchPromise?: Promise<DeepReadonly<Capability[]>>;
+
+ constructor() {
+ this.fetchCapabilities();
+ }
+
+ public fetchCapabilities(): Promise<DeepReadonly<Capability[]>> {
+ if (!this.fetchPromise) {
+ this.privateState.isLoading = true;
+ this.fetchPromise = AjaxHelper.fetch<Capability[]>({
+ method: 'UsersManager.getAvailableCapabilities',
+ }).then((capabilities) => {
+ this.privateState.capabilities = capabilities;
+ return this.capabilities.value;
+ }).finally(() => {
+ this.privateState.isLoading = false;
+ });
+ }
+
+ return this.fetchPromise!;
+ }
+}
+
+export default lazyInitSingleton(CapabilitiesStore) as CapabilitiesStore;
diff --git a/plugins/UsersManager/vue/src/CapabilitiesStore/Capability.ts b/plugins/UsersManager/vue/src/CapabilitiesStore/Capability.ts
new file mode 100644
index 0000000000..0f099b8a32
--- /dev/null
+++ b/plugins/UsersManager/vue/src/CapabilitiesStore/Capability.ts
@@ -0,0 +1,17 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+interface Capability {
+ id: string;
+ name: string;
+ description: string;
+ helpUrl: string;
+ includedInRoles: string[];
+ category: string;
+}
+
+export default Capability;
diff --git a/plugins/UsersManager/vue/src/index.ts b/plugins/UsersManager/vue/src/index.ts
new file mode 100644
index 0000000000..7411497560
--- /dev/null
+++ b/plugins/UsersManager/vue/src/index.ts
@@ -0,0 +1,10 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+import './CapabilitiesEdit/CapabilitiesEdit.adapter';
+
+export { default as CapabilitiesEdit } from './CapabilitiesEdit/CapabilitiesEdit.vue';
+export { default as Capability } from './CapabilitiesStore/Capability';