diff --git a/build/docma-config.json b/build/docma-config.json index 8bb5eb21058..00d8be5a4bb 100644 --- a/build/docma-config.json +++ b/build/docma-config.json @@ -295,6 +295,7 @@ "web/client/plugins/ResourcesCatalog/EditContext.jsx", "web/client/plugins/ResourcesCatalog/Favorites.jsx", "web/client/plugins/ResourcesCatalog/Footer.jsx", + "web/client/plugins/ResourcesCatalog/GroupManager.jsx", "web/client/plugins/ResourcesCatalog/HomeDescription.jsx", "web/client/plugins/ResourcesCatalog/ResourceDetails.jsx", "web/client/plugins/ResourcesCatalog/ResourcesFiltersForm.jsx", @@ -302,6 +303,7 @@ "web/client/plugins/ResourcesCatalog/Save.jsx", "web/client/plugins/ResourcesCatalog/SaveAs.jsx", "web/client/plugins/ResourcesCatalog/TagsManager.jsx", + "web/client/plugins/ResourcesCatalog/UserManager.jsx", "web/client/plugins/RulesDataGrid.jsx", "web/client/plugins/RulesEditor.jsx", "web/client/plugins/RulesManagerFooter.jsx", @@ -334,10 +336,8 @@ "web/client/plugins/containers/ToolsContainer.jsx", "web/client/plugins/featuregrid/FeatureEditor.jsx", "web/client/plugins/index.jsdoc", - "web/client/plugins/manager/GroupManager.jsx", "web/client/plugins/manager/Manager.jsx", "web/client/plugins/manager/ManagerMenu.jsx", - "web/client/plugins/manager/UserManager.jsx", "web/client/plugins/print/Graticule.jsx", "web/client/plugins/print/OutputFormat.jsx", "web/client/plugins/print/Projection.jsx", diff --git a/docs/developer-guide/mapstore-migration-guide.md b/docs/developer-guide/mapstore-migration-guide.md index d1265731abe..e53757ca5d0 100644 --- a/docs/developer-guide/mapstore-migration-guide.md +++ b/docs/developer-guide/mapstore-migration-guide.md @@ -22,6 +22,22 @@ This is a list of things to check if you want to update from a previous version ## Migration from 2024.02.00 to 2025.01.00 +### Removing Header from the Admin section and deprecating the old UserManager and GroupManagerPlugin + +The old `UserManager` and `GroupManager` plugin has been removed and replace with new plugins under the `web/client/plugins/ResourcesCatalog/` folder. Also the `Header` plugin has been removed from Admin/Manager so the configuration in `localConfig.json` should be updated as follow: + +```diff +{ + "plugins": { + "manager": [ +- "Header", + "Redirect", + ... + ] + } +} +``` + ### Changes in the Login plugin and deprecation of ManagerMenu The `ManagerMenu` plugin has been deprecated. The manager menu items are now handled by the `Login` plugin. The `Login` plugin now includes the `ManagerMenu` functionality, so the `ManagerMenu` plugin should be removed from the `localConfig.json` configuration. diff --git a/web/client/actions/__tests__/usergroups-test.js b/web/client/actions/__tests__/usergroups-test.js index 13ab0379211..ee45f8d2599 100644 --- a/web/client/actions/__tests__/usergroups-test.js +++ b/web/client/actions/__tests__/usergroups-test.js @@ -11,10 +11,6 @@ import expect from 'expect'; import assign from 'object-assign'; import { - GETGROUPS, - STATUS_SUCCESS, - STATUS_ERROR, - getUserGroups, editGroup, EDITGROUP, changeGroupMetadata, @@ -25,7 +21,17 @@ import { DELETEGROUP, STATUS_DELETED, searchUsers, - SEARCHUSERS + SEARCHUSERS, + updateUserGroups, + UPDATE_USER_GROUPS, + updateUserGroupsMetadata, + UPDATE_USER_GROUPS_METADATA, + loadingUserGroups, + LOADING_USER_GROUPS, + searchUserGroups, + SEARCH_USER_GROUPS, + resetSearchUserGroups, + RESET_SEARCH_USER_GROUPS } from '../usergroups'; import GeoStoreDAO from '../../api/GeoStoreDAO'; @@ -41,44 +47,43 @@ describe('Test correctness of the usergroups actions', () => { afterEach(() => { GeoStoreDAO.addBaseUrl = oldAddBaseUri; }); - it('get UserGroups', (done) => { - const retFun = getUserGroups('usergroups.json', {params: {start: 0, limit: 10}}); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - expect(action.type).toBe(GETGROUPS); - count++; - if (count === 2) { - expect(action.status).toBe(STATUS_SUCCESS); - expect(action.groups).toExist(); - expect(action.groups[0]).toExist(); - expect(action.groups[0].groupName).toExist(); - done(); - } - }, () => ({ - userGroups: { - searchText: "*" - } - })); + it('updateUserGroups', () => { + const userGroups = [{ id: '01' }]; + const action = updateUserGroups(userGroups); + expect(action.type).toBe(UPDATE_USER_GROUPS); + expect(action.userGroups).toBe(userGroups); + }); + it('updateUserGroupsMetadata', () => { + const metadata = {}; + const action = updateUserGroupsMetadata(metadata); + expect(action.type).toBe(UPDATE_USER_GROUPS_METADATA); + expect(action.metadata).toBe(metadata); }); - it('getUserGroups error', (done) => { - const retFun = getUserGroups('MISSING_LINK', {params: {start: 0, limit: 10}}); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - expect(action.type).toBe(GETGROUPS); - count++; - if (count === 2) { - expect(action.status).toBe(STATUS_ERROR); - expect(action.error).toExist(); - done(); - } - }); + it('loadingUserGroups', () => { + const action = loadingUserGroups(true); + expect(action.type).toBe(LOADING_USER_GROUPS); + expect(action.loading).toBe(true); + }); + it('searchUserGroups', () => { + const params = { q: '' }; + let action = searchUserGroups({ params }); + expect(action.type).toBe(SEARCH_USER_GROUPS); + expect(action.params).toBe(params); + action = searchUserGroups({ refresh: true }); + expect(action.refresh).toBe(true); + action = searchUserGroups({ clear: true }); + expect(action.clear).toBe(true); }); + + it('resetSearchUserGroups', () => { + const action = resetSearchUserGroups(); + expect(action.type).toBe(RESET_SEARCH_USER_GROUPS); + }); + it('edit UserGroup', (done) => { const retFun = editGroup({id: 1}); expect(retFun).toExist(); diff --git a/web/client/actions/__tests__/users-test.js b/web/client/actions/__tests__/users-test.js index 60a3bba7486..96c76113f7d 100644 --- a/web/client/actions/__tests__/users-test.js +++ b/web/client/actions/__tests__/users-test.js @@ -11,8 +11,6 @@ import expect from 'expect'; import assign from 'object-assign'; import { - USERMANAGER_GETUSERS, - getUsers, editUser, USERMANAGER_EDIT_USER, changeUserMetadata, @@ -20,7 +18,17 @@ import { saveUser, USERMANAGER_UPDATE_USER, deleteUser, - USERMANAGER_DELETE_USER + USERMANAGER_DELETE_USER, + updateUsers, + UPDATE_USERS, + updateUsersMetadata, + UPDATE_USERS_METADATA, + loadingUsers, + LOADING_USERS, + searchUsers, + SEARCH_USERS, + resetSearchUsers, + RESET_SEARCH_USERS } from '../users'; import GeoStoreDAO from '../../api/GeoStoreDAO'; @@ -36,84 +44,41 @@ describe('Test correctness of the users actions', () => { afterEach(() => { GeoStoreDAO.addBaseUrl = oldAddBaseUri; }); - it('getUsers', (done) => { - const retFun = getUsers('users.json', {params: {start: 0, limit: 10}}); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - expect(action.type).toBe(USERMANAGER_GETUSERS); - count++; - // we check the second action because the first one is the "loading" one - if (count === 2) { - expect(action.users).toExist(); - expect(action.users[0]).toExist(); - expect(action.users[0].groups).toExist(); - done(); - } - - }); - }); - it('getUsers with old search', (done) => { - const retFun = getUsers(); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - expect(action.type).toBe(USERMANAGER_GETUSERS); - count++; - if (count === 2) { - expect(action.users).toExist(); - expect(action.users[0]).toExist(); - expect(action.users[0].groups).toExist(); - done(); - } - }, () => ({users: { searchText: "users.json"}})); + it('updateUsers', () => { + const users = [{ id: '01' }]; + const action = updateUsers(users); + expect(action.type).toBe(UPDATE_USERS); + expect(action.users).toBe(users); }); - it('getUsers with empty search', (done) => { - const retFun = getUsers(false, {params: {start: 5, limit: 10}}); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - expect(action.type).toBe(USERMANAGER_GETUSERS); - count++; - if (count === 2) { - expect(action.searchText).toBe("*"); - expect(action.start).toBe(5); - expect(action.limit).toBe(10); - done(); - } - }, () => ({})); + it('updateUsersMetadata', () => { + const metadata = {}; + const action = updateUsersMetadata(metadata); + expect(action.type).toBe(UPDATE_USERS_METADATA); + expect(action.metadata).toBe(metadata); }); - it('getUsers error', (done) => { - const retFun = getUsers('MISSING_LINK', {params: {start: 0, limit: 10}}); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - expect(action.type).toBe(USERMANAGER_GETUSERS); - count++; - if (count === 2) { - expect(action.error).toExist(); - done(); - } - }); + it('loadingUsers', () => { + const action = loadingUsers(true); + expect(action.type).toBe(LOADING_USERS); + expect(action.loading).toBe(true); }); - it('getUsers issue returning empty response', (done) => { - const retFun = getUsers('empty.json', {params: {start: 0, limit: 10}}); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - expect(action.type).toBe(USERMANAGER_GETUSERS); - count++; - if (count === 2) { - expect(action).toExist(); - done(); - } - - }); + it('searchUsers', () => { + const params = { q: '' }; + let action = searchUsers({ params }); + expect(action.type).toBe(SEARCH_USERS); + expect(action.params).toBe(params); + action = searchUsers({ refresh: true }); + expect(action.refresh).toBe(true); + action = searchUsers({ clear: true }); + expect(action.clear).toBe(true); + }); + it('resetSearchUsers', () => { + const action = resetSearchUsers(); + expect(action.type).toBe(RESET_SEARCH_USERS); }); it('editUser', (done) => { diff --git a/web/client/actions/usergroups.js b/web/client/actions/usergroups.js index 6bd756b1d2b..750c43fdbf8 100644 --- a/web/client/actions/usergroups.js +++ b/web/client/actions/usergroups.js @@ -1,4 +1,4 @@ -/** +/* * Copyright 2015, GeoSolutions Sas. * All rights reserved. * @@ -6,99 +6,68 @@ * LICENSE file in the root directory of this source tree. */ -export const GETGROUPS = 'GROUPMANAGER_GETGROUPS'; export const EDITGROUP = 'GROUPMANAGER_EDITGROUP'; export const EDITGROUPDATA = 'GROUPMANAGER_EDITGROUP_DATA'; export const UPDATEGROUP = 'GROUPMANAGER_UPDATE_GROUP'; export const DELETEGROUP = 'GROUPMANAGER_DELETEGROUP'; -export const SEARCHTEXTCHANGED = 'GROUPMANAGER_SEARCHTEXTCHANGED'; export const SEARCHUSERS = 'GROUPMANAGER_SEARCHUSERS'; + +export const UPDATE_USER_GROUPS = 'USER_GROUPS:UPDATE_USER_GROUPS'; +export const UPDATE_USER_GROUPS_METADATA = 'USER_GROUPS:UPDATE_USER_GROUPS_METADATA'; +export const SEARCH_USER_GROUPS = 'USER_GROUPS:SEARCH_USER_GROUPS'; +export const RESET_SEARCH_USER_GROUPS = 'USER_GROUPS:RESET_SEARCH_USER_GROUPS'; +export const LOADING_USER_GROUPS = 'USER_GROUPS:LOADING_USER_GROUPS'; + export const STATUS_LOADING = "loading"; export const STATUS_SUCCESS = "success"; export const STATUS_ERROR = "error"; -// export const STATUS_NEW = "new"; export const STATUS_SAVING = "saving"; export const STATUS_SAVED = "saved"; export const STATUS_CREATING = "creating"; export const STATUS_CREATED = "created"; export const STATUS_DELETED = "deleted"; -/* -export const USERGROUPMANAGER_UPDATE_GROUP = 'USERMANAGER_UPDATE_GROUP'; -export const USERGROUPMANAGER_DELETE_GROUP = 'USERMANAGER_DELETE_GROUP'; -export const USERGROUPMANAGER_SEARCH_TEXT_CHANGED = 'USERGROUPMANAGER_SEARCH_TEXT_CHANGED'; -*/ import API from '../api/GeoStoreDAO'; - import { get } from 'lodash'; -export function getUserGroupsLoading(text, start, limit) { + +export function updateUserGroups(userGroups) { return { - type: GETGROUPS, - status: STATUS_LOADING, - searchText: text, - start, - limit + type: UPDATE_USER_GROUPS, + userGroups }; } -export function getUserGroupSuccess(text, start, limit, groups, totalCount) { - return { - type: GETGROUPS, - status: STATUS_SUCCESS, - searchText: text, - start, - limit, - groups, - totalCount +export function updateUserGroupsMetadata(metadata) { + return { + type: UPDATE_USER_GROUPS_METADATA, + metadata }; } -export function getUserGroupError(text, start, limit, error) { + +export function loadingUserGroups(loading) { return { - type: GETGROUPS, - status: STATUS_ERROR, - searchText: text, - start, - limit, - error + type: LOADING_USER_GROUPS, + loading }; } -export function getUserGroups(searchText, options) { - let params = options && options.params; - let start; - let limit; - if (params) { - start = params.start; - limit = params.limit; - } - return (dispatch, getState) => { - let text = searchText; - let state = getState && getState(); - if (state) { - let oldText = get(state, "usergroups.searchText"); - text = searchText || oldText || "*"; - start = start !== null && start !== undefined ? start : get(state, "usergroups.start") || 0; - limit = limit || get(state, "usergroups.limit") || 12; - } - dispatch(getUserGroupsLoading(text, start, limit)); - return API.getGroups(text, {...options, params: {start, limit}}).then((response) => { - let groups; - // this because _.get returns an array with an undefined element isntead of null - if (!response || !response.ExtGroupList || !response.ExtGroupList.Group) { - groups = []; - } else { - groups = get(response, "ExtGroupList.Group"); - } +export function searchUserGroups({ params, clear, refresh }) { + return { + type: SEARCH_USER_GROUPS, + clear, + params, + refresh + }; +} - let totalCount = get(response, "ExtGroupList.GroupCount"); - groups = Array.isArray(groups) ? groups : [groups]; - dispatch(getUserGroupSuccess(text, start, limit, groups, totalCount)); - }).catch((error) => { - dispatch(getUserGroupError(text, start, limit, error)); - }); +export function resetSearchUserGroups() { + return { + type: RESET_SEARCH_USER_GROUPS }; } + + export function editGroupLoading(group) { return { type: EDITGROUP, @@ -215,7 +184,7 @@ export function saveGroup(group, options = {}) { dispatch(savingGroup(newGroup)); return API.updateGroup(newGroup, options).then((groupDetails) => { dispatch(savedGroup(groupDetails)); - dispatch(getUserGroups()); + dispatch(searchUserGroups({ refresh: true })); }).catch((error) => { dispatch(saveError(newGroup, error)); }); @@ -224,7 +193,7 @@ export function saveGroup(group, options = {}) { dispatch(creatingGroup(newGroup)); return API.createGroup(newGroup, options).then((id) => { dispatch(groupCreated(id, newGroup)); - dispatch(getUserGroups()); + dispatch(searchUserGroups({ refresh: true })); }).catch((error) => { dispatch(createError(newGroup, error)); }); @@ -270,7 +239,7 @@ export function deleteGroup(id, status = "confirm") { dispatch(deletingGroup(id)); API.deleteGroup(id).then(() => { dispatch(deleteGroupSuccess(id)); - dispatch(getUserGroups()); + dispatch(searchUserGroups({ refresh: true })); }).catch((error) => { dispatch(deleteGroupError(id, error)); }); @@ -278,13 +247,6 @@ export function deleteGroup(id, status = "confirm") { } return () => {}; } - -export function groupSearchTextChanged(text) { - return { - type: SEARCHTEXTCHANGED, - text - }; -} export function searchUsersSuccessLoading() { return { type: SEARCHUSERS, diff --git a/web/client/actions/users.js b/web/client/actions/users.js index 041e11cbf32..fb56b9979d2 100644 --- a/web/client/actions/users.js +++ b/web/client/actions/users.js @@ -1,4 +1,4 @@ -/** +/* * Copyright 2015, GeoSolutions Sas. * All rights reserved. * @@ -6,90 +6,54 @@ * LICENSE file in the root directory of this source tree. */ -export const USERMANAGER_GETUSERS = 'USERMANAGER_GETUSERS'; export const USERMANAGER_EDIT_USER = 'USERMANAGER_EDIT_USER'; export const USERMANAGER_EDIT_USER_DATA = 'USERMANAGER_EDIT_USER_DATA'; export const USERMANAGER_UPDATE_USER = 'USERMANAGER_UPDATE_USER'; export const USERMANAGER_DELETE_USER = 'USERMANAGER_DELETE_USER'; export const USERMANAGER_GETGROUPS = 'USERMANAGER_GETGROUPS'; -export const USERS_SEARCH_TEXT_CHANGED = 'USERS_SEARCH_TEXT_CHANGED'; + +export const UPDATE_USERS = 'USERS:UPDATE_USERS'; +export const UPDATE_USERS_METADATA = 'USERS:UPDATE_USERS_METADATA'; +export const SEARCH_USERS = 'USERS:SEARCH_USERS'; +export const RESET_SEARCH_USERS = 'USERS:RESET_SEARCH_USERS'; +export const LOADING_USERS = 'USERS:LOADING_USERS'; import API from '../api/GeoStoreDAO'; -import { get, assign } from 'lodash'; import SecurityUtils from '../utils/SecurityUtils'; -export function getUsersloading(text, start, limit) { +export function updateUsers(users) { return { - type: USERMANAGER_GETUSERS, - status: "loading", - searchText: text, - start, - limit + type: UPDATE_USERS, + users }; } -export function getUsersSuccess(text, start, limit, users, totalCount) { - return { - type: USERMANAGER_GETUSERS, - status: "success", - searchText: text, - start, - limit, - users, - totalCount +export function updateUsersMetadata(metadata) { + return { + type: UPDATE_USERS_METADATA, + metadata }; } -export function getUsersError(text, start, limit, error) { + +export function loadingUsers(loading) { return { - type: USERMANAGER_GETUSERS, - status: "error", - searchText: text, - start, - limit, - error + type: LOADING_USERS, + loading }; } -export function getUsers(searchText, options) { - let params = options && options.params; - let start; - let limit; - if (params) { - start = params.start; - limit = params.limit; - } - return (dispatch, getState) => { - let text = searchText; - let state = getState && getState(); - if (state) { - let oldText = get(state, "users.searchText"); - text = searchText || oldText || "*"; - start = start !== null && start !== undefined ? start : get(state, "users.start") || 0; - limit = limit || get(state, "users.limit") || 12; - } - dispatch(getUsersloading(text, start, limit)); - return API.getUsers(text, {...options, params: {start, limit}}).then((response) => { - let users; - // this because _.get returns an array with an undefined element isntead of null - if (!response || !response.ExtUserList || !response.ExtUserList.User) { - users = []; - } else { - users = get(response, "ExtUserList.User"); - } +export function searchUsers({ params, clear, refresh }) { + return { + type: SEARCH_USERS, + clear, + params, + refresh + }; +} - let totalCount = get(response, "ExtUserList.UserCount"); - users = Array.isArray(users) ? users : [users]; - users = users.map((user) => { - let groups = get(user, "groups.group"); - groups = Array.isArray(groups) ? groups : [groups]; - return assign({}, user, { - groups - }); - }); - dispatch(getUsersSuccess(text, start, limit, users, totalCount)); - }).catch((error) => { - dispatch(getUsersError(text, start, limit, error)); - }); +export function resetSearchUsers() { + return { + type: RESET_SEARCH_USERS }; } @@ -120,7 +84,6 @@ export function getGroups(user) { dispatch(getGroupsError(error)); }); }; - } export function editUserLoading(user) { @@ -243,7 +206,7 @@ export function createError(user, error) { export function saveUser(user, options = {}) { return (dispatch) => { // remove lastError before save - let newUser = assign({}, {...user}); + let newUser = { ...user }; if (newUser && newUser.lastError) { delete newUser.lastError; } @@ -251,7 +214,7 @@ export function saveUser(user, options = {}) { dispatch(savingUser(newUser)); return API.updateUser(newUser.id, {...newUser, groups: { group: newUser.groups}}, options).then((userDetails) => { dispatch(savedUser(userDetails)); - dispatch(getUsers()); + dispatch(searchUsers({ refresh: true })); }).catch((error) => { dispatch(saveError(newUser, error)); }); @@ -266,11 +229,10 @@ export function saveUser(user, options = {}) { } return API.createUser(userToPost, options).then((id) => { dispatch(userCreated(id, newUser)); - dispatch(getUsers()); + dispatch(searchUsers({ refresh: true })); }).catch((error) => { dispatch(createError(newUser, error)); }); - }; } export function changeUserMetadata(key, newValue) { @@ -319,7 +281,7 @@ export function deleteUser(id, status = "confirm") { dispatch(deletingUser(id)); API.deleteUser(id).then(() => { dispatch(deleteUserSuccess(id)); - dispatch(getUsers()); + dispatch(searchUsers({ refresh: true })); }).catch((error) => { dispatch(deleteUserError(id, error)); }); @@ -328,9 +290,3 @@ export function deleteUser(id, status = "confirm") { return () => {}; } -export function usersSearchTextChanged(text) { - return { - type: USERS_SEARCH_TEXT_CHANGED, - text - }; -} diff --git a/web/client/components/manager/users/GroupCard.jsx b/web/client/components/manager/users/GroupCard.jsx deleted file mode 100644 index 252eae8c399..00000000000 --- a/web/client/components/manager/users/GroupCard.jsx +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import './style/usercard.css'; - -import PropTypes from 'prop-types'; -import React from 'react'; -import { Glyphicon } from 'react-bootstrap'; - -import Button from '../../misc/Button'; -import Message from '../../../components/I18N/Message'; -import GridCard from '../../misc/GridCard'; - -class GroupCard extends React.Component { - static propTypes = { - // props - style: PropTypes.object, - group: PropTypes.object, - titleStyle: PropTypes.object, - headerStyle: PropTypes.object, - innerItemStyle: PropTypes.object, - avatarStyle: PropTypes.object, - nameStyle: PropTypes.object, - actions: PropTypes.array - }; - - static defaultProps = { - style: { - position: "relative", - backgroundSize: "cover", - backgroundPosition: "center", - backgroundRepeat: "repeat-x" - }, - titleStyle: { - display: "flex" - }, - headerStyle: { - flexGrow: 1, - overflow: "hidden", - whiteSpace: "nowrap", - textOverflow: "ellipsis", - width: 0 - }, - innerItemStyle: {}, - avatarStyle: { - margin: "10px" - }, - nameStyle: { - borderBottom: "1px solid #ddd", - fontSize: 18, - fontWeight: "bold", - overflow: "auto", - wordWrap: "break-word", - minHeight: "1.5em" - } - }; - - renderStatus = () => { - return (
-
- {this.props.group.enabled ? - : - } -
); - }; - - renderAvatar = () => { - return (
); - }; - - renderDescription = () => { - return (
-
-
{this.props.group.description ? this.props.group.description : }
-
); - }; - - renderName = () => { - return (
{this.props.group.groupName}
); - }; - - renderHeader = () => { - return
{this.props.group.groupName}
; - } - - render() { - return ( - -
- {this.renderAvatar()} -
- {this.renderName()} - {this.renderDescription()} -
-
- {this.renderStatus()} -
- ); - } -} - -export default GroupCard; diff --git a/web/client/plugins/manager/users/GroupDeleteConfirm.jsx b/web/client/components/manager/users/GroupDeleteConfirm.jsx similarity index 63% rename from web/client/plugins/manager/users/GroupDeleteConfirm.jsx rename to web/client/components/manager/users/GroupDeleteConfirm.jsx index 4005aa5f5a9..4fc86d067d4 100644 --- a/web/client/plugins/manager/users/GroupDeleteConfirm.jsx +++ b/web/client/components/manager/users/GroupDeleteConfirm.jsx @@ -1,21 +1,16 @@ -import PropTypes from 'prop-types'; - -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ -import React from 'react'; -import { connect } from 'react-redux'; -import { deleteGroup } from '../../../actions/usergroups'; +import React from 'react'; +import PropTypes from 'prop-types'; import { Alert } from 'react-bootstrap'; import Confirm from '../../../components/layout/ConfirmDialog'; -import GroupCard from '../../../components/manager/users/GroupCard'; import Message from '../../../components/I18N/Message'; -import { findIndex } from 'lodash'; class GroupDeleteConfirm extends React.Component { static propTypes = { @@ -47,6 +42,10 @@ class GroupDeleteConfirm extends React.Component { } }; + handleDeleteGroup = () =>{ + this.props.deleteGroup(this.props.deleteId, "delete"); + } + render() { if (!this.props.group) { return null; @@ -54,34 +53,16 @@ class GroupDeleteConfirm extends React.Component { return ( this.props.deleteGroup(this.props.deleteId, "cancelled")} - onConfirm={ () => { this.props.deleteGroup(this.props.deleteId, "delete"); } } + onConfirm={this.handleDeleteGroup} confirmId={this.renderConfirmButtonContent()} cancelId="cancel" preventHide titleId={"usergroups.confirmDeleteGroup"} + titleParams={{title: this.props.group.groupName}} disabled={this.props.deleteStatus === "deleting"}> -
{this.renderError()}
); } } -export default connect((state) => { - let groupsstate = state && state.usergroups; - if (!groupsstate) return {}; - let groups = groupsstate && groupsstate.groups; - let deleteId = groupsstate.deletingGroup && groupsstate.deletingGroup.id; - if (groups && deleteId) { - let index = findIndex(groups, (user) => user.id === deleteId); - let group = groups[index]; - return { - group, - deleteId, - deleteError: groupsstate.deletingGroup.error, - deleteStatus: groupsstate.deletingGroup.status - }; - } - return { - deleteId - }; -}, {deleteGroup} )(GroupDeleteConfirm); +export default GroupDeleteConfirm; diff --git a/web/client/components/manager/users/GroupDialog.jsx b/web/client/components/manager/users/GroupDialog.jsx index c7a467b6cba..5ca41eb4f42 100644 --- a/web/client/components/manager/users/GroupDialog.jsx +++ b/web/client/components/manager/users/GroupDialog.jsx @@ -152,13 +152,17 @@ class GroupDialog extends React.Component { return [this.isSaving() ? : null, message]; }; + handleSaveGroup = () =>{ + this.props.onSave(this.props.group); + } + renderButtons = () => { let CloseBtn = ; return [ CloseBtn, ]; diff --git a/web/client/components/manager/users/GroupGrid.jsx b/web/client/components/manager/users/GroupGrid.jsx deleted file mode 100644 index 7110869430d..00000000000 --- a/web/client/components/manager/users/GroupGrid.jsx +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; - -import { Grid, Row, Col } from 'react-bootstrap'; -import GroupCard from './GroupCard'; -import Spinner from 'react-spinkit'; -import PropTypes from 'prop-types'; -import Message from '../../I18N/Message'; -import { getMessageById } from '../../../utils/LocaleUtils'; -import SecurityUtils from '../../../utils/SecurityUtils'; - -class GroupsGrid extends React.Component { - static propTypes = { - loadGroups: PropTypes.func, - onEdit: PropTypes.func, - onDelete: PropTypes.func, - myUserId: PropTypes.number, - fluid: PropTypes.bool, - groups: PropTypes.array, - loading: PropTypes.bool, - bottom: PropTypes.node, - colProps: PropTypes.object - }; - - static contextTypes = { - messages: PropTypes.object - }; - - static defaultProps = { - loadGroups: () => {}, - onEdit: () => {}, - onDelete: () => {}, - fluid: true, - colProps: { - xs: 12, - sm: 6, - md: 4, - lg: 3, - style: { - "marginBottom": "20px" - } - } - }; - - componentDidMount() { - this.props.loadGroups(); - } - - renderLoading = () => { - if (this.props.loading) { - return (
); - } - return null; - }; - - renderGroups = (groups) => { - return groups.map((group) => { - let actions = [{ - onClick: () => {this.props.onEdit(group); }, - glyph: "wrench", - tooltip: getMessageById(this.context.messages, "usergroups.editGroup") - }, { - onClick: () => {this.props.onDelete(group && group.id); }, - glyph: "remove-circle", - tooltip: getMessageById(this.context.messages, "usergroups.deleteGroup") - }]; - if ( group && group.groupName === SecurityUtils.USER_GROUP_ALL) { - actions = []; - } - - return ; - }); - }; - - render() { - return ( - - {this.renderLoading()} - - {this.renderGroups(this.props.groups || [])} - - {this.props.bottom} - - ); - } -} - -export default GroupsGrid; diff --git a/web/client/components/manager/users/UserCard.jsx b/web/client/components/manager/users/UserCard.jsx deleted file mode 100644 index 02c1e390da2..00000000000 --- a/web/client/components/manager/users/UserCard.jsx +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import PropTypes from 'prop-types'; -import React from 'react'; -import { Glyphicon } from 'react-bootstrap'; - -import GridCard from '../../misc/GridCard'; - -import Message from '../../../components/I18N/Message'; -import Button from '../../misc/Button'; - - -import './style/usercard.css'; - -class UserCard extends React.Component { - static propTypes = { - // props - style: PropTypes.object, - user: PropTypes.object, - titleStyle: PropTypes.object, - headerStyle: PropTypes.object, - innerItemStyle: PropTypes.object, - avatarStyle: PropTypes.object, - nameStyle: PropTypes.object, - actions: PropTypes.array - }; - - static defaultProps = { - style: { - position: "relative", - backgroundSize: "cover", - backgroundPosition: "center", - backgroundRepeat: "repeat-x" - }, - titleStyle: { - display: "flex" - }, - headerStyle: { - flexGrow: 1, - overflow: "hidden", - whiteSpace: "nowrap", - textOverflow: "ellipsis", - width: 0 - }, - innerItemStyle: {}, - avatarStyle: { - margin: "10px" - }, - nameStyle: { - borderBottom: "1px solid #ddd", - fontSize: 18, - fontWeight: "bold", - overflow: "auto", - wordWrap: "break-word", - minHeight: "1.5em" - } - }; - - renderStatus = () => { - return (
-
- {this.props.user.enabled ? - : - } -
); - }; - - renderGroups = () => { - return (
-
- {this.props.user && this.props.user.groups ? this.props.user.groups - .filter(({ groupName } = {}) => groupName) - .map(({ id, groupName } = {}) => (
{groupName}
)) : null} -
-
); - }; - - renderRole = () => { - return (
- {this.props.user.role} -
); - }; - - renderAvatar = () => { - return (
); - }; - - renderName = () => { - return (
{this.props.user.name}
); - }; - - renderHeader = () => { - return
{this.props.user.name}
; - } - - render() { - return ( - -
- {this.renderAvatar()} -
- {this.renderName()} -
- {this.renderRole()} - {this.renderGroups()} -
-
-
- {this.renderStatus()} -
- ); - } -} -export default UserCard; diff --git a/web/client/plugins/manager/users/UserDeleteConfirm.jsx b/web/client/components/manager/users/UserDeleteConfirm.jsx similarity index 62% rename from web/client/plugins/manager/users/UserDeleteConfirm.jsx rename to web/client/components/manager/users/UserDeleteConfirm.jsx index e3dcaee4406..6d2db556e38 100644 --- a/web/client/plugins/manager/users/UserDeleteConfirm.jsx +++ b/web/client/components/manager/users/UserDeleteConfirm.jsx @@ -1,6 +1,4 @@ -import PropTypes from 'prop-types'; - -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * @@ -8,14 +6,10 @@ import PropTypes from 'prop-types'; * LICENSE file in the root directory of this source tree. */ import React from 'react'; - -import { connect } from 'react-redux'; -import { deleteUser } from '../../../actions/users'; +import PropTypes from 'prop-types'; import { Alert } from 'react-bootstrap'; import Confirm from '../../../components/layout/ConfirmDialog'; -import UserCard from '../../../components/manager/users/UserCard'; import Message from '../../../components/I18N/Message'; -import { findIndex } from 'lodash'; class UserDeleteConfirm extends React.Component { static propTypes = { @@ -47,6 +41,10 @@ class UserDeleteConfirm extends React.Component { } }; + handleDeleteUser = () =>{ + this.props.deleteUser(this.props.deleteId, "delete"); + } + render() { if (!this.props.user) { return null; @@ -54,34 +52,16 @@ class UserDeleteConfirm extends React.Component { return ( this.props.deleteUser(this.props.deleteId, "cancelled")} - onConfirm={ () => { this.props.deleteUser(this.props.deleteId, "delete"); } } + onConfirm={this.handleDeleteUser} cancelId="cancel" confirmId={this.renderConfirmButtonContent()} disabled={this.props.deleteStatus === "deleting"} preventHide - titleId={"users.confirmDeleteUser"}> -
+ titleId={"users.confirmDeleteUser"} + titleParams={{title: this.props.user.name}}>
{this.renderError()}
); } } -export default connect((state) => { - let usersState = state && state.users; - if (!usersState) return {}; - let users = usersState && usersState.users; - let deleteId = usersState.deletingUser && usersState.deletingUser.id; - if (users && deleteId) { - let index = findIndex(users, (user) => user.id === deleteId); - let user = users[index]; - return { - user, - deleteId, - deleteError: usersState.deletingUser.error, - deleteStatus: usersState.deletingUser.status - }; - } - return { - deleteId - }; -}, {deleteUser} )(UserDeleteConfirm); +export default UserDeleteConfirm; diff --git a/web/client/components/manager/users/UserDialog.jsx b/web/client/components/manager/users/UserDialog.jsx index b68a62682b8..5bf5b48da8a 100644 --- a/web/client/components/manager/users/UserDialog.jsx +++ b/web/client/components/manager/users/UserDialog.jsx @@ -236,13 +236,17 @@ class UserDialog extends React.Component { return [this.isSaving() ? : null, message]; }; + handleSaveUser = () =>{ + this.props.onSave(this.props.user); + } + renderButtons = () => { let CloseBtn = ; return [ CloseBtn, ]; diff --git a/web/client/components/manager/users/UserGrid.jsx b/web/client/components/manager/users/UserGrid.jsx deleted file mode 100644 index 7bf3930ee68..00000000000 --- a/web/client/components/manager/users/UserGrid.jsx +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; - -import { Grid, Row, Col } from 'react-bootstrap'; -import UserCard from './UserCard'; -import PropTypes from 'prop-types'; -import Spinner from 'react-spinkit'; -import Message from '../../I18N/Message'; -import { getMessageById } from '../../../utils/LocaleUtils'; - -class UsersGrid extends React.Component { - static propTypes = { - loadUsers: PropTypes.func, - onEdit: PropTypes.func, - onDelete: PropTypes.func, - myUserId: PropTypes.number, - fluid: PropTypes.bool, - users: PropTypes.array, - loading: PropTypes.bool, - bottom: PropTypes.node, - colProps: PropTypes.object - }; - - static contextTypes = { - messages: PropTypes.object - }; - - static defaultProps = { - loadUsers: () => {}, - onEdit: () => {}, - onDelete: () => {}, - fluid: true, - colProps: { - xs: 12, - sm: 6, - md: 4, - lg: 3, - style: { - "marginBottom": "20px" - } - } - }; - - componentDidMount() { - this.props.loadUsers(); - } - - renderLoading = () => { - if (this.props.loading) { - return (
); - } - return null; - }; - - renderUsers = (users) => { - return users.map((user) => { - let actions = [{ - onClick: () => {this.props.onEdit(user); }, - glyph: "wrench", - tooltip: getMessageById(this.context.messages, "users.editUser") - }]; - if ( user && user.role === "GUEST") { - actions = []; - } else if (user.id !== this.props.myUserId) { - actions.push({ - onClick: () => {this.props.onDelete(user && user.id); }, - glyph: "remove-circle", - tooltip: getMessageById(this.context.messages, "users.deleteUser") - }); - } - - return ; - }); - }; - - render() { - return ( - - {this.renderLoading()} - - {this.renderUsers(this.props.users || [])} - - {this.props.bottom} - - ); - } -} - -export default UsersGrid; diff --git a/web/client/components/manager/users/__tests__/GroupCard-test.jsx b/web/client/components/manager/users/__tests__/GroupCard-test.jsx deleted file mode 100644 index 6b92343f26c..00000000000 --- a/web/client/components/manager/users/__tests__/GroupCard-test.jsx +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import React from 'react'; - -import expect from 'expect'; -import ReactDOM from 'react-dom'; -import GroupCard from '../GroupCard'; -import ReactTestUtils from 'react-dom/test-utils'; -const group1 = { - id: 1, - groupName: "GROUP1", - description: "description", - enabled: true, - users: [{ - name: "USER1", - id: 100 - }] -}; - -describe("Test GroupCard Component", () => { - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - it('Test group rendering', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - let title = ReactTestUtils.scryRenderedDOMComponentsWithClass( - comp, - "gridcard-title" - ); - expect(title.length).toBe(1); - expect(ReactTestUtils.scryRenderedDOMComponentsWithClass( - comp, - "group-thumb-description" - ).length).toBe(1); - }); - it('Test groupname rendering inside the card', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - let items = document.querySelectorAll('#container .gridcard .user-data-container .user-card-info-container > div'); - let renderName = items[0]; - expect(renderName.innerHTML).toBe(group1.groupName); - }); -}); diff --git a/web/client/components/manager/users/__tests__/GroupGrid-test.jsx b/web/client/components/manager/users/__tests__/GroupGrid-test.jsx deleted file mode 100644 index 02e48220dbc..00000000000 --- a/web/client/components/manager/users/__tests__/GroupGrid-test.jsx +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import React from 'react'; - -import expect from 'expect'; -import ReactDOM from 'react-dom'; -import ReactTestUtils from 'react-dom/test-utils'; -import GroupGrid from '../GroupGrid'; -const group1 = { - id: 1, - groupName: "GROUP1", - description: "description", - enabled: true, - users: [{ - name: "USER1", - id: 100 - }] -}; - -describe("Test GroupGrid Component", () => { - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - it('Test group grid rendering', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - let domNode = ReactDOM.findDOMNode(comp); - expect(domNode.className).toBe("container-fluid"); - let rows = ReactTestUtils.scryRenderedDOMComponentsWithClass( - comp, - "row" - ); - expect(rows).toExist(); - expect(rows.length).toBe(1); - let card = ReactTestUtils.scryRenderedDOMComponentsWithClass(comp, "gridcard"); - expect(card).toExist(); - expect(card.length).toBe(1); - let buttons = ReactTestUtils.scryRenderedDOMComponentsWithClass( - comp, - "square-button-md" - ); - ReactTestUtils.Simulate.click(buttons[0]); - ReactTestUtils.Simulate.click(buttons[1]); - expect(buttons.length).toBe(2); - }); - it('Test everyone\'s group rendering in grid', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - let domNode = ReactDOM.findDOMNode(comp); - expect(domNode.className).toBe("container-fluid"); - let buttons = ReactTestUtils.scryRenderedDOMComponentsWithClass( - comp, - "square-button-md" - ); - expect(buttons.length).toBe(0); - }); - it('Test group grid events', () => { - const testHandlers = { - onEdit: () => {}, - onDelete: () => {} - }; - const spyEdit = expect.spyOn(testHandlers, 'onEdit'); - const spyDelete = expect.spyOn(testHandlers, 'onDelete'); - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - let domNode = ReactDOM.findDOMNode(comp); - expect(domNode.className).toBe("container-fluid"); - let buttons = ReactTestUtils.scryRenderedDOMComponentsWithClass( - comp, - "square-button-md" - ); - expect(buttons.length).toBe(2); - ReactTestUtils.Simulate.click(buttons[0]); - ReactTestUtils.Simulate.click(buttons[1]); - expect(spyEdit.calls.length).toEqual(1); - expect(spyDelete.calls.length).toEqual(1); - }); - -}); diff --git a/web/client/components/manager/users/__tests__/UserCard-test.jsx b/web/client/components/manager/users/__tests__/UserCard-test.jsx deleted file mode 100644 index 4d6566ad583..00000000000 --- a/web/client/components/manager/users/__tests__/UserCard-test.jsx +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import React from 'react'; - -import expect from 'expect'; -import ReactDOM from 'react-dom'; -import UserCard from '../UserCard'; -const enabledUser = { - id: 1, - name: "USER1", - role: "USER", - enabled: true, - groups: [{ - groupName: "GROUP1" - }] -}; -const disabledUser = { - id: 2, - name: "USER2", - role: "USER", - enabled: false, - groups: [{ - groupName: "GROUP1" - }] -}; -const adminUser = { - id: 3, - name: "ADMIN", - role: "ADMIN", - enabled: true -}; -describe("Test UserCard Component", () => { - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - it('Test enabled user rendering', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - expect(document.querySelector('#container .gridcard')).toExist(); - }); - it('Test disabled user rendering', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - expect(document.querySelector('#container .gridcard')).toExist(); - }); - it('Test admin user rendering', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - expect(document.querySelector('#container .gridcard')).toExist(); - }); - it('Test admin user with undefined group do not crash', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - expect(document.querySelector('#container .gridcard')).toExist(); - }); - it('Test username rendering inside the card', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - let items = document.querySelectorAll('#container .gridcard .user-data-container .user-card-info-container > div'); - let renderName = items[0]; - expect(renderName.innerHTML).toBe(enabledUser.name); - }); -}); diff --git a/web/client/components/manager/users/__tests__/UserGrid-test.jsx b/web/client/components/manager/users/__tests__/UserGrid-test.jsx deleted file mode 100644 index 9c271035d77..00000000000 --- a/web/client/components/manager/users/__tests__/UserGrid-test.jsx +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import React from 'react'; - -import expect from 'expect'; -import ReactDOM from 'react-dom'; -import UserGrid from '../UserGrid'; -const enabledUser = { - id: 1, - name: "USER1", - role: "USER", - enabled: true, - groups: [{ - groupName: "GROUP1" - }] -}; -const disabledUser = { - id: 2, - name: "USER2", - role: "USER", - enabled: false, - groups: [{ - groupName: "GROUP1" - }] -}; -const adminUser = { - id: 3, - name: "ADMIN", - role: "ADMIN", - enabled: true -}; -const guestUser = { - id: 4, - name: "guest", - role: "GUEST", - enabled: true -}; -describe("Test UserGrid Component", () => { - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - it('Test users rendering', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - }); - it('Test guestUser user', () => { - let comp = ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); - }); -}); diff --git a/web/client/components/manager/users/style/usercard.css b/web/client/components/manager/users/style/usercard.css deleted file mode 100644 index f82f1c1664f..00000000000 --- a/web/client/components/manager/users/style/usercard.css +++ /dev/null @@ -1,67 +0,0 @@ -.user-thumb, .group-thumb { - max-width: 100%; - overflow: hidden; - height: 200px; - font-size: 12px; - cursor: auto; -} -.user-thumb .gridcard-title, .group-thumb .gridcard-title { - min-height: 25px; -} - -.user-thumb .gridcard-tools,.group-thumb .gridcard-tools { - right: 0 -} -.user-data-container { - display: flex; - width: 100%; - height: 132px; -} -.user-card-info-container { - display: flex; - flex-direction: column; - flex-grow: 1; - padding: 5px 10px 0 10px; - width: 0; -} -.user-card-rolegroups-container { - display: flex; - min-height: 72px; - padding-top: 3px; -} -.user-thumb .role-containter{ - width: 50px; - margin-right: 20px -} -.user-thumb .avatar-containter{ - width: 50px; -} -.user-thumb .groups-container { - display: flex; - flex-direction: column; - flex-grow: 1; - white-space: nowrap; - overflow: hidden; -} -.user-thumb .groups-container .groups-list{ - width: 100%; - overflow-y: auto; - overflow-x: hidden; -} - -.user-thumb .groups-container .groups-list .group-item { - overflow-x: hidden; - text-overflow: ellipsis; -} -.group-thumb .group-thumb-description { - font-size: 12px; - text-align: justify; - min-height: 72px; - display: flex; - flex-direction: column; - padding-top: 3px; -} -.group-thumb .group-thumb-description .group-thumb-description-content { - overflow: auto; - word-wrap: break-word; -} \ No newline at end of file diff --git a/web/client/components/maps/modals/ConfirmModal.jsx b/web/client/components/maps/modals/ConfirmModal.jsx deleted file mode 100644 index 4ea936bf46a..00000000000 --- a/web/client/components/maps/modals/ConfirmModal.jsx +++ /dev/null @@ -1,95 +0,0 @@ - -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -import Button from '../../misc/Button'; -import Modal from '../../misc/Modal'; -import Spinner from 'react-spinkit'; - -/** - * A Modal window to show a confirmation dialog - */ -class ConfirmModal extends React.Component { - static propTypes = { - // props - className: PropTypes.string, - show: PropTypes.bool, - options: PropTypes.object, - onConfirm: PropTypes.func, - onClose: PropTypes.func, - closeGlyph: PropTypes.string, - style: PropTypes.object, - buttonSize: PropTypes.string, - includeCloseButton: PropTypes.bool, - body: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - titleText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - confirmText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - cancelText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - running: PropTypes.bool - }; - - static defaultProps = { - onConfirm: ()=> {}, - onClose: () => {}, - options: { - animation: false - }, - className: "", - closeGlyph: "", - style: {}, - includeCloseButton: true, - body: "", - titleText: "Confirm Delete", - confirmText: "Delete", - cancelText: "Cancel" - }; - - onConfirm = () => { - this.props.onConfirm(); - }; - - render() { - const footer = (
- - {this.props.includeCloseButton ? : } - ); - const body = this.props.body; - return ( - - - {this.props.titleText} - - - {body} - - - {footer} - - ); - } -} - -export default ConfirmModal; diff --git a/web/client/components/misc/ConfirmDialog.jsx b/web/client/components/misc/ConfirmDialog.jsx deleted file mode 100644 index 7056042d2b2..00000000000 --- a/web/client/components/misc/ConfirmDialog.jsx +++ /dev/null @@ -1,85 +0,0 @@ -/* -* Copyright 2016, GeoSolutions Sas. -* All rights reserved. -* -* This source code is licensed under the BSD-style license found in the -* LICENSE file in the root directory of this source tree. -*/ - -import assign from 'object-assign'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import { ButtonGroup, Glyphicon } from 'react-bootstrap'; - -import Button from '../misc/Button'; -import Message from '../I18N/Message'; -import Dialog from './Dialog'; - -/** - * A Modal window to show password reset form - */ -class ConfirmDialog extends React.Component { - static propTypes = { - // props - show: PropTypes.bool, - draggable: PropTypes.bool, - onClose: PropTypes.func, - onConfirm: PropTypes.func, - onSave: PropTypes.func, - modal: PropTypes.bool, - closeGlyph: PropTypes.string, - style: PropTypes.object, - buttonSize: PropTypes.string, - inputStyle: PropTypes.object, - title: PropTypes.node, - confirmButtonContent: PropTypes.node, - confirmButtonDisabled: PropTypes.bool, - closeText: PropTypes.node, - confirmButtonBSStyle: PropTypes.string, - focusConfirm: PropTypes.bool - }; - - static defaultProps = { - onClose: () => { }, - onChange: () => { }, - modal: true, - title: , - closeGlyph: "", - confirmButtonBSStyle: "danger", - confirmButtonDisabled: false, - confirmButtonContent: || "Confirm", - closeText: , - includeCloseButton: true, - focusConfirm: false - }; - - componentDidMount() { - this.props.focusConfirm && ReactDOM.findDOMNode(this.confirm).focus(); - } - - render() { - return ( - - {this.props.title} - - -
- {this.props.children} -
-
- - - - -
-
); - } - - setConfirmRef = (c) => { this.confirm = c; return this.confirm; }; -} - -export default ConfirmDialog; diff --git a/web/client/components/misc/GridCard.jsx b/web/client/components/misc/GridCard.jsx deleted file mode 100644 index 294efbf08b4..00000000000 --- a/web/client/components/misc/GridCard.jsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; - -import PropTypes from 'prop-types'; -import isNil from 'lodash/isNil'; -import Toolbar from './toolbar/Toolbar'; -import './style/gridcard.css'; - -class GridCard extends React.Component { - static propTypes = { - style: PropTypes.object, - titleStyle: PropTypes.object, - className: PropTypes.string, - header: PropTypes.node, - actions: PropTypes.array, - onClick: PropTypes.func, - toolbarProps: PropTypes.object - }; - - static defaultProps = { - actions: [], - header: "", - toolbarProps: { - btnDefaultProps: { - className: 'square-button-md', - bsStyle: 'primary' - } - } - }; - - renderActions = () => { - return ( -
- -
- ); - }; - - render() { - return (
e.key === 'Enter' ? this.props.onClick(e) : null} - > - {!isNil(this.props.header) ?
{this.props.header}
: null} - {this.props.children} - {this.renderActions()} -
) - ; - } -} - -export default GridCard; diff --git a/web/client/components/misc/PaginationToolbar.jsx b/web/client/components/misc/PaginationToolbar.jsx deleted file mode 100644 index 5a6d710914d..00000000000 --- a/web/client/components/misc/PaginationToolbar.jsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; - -import PropTypes from 'prop-types'; -import { Row, Col, Pagination } from 'react-bootstrap'; -import Message from '../I18N/Message'; -import Loader from '../misc/Loader'; - -class PaginationToolbar extends React.Component { - static propTypes = { - // from zero - page: PropTypes.number, - total: PropTypes.number, - pageSize: PropTypes.number, - items: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), - msgId: PropTypes.string, - loading: PropTypes.bool, - onSelect: PropTypes.func, - bsSize: PropTypes.string, - maxButtons: PropTypes.number - }; - - static defaultProps = { - page: 0, - pageSize: 20, - msgId: "pageInfo", - items: [], - onSelect: () => {}, - maxButtons: 5 - }; - - onSelect = (eventKey) => { - this.props.onSelect(eventKey && eventKey - 1); - }; - - renderLoading = () => { - return this.props.loading ? :
; - }; - - render() { - let {pageSize, page, total, items} = this.props; - let msg = ; - if (this.props.loading && this.props.items.length === 0) { - return null; // no pagination - } - return ( - - - {this.renderLoading()} - - - - - -
{this.props.loading ? : msg}
- -
- ); - } -} - -export default PaginationToolbar; diff --git a/web/client/components/misc/__tests__/ConfirmDialog-test.jsx b/web/client/components/misc/__tests__/ConfirmDialog-test.jsx deleted file mode 100644 index a51b37e0beb..00000000000 --- a/web/client/components/misc/__tests__/ConfirmDialog-test.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import expect from 'expect'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import ConfirmDialog from '../ConfirmDialog'; - -describe("ConfirmDialog component", () => { - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - it('creates componet with defaults', () => { - const cmp = ReactDOM.render(, document.getElementById("container")); - expect(cmp).toExist(); - }); - - it('creates component with content', () => { - const cmp = ReactDOM.render(
some content
, document.getElementById("container")); - expect(cmp).toExist(); - let el = ReactDOM.findDOMNode(cmp); - el.click(); - let background = document.getElementsByClassName("modal").item(0); - let dialog = document.getElementsByClassName("modal-dialog").item(0); - expect(background).toExist(); - dialog.click(); - // TODO spy onClose not called - background.click(); - // TODO spy onClose called - }); - -}); diff --git a/web/client/components/misc/__tests__/GridCard-test.jsx b/web/client/components/misc/__tests__/GridCard-test.jsx deleted file mode 100644 index 00e4183cc7b..00000000000 --- a/web/client/components/misc/__tests__/GridCard-test.jsx +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import React from 'react'; - -import ReactDOM from 'react-dom'; -import GridCard from '../GridCard.jsx'; -import expect from 'expect'; -import TestUtils from 'react-dom/test-utils'; - -describe('This test for GridCard', () => { - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - // test DEFAULTS - it('creates the component with defaults', () => { - const item = ReactDOM.render(, document.getElementById("container")); - expect(item).toExist(); - - const mapItemDom = ReactDOM.findDOMNode(item); - expect(mapItemDom).toExist(); - - expect(mapItemDom.className).toBe('gridcard'); - const headings = mapItemDom.getElementsByClassName('gridcard-title'); - expect(headings.length).toBe(1); - }); - // test DEFAULTS - it('creates the component with data', () => { - const testName = "test"; - const testDescription = "testDescription"; - const item = ReactDOM.render({testDescription}, document.getElementById("container")); - expect(item).toExist(); - - const itemDom = ReactDOM.findDOMNode(item); - expect(itemDom).toExist(); - - expect(itemDom.className).toBe('gridcard'); - const headings = itemDom.getElementsByClassName('gridcard-title'); - expect(headings.length).toBe(1); - expect(headings[0].innerHTML).toBe(testName); - }); - - it('test actions', () => { - const testName = "test"; - const testDescription = "testDescription"; - var component = TestUtils.renderIntoDocument( {}}]}>{testDescription}>); - var button = TestUtils.findRenderedDOMComponentWithTag( - component, 'button' - ); - expect(button).toExist(); - }); - - it('enter triggers onClick event', (done) => { - const container = document.getElementById('container'); - const testName = "test"; - const testDescription = "testDescription"; - ReactDOM.render( - {done();}}>{testDescription}, container); - TestUtils.Simulate.keyDown(container.firstElementChild, {key: 'Enter'}); - }); -}); diff --git a/web/client/components/misc/__tests__/PaginationToolbar-test.jsx b/web/client/components/misc/__tests__/PaginationToolbar-test.jsx deleted file mode 100644 index dd27839ce4c..00000000000 --- a/web/client/components/misc/__tests__/PaginationToolbar-test.jsx +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import React from 'react'; - -import ReactDOM from 'react-dom'; -import expect from 'expect'; -import PaginationToolbar from '../PaginationToolbar'; - -describe('PaginationToolbar', () => { - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - // test DEFAULTS - it('creates the component with defaults', () => { - const item = ReactDOM.render(, document.getElementById("container")); - expect(item).toExist(); - }); - - - // test items - it('creates the component with items', () => { - const item = ReactDOM.render(, document.getElementById("container")); - expect(item).toExist(); - const pagination = ReactDOM.findDOMNode(item).getElementsByClassName("pagination"); - expect(pagination).toExist(); - expect(pagination.length).toBe(1); - const buttons = pagination[0].getElementsByTagName('a'); - expect(buttons).toExist(); - expect(buttons.length).toBe(5); // current page + prev, next... - }); - // test loading - it('creates the component loading', () => { - const item = ReactDOM.render(, document.getElementById("container")); - expect(item).toExist(); - const spinner = ReactDOM.findDOMNode(item).getElementsByClassName('mapstore-small-size-loader'); - expect(spinner).toExist(); - expect(spinner.length).toBe(1); - }); -}); diff --git a/web/client/configs/localConfig.json b/web/client/configs/localConfig.json index 87d00643bec..6ef99219b9d 100644 --- a/web/client/configs/localConfig.json +++ b/web/client/configs/localConfig.json @@ -1227,7 +1227,6 @@ } ], "manager": [ - "Header", "Redirect", "Manager", "Home", diff --git a/web/client/plugins/ResourcesCatalog/DeleteResource.jsx b/web/client/plugins/ResourcesCatalog/DeleteResource.jsx index 16156384a3d..7767a8c0140 100644 --- a/web/client/plugins/ResourcesCatalog/DeleteResource.jsx +++ b/web/client/plugins/ResourcesCatalog/DeleteResource.jsx @@ -35,7 +35,7 @@ function DeleteResource({ const Component = component; const [showModal, setShowModal] = useState(false); const [deleting, setDeleting] = useState(false); - const [errorId, setErrorId] = useState(false); + const [errorId, setErrorId] = useState(""); const isMounted = useIsMounted(); function handleCancel() { diff --git a/web/client/plugins/ResourcesCatalog/GroupManager.jsx b/web/client/plugins/ResourcesCatalog/GroupManager.jsx new file mode 100644 index 00000000000..c098926cdf9 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/GroupManager.jsx @@ -0,0 +1,408 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useRef } from 'react'; +import { createPlugin } from '../../utils/PluginsUtils'; +import { connect } from 'react-redux'; +import { createStructuredSelector } from 'reselect'; +import { getRouterLocation } from './selectors/resources'; +import usePluginItems from '../../hooks/usePluginItems'; +import ConnectedResourcesGrid from './containers/ResourcesGrid'; +import { hashLocationToHref } from './utils/ResourcesFiltersUtils'; +import { getResourceTypesInfo, getResourceId } from './utils/ResourcesUtils'; +import GeoStoreDAO from '../../api/GeoStoreDAO'; +import Message from '../../components/I18N/Message'; +import { Button } from 'react-bootstrap'; +import { castArray, findIndex } from 'lodash'; +import InputControl from './components/InputControl'; +import usergroupsReducer from '../../reducers/usergroups'; +import GroupDeleteConfirm from '../../components/manager/users/GroupDeleteConfirm'; +import GroupDialog from '../../components/manager/users/GroupDialog'; + +import { + changeGroupMetadata, + saveGroup, + searchUsers, + deleteGroup, + editGroup, + searchUserGroups, + loadingUserGroups, + updateUserGroups, + updateUserGroupsMetadata, + resetSearchUserGroups +} from '../../actions/usergroups'; + +import { + getTotalUserGroups, + getUserGroupsLoading, + getUserGroups, + getUserGroupsError, + getIsFirstRequest, + getCurrentPage, + getSearch, + getCurrentParams +} from '../../selectors/usergroups'; +import SecurityUtils from '../../utils/SecurityUtils'; + +const ConnectedGroupDialog = connect((state) => { + const usergroups = state && state.usergroups; + return { + modal: true, + availableUsers: usergroups && usergroups.availableUsers, + availableUsersCount: usergroups && usergroups.availableUsersCount, + availableUsersLoading: usergroups && usergroups.availableUsersLoading, + show: usergroups && !!usergroups.currentGroup, + group: usergroups && usergroups.currentGroup + }; +}, { + searchUsers: searchUsers.bind(null), + onChange: changeGroupMetadata.bind(null), + onClose: editGroup.bind(null, null), + onSave: saveGroup.bind(null) +})(GroupDialog); + +const ConnectedGroupDeleteConfirm = connect((state) => { + const groupsstate = state && state.usergroups; + if (!groupsstate) return {}; + const groups = getUserGroups(state); + const deleteId = groupsstate.deletingGroup && groupsstate.deletingGroup.id; + if (groups && deleteId) { + const index = findIndex(groups, (user) => user.id === deleteId); + const group = groups[index]; + return { + group, + deleteId, + deleteError: groupsstate.deletingGroup.error, + deleteStatus: groupsstate.deletingGroup.status + }; + } + return { + deleteId + }; +}, { + deleteGroup +})(GroupDeleteConfirm); + +function requestGroups({ params }) { + const { + page = 1, + pageSize = 12, + q + } = params || {}; + return GeoStoreDAO.getGroups(q ? `*${q}*` : '*', { + params: { + start: parseFloat(page - 1) * pageSize, + limit: pageSize + } + }) + .then((response) => { + const groups = castArray(response?.ExtGroupList?.Group || []); + const totalCount = response?.ExtGroupList?.GroupCount; + return { + total: totalCount, + isNextPageAvailable: page < (totalCount / pageSize), + resources: groups.map((group) => group) + }; + }); +} + + +function NewGroup({onNewGroup}) { + return <> + + ; +} + +function EditGroup({ component, onEdit, resource: group }) { + const Component = component; + function handleClick() { + onEdit(group); + } + if (group?.groupName === SecurityUtils.USER_GROUP_ALL) { + return null; + } + return (); +} + +function DeleteGroup({component, onDelete, resource: group}) { + const Component = component; + function handleClick() { + onDelete(group && group.id); + } + if (group?.groupName === SecurityUtils.USER_GROUP_ALL) { + return null; + } + return (); +} + +function GroupFilter({onSearch, query }) { + const handleFieldChange = (params) => { + onSearch({params: { q: params }}); + }; + return (); +} + +const userGroupsToolsConnect = connect(null, { + onEdit: editGroup, + onDelete: deleteGroup, + onSearch: searchUserGroups, + onNewGroup: editGroup.bind(null, {}) +}); + +const ConnectedNewGroup = userGroupsToolsConnect(NewGroup); +const ConnectedEditGroup = userGroupsToolsConnect(EditGroup); +const ConnectedDeleteGroup = userGroupsToolsConnect(DeleteGroup); +const ConnectedGroupFilter = userGroupsToolsConnect(GroupFilter); + +function GroupManager({ + active = true, + items, + order = null, + metadata = { + grid: [ + { + path: 'groupName', + target: 'header', + showFullContent: true, + icon: { glyph: '1-group', type: 'glyphicon' } + }, + { + path: 'description', + showFullContent: true, + target: 'footer' + } + ] + }, + showMembersTab, + showAttributesTab, + attributeFields, + ...props +}, context) { + + const { loadedPlugins } = context; + + const configuredItems = usePluginItems({ items, loadedPlugins }, []); + + const updatedLocation = useRef(); + updatedLocation.current = props.location; + function handleFormatHref(options) { + return hashLocationToHref({ + location: updatedLocation.current, + excludeQueryKeys: ['page'], + ...options + }); + } + + if (!active) { + return null; + } + + return ( + <> + { + return { + items: [ + ...(resource.enabled === true ? [{ + type: 'icon', + tooltipId: 'users.active', + glyph: 'ok-sign', + iconType: 'glyphicon', + variant: 'success' + }] : [{ + type: 'icon', + tooltipId: 'users.inactive', + glyph: 'minus-sign', + iconType: 'glyphicon', + variant: 'danger' + }]) + ] + }; + }} + formatHref={handleFormatHref} + getResourceTypesInfo={getResourceTypesInfo} + getResourceId={getResourceId} + cardLayoutStyle="grid" + hideThumbnail + resourcesFoundMsgId="usergroups.userGroupsFound" + /> + + + + + ); +} + +const GroupManagerPlugin = connect( + createStructuredSelector({ + totalResources: getTotalUserGroups, + loading: getUserGroupsLoading, + location: getRouterLocation, + resources: getUserGroups, + error: getUserGroupsError, + isFirstRequest: getIsFirstRequest, + page: getCurrentPage, + search: getSearch, + storedParams: getCurrentParams + }), + { + setLoading: loadingUserGroups, + setResources: updateUserGroups, + setResourcesMetadata: updateUserGroupsMetadata, + onResetSearch: resetSearchUserGroups + } +)(GroupManager); + +GroupManagerPlugin.defaultProps = { + id: 'groups' +}; + +/** + * Allows an administrator to browse user groups. + * Renders in {@link #plugins.Manager|Manager} plugin. + * @name GroupManager + * @deprecated + * @property {object[]} cfg.attributeFields attributes that should be shown in attributes tab of group manager. By default this array contains one `notes` attribute with `controlType`: `text`. Every object in this array can contain: + * - `name`: the name of the attribute + * - `title`: the string to show as label for the attribute. If not present, `name` property will be used. + * - `controlType`: The input control to use. can be : `string` (input), `text` (text area), `date` (date picker) and `select`. By default it is `string` + * - `controlAttributes`: attributes specific of the `controlType`. For instance the `options` for the `select` control. See the examples for more details. + * @property {boolean} cfg.showMembersTab shows/hides group members tab, default true + * @property {boolean} cfg.showAttributesTab shows/hides group attributes tab, default false + * @memberof plugins + * @class + * @example + * { "name": "GroupManager", + * "cfg": { + * "showMembersTab": false, + * "showAttributesTab": true, + * "attributeFields": [ + * { + * "title": "Simple text", + * "name": "normalString", + * "controlType": "string" + * }, + * { + * "title": "Input creates different attributes entries, separated by ;", + * "name": "multistring", + * "controlType": "string", + * "controlAttributes": { + * "multiAttribute": true, + * "separator": ";" + * } + * }, + * { + * "title": "Notes", + * "name": "notes", + * "controlType": "text" + * }, + * { + * "title": "Notes, multiple entries, separated by new-line", + * "name": "multiple-notes", + * "controlType": "text", + * "controlAttributes": { + * "multiAttribute": true, + * "separator": "\n" + * } + * }, + * { + * "title": "Single select with options in", + * "name": "select", + * "controlType": "select", + * "options": [ + * { + * "label": "Option 1", + * "value": "opt1" + * }, + * { + * "label": "Option 2", + * "value": "opt2" + * } + * ] + * }, + * { + * "title": "Multiple selections in multiple attributes (using remote service)", + * "name": "organizations", + * "controlType": "select", + * "source": { + * "url": "assets/json/organizations.json", + * "path": "organizations", + * "valueAttribute": "value", + * "labelAttribute": "label" + * }, + * "controlAttributes": { + * "multiAttribute": true, + * "isMulti": true + * } + * }, + * { + * "title": "Multiple selections in single attribute, concatenated (using remote service)", + * "name": "organizations_dependent", + * "controlType": "select", + * "source": { + * "url": "assets/json/organizations.json", + * "path": "organizations", + * "valueAttribute": "value", + * "labelAttribute": "label" + * }, + * "controlAttributes": { + * "separator": ";", + * "isMulti": true + * } + * } + * ] + * } + * } + */ +export default createPlugin('GroupManager', { + component: GroupManagerPlugin, + containers: { + Manager: { + name: 'groupmanager', + position: 2, + priority: 1, + glyph: "1-user-mod" + } + }, + epics: {}, + reducers: { + usergroups: usergroupsReducer + } +}); diff --git a/web/client/plugins/ResourcesCatalog/ResourcesGrid.jsx b/web/client/plugins/ResourcesCatalog/ResourcesGrid.jsx index 42769a4063c..2acddff4268 100644 --- a/web/client/plugins/ResourcesCatalog/ResourcesGrid.jsx +++ b/web/client/plugins/ResourcesCatalog/ResourcesGrid.jsx @@ -10,7 +10,6 @@ import React, { useRef } from 'react'; import { createPlugin } from '../../utils/PluginsUtils'; import { connect } from 'react-redux'; import { createStructuredSelector } from 'reselect'; -import { getResources, getRouterLocation, getSelectedResource } from './selectors/resources'; import resourcesReducer from './reducers/resources'; import usePluginItems from '../../hooks/usePluginItems'; @@ -18,6 +17,24 @@ import ConnectedResourcesGrid from './containers/ResourcesGrid'; import { hashLocationToHref } from './utils/ResourcesFiltersUtils'; import { requestResources } from './api/resources'; import { getResourceTypesInfo, getResourceStatus, getResourceId } from './utils/ResourcesUtils'; +import { + loadingResources, + resetSearchResources, + updateResources, + updateResourcesMetadata +} from './actions/resources'; +import { + getResourcesLoading, + getResourcesError, + getIsFirstRequest, + getTotalResources, + getCurrentPage, + getSearch, + getCurrentParams, + getResources, + getRouterLocation, + getSelectedResource +} from './selectors/resources'; /** * This plugins allows to render a resources grid, it could be configured multiple times in the localConfig with different id @@ -401,6 +418,7 @@ function ResourcesGrid({ const configuredItems = usePluginItems({ items, loadedPlugins }, []); + const updatedLocation = useRef(); updatedLocation.current = props.location; function handleFormatHref(options) { @@ -428,10 +446,23 @@ function ResourcesGrid({ const ResourcesGridPlugin = connect( createStructuredSelector({ + totalResources: getTotalResources, + loading: getResourcesLoading, location: getRouterLocation, resources: getResources, - selectedResource: getSelectedResource - }) + selectedResource: getSelectedResource, + error: getResourcesError, + isFirstRequest: getIsFirstRequest, + page: getCurrentPage, + search: getSearch, + storedParams: getCurrentParams + }), + { + setLoading: loadingResources, + setResources: updateResources, + setResourcesMetadata: updateResourcesMetadata, + onResetSearch: resetSearchResources + } )(ResourcesGrid); export default createPlugin('ResourcesGrid', { diff --git a/web/client/plugins/ResourcesCatalog/UserManager.jsx b/web/client/plugins/ResourcesCatalog/UserManager.jsx new file mode 100644 index 00000000000..0d22d20fe04 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/UserManager.jsx @@ -0,0 +1,326 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useRef } from 'react'; +import { createPlugin } from '../../utils/PluginsUtils'; +import { connect } from 'react-redux'; +import { createStructuredSelector } from 'reselect'; +import { getRouterLocation } from './selectors/resources'; +import { + editUser, + deleteUser, + changeUserMetadata, + saveUser, + loadingUsers, + updateUsers, + updateUsersMetadata, + resetSearchUsers, + searchUsers +} from '../../actions/users'; + +import { castArray, findIndex } from 'lodash'; +import usePluginItems from '../../hooks/usePluginItems'; +import ConnectedResourcesGrid from './containers/ResourcesGrid'; +import { hashLocationToHref } from './utils/ResourcesFiltersUtils'; +import { getResourceTypesInfo, getResourceId } from './utils/ResourcesUtils'; +import GeoStoreDAO from '../../api/GeoStoreDAO'; +import { Button } from 'react-bootstrap'; +import InputControl from './components/InputControl'; +import Message from '../../components/I18N/Message'; + +import UserDeleteConfirm from '../../components/manager/users/UserDeleteConfirm'; +import UserDialog from '../../components/manager/users/UserDialog'; +import usersReducer from '../../reducers/users'; +import { + getTotalUsers, + getUsersLoading, + getUsers, + getUsersError, + getIsFirstRequest, + getCurrentPage, + getSearch, + getCurrentParams +} from '../../selectors/users'; +import { userSelector } from '../../selectors/security'; + +const ConnectedUserDialog = connect((state) => { + const users = state && state.users; + return { + modal: true, + show: users && !!users.currentUser, + user: users && users.currentUser, + groups: users && users.groups, + hidePasswordFields: users && users.currentUser && state.security && state.security.user && state.security.user.id === users.currentUser.id + }; +}, { + onChange: changeUserMetadata.bind(null), + onClose: editUser.bind(null, null), + onSave: saveUser.bind(null) +})(UserDialog); + +const ConnectedUserDeleteConfirm = connect((state) => { + const usersState = state && state.users; + if (!usersState) return {}; + const users = getUsers(state); + const deleteId = usersState.deletingUser && usersState.deletingUser.id; + if (users && deleteId) { + const index = findIndex(users, (user) => user.id === deleteId); + const user = users[index]; + return { + user, + deleteId, + deleteError: usersState.deletingUser.error, + deleteStatus: usersState.deletingUser.status + }; + } + return { + deleteId + }; +}, { + deleteUser +})(UserDeleteConfirm); + +function requestUsers({ params }) { + const { + page = 1, + pageSize = 12, + q + } = params || {}; + return GeoStoreDAO.getUsers(q ? `*${q}*` : '*', { + params: { + start: parseFloat(page - 1) * pageSize, + limit: pageSize + } + }).then((response) => { + const totalCount = response?.ExtUserList?.UserCount; + const users = castArray(response?.ExtUserList?.User || []); + const transformedUsers = users.map(user => ({ + ...user, + groups: user?.groups?.group + ? Array.isArray(user.groups.group) + ? user.groups.group + : [user.groups.group].filter(Boolean) + : [] + })); + return { + total: totalCount, + isNextPageAvailable: page < (totalCount / pageSize), + resources: transformedUsers + }; + }); +} + +function NewUser({onNewUser}) { + return <> + + ; +} + +function EditUser({ component, onEdit, resource: user }) { + const Component = component; + function handleClick() { + onEdit(user); + } + if (user.role === 'GUEST') { + return null; + } + return (); +} + +function DeleteUser({component, onDelete, resource: user, user: myUser }) { + const Component = component; + function handleClick() { + onDelete(user && user.id); + } + if (user.role === 'GUEST' || myUser.id === user.id) { + return null; + } + return (); +} + +function UserFilter({onSearch, query }) { + const handleFieldChange = (params) => { + onSearch({ params: { q: params } }); + }; + return (); +} + +const usersToolsConnect = connect(createStructuredSelector({ + user: userSelector +}), { + onEdit: editUser, + onDelete: deleteUser, + onSearch: searchUsers, + onNewUser: editUser.bind(null, { role: "USER", "enabled": true }) +}); + +const ConnectedNewUser = usersToolsConnect(NewUser); +const ConnectedEditUser = usersToolsConnect(EditUser); +const ConnectedDeleteUser = usersToolsConnect(DeleteUser); +const ConnectedFilter = usersToolsConnect(UserFilter); + +function UserManager({ + active = true, + items, + order = null, + metadata = { + grid: [ + { + path: 'name', + target: 'header', + icon: { glyph: 'user', type: 'glyphicon' } + }, + { + path: 'groups', + itemValue: 'groupName', + type: 'tag', + showFullContent: true, + target: 'footer' + } + ] + }, + attributeFields, + ...props +}, context) { + const { loadedPlugins } = context; + + const configuredItems = usePluginItems({ items, loadedPlugins }, []); + + const updatedLocation = useRef(); + updatedLocation.current = props.location; + function handleFormatHref(options) { + return hashLocationToHref({ + location: updatedLocation.current, + excludeQueryKeys: ['page'], + ...options + }); + } + + if (!active) { + return null; + } + + return ( +
+ { + return { + items: [ + ...(resource.role === 'ADMIN' ? [{ + type: 'icon', + tooltipId: 'users.admin', + glyph: 'shield' + }] : []), + ...(resource.enabled === true ? [{ + type: 'icon', + tooltipId: 'users.active', + glyph: 'ok-sign', + iconType: 'glyphicon', + variant: 'success' + }] : [{ + type: 'icon', + tooltipId: 'users.inactive', + glyph: 'minus-sign', + iconType: 'glyphicon', + variant: 'danger' + }]) + ] + }; + }} + formatHref={handleFormatHref} + getResourceTypesInfo={getResourceTypesInfo} + getResourceId={getResourceId} + cardLayoutStyle="grid" + hideThumbnail + resourcesFoundMsgId="users.usersFound" + /> + + +
+ + ); +} + +const UserManagerPlugin = connect( + createStructuredSelector({ + totalResources: getTotalUsers, + loading: getUsersLoading, + location: getRouterLocation, + resources: getUsers, + error: getUsersError, + isFirstRequest: getIsFirstRequest, + page: getCurrentPage, + search: getSearch, + storedParams: getCurrentParams + }), + { + setLoading: loadingUsers, + setResources: updateUsers, + setResourcesMetadata: updateUsersMetadata, + onResetSearch: resetSearchUsers + } +)(UserManager); + + +UserManagerPlugin.defaultProps = { + id: 'users' +}; + +/** + * Allows an administrator to browse users. + * Renders in {@link #plugins.Manager|Manager} plugin. + * @deprecated + * @name UserManager + * @memberof plugins + * @property {object[]} cfg.attributeFields attributes that should be shown in attributes tab of user dialog. + * @class + */ +export default createPlugin('UserManager', { + component: UserManagerPlugin, + containers: { + Manager: { + name: 'usermanager', + position: 1, + priority: 1, + glyph: "1-user-mod" + } + }, + epics: {}, + reducers: { + users: usersReducer + } +}); diff --git a/web/client/plugins/ResourcesCatalog/components/Menu.jsx b/web/client/plugins/ResourcesCatalog/components/Menu.jsx index e8025970ac6..4c7b967b17e 100644 --- a/web/client/plugins/ResourcesCatalog/components/Menu.jsx +++ b/web/client/plugins/ResourcesCatalog/components/Menu.jsx @@ -45,15 +45,18 @@ const Menu = forwardRef(({ {items .map((item, idx) => { return ( - + <> + + + ); })} diff --git a/web/client/plugins/ResourcesCatalog/components/MenuItem.jsx b/web/client/plugins/ResourcesCatalog/components/MenuItem.jsx index eefadeed6e3..dd0bd9fa94c 100644 --- a/web/client/plugins/ResourcesCatalog/components/MenuItem.jsx +++ b/web/client/plugins/ResourcesCatalog/components/MenuItem.jsx @@ -94,8 +94,9 @@ const MenuItem = ({ size, alignRight, variant, - target: defaultTarget, - menuItemComponent + menuItemComponent, + onClick, + target: defaultTarget }) => { const { @@ -185,6 +186,7 @@ const MenuItem = ({ if (type === 'button') { return (
  • diff --git a/web/client/plugins/ResourcesCatalog/components/ResourcesMenu.jsx b/web/client/plugins/ResourcesCatalog/components/ResourcesMenu.jsx index 1b10233a8e9..06434c1565c 100644 --- a/web/client/plugins/ResourcesCatalog/components/ResourcesMenu.jsx +++ b/web/client/plugins/ResourcesCatalog/components/ResourcesMenu.jsx @@ -132,9 +132,11 @@ const ResourcesMenu = forwardRef(({ columns, setColumns, metadata, - target + target, + resourcesFoundMsgId = "resourcesCatalog.resourcesFound" }, ref) => { + const { defaultLabelId, options: orderOptions = [], @@ -204,7 +206,7 @@ const ResourcesMenu = forwardRef(({ {loading ? - : } + : } } footer={ @@ -252,6 +248,7 @@ function ResourcesGrid({ formatHref={formatHref} getResourceTypesInfo={getResourceTypesInfo} getResourceId={getResourceId} + hideThumbnail={hideThumbnail} />
    @@ -261,22 +258,11 @@ function ResourcesGrid({ const ConnectedResourcesGrid = connect( createStructuredSelector({ user: userSelector, - totalResources: getTotalResources, - loading: getResourcesLoading, location: getRouterLocation, - monitoredState: getMonitoredStateSelector, - error: getResourcesError, - isFirstRequest: getIsFirstRequest, - page: getCurrentPage, - search: getSearch, - storedParams: getCurrentParams + monitoredState: getMonitoredStateSelector }), { - onPush: push, - setLoading: loadingResources, - setResources: updateResources, - setResourcesMetadata: updateResourcesMetadata, - onResetSearch: resetSearchResources + onPush: push } )(withResizeDetector(ResourcesGrid)); diff --git a/web/client/plugins/ResourcesCatalog/index.js b/web/client/plugins/ResourcesCatalog/index.js index 980462e76cb..ce3d54735a8 100644 --- a/web/client/plugins/ResourcesCatalog/index.js +++ b/web/client/plugins/ResourcesCatalog/index.js @@ -17,4 +17,6 @@ export { default as BrandNavbarPlugin } from './BrandNavbar'; export { default as FooterPlugin } from './Footer'; export { default as SavePlugin } from './Save'; export { default as SaveAsPlugin } from './SaveAs'; -export { default as TagsManager } from './TagsManager'; +export { default as TagsManagerPlugin } from './TagsManager'; +export { default as UserManagerPlugin } from './UserManager'; +export { default as GroupManagerPlugin } from './GroupManager'; diff --git a/web/client/plugins/ResourcesCatalog/utils/ResourcesUtils.js b/web/client/plugins/ResourcesCatalog/utils/ResourcesUtils.js index cbb1363194c..df0e5b2642f 100644 --- a/web/client/plugins/ResourcesCatalog/utils/ResourcesUtils.js +++ b/web/client/plugins/ResourcesCatalog/utils/ResourcesUtils.js @@ -63,7 +63,7 @@ export const getResourceTypesInfo = (resource) => { icon, thumbnailUrl, viewerPath, - viewerUrl: `#${viewerPath}` + viewerUrl: viewerPath ? `#${viewerPath}` : false }; }; /** diff --git a/web/client/plugins/manager/GroupManager.jsx b/web/client/plugins/manager/GroupManager.jsx deleted file mode 100644 index 8c6de8e9b06..00000000000 --- a/web/client/plugins/manager/GroupManager.jsx +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { trim } from 'lodash'; -import assign from 'object-assign'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { Glyphicon, Grid } from 'react-bootstrap'; -import { connect } from 'react-redux'; - -import Button from '../../components/misc/Button'; -import { editGroup, getUserGroups, groupSearchTextChanged } from '../../actions/usergroups'; -import Message from '../../components/I18N/Message'; -import SearchBar from '../../components/search/SearchBar'; -import GroupDeleteConfirm from './users/GroupDeleteConfirm'; -import GroupDialog from './users/GroupDialog'; -import GroupsGrid from './users/GroupGrid'; -import usergroups from '../../reducers/usergroups'; - -class GroupManager extends React.Component { - static propTypes = { - onNewGroup: PropTypes.func, - className: PropTypes.string, - hideOnBlur: PropTypes.bool, - placeholderMsgId: PropTypes.string, - typeAhead: PropTypes.bool, - splitTools: PropTypes.bool, - isSearchClickable: PropTypes.bool, - searchText: PropTypes.string, - onSearch: PropTypes.func, - onSearchReset: PropTypes.func, - onSearchTextChange: PropTypes.func, - start: PropTypes.number, - limit: PropTypes.number, - showMembersTab: PropTypes.bool, - showAttributesTab: PropTypes.bool, - attributeFields: PropTypes.array - }; - - static defaultProps = { - className: "user-search", - hideOnBlur: false, - isSearchClickable: true, - splitTools: false, - placeholderMsgId: "usergroups.searchGroups", - typeAhead: false, - searchText: "", - start: 0, - limit: 20, - onNewGroup: () => {}, - onSearch: () => {}, - onSearchReset: () => {}, - onSearchTextChange: () => {} - }; - - onNew = () => { - this.props.onNewGroup(); - }; - - render() { - return (
    - - -

    - -
    - - - -
    ); - } -} - -/** - * Allows an administrator to browse user groups. - * Renders in {@link #plugins.Manager|Manager} plugin. - * @name GroupManager - * @property {object[]} [attributeFields] attributes that should be shown in attributes tab of group manager. By default this array contains one `notes` attribute with `controlType`: `text`. Every object in this array can contain: - * - `name`: the name of the attribute - * - `title`: the string to show as label for the attribute. If not present, `name` property will be used. - * - `controlType`: The input control to use. can be : `string` (input), `text` (text area), `date` (date picker) and `select`. By default it is `string` - * - `controlAttributes`: attributes specific of the `controlType`. For instance the `options` for the `select` control. See the examples for more details. - * @property {boolean} [showMembersTab=true] shows/hides group members tab - * @property {boolean} [showAttributesTab=false] shows/hides group attributes tab - * @memberof plugins - * @class - * @example - * { "name": "GroupManager", - * "cfg": { - * "showMembersTab": false, - * "showAttributesTab": true, - * "attributeFields": [ - * { - * "title": "Simple text", - * "name": "normalString", - * "controlType": "string" - * }, - * { - * "title": "Input creates different attributes entries, separated by ;", - * "name": "multistring", - * "controlType": "string", - * "controlAttributes": { - * "multiAttribute": true, - * "separator": ";" - * } - * }, - * { - * "title": "Notes", - * "name": "notes", - * "controlType": "text" - * }, - * { - * "title": "Notes, multiple entries, separated by new-line", - * "name": "multiple-notes", - * "controlType": "text", - * "controlAttributes": { - * "multiAttribute": true, - * "separator": "\n" - * } - * }, - * { - * "title": "Single select with options in", - * "name": "select", - * "controlType": "select", - * "options": [ - * { - * "label": "Option 1", - * "value": "opt1" - * }, - * { - * "label": "Option 2", - * "value": "opt2" - * } - * ] - * }, - * { - * "title": "Multiple selections in multiple attributes (using remote service)", - * "name": "organizations", - * "controlType": "select", - * "source": { - * "url": "assets/json/organizations.json", - * "path": "organizations", - * "valueAttribute": "value", - * "labelAttribute": "label" - * }, - * "controlAttributes": { - * "multiAttribute": true, - * "isMulti": true - * } - * }, - * { - * "title": "Multiple selections in single attribute, concatenated (using remote service)", - * "name": "organizations_dependent", - * "controlType": "select", - * "source": { - * "url": "assets/json/organizations.json", - * "path": "organizations", - * "valueAttribute": "value", - * "labelAttribute": "label" - * }, - * "controlAttributes": { - * "separator": ";", - * "isMulti": true - * } - * } - * ] - * } - * } - */ -export default { - GroupManagerPlugin: assign( - connect((state) => { - let searchState = state && state.usergroups; - return { - start: searchState && searchState.start, - limit: searchState && searchState.limit, - searchText: searchState && searchState.searchText && trim(searchState.searchText, '*') || "" - }; - }, { - onNewGroup: editGroup.bind(null, {}), - onSearchTextChange: groupSearchTextChanged, - onSearch: getUserGroups - }, (stateProps, dispatchProps, ownProps) => { - return { - ...stateProps, - ...dispatchProps, - ...ownProps, - onSearchReset: (text) => { - let limit = stateProps.limit; - let searchText = text && text !== "" ? "*" + text + "*" : "*"; - dispatchProps.onSearch(searchText, {params: {start: 0, limit}}); - }, - onSearch: (text) => { - let limit = stateProps.limit; - let searchText = text && text !== "" ? "*" + text + "*" : "*"; - dispatchProps.onSearch(searchText, {params: {start: 0, limit}}); - } - }; - })(GroupManager), { - hide: true, - Manager: { - id: "groupmanager", - name: 'groupmanager', - position: 1, - priority: 1, - title: , - glyph: "1-group-mod" - }}), - reducers: { - usergroups - } -}; diff --git a/web/client/plugins/manager/Manager.jsx b/web/client/plugins/manager/Manager.jsx index 8d98921237f..f2c0c4fac0f 100644 --- a/web/client/plugins/manager/Manager.jsx +++ b/web/client/plugins/manager/Manager.jsx @@ -9,91 +9,78 @@ import React from 'react'; import PropTypes from 'prop-types'; import { itemSelected } from '../../actions/manager'; -import { Nav, NavItem, Glyphicon } from 'react-bootstrap'; +import { Tabs, Tab } from 'react-bootstrap'; import { connect } from 'react-redux'; import { Message } from '../../components/I18N/I18N'; -import './style/manager.css'; +import usePluginItems from '../../hooks/usePluginItems'; -class Manager extends React.Component { - static propTypes = { - navStyle: PropTypes.object, - items: PropTypes.array, - itemSelected: PropTypes.func, - selectedTool: PropTypes.string - }; +function Manager({ items, selectedTool, onItemSelected }, context) { + const { loadedPlugins } = context; + const configuredItems = usePluginItems({ items, loadedPlugins }, []); - static contextTypes = { - router: PropTypes.object - }; - - static defaultProps = { - items: [], - selectedTool: "importer", - itemSelected: () => {}, - navStyle: { - flex: "inherit" - } - }; - - renderToolIcon = (tool) => { - if (tool.glyph) { - return ; - } - return null; - }; - - renderNavItems = () => { - return this.props.items.map((tool) => - ( { - event.preventDefault(); - this.props.itemSelected(tool.id); - this.context.router.history.push("/manager/" + tool.id); - }}> - {this.renderToolIcon(tool)} -  {tool.msgId ? : tool.title || tool.id} - )); - }; - - renderPlugin = () => { - for ( let i = 0; i < this.props.items.length; i++) { - let tool = this.props.items[i]; - if (tool.id === this.props.selectedTool) { - return ; - } - } - return null; - - }; - - render() { - return (
    - -
    {this.renderPlugin()}
    -
    ); - } + return ( + { + onItemSelected(name); + context.router.history.push("/manager/" + name); + }} + style={{ + maxWidth: 1440, + position: 'relative', + margin: '1rem auto' + }} + > + {configuredItems.map(({ name, Component }) => + (} + > + + ))} + + ); } +Manager.contextTypes = { + router: PropTypes.object, + loadedPlugins: PropTypes.object +}; + /** * Base container for Manager plugins like {@link #plugins.UserManager|UserManager} or * {@link #plugins.GroupManager|GroupManager} - * usually rendered in {@link #pages.Manager|Manager Page}. * @name Manager * @class * @memberof plugins + * @prop {object[]} items this property contains the items injected from the other plugins, + * using the `containers` option in the plugin that want to inject new menu items as tab content. + * createPlugin( + * 'MyPlugin', + * { + * component: MyPlugin, + * containers: { + * Manager: { + * name: 'uniquepagename', // this is used as identifier for the page and label. A page must be configured in order to inject additional plugins + * position: 1, + * priority: 1, + * glyph: "1-user-mod" // glyph used in the tab + * } + * // ... + * ``` */ export default { ManagerPlugin: connect((state, ownProps) => ({ selectedTool: ownProps.tool }), { - itemSelected + onItemSelected: itemSelected + }, (stateProps, dispatchProps, ownProps)=>{ + return { + ...stateProps, + ...dispatchProps, + ...ownProps + }; })(Manager) }; diff --git a/web/client/plugins/manager/UserManager.jsx b/web/client/plugins/manager/UserManager.jsx deleted file mode 100644 index ffc6d7bbd88..00000000000 --- a/web/client/plugins/manager/UserManager.jsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { trim } from 'lodash'; -import assign from 'object-assign'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { Glyphicon, Grid } from 'react-bootstrap'; -import { connect } from 'react-redux'; - -import { editUser, getUsers, usersSearchTextChanged } from '../../actions/users'; -import Message from '../../components/I18N/Message'; -import SearchBar from '../../components/search/SearchBar'; -import UserDeleteConfirm from './users/UserDeleteConfirm'; -import UserDialog from './users/UserDialog'; -import UserGrid from './users/UserGrid'; -import Button from '../../components/misc/Button'; - -class UserManager extends React.Component { - static propTypes = { - attributeFields: PropTypes.array, - onNewUser: PropTypes.func, - splitTools: PropTypes.bool, - isSearchClickable: PropTypes.bool, - className: PropTypes.string, - hideOnBlur: PropTypes.bool, - placeholderMsgId: PropTypes.string, - typeAhead: PropTypes.bool, - searchText: PropTypes.string, - onSearch: PropTypes.func, - onSearchReset: PropTypes.func, - onSearchTextChange: PropTypes.func, - start: PropTypes.number, - limit: PropTypes.number - }; - - static defaultProps = { - className: "user-search", - hideOnBlur: false, - isSearchClickable: true, - splitTools: false, - placeholderMsgId: "users.searchUsers", - typeAhead: false, - searchText: "", - start: 0, - limit: 20, - onSearch: () => {}, - onSearchReset: () => {}, - onSearchTextChange: () => {}, - onNewUser: () => {} - }; - - onNew = () => { - this.props.onNewUser(); - }; - - render() { - return (
    - - -

    - -
    - - - -
    ); - } -} - -/** - * Allows an administrator to browse users. - * Renders in {@link #plugins.Manager|Manager} plugin. - * @name UserManager - * @memberof plugins - * @property {object[]} attributeFields attributes that should be shown in attributes tab of user dialog. - * @class - */ -export default { - UserManagerPlugin: assign( - connect((state) => { - let searchState = state && state.users; - return { - start: searchState && searchState.start, - limit: searchState && searchState.limit, - searchText: searchState && searchState.searchText && trim(searchState.searchText, '*') || "" - }; - }, - { - onNewUser: editUser.bind(null, {role: "USER", "enabled": true}), - onSearchTextChange: usersSearchTextChanged, - onSearch: getUsers - }, (stateProps, dispatchProps, ownProps) => { - return { - ...stateProps, - ...dispatchProps, - ...ownProps, - onSearchReset: (text) => { - let limit = stateProps.limit; - let searchText = text && text !== "" ? "*" + text + "*" : "*"; - dispatchProps.onSearch(searchText, {params: {start: 0, limit}}); - }, - onSearch: (text) => { - let limit = stateProps.limit; - let searchText = text && text !== "" ? "*" + text + "*" : "*"; - dispatchProps.onSearch(searchText, {params: {start: 0, limit}}); - } - }; - })(UserManager), { - hide: true, - Manager: { - id: "usermanager", - name: 'usermanager', - position: 1, - priority: 1, - title: , - glyph: "1-user-mod" - }}), - reducers: { - users: require('../../reducers/users').default - } -}; diff --git a/web/client/plugins/manager/style/manager.css b/web/client/plugins/manager/style/manager.css deleted file mode 100644 index 0a3f26a5408..00000000000 --- a/web/client/plugins/manager/style/manager.css +++ /dev/null @@ -1,26 +0,0 @@ -.Manager-Container { - display: flex; - flex-direction: row; - margin: 10px; - height: 100%; -} - -.Manager-Container .Manager-Tools-Nav .glyphicon { - vertical-align: middle; -} - -.Manager-Container .Manager-Tools-Nav .nav-msg { - display: none; -} -@media (min-width: 1200px) { - .Manager-Container .Manager-Tools-Nav .nav-msg { - display: inline-block; - } -} -.Manager-Container .Manager-Tools-Nav { - order: -1; - max-width: 20%; -} -.Manager-Container .usermanager-title { - margin-top: 0; -} diff --git a/web/client/plugins/manager/users/GroupDialog.jsx b/web/client/plugins/manager/users/GroupDialog.jsx deleted file mode 100644 index 8f2c28d45c3..00000000000 --- a/web/client/plugins/manager/users/GroupDialog.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import { connect } from 'react-redux'; -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import { bindActionCreators } from 'redux'; - -import { changeGroupMetadata, editGroup, saveGroup, searchUsers } from '../../../actions/usergroups'; -import GroupDialog from '../../../components/manager/users/GroupDialog'; - -const mapStateToProps = (state) => { - const usergroups = state && state.usergroups; - return { - modal: true, - availableUsers: usergroups && usergroups.availableUsers, - availableUsersCount: usergroups && usergroups.availableUsersCount, - availableUsersLoading: usergroups && usergroups.availableUsersLoading, - show: usergroups && !!usergroups.currentGroup, - group: usergroups && usergroups.currentGroup - }; -}; -const mapDispatchToProps = (dispatch) => { - return bindActionCreators({ - searchUsers: searchUsers.bind(null), - onChange: changeGroupMetadata.bind(null), - onClose: editGroup.bind(null, null), - onSave: saveGroup.bind(null) - }, dispatch); -}; - -export default connect(mapStateToProps, mapDispatchToProps)(GroupDialog); diff --git a/web/client/plugins/manager/users/GroupGrid.jsx b/web/client/plugins/manager/users/GroupGrid.jsx deleted file mode 100644 index f9c8d2f78de..00000000000 --- a/web/client/plugins/manager/users/GroupGrid.jsx +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import assign from 'object-assign'; -import React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; - -import { deleteGroup, editGroup, getUserGroups } from '../../../actions/usergroups'; -import GroupGrid from '../../../components/manager/users/GroupGrid'; -import PaginationToolbar from './GroupsPaginationToolbar'; - -const mapStateToProps = (state) => { - const usergroups = state && state.usergroups; - return { - groups: usergroups && state.usergroups.groups, - loading: usergroups && usergroups.status === "loading", - stateProps: usergroups && usergroups.stateProps, - start: usergroups && usergroups.start, - limit: usergroups && usergroups.limit, - myUserId: state && state.security && state.security.user && state.security.user.id - }; -}; -const mapDispatchToProps = (dispatch) => { - return bindActionCreators({ - loadGroups: getUserGroups, - onEdit: editGroup, - onDelete: deleteGroup - }, dispatch); -}; -const mergeProps = (stateProps, dispatchProps, ownProps) => { - return assign({}, stateProps, dispatchProps, ownProps, { - bottom: , - loadGroups: () => { - dispatchProps.loadGroups(stateProps && stateProps.searchText, { - params: { - start: stateProps && stateProps.start || 0, - limit: stateProps && stateProps.limit || 12 - } - }); - } - }); -}; -export default connect(mapStateToProps, mapDispatchToProps, mergeProps, {pure: false})(GroupGrid); diff --git a/web/client/plugins/manager/users/GroupsPaginationToolbar.jsx b/web/client/plugins/manager/users/GroupsPaginationToolbar.jsx deleted file mode 100644 index 4ea355dcbc3..00000000000 --- a/web/client/plugins/manager/users/GroupsPaginationToolbar.jsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import { connect } from 'react-redux'; - -import { getUserGroups } from '../../../actions/usergroups'; -import PaginationToolbarComp from '../../../components/misc/PaginationToolbar'; -const PaginationToolbar = connect((state) => { - if (!state.usergroups ) { - return {}; - } - let {start, limit, groups, status, totalCount, searchText} = state.usergroups; - let page = 0; - if (groups && totalCount) { // must be !==0 and exist to do the division - page = Math.ceil(start / limit); - } - - return { - page: page, - pageSize: limit, - items: groups, - total: totalCount, - searchText, - loading: status === "loading" - }; -}, {onSelect: getUserGroups}, (stateProps, dispatchProps) => { - - return { - ...stateProps, - onSelect: (pageNumber) => { - let start = stateProps.pageSize * pageNumber; - let limit = stateProps.pageSize; - dispatchProps.onSelect(stateProps.searchText, {params: { start, limit}}); - } - }; -})(PaginationToolbarComp); -export default PaginationToolbar; diff --git a/web/client/plugins/manager/users/UserDialog.jsx b/web/client/plugins/manager/users/UserDialog.jsx deleted file mode 100644 index 3e0af9c6caf..00000000000 --- a/web/client/plugins/manager/users/UserDialog.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import { connect } from 'react-redux'; -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import { bindActionCreators } from 'redux'; - -import { changeUserMetadata, editUser, saveUser } from '../../../actions/users'; -import UserDialog from '../../../components/manager/users/UserDialog'; - -const mapStateToProps = (state) => { - const users = state && state.users; - return { - modal: true, - show: users && !!users.currentUser, - user: users && users.currentUser, - groups: users && users.groups, - hidePasswordFields: users && users.currentUser && state.security && state.security.user && state.security.user.id === users.currentUser.id - }; -}; -const mapDispatchToProps = (dispatch) => { - return bindActionCreators({ - onChange: changeUserMetadata.bind(null), - onClose: editUser.bind(null, null), - onSave: saveUser.bind(null) - }, dispatch); -}; - -export default connect(mapStateToProps, mapDispatchToProps)(UserDialog); diff --git a/web/client/plugins/manager/users/UserGrid.jsx b/web/client/plugins/manager/users/UserGrid.jsx deleted file mode 100644 index fd6ba5f7849..00000000000 --- a/web/client/plugins/manager/users/UserGrid.jsx +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -import assign from 'object-assign'; -import React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; - -import { deleteUser, editUser, getUsers } from '../../../actions/users'; -import UserGrid from '../../../components/manager/users/UserGrid'; -import PaginationToolbar from './UsersPaginationToolbar'; - -const mapStateToProps = (state) => { - const users = state && state.users; - return { - users: users && users.users, - loading: users && users.status === "loading", - stateProps: users && users.stateProps, - start: users && users.start, - limit: users && users.limit, - myUserId: state && state.security && state.security.user && state.security.user.id - }; -}; -const mapDispatchToProps = (dispatch) => { - return bindActionCreators({ - loadUsers: getUsers, - onEdit: editUser, - onDelete: deleteUser - }, dispatch); -}; -const mergeProps = (stateProps, dispatchProps, ownProps) => { - return assign({}, stateProps, dispatchProps, ownProps, { - bottom: , - loadUsers: () => { - dispatchProps.loadUsers(stateProps && stateProps.searchText, { - params: { - start: stateProps && stateProps.start || 0, - limit: stateProps && stateProps.limit || 12 - } - }); - } - }); -}; -export default connect(mapStateToProps, mapDispatchToProps, mergeProps, {pure: false})(UserGrid); diff --git a/web/client/plugins/manager/users/UsersPaginationToolbar.jsx b/web/client/plugins/manager/users/UsersPaginationToolbar.jsx deleted file mode 100644 index 92e6310be91..00000000000 --- a/web/client/plugins/manager/users/UsersPaginationToolbar.jsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import { connect } from 'react-redux'; - -import { getUsers } from '../../../actions/users'; -import PaginationToolbarComp from '../../../components/misc/PaginationToolbar'; -const PaginationToolbar = connect((state) => { - if (!state.users ) { - return {}; - } - let {start, limit, users, status, totalCount, searchText} = state.users; - let page = 0; - if (users && totalCount) { // must be !==0 and exist to do the division - page = Math.ceil(start / limit); - } - - return { - page: page, - pageSize: limit, - items: users, - total: totalCount, - searchText, - loading: status === "loading" - }; -}, {onSelect: getUsers}, (stateProps, dispatchProps) => { - - return { - ...stateProps, - onSelect: (pageNumber) => { - let start = stateProps.pageSize * pageNumber; - let limit = stateProps.pageSize; - dispatchProps.onSelect(stateProps.searchText, {params: { start, limit}}); - } - }; -})(PaginationToolbarComp); -export default PaginationToolbar; diff --git a/web/client/product/assets/css/manager.css b/web/client/product/assets/css/manager.css deleted file mode 100644 index e7518fe4f19..00000000000 --- a/web/client/product/assets/css/manager.css +++ /dev/null @@ -1,28 +0,0 @@ -.manager .mapstore-header { - background-image: url('../img/manager-header.jpg'); - height: 200px; - z-index: -100; - width: 100%; - background-size: cover; - background-repeat: no-repeat; - overflow: hidden; - background-position: center; - position: relative; - top: -20px; -} - -.manager .MapSearchBar.user-search { - top: -100px; - height: 0; - overflow: visible; -} -.manager .MapSearchBar.user-search .input-group { - box-shadow: -1px 1px 5px 1px rgba(94,94,94,1); -} -.manager .Manager-Container { - margin: 0; -} - -.manager #home-button { - float: right; -} diff --git a/web/client/product/assets/img/manager-header.jpg b/web/client/product/assets/img/manager-header.jpg deleted file mode 100644 index 9acc7f8c1dd..00000000000 Binary files a/web/client/product/assets/img/manager-header.jpg and /dev/null differ diff --git a/web/client/product/pages/Manager.jsx b/web/client/product/pages/Manager.jsx index fcb72277b72..30e88474c07 100644 --- a/web/client/product/pages/Manager.jsx +++ b/web/client/product/pages/Manager.jsx @@ -13,8 +13,6 @@ import {connect} from 'react-redux'; import {resetControls} from '../../actions/controls'; import Page from '../../containers/Page'; -import('../assets/css/manager.css'); - /** * @name Manager * @memberof pages diff --git a/web/client/product/plugins.js b/web/client/product/plugins.js index 6cada6f81d2..ba8c6f0ba0a 100644 --- a/web/client/product/plugins.js +++ b/web/client/product/plugins.js @@ -52,7 +52,6 @@ export const plugins = { // ### DYNAMIC PLUGINS ### // // product plugins AboutPlugin: toModulePlugin('About', () => import(/* webpackChunkName: 'plugins/about' */ './plugins/About')), - HeaderPlugin: toModulePlugin('Header', () => import(/* webpackChunkName: 'plugins/header' */ './plugins/Header')), // framework plugins MapTypePlugin: toModulePlugin('MapType', () => import(/* webpackChunkName: 'plugins/mapType' */ './plugins/MapType')), AddGroupPlugin: toModulePlugin('AddGroup', () => import(/* webpackChunkName: 'plugins/about' */'../plugins/AddGroup')), @@ -78,7 +77,6 @@ export const plugins = { GeoStoryImport: toModulePlugin('GeoStoryImport', () => import(/* webpackChunkName: 'plugins/geoStoryImport' */ '../plugins/GeoStoryImport')), GeoProcessing: toModulePlugin('GeoProcessing', () => import(/* webpackChunkName: 'plugins/GeoProcessing' */ '../plugins/GeoProcessing')), GeoStoryNavigationPlugin: toModulePlugin('GeoStoryNavigation', () => import(/* webpackChunkName: 'plugins/geoStoryNavigation' */ '../plugins/GeoStoryNavigation')), - GroupManagerPlugin: toModulePlugin('GroupManager', () => import(/* webpackChunkName: 'plugins/groupManager' */ '../plugins/manager/GroupManager')), GlobeViewSwitcherPlugin: toModulePlugin('GlobeViewSwitcher', () => import(/* webpackChunkName: 'plugins/globeViewSwitcher' */ '../plugins/GlobeViewSwitcher')), GoFull: toModulePlugin('GoFull', () => import(/* webpackChunkName: 'plugins/goFull' */ '../plugins/GoFull')), GridContainerPlugin: toModulePlugin('GridContainer', () => import(/* webpackChunkName: 'plugins/gridContainer' */ '../plugins/GridContainer')), @@ -132,7 +130,6 @@ export const plugins = { TutorialPlugin: toModulePlugin('Tutorial', () => import(/* webpackChunkName: 'plugins/tutorial' */ '../plugins/Tutorial')), UndoPlugin: toModulePlugin('Undo', () => import(/* webpackChunkName: 'plugins/history' */ '../plugins/History')), UserExtensionsPlugin: toModulePlugin('UserExtensions', () => import(/* webpackChunkName: 'plugins/userExtensions' */ '../plugins/UserExtensions')), - UserManagerPlugin: toModulePlugin('UserManager', () => import(/* webpackChunkName: 'plugins/userManager' */ '../plugins/manager/UserManager')), WidgetsBuilderPlugin: toModulePlugin('WidgetsBuilder', () => import(/* webpackChunkName: 'plugins/widgetsBuilder' */ '../plugins/WidgetsBuilder')), WidgetsPlugin: toModulePlugin('Widgets', () => import(/* webpackChunkName: 'plugins/widgets' */ '../plugins/Widgets')), WidgetsTrayPlugin: toModulePlugin('WidgetsTray', () => import(/* webpackChunkName: 'plugins/widgetsTray' */ '../plugins/WidgetsTray')), diff --git a/web/client/product/plugins/Header.jsx b/web/client/product/plugins/Header.jsx deleted file mode 100644 index c70a70e31c9..00000000000 --- a/web/client/product/plugins/Header.jsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2016, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ -import React from 'react'; - -import PropTypes from 'prop-types'; - -/** - * Header of MapStore, rendered in the home page (big full-width image). - * @name Header - * @class - * @memberof plugins - * @prop {object} [style] the style for the main div. - */ -class Header extends React.Component { - static propTypes = { - style: PropTypes.object, - className: PropTypes.object - }; - - render() { - return ( -
    - ); - } -} - -export default { - HeaderPlugin: Header -}; diff --git a/web/client/reducers/__tests__/usergroups-test.js b/web/client/reducers/__tests__/usergroups-test.js index 730214af444..a9f2d4889e2 100644 --- a/web/client/reducers/__tests__/usergroups-test.js +++ b/web/client/reducers/__tests__/usergroups-test.js @@ -10,51 +10,89 @@ import expect from 'expect'; import usergroups from '../usergroups'; import { - GETGROUPS, EDITGROUP, EDITGROUPDATA, - SEARCHTEXTCHANGED, SEARCHUSERS, UPDATEGROUP, DELETEGROUP, STATUS_SUCCESS, STATUS_LOADING, STATUS_SAVED, - STATUS_ERROR + STATUS_ERROR, + updateUserGroups, + updateUserGroupsMetadata, + loadingUserGroups, + searchUserGroups, + resetSearchUserGroups } from '../../actions/usergroups'; describe('Test the usergroups reducer', () => { - it('default loading', () => { - let oldState = {test: "test"}; - const state = usergroups(oldState, { - type: "TEST_UNKNOWN_ACTION", - status: 'loading' + + it('updateUserGroups', () => { + const userGroupsItems = [{ id: '01' }]; + const state = usergroups({}, updateUserGroups(userGroupsItems)); + expect(state).toEqual({ + grid: { + isFirstRequest: false, + userGroups: userGroupsItems + } }); - expect(state).toBe(oldState); }); - it('search text change', () => { - const state = usergroups(undefined, { - type: SEARCHTEXTCHANGED, - text: "TEXT" + + it('updateUserGroupsMetadata', () => { + const metadata = { total: 1, isNextPageAvailable: false, params: { q: 'a' }, locationSearch: '?q=a', locationPathname: '/' }; + const state = usergroups({ grid: { params: { q: 'ab' } } }, updateUserGroupsMetadata(metadata)); + expect(state).toEqual({ + grid: { + total: 1, + isNextPageAvailable: false, + error: undefined, + params: { q: 'a' }, + previousParams: { q: 'ab' }, + locationSearch: '?q=a', + locationPathname: '/' + } }); - expect(state.searchText).toBe("TEXT"); }); - it('set loading', () => { - const state = usergroups(undefined, { - type: GETGROUPS, - status: STATUS_LOADING + + it('loadingUserGroups', () => { + const state = usergroups({}, loadingUserGroups(true)); + expect(state).toEqual({ + grid: { + loading: true, + error: false + } }); - expect(state.status).toBe('loading'); }); - it('get groups', () => { - const state = usergroups(undefined, { - type: GETGROUPS, - status: STATUS_SUCCESS, - groups: [], - totalCount: 0 + + it('searchUserGroups', () => { + let state = usergroups({}, searchUserGroups({ params: { q: 'a' } })); + expect(state.grid.search.id).toBeTruthy(); + expect(state.grid.search.params).toEqual({ q: 'a' }); + state = usergroups({}, searchUserGroups({ refresh: true })); + expect(state.grid.search.id).toBeTruthy(); + expect(state.grid.search.refresh).toBe(true); + state = usergroups({}, searchUserGroups({ clear: true })); + expect(state.grid.search.id).toBeTruthy(); + expect(state.grid.search.clear).toBe(true); + }); + + it('resetSearchUserGroups', () => { + const state = usergroups({ grid: { search: { refresh: true } } }, resetSearchUserGroups()); + expect(state).toEqual({ + grid: { + search: null + } }); - expect(state.groups).toExist(); - expect(state.groups.length).toBe(0); + }); + + it('default loading', () => { + let oldState = {test: "test"}; + const state = usergroups(oldState, { + type: "TEST_UNKNOWN_ACTION", + status: 'loading' + }); + expect(state).toBe(oldState); }); it('edit group', () => { const state = usergroups(undefined, { diff --git a/web/client/reducers/__tests__/users-test.js b/web/client/reducers/__tests__/users-test.js index 4f094fdeffb..2dbf69bb38f 100644 --- a/web/client/reducers/__tests__/users-test.js +++ b/web/client/reducers/__tests__/users-test.js @@ -10,17 +10,80 @@ import expect from 'expect'; import users from '../users'; import { - USERMANAGER_GETUSERS, USERMANAGER_EDIT_USER, USERMANAGER_EDIT_USER_DATA, USERMANAGER_UPDATE_USER, USERMANAGER_DELETE_USER, - USERMANAGER_GETGROUPS + USERMANAGER_GETGROUPS, + updateUsers, + updateUsersMetadata, + loadingUsers, + searchUsers, + resetSearchUsers } from '../../actions/users'; import { UPDATEGROUP, STATUS_CREATED, DELETEGROUP, STATUS_DELETED } from '../../actions/usergroups'; describe('Test the users reducer', () => { + + it('updateUsers', () => { + const userItems = [{ id: '01' }]; + const state = users({}, updateUsers(userItems)); + expect(state).toEqual({ + grid: { + isFirstRequest: false, + users: userItems + } + }); + }); + + it('updateUsersMetadata', () => { + const metadata = { total: 1, isNextPageAvailable: false, params: { q: 'a' }, locationSearch: '?q=a', locationPathname: '/' }; + const state = users({ grid: { params: { q: 'ab' } } }, updateUsersMetadata(metadata)); + expect(state).toEqual({ + grid: { + total: 1, + isNextPageAvailable: false, + error: undefined, + params: { q: 'a' }, + previousParams: { q: 'ab' }, + locationSearch: '?q=a', + locationPathname: '/' + } + }); + }); + + it('loadingUsers', () => { + const state = users({}, loadingUsers(true)); + expect(state).toEqual({ + grid: { + loading: true, + error: false + } + }); + }); + + it('searchUsers', () => { + let state = users({}, searchUsers({ params: { q: 'a' } })); + expect(state.grid.search.id).toBeTruthy(); + expect(state.grid.search.params).toEqual({ q: 'a' }); + state = users({}, searchUsers({ refresh: true })); + expect(state.grid.search.id).toBeTruthy(); + expect(state.grid.search.refresh).toBe(true); + state = users({}, searchUsers({ clear: true })); + expect(state.grid.search.id).toBeTruthy(); + expect(state.grid.search.clear).toBe(true); + }); + + it('resetSearchUsers', () => { + const state = users({ grid: { search: { refresh: true } } }, resetSearchUsers()); + expect(state).toEqual({ + grid: { + search: null + } + }); + }); + it('default loading', () => { let oldState = {test: "test"}; const state = users(oldState, { @@ -29,23 +92,6 @@ describe('Test the users reducer', () => { }); expect(state).toBe(oldState); }); - it('set loading', () => { - const state = users(undefined, { - type: USERMANAGER_GETUSERS, - status: 'loading' - }); - expect(state.status).toBe('loading'); - }); - it('set users', () => { - const state = users(undefined, { - type: USERMANAGER_GETUSERS, - status: 'success', - users: [], - totalCount: 0 - }); - expect(state.users).toExist(); - expect(state.users.length).toBe(0); - }); it('edit user', () => { const state = users(undefined, { type: USERMANAGER_EDIT_USER, diff --git a/web/client/reducers/usergroups.js b/web/client/reducers/usergroups.js index a6f8504c541..e63df60caf9 100644 --- a/web/client/reducers/usergroups.js +++ b/web/client/reducers/usergroups.js @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * @@ -6,37 +6,90 @@ * LICENSE file in the root directory of this source tree. */ +import isNil from 'lodash/isNil'; +import uuid from 'uuid/v1'; import { - GETGROUPS, SEARCHUSERS, EDITGROUP, EDITGROUPDATA, DELETEGROUP, UPDATEGROUP, - SEARCHTEXTCHANGED + UPDATE_USER_GROUPS, + UPDATE_USER_GROUPS_METADATA, + LOADING_USER_GROUPS, + SEARCH_USER_GROUPS, + RESET_SEARCH_USER_GROUPS } from '../actions/usergroups'; import assign from 'object-assign'; -function usergroups(state = { - start: 0, - limit: 12 -}, action) { - switch (action.type) { - case GETGROUPS: - return assign({}, state, { - searchText: action.searchText, - status: action.status, - groups: action.status === "loading" ? state.groups : action.groups, - start: action.start, - limit: action.limit, - totalCount: action.status === "loading" ? state.totalCount : action.totalCount - }); - case SEARCHTEXTCHANGED: { - return assign({}, state, { - searchText: action.text - }); +function usergroups(state = {}, action) { + switch (action.type) { + case UPDATE_USER_GROUPS: { + return { + ...state, + grid: { + ...state?.grid, + isFirstRequest: false, + userGroups: action.userGroups + } + }; + } + case UPDATE_USER_GROUPS_METADATA: { + return { + ...state, + grid: { + ...state?.grid, + total: action.metadata.total, + isNextPageAvailable: action.metadata.isNextPageAvailable, + error: action.metadata.error, + ...(action.metadata.params && + { + params: action.metadata.params, + previousParams: state?.grid?.params + }), + ...(!isNil(action.metadata.locationSearch) && + { + locationSearch: action.metadata.locationSearch + }), + ...(!isNil(action.metadata.locationPathname) && + { + locationPathname: action.metadata.locationPathname + }) + } + }; } + case LOADING_USER_GROUPS: { + return { + ...state, + grid: { + ...state?.grid, + loading: action.loading, + ...(action.loading && { error: false }) + } + }; + } + case SEARCH_USER_GROUPS: + return { + ...state, + grid: { + ...state?.grid, + search: { + id: uuid(), + params: action.params, + clear: action.clear, + refresh: action.refresh + } + } + }; + case RESET_SEARCH_USER_GROUPS: + return { + ...state, + grid: { + ...state?.grid, + search: null + } + }; case EDITGROUP: { let newGroup = action.status ? { status: action.status, diff --git a/web/client/reducers/users.js b/web/client/reducers/users.js index 75e12198426..57dd851ec0e 100644 --- a/web/client/reducers/users.js +++ b/web/client/reducers/users.js @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * @@ -6,52 +6,92 @@ * LICENSE file in the root directory of this source tree. */ +import isNil from 'lodash/isNil'; +import uuid from 'uuid/v1'; import { - USERMANAGER_GETUSERS, USERMANAGER_EDIT_USER, USERMANAGER_EDIT_USER_DATA, USERMANAGER_UPDATE_USER, USERMANAGER_DELETE_USER, USERMANAGER_GETGROUPS, - USERS_SEARCH_TEXT_CHANGED + UPDATE_USERS, + UPDATE_USERS_METADATA, + SEARCH_USERS, + RESET_SEARCH_USERS, + LOADING_USERS } from '../actions/users'; import { UPDATEGROUP, STATUS_CREATED, DELETEGROUP, STATUS_DELETED } from '../actions/usergroups'; + import assign from 'object-assign'; -/** - * Reducer for a user - * * It contains the following parts: - * - * { - * searchText: {string} The text string - * status: {string} one of "loading", "new", "saving", "error", "modified", "cancelled" - * } - * - * @param {object} state - The current state - * @param {object} action - The performed action - * - * - */ -function users(state = { - start: 0, - limit: 12 -}, action) { +function users(state = {}, action) { switch (action.type) { - case USERMANAGER_GETUSERS: - return assign({}, state, { - searchText: action.searchText, - status: action.status, - users: action.status === "loading" ? state.users : action.users, - start: action.start, - limit: action.limit, - totalCount: action.status === "loading" ? state.totalCount : action.totalCount - }); - case USERS_SEARCH_TEXT_CHANGED: { - return assign({}, state, { - searchText: action.text - }); + case UPDATE_USERS: { + return { + ...state, + grid: { + ...state?.grid, + isFirstRequest: false, + users: action.users + } + }; + } + case UPDATE_USERS_METADATA: { + return { + ...state, + grid: { + ...state?.grid, + total: action.metadata.total, + isNextPageAvailable: action.metadata.isNextPageAvailable, + error: action.metadata.error, + ...(action.metadata.params && + { + params: action.metadata.params, + previousParams: state?.grid?.params + }), + ...(!isNil(action.metadata.locationSearch) && + { + locationSearch: action.metadata.locationSearch + }), + ...(!isNil(action.metadata.locationPathname) && + { + locationPathname: action.metadata.locationPathname + }) + } + }; } + case LOADING_USERS: { + return { + ...state, + grid: { + ...state?.grid, + loading: action.loading, + ...(action.loading && { error: false }) + } + }; + } + case SEARCH_USERS: + return { + ...state, + grid: { + ...state?.grid, + search: { + id: uuid(), + params: action.params, + clear: action.clear, + refresh: action.refresh + } + } + }; + case RESET_SEARCH_USERS: + return { + ...state, + grid: { + ...state?.grid, + search: null + } + }; case USERMANAGER_EDIT_USER: { let newUser = action.status ? { status: action.status, diff --git a/web/client/selectors/__tests__/usergroups-test.js b/web/client/selectors/__tests__/usergroups-test.js new file mode 100644 index 00000000000..a36b18fe829 --- /dev/null +++ b/web/client/selectors/__tests__/usergroups-test.js @@ -0,0 +1,54 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + getTotalUserGroups, + getUserGroupsLoading, + getUserGroups, + getUserGroupsError, + getIsFirstRequest, + getCurrentPage, + getSearch, + getCurrentParams +} from '../usergroups'; +import expect from 'expect'; + +describe('usergroups selectors', () => { + it('getTotalUserGroups', () => { + expect(getTotalUserGroups()).toBe(0); + expect(getTotalUserGroups({ usergroups: { grid: { total: 10 } } })).toBe(10); + }); + it('getUserGroupsLoading', () => { + expect(getUserGroupsLoading()).toBe(undefined); + expect(getUserGroupsLoading({ usergroups: { grid: { loading: false } } })).toBe(false); + }); + it('getUserGroups', () => { + expect(getUserGroups()).toEqual([]); + expect(getUserGroups({ usergroups: { grid: { userGroups: [{ id: '01' }] } } })).toEqual([{ id: '01' }]); + }); + it('getUserGroupsError', () => { + expect(getUserGroupsError()).toBe(undefined); + expect(getUserGroupsError({ usergroups: { grid: { error: false } } })).toBe(false); + }); + it('getIsFirstRequest', () => { + expect(getIsFirstRequest()).toBe(true); + expect(getIsFirstRequest({ usergroups: { grid: { isFirstRequest: false } } })).toBe(false); + }); + it('getCurrentPage', () => { + expect(getCurrentPage()).toBe(1); + expect(getCurrentPage({ usergroups: { grid: { params: { page: 2 } } } })).toBe(2); + }); + it('getSearch', () => { + expect(getSearch()).toBe(null); + expect(getSearch({ usergroups: { grid: { search: { q: 'a' } } } })).toEqual({ q: 'a' }); + }); + it('getCurrentParams', () => { + expect(getCurrentParams()).toBe(undefined); + expect(getCurrentParams({ usergroups: { grid: { params: { page: 2 } } } })).toEqual({ page: 2 }); + }); +}); diff --git a/web/client/selectors/__tests__/users-test.js b/web/client/selectors/__tests__/users-test.js new file mode 100644 index 00000000000..6a047bf8fcd --- /dev/null +++ b/web/client/selectors/__tests__/users-test.js @@ -0,0 +1,54 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + getTotalUsers, + getUsersLoading, + getUsers, + getUsersError, + getIsFirstRequest, + getCurrentPage, + getSearch, + getCurrentParams +} from '../users'; +import expect from 'expect'; + +describe('users selectors', () => { + it('getTotalUsers', () => { + expect(getTotalUsers()).toBe(0); + expect(getTotalUsers({ users: { grid: { total: 10 } } })).toBe(10); + }); + it('getUsersLoading', () => { + expect(getUsersLoading()).toBe(undefined); + expect(getUsersLoading({ users: { grid: { loading: false } } })).toBe(false); + }); + it('getUsers', () => { + expect(getUsers()).toEqual([]); + expect(getUsers({ users: { grid: { users: [{ id: '01' }] } } })).toEqual([{ id: '01' }]); + }); + it('getUsersError', () => { + expect(getUsersError()).toBe(undefined); + expect(getUsersError({ users: { grid: { error: false } } })).toBe(false); + }); + it('getIsFirstRequest', () => { + expect(getIsFirstRequest()).toBe(true); + expect(getIsFirstRequest({ users: { grid: { isFirstRequest: false } } })).toBe(false); + }); + it('getCurrentPage', () => { + expect(getCurrentPage()).toBe(1); + expect(getCurrentPage({ users: { grid: { params: { page: 2 } } } })).toBe(2); + }); + it('getSearch', () => { + expect(getSearch()).toBe(null); + expect(getSearch({ users: { grid: { search: { q: 'a' } } } })).toEqual({ q: 'a' }); + }); + it('getCurrentParams', () => { + expect(getCurrentParams()).toBe(undefined); + expect(getCurrentParams({ users: { grid: { params: { page: 2 } } } })).toEqual({ page: 2 }); + }); +}); diff --git a/web/client/selectors/usergroups.js b/web/client/selectors/usergroups.js new file mode 100644 index 00000000000..9c5d21a372d --- /dev/null +++ b/web/client/selectors/usergroups.js @@ -0,0 +1,16 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +export const getTotalUserGroups = state => state?.usergroups?.grid?.total || 0; +export const getUserGroupsLoading = state => state?.usergroups?.grid?.loading; +export const getUserGroups = state => state?.usergroups?.grid?.userGroups || []; +export const getUserGroupsError = state => state?.usergroups?.grid?.error; +export const getIsFirstRequest = state => state?.usergroups?.grid?.isFirstRequest !== false; +export const getCurrentPage = state => state?.usergroups?.grid?.params?.page ?? 1; +export const getSearch = state => state?.usergroups?.grid?.search || null; +export const getCurrentParams = state => state?.usergroups?.grid?.params; diff --git a/web/client/selectors/users.js b/web/client/selectors/users.js new file mode 100644 index 00000000000..c283517a100 --- /dev/null +++ b/web/client/selectors/users.js @@ -0,0 +1,16 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +export const getTotalUsers = state => state?.users?.grid?.total || 0; +export const getUsersLoading = state => state?.users?.grid?.loading; +export const getUsers = state => state?.users?.grid?.users || []; +export const getUsersError = state => state?.users?.grid?.error; +export const getIsFirstRequest = state => state?.users?.grid?.isFirstRequest !== false; +export const getCurrentPage = state => state?.users?.grid?.params?.page ?? 1; +export const getSearch = state => state?.users?.grid?.search || null; +export const getCurrentParams = state => state?.users?.grid?.params; diff --git a/web/client/test-resources/data.es-ES.json b/web/client/test-resources/data.es-ES.json index 1d1fc76b2a7..26fc7f429b0 100644 --- a/web/client/test-resources/data.es-ES.json +++ b/web/client/test-resources/data.es-ES.json @@ -565,7 +565,11 @@ "confirmDeleteUser": "¿Está seguro de borrar el usuario?", "errorDelete": "A ocurrido un error al borrar el usuario:", "errorSaving": "A ocurrido un error al guardar el usuario:", - "selectedGroups": "GRUPOS SELECCIONADOS" + "selectedGroups": "GRUPOS SELECCIONADOS", + "usersFound": "{count, plural, =0 {0 Usuarios encontrados} =1 {1 Usuario encontrado} other {# Usuarios encontrados}}", + "admin": "Administrador", + "active": "Activo", + "inactive": "Inactivo" }, "usergroups": { "searchGroups": "buscar grupo ...", @@ -589,7 +593,10 @@ "noUsers": "El grupo no tiene usuarios", "errorSaving": "A ocurrido un error al guardar el grupo", "errorDelete": "A ocurrido un error al borrar el grupo", - "confirmDeleteGroup": "¿Está seguro de borrar el grupo?" + "confirmDeleteGroup": "¿Está seguro de borrar el grupo?", + "userGroupsFound": "{count, plural, =0 {0 Grupos encontrados} =1 {1 Grupo encontrado} other {# Grupos encontrados}}", + "active": "Activo", + "inactive": "Inactivo" }, "share":{ "title": "Compartir", diff --git a/web/client/themes/default/less/resources-catalog/_resource-card.less b/web/client/themes/default/less/resources-catalog/_resource-card.less index 2095feaaa28..751531e9adc 100644 --- a/web/client/themes/default/less/resources-catalog/_resource-card.less +++ b/web/client/themes/default/less/resources-catalog/_resource-card.less @@ -18,6 +18,9 @@ // ************** .ms-resource-card { + --tag-color-r: 230; + --tag-color-g: 230; + --tag-color-b: 230; > div a { position: relative; } diff --git a/web/client/themes/default/less/resources-catalog/_resources-container.less b/web/client/themes/default/less/resources-catalog/_resources-container.less index 8c351ca6db2..a6140fe7772 100644 --- a/web/client/themes/default/less/resources-catalog/_resources-container.less +++ b/web/client/themes/default/less/resources-catalog/_resources-container.less @@ -20,6 +20,12 @@ margin-top: auto; margin-bottom: 0; } + .ms-resource-status { + align-self: flex-start; + } + .ms-resource-card-buttons { + align-self: flex-end; + } } } } diff --git a/web/client/translations/data.ca-ES.json b/web/client/translations/data.ca-ES.json index 3b5899c63a3..b374836ee02 100644 --- a/web/client/translations/data.ca-ES.json +++ b/web/client/translations/data.ca-ES.json @@ -1395,11 +1395,15 @@ "userCreated": "Creat!", "deleting": "Suprimint ...", "delete": "Esborrar", - "confirmDeleteUser": "Esteu segur que voleu suprimir aquest usuari?", + "confirmDeleteUser": "Esteu segur que voleu suprimir aquest \"{title}\" usuari?", "errorDelete": "Hi va haver un error en suprimir aquest usuari:", "errorSaving": "Hi va haver un error en desar l'usuari:", "selectedGroups": "Grups seleccionats", - "requiredFiedsMessage": "Calen camps marcats amb asterisc (*)" + "requiredFiedsMessage": "Calen camps marcats amb asterisc (*)", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Grups de cerca ...", @@ -1424,9 +1428,12 @@ "noUsers": "No hi ha usuaris per a aquest grup", "errorSaving": "Hi va haver un error en desar aquest grup", "errorDelete": "Hi va haver un error en suprimir aquest grup", - "confirmDeleteGroup": "Esteu segur que voleu suprimir aquest grup?", + "confirmDeleteGroup": "Esteu segur que voleu suprimir aquest \"{title}\" grup?", "generalInformation": "General", - "members": "Membres" + "members": "Membres", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Compartir", @@ -4304,6 +4311,7 @@ "filterMapsByContext": "Mapes per context", "tags": "Etiquetes", "deleteResource": "Esborrar", + "editResource": "Editar", "deleteResourceTitle": "Esteu segur que voleu suprimir aquest recurs?", "deleteResourceDescription": "Aquest recurs i tots els recursos vinculats seran suprimits", "deleteResourceConfirm": "Esborrar", diff --git a/web/client/translations/data.da-DK.json b/web/client/translations/data.da-DK.json index 28a6514e464..5fad25eef2e 100644 --- a/web/client/translations/data.da-DK.json +++ b/web/client/translations/data.da-DK.json @@ -1293,11 +1293,15 @@ "userCreated": "Created!", "deleting": "Deleting...", "delete": "Delete", - "confirmDeleteUser": "Are you sure you want to delete this user?", + "confirmDeleteUser": "Are you sure you want to delete \"{title}\" user?", "errorDelete": "There was an error deleting this user:", "errorSaving": "There was an error saving the user:", "selectedGroups": "SELECTED GROUPS", - "requiredFiedsMessage": "Fields marked with asterisk (*) are required" + "requiredFiedsMessage": "Fields marked with asterisk (*) are required", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Search Groups...", @@ -1322,9 +1326,12 @@ "noUsers": "No users for this group", "errorSaving": "There was an error saving this group", "errorDelete": "There was an error deleting this group", - "confirmDeleteGroup": "Are you sure you want to delete this group?", + "confirmDeleteGroup": "Are you sure you want to delete this \"{title}\" group?", "generalInformation": "General", - "members": "Members" + "members": "Members", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Share", @@ -3929,6 +3936,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json index dcf5c606b75..77639cc42ff 100644 --- a/web/client/translations/data.de-DE.json +++ b/web/client/translations/data.de-DE.json @@ -440,7 +440,9 @@ "theme_combo": "Thema wählen:", "maps_title": "Karten", "locales_combo": "Sprache:", - "featuredMaps": "Empfohlene Karten" + "featuredMaps": "Empfohlene Karten", + "groupmanagerTab": "Gruppen", + "usermanagerTab": "Benutzer" }, "newMap": "Neue Karte", "newMapEmpty": "Leere Karte", @@ -1433,14 +1435,18 @@ "userCreated": "Erstellt!", "deleting": "Löschen...", "delete": "Löschen", - "confirmDeleteUser": "Sind Sie sicher, das Sie diesen Benutzer löschen möchten?", + "confirmDeleteUser": "Sind Sie sicher, das Sie diesen Benutzer löschen \"{title}\" möchten?", "errorDelete": "Es gab einen Fehler beim Löschen von Benutzer:", "errorSaving": "Es gab einen Fehler beim Speichern von Benutzer:", "selectedGroups": "Ausgewählte Gruppen", - "requiredFiedsMessage": "Felder, welche mit einem Sternchen (*) markiert sind, sind Pflichtfelder" + "requiredFiedsMessage": "Felder, welche mit einem Sternchen (*) markiert sind, sind Pflichtfelder", + "usersFound": "{count, plural, =0 {0 Benutzer gefunden} =1 {1 Benutzer gefunden} other {# Benutzer gefunden}}", + "admin": "Administrator", + "active": "Aktiv", + "inactive": "Inaktiv" }, "usergroups": { - "searchGroups": "Suche Gruppen...", + "searchGroups": "Suche gruppen...", "groups": "Gruppen", "nameLimit": "Der Name ist auf 255 Zeichen limitiert.", "descLimit": "Die Beschreibung ist auf 255 Zeichen limitiert.", @@ -1462,9 +1468,12 @@ "noUsers": "Keine Benutzer für diese Gruppe gefunden", "errorSaving": "Es gab einen Fehler beim Speichern dieser Gruppe", "errorDelete": "Es gab einen Fehler beim Löschen dieser Gruppe", - "confirmDeleteGroup": "Sind Sie sicher, das Sie diese Gruppe löschen möchten?", + "confirmDeleteGroup": "Sind Sie sicher, das Sie diese Gruppe löschen möchten \"{title}\" ?", "generalInformation": "Allgemein", - "members": "Mitglied" + "members": "Mitglied", + "userGroupsFound": "{count, plural, =0 {0 Gruppen gefunden} =1 {1 Gruppe gefunden} other {# Gruppen gefunden}}", + "active": "Aktiv", + "inactive": "Inaktiv" }, "share": { "title": "Teilen", @@ -4347,6 +4356,7 @@ "filterMapsByContext": "Karten nach Kontext", "tags": "Tags", "deleteResource": "Löschen", + "editResource": "Bearbeiten", "deleteResourceTitle": "Möchten Sie diese Ressource wirklich löschen?", "deleteResourceDescription": "Diese Ressource und alle verknüpften Ressourcen werden gelöscht", "deleteResourceConfirm": "Löschen", diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json index 31597b0b72a..bf8490cb8b1 100644 --- a/web/client/translations/data.en-US.json +++ b/web/client/translations/data.en-US.json @@ -402,7 +402,9 @@ "theme_combo": "Select theme:", "maps_title": "Maps", "locales_combo": "Language:", - "featuredMaps": "Featured" + "featuredMaps": "Featured", + "groupmanagerTab": "Groups", + "usermanagerTab": "Users" }, "newMap": "New map", "newMapEmpty": "Empty map", @@ -1378,7 +1380,7 @@ "title": "Manage Accounts", "users": "Users", "manageUsers": "Manage Users", - "searchUsers": "search for users...", + "searchUsers": "Search users...", "newUser": "New User", "editUser": "Edit user", "deleteUser": "Delete User", @@ -1394,14 +1396,18 @@ "userCreated": "Created!", "deleting": "Deleting...", "delete": "Delete", - "confirmDeleteUser": "Are you sure you want to delete this user?", + "confirmDeleteUser": "Are you sure you want to delete \"{title}\" user?", "errorDelete": "There was an error deleting this user:", "errorSaving": "There was an error saving the user:", "selectedGroups": "SELECTED GROUPS", - "requiredFiedsMessage": "Fields marked with asterisk (*) are required" + "requiredFiedsMessage": "Fields marked with asterisk (*) are required", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { - "searchGroups": "Search Groups...", + "searchGroups": "Search groups...", "groups": "Groups", "nameLimit": "The name is limited to 255 characters.", "descLimit": "The description is limited to 255 characters.", @@ -1423,9 +1429,12 @@ "noUsers": "No users for this group", "errorSaving": "There was an error saving this group", "errorDelete": "There was an error deleting this group", - "confirmDeleteGroup": "Are you sure you want to delete this group?", + "confirmDeleteGroup": "Are you sure you want to delete \"{title}\" group?", "generalInformation": "General", - "members": "Members" + "members": "Members", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Share", @@ -4307,7 +4316,6 @@ "createContext": "Create context", "createMapFromContext": "Create map from this context", "viewResourceProperties": "Open properties", - "editResourceProperties": "Edit properties", "uploadImage": "Upload image", "removeThumbnail": "Remove thumbnail", "apply": "Apply", @@ -4318,6 +4326,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json index e65811cdd7a..5176923e25e 100644 --- a/web/client/translations/data.es-ES.json +++ b/web/client/translations/data.es-ES.json @@ -402,7 +402,9 @@ "theme_combo": "Seleccione un tema:", "maps_title": "Mapa", "locales_combo": "Idioma:", - "featuredMaps": "Mapas Destacados" + "featuredMaps": "Mapas Destacados", + "groupmanagerTab": "Grupos", + "usermanagerTab": "Usuarios" }, "newMap": "Nuevo mapa", "newMapEmpty": "Mapa vacío", @@ -1378,7 +1380,7 @@ "title": "Administrador de cuentas de usuario", "users": "Usuarios", "manageUsers": "Administrar los usuarios", - "searchUsers": "buscar usuarios ...", + "searchUsers": "Buscar usuarios ...", "newUser": "Nuevo", "editUser": "Modificar", "deleteUser": "Borrar", @@ -1394,14 +1396,18 @@ "userCreated": "¡Creado!", "deleting": "Borrando...", "delete": "Borrar", - "confirmDeleteUser": "¿Está seguro de borrar el usuario?", + "confirmDeleteUser": "¿Está seguro de borrar el \"{title}\" usuario?", "errorDelete": "Ha ocurrido un error al borrar el usuario:", "errorSaving": "Ha ocurrido un error al guardar el usuario:", "selectedGroups": "GRUPOS SELECCIONADOS", - "requiredFiedsMessage": "Se requiere llenar campos marcados con asterisco (*)" + "requiredFiedsMessage": "Se requiere llenar campos marcados con asterisco (*)", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { - "searchGroups": "buscar grupo ...", + "searchGroups": "Buscar grupo ...", "groups": "Grupos", "nameLimit": "El nombre está limitado a 255 caracteres.", "descLimit": "La descripción está limitada a 255 caracteres.", @@ -1423,9 +1429,12 @@ "noUsers": "El grupo no tiene usuarios", "errorSaving": "Ha ocurrido un error al guardar el grupo", "errorDelete": "Ha ocurrido un error al borrar el grupo", - "confirmDeleteGroup": "¿Está seguro de borrar el grupo?", + "confirmDeleteGroup": "¿Está seguro de borrar el \"{title}\" grupo?", "generalInformation": "General", - "members": "Miembros" + "members": "Miembros", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Compartir", @@ -4308,6 +4317,7 @@ "filterMapsByContext": "Mapas por contexto", "tags": "Etiquetas", "deleteResource": "Eliminar", + "editResource": "Editar", "deleteResourceTitle": "¿Está seguro de que desea eliminar este recurso?", "deleteResourceDescription": "Este recurso y todos los recursos vinculados se eliminarán eliminado", "deleteResourceConfirm": "Eliminar", diff --git a/web/client/translations/data.fi-FI.json b/web/client/translations/data.fi-FI.json index c189793780d..a21c1174149 100644 --- a/web/client/translations/data.fi-FI.json +++ b/web/client/translations/data.fi-FI.json @@ -1038,11 +1038,15 @@ "userCreated": "Luotu!", "deleting": "Poistetaan...", "delete": "Poista", - "confirmDeleteUser": "Haluatko varmasti poistaa tämän käyttäjän?", + "confirmDeleteUser": "Haluatko varmasti poistaa tämän \"{title}\" käyttäjän?", "errorDelete": "Tämän käyttäjän poistamisessa tapahtui virhe:", "errorSaving": "Käyttäjää tallennettaessa tapahtui virhe:", "selectedGroups": "VALITUT RYHMÄT", - "requiredFiedsMessage": "Tähdellä (*) merkityt kentät ovat pakollisia" + "requiredFiedsMessage": "Tähdellä (*) merkityt kentät ovat pakollisia", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Hakuryhmät...", @@ -1067,7 +1071,10 @@ "noUsers": "Ei käyttäjiä tässä ryhmässä", "errorSaving": "Tätä ryhmää tallennettaessa tapahtui virhe", "errorDelete": "Tämän ryhmän poistamisessa tapahtui virhe", - "confirmDeleteGroup": "Haluatko varmasti poistaa tämän ryhmän?" + "confirmDeleteGroup": "Haluatko varmasti poistaa tämän \"{title}\" ryhmän?", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Jaa", @@ -2728,6 +2735,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json index 1069f3c7d17..8f3154406c2 100644 --- a/web/client/translations/data.fr-FR.json +++ b/web/client/translations/data.fr-FR.json @@ -402,7 +402,9 @@ "theme_combo": "Sélectionner un thème :", "maps_title": "Cartes", "locales_combo": "Langue:", - "featuredMaps": "Cartes à la une" + "featuredMaps": "Cartes à la une", + "groupmanagerTab": "Groupes", + "usermanagerTab": "Utilisateurs" }, "newMap": "Nouvelle carte", "newMapEmpty": "Carte vide", @@ -1378,7 +1380,7 @@ "title": "Gérer les comptes", "users": "Utilisateurs", "manageUsers": "Gérer les utilisateurs", - "searchUsers": "rechercher des utilisateurs ...", + "searchUsers": "Rechercher des utilisateurs ...", "newUser": "Nouvel", "editUser": "Modifier", "deleteUser": "Supprimer", @@ -1394,14 +1396,18 @@ "userCreated": "Créé!", "deleting": "Suppression...", "delete": "Effacer", - "confirmDeleteUser": "Êtes-vous sûr de vouloir supprimer cet utilisateur?", + "confirmDeleteUser": "Êtes-vous sûr de vouloir supprimer cet \"{title}\" utilisateur?", "errorDelete": "Une erreur s'est produite lors de la suppression de cet utilisateur:", "errorSaving": "Une erreur s'est produite lors de l'enregistrement de cet utilisateur:", "selectedGroups": "CERTAINS GROUPES", - "requiredFiedsMessage": "Les champs marqués d'un astérisque (*) sont obligatoires" + "requiredFiedsMessage": "Les champs marqués d'un astérisque (*) sont obligatoires", + "usersFound": "{count, plural, =0 {0 Utilisateurs trouvés} =1 {1 Utilisateur trouvé} other {# Utilisateurs trouvés}}", + "admin": "Admin", + "active": "Actif", + "inactive": "Inactif" }, "usergroups": { - "searchGroups": "rechercher des groupes...", + "searchGroups": "Rechercher des groupes...", "groups": "Groupes", "nameLimit": "Le nom est limité à 255 caractères.", "descLimit": "La description est limitée à 255 caractères.", @@ -1423,9 +1429,12 @@ "noUsers": "Aucun utilisateur pour ce groupe", "errorSaving": "Une erreur s'est produite lors de l'enregistrement de ce groupe", "errorDelete": "Une erreur s'est produite lors de la suppression de ce groupe", - "confirmDeleteGroup": "Voulez-vous vraiment supprimer ce groupe?", + "confirmDeleteGroup": "Voulez-vous vraiment supprimer ce \"{title}\" groupe?", "generalInformation": "Général", - "members": "Membres" + "members": "Membres", + "userGroupsFound": "{count, plural, =0 {0 Groupes trouvés} =1 {1 Groupe trouvé} other {# Groupes trouvés}}", + "active": "Actif", + "inactive": "Inactif" }, "share": { "title": "Partager", @@ -4309,6 +4318,7 @@ "filterMapsByContext": "Cartes par contexte", "tags": "Tags", "deleteResource": "Supprimer", + "editResource": "Modifier", "deleteResourceTitle": "Êtes-vous sûr de vouloir supprimer cette ressource?", "deleteResourceDescription": "Cette ressource et toutes les ressources liées seront supprimées", "deleteResourceConfirm": "Supprimer", diff --git a/web/client/translations/data.hr-HR.json b/web/client/translations/data.hr-HR.json index 28689da5ffa..09c23b9ec36 100644 --- a/web/client/translations/data.hr-HR.json +++ b/web/client/translations/data.hr-HR.json @@ -1018,11 +1018,15 @@ "userCreated": "Kreiran!", "deleting": "Brisanje...", "delete": "Briši", - "confirmDeleteUser": "Želite li zaista izbrisati ovoga korisnika?", + "confirmDeleteUser": "Želite li zaista izbrisati ovoga \"{title}\" korisnika?", "errorDelete": "Došlo je do greške prilikom brisanja ovoga korisnika:", "errorSaving": "Došlo je do greške prilikom spremanja ovoga korisnika:", "selectedGroups": "ODABRANE GRUPE", - "requiredFiedsMessage": "Polja označena zvjezdicom (*) su obavezna" + "requiredFiedsMessage": "Polja označena zvjezdicom (*) su obavezna", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Pretraži grupe...", @@ -1047,7 +1051,10 @@ "noUsers": "Nema korisnika za ovu grupu", "errorSaving": "Došlo je do greške prilikom spremanja ove grupe", "errorDelete": "Došlo je do greške prilikom brisanja ove grupe", - "confirmDeleteGroup": "Želite li zaista izbrisati ovu grupu?" + "confirmDeleteGroup": "Želite li zaista izbrisati ovu \"{title}\" grupu?", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Podijeli", @@ -2120,6 +2127,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.is-IS.json b/web/client/translations/data.is-IS.json index 0e145d2d4ba..5152aa901c6 100644 --- a/web/client/translations/data.is-IS.json +++ b/web/client/translations/data.is-IS.json @@ -1305,11 +1305,15 @@ "userCreated": "Created!", "deleting": "Deleting...", "delete": "Delete", - "confirmDeleteUser": "Are you sure you want to delete this user?", + "confirmDeleteUser": "Are you sure you want to delete \"{title}\" user?", "errorDelete": "There was an error deleting this user:", "errorSaving": "There was an error saving the user:", "selectedGroups": "SELECTED GROUPS", - "requiredFiedsMessage": "Fields marked with asterisk (*) are required" + "requiredFiedsMessage": "Fields marked with asterisk (*) are required", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Search Groups...", @@ -1334,9 +1338,12 @@ "noUsers": "No users for this group", "errorSaving": "There was an error saving this group", "errorDelete": "There was an error deleting this group", - "confirmDeleteGroup": "Are you sure you want to delete this group?", + "confirmDeleteGroup": "Are you sure you want to delete this \"{title}\" group?", "generalInformation": "General", - "members": "Members" + "members": "Members", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Share", @@ -3962,6 +3969,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json index cb5f968ecd1..11c7571e2d1 100644 --- a/web/client/translations/data.it-IT.json +++ b/web/client/translations/data.it-IT.json @@ -403,7 +403,9 @@ "theme_combo": "Seleziona Tema:", "maps_title": "Mappe", "locales_combo": "Lingua:", - "featuredMaps": "In Evidenza" + "featuredMaps": "In Evidenza", + "groupmanagerTab": "Gruppi", + "usermanagerTab": "Utenti" }, "newMap": "Nuova Mappa", "newMapEmpty": "Mappa vuota", @@ -1395,11 +1397,15 @@ "userCreated": "Creato!", "deleting": "Rimozione...", "delete": "Rimuovi", - "confirmDeleteUser": "Sei sicuro di voler cancellare questo utente?", + "confirmDeleteUser": "Sei sicuro di voler cancellare questo \"{title}\" utente?", "errorDelete": "Si è verificato un errore rimuovendo l'utente:", "errorSaving": "Si è verificato un errore salvando questo utente:", "selectedGroups": "GRUPPI SELEZIONATI", - "requiredFiedsMessage": "I campi contrassegnati con l'asterisco (*) sono obbligatori" + "requiredFiedsMessage": "I campi contrassegnati con l'asterisco (*) sono obbligatori", + "usersFound": "{count, plural, =0 {0 Utenti trovati} =1 {1 Utente trovato} other {# Utenti trovati}}", + "admin": "Amministratore", + "active": "Attivo", + "inactive": "Inattivo" }, "usergroups": { "searchGroups": "Cerca gruppi...", @@ -1424,9 +1430,12 @@ "noUsers": "Nessun utente membro di questo gruppo", "errorSaving": "Si è verificato un errore salvando questo gruppo:", "errorDelete": "Si è verificato un errore rimuovendo questo utente:", - "confirmDeleteGroup": "Sei sicuro di voler cancellare questo gruppo?", + "confirmDeleteGroup": "Sei sicuro di voler cancellare questo \"{title}\" gruppo?", "generalInformation": "Generale", - "members": "Membri" + "members": "Membri", + "userGroupsFound": "{count, plural, =0 {0 Gruppi trovati} =1 {1 Gruppo trovato} other {# Gruppi trovati}}", + "active": "Attivo", + "inactive": "Inattivo" }, "share": { "title": "Condividi", @@ -4311,6 +4320,7 @@ "filterMapsByContext": "Mappe per contesto", "tags": "Tag", "deleteResource": "Elimina", + "editResource": "Modifica", "deleteResourceTitle": "Vuoi davvero eliminare questa risorsa?", "deleteResourceDescription": "Questa risorsa e tutte le risorse collegate verranno eliminate", "deleteResourceConfirm": "Elimina", diff --git a/web/client/translations/data.nl-NL.json b/web/client/translations/data.nl-NL.json index d306d3d0e70..c0d3ec979dc 100644 --- a/web/client/translations/data.nl-NL.json +++ b/web/client/translations/data.nl-NL.json @@ -1373,11 +1373,15 @@ "userCreated": "Aangemaakt!", "deleting": "Verwijderen...", "delete": "Verwijder", - "confirmDeleteUser": "Weet u zeker dat u deze gebruiker wilt verwijderen??", + "confirmDeleteUser": "Weet u zeker dat u deze gebruiker wilt \"{title}\" verwijderen??", "errorDelete": "Er is een fout opgetreden bij het verwijderen van deze gebruiker:", "errorSaving": "Er is een fout opgetreden bij het opslaan van de gebruiker:", "selectedGroups": "GESELECTEERDE GROEPEN", - "requiredFiedsMessage": "Velden gemarkeerd met een ster (*) zijn verplicht" + "requiredFiedsMessage": "Velden gemarkeerd met een ster (*) zijn verplicht", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Zoek groepen...", @@ -1402,9 +1406,12 @@ "noUsers": "Geen gebruikers voor deze groep", "errorSaving": "Er is een fout opgetreden bij het opslaan van deze groep", "errorDelete": "Er is een fout opgetreden bij het verwijderen van deze groep", - "confirmDeleteGroup": "Weet u zeker dat u deze groep wilt verwijderen?", + "confirmDeleteGroup": "Weet u zeker dat u deze groep wilt \"{title}\" verwijderen?", "generalInformation": "Algemeen", - "members": "Leden" + "members": "Leden", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Delen", @@ -4293,6 +4300,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.pt-PT.json b/web/client/translations/data.pt-PT.json index 27d2c837c19..ad6241b3ae7 100644 --- a/web/client/translations/data.pt-PT.json +++ b/web/client/translations/data.pt-PT.json @@ -996,11 +996,15 @@ "userCreated": "Created!", "deleting": "Deleting...", "delete": "Delete", - "confirmDeleteUser": "Are you sure you want to delete this user?", + "confirmDeleteUser": "Are you sure you want to delete \"{title}\" user?", "errorDelete": "There was an error deleting this user:", "errorSaving": "There was an error saving the user:", "selectedGroups": "SELECTED GROUPS", - "requiredFiedsMessage": "Fields marked with asterisk (*) are required" + "requiredFiedsMessage": "Fields marked with asterisk (*) are required", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Search Groups...", @@ -1025,7 +1029,10 @@ "noUsers": "No users for this group", "errorSaving": "There was an error saving this group", "errorDelete": "There was an error deleting this group", - "confirmDeleteGroup": "Are you sure you want to delete this group?" + "confirmDeleteGroup": "Are you sure you want to delete this \"{title}\" group?", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Share", @@ -2078,6 +2085,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.sk-SK.json b/web/client/translations/data.sk-SK.json index b27f3828568..22ba1451e5a 100644 --- a/web/client/translations/data.sk-SK.json +++ b/web/client/translations/data.sk-SK.json @@ -1238,11 +1238,15 @@ "userCreated": "Vytvorené!", "deleting": "Odstraňuje sa...", "delete": "Odstrániť", - "confirmDeleteUser": "Si si istý, že chceš odstrániť tohto používateľa?", + "confirmDeleteUser": "Si si istý, že chceš odstrániť tohto \"{title}\" používateľa?", "errorDelete": "Pri odstraňovaní tohto používateľa sa vyskytla chyba:", "errorSaving": "Pri ukladaní používateľa sa vyskytla chyba:", "selectedGroups": "VYBRANÉ SKUPINY", - "requiredFiedsMessage": "Polia označené hviezdičkou (*) sú povinné" + "requiredFiedsMessage": "Polia označené hviezdičkou (*) sú povinné", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Hladať skupiny...", @@ -1267,7 +1271,10 @@ "noUsers": "V tejt skupine nie sú žiadni používatelia", "errorSaving": "Pri ukladaní tejto skupiny sa vyskytla chyba", "errorDelete": "Pri odstraňovaní tejto skupiny sa vyskytla chyba", - "confirmDeleteGroup": "Si si istý, že chceš odstrániť túto skupinu?" + "confirmDeleteGroup": "Si si istý, že chceš odstrániť túto \"{title}\" skupinu?", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Zdieľať", @@ -3467,6 +3474,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.sv-SE.json b/web/client/translations/data.sv-SE.json index 6ceae65d11d..a3c4e3e9442 100644 --- a/web/client/translations/data.sv-SE.json +++ b/web/client/translations/data.sv-SE.json @@ -1208,11 +1208,15 @@ "userCreated": "Skapad!", "deleting": "Radera ...", "delete": "Ta bort", - "confirmDeleteUser": "Är du säker på att du vill ta bort den här användaren?", + "confirmDeleteUser": "Är du säker på att du vill ta bort den här \"{title}\" användaren?", "errorDelete": "Ett fel uppstod när den här användaren skulle raderas:", "errorSaving": "Ett fel uppstod när användaren skulle sparas:", "selectedGroups": "VALDA GRUPPER", - "requiredFiedsMessage": "Fält markerade med asterisk (*) är obligatoriska" + "requiredFiedsMessage": "Fält markerade med asterisk (*) är obligatoriska", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Sök grupper ...", @@ -1237,7 +1241,10 @@ "noUsers": "Inga användare för den här gruppen", "errorSaving": "Ett fel uppstod när den här gruppen skulle sparas", "errorDelete": "Ett fel uppstod när den här gruppen skulle raderas", - "confirmDeleteGroup": "Är du säker på att du vill ta bort den här gruppen?" + "confirmDeleteGroup": "Är du säker på att du vill ta bort den här \"{title}\" gruppen?", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Dela", @@ -3500,6 +3507,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.vi-VN.json b/web/client/translations/data.vi-VN.json index 54dee0cecd1..38317571743 100644 --- a/web/client/translations/data.vi-VN.json +++ b/web/client/translations/data.vi-VN.json @@ -1716,7 +1716,7 @@ }, "usergroups": { "addMember": "Thêm thành viên:", - "confirmDeleteGroup": "Bạn có chắc chắn muốn xóa nhóm này?", + "confirmDeleteGroup": "Bạn có chắc chắn muốn xóa nhóm \"{title}\" này?", "createGroup": "Tạo mới", "creatingGroup": "Đang tạo...", "deleteGroup": "Xóa nhóm", @@ -1737,10 +1737,13 @@ "noUsers": "Không có người dùng cho nhóm này", "removeUser": "Xóa người dùng", "saveGroup": "Lưu lại", - "searchGroups": "Tìm kiếm nhóm ..." + "searchGroups": "Tìm kiếm nhóm ...", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "users": { - "confirmDeleteUser": "Bạn có chắc chắn muốn xóa người dùng này?", + "confirmDeleteUser": "Bạn có chắc chắn muốn xóa người dùng \"{title}\" này?", "createUser": "Tạo mới", "creatingUser": "Đang tạo...", "delete": "Xóa", @@ -1763,7 +1766,11 @@ "title": "Quản lý tài khoản", "userCreated": "Đã tạo!", "userSaved": "Đã lưu!", - "users": "Người dùng" + "users": "Người dùng", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "vectorstyler": { "addrulebtn": "Thêm quy tắc", @@ -2098,7 +2105,6 @@ "createContext": "Create context", "createMapFromContext": "Create map from this context", "viewResourceProperties": "Open properties", - "editResourceProperties": "Edit properties", "uploadImage": "Upload image", "removeThumbnail": "Remove thumbnail", "apply": "Apply", @@ -2109,6 +2115,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete", diff --git a/web/client/translations/data.zh-ZH.json b/web/client/translations/data.zh-ZH.json index 839f5ebaa94..04796bc88cd 100644 --- a/web/client/translations/data.zh-ZH.json +++ b/web/client/translations/data.zh-ZH.json @@ -975,11 +975,15 @@ "userCreated": "Created!", "deleting": "Deleting...", "delete": "Delete", - "confirmDeleteUser": "Are you sure you want to delete this user?", + "confirmDeleteUser": "Are you sure you want to delete \"{title}\" user?", "errorDelete": "There was an error deleting this user:", "errorSaving": "There was an error saving the user:", "selectedGroups": "SELECTED GROUPS", - "requiredFiedsMessage": "Fields marked with asterisk (*) are required" + "requiredFiedsMessage": "Fields marked with asterisk (*) are required", + "usersFound": "{count, plural, =0 {0 Users found} =1 {1 User found} other {# Users found}}", + "admin": "Admin", + "active": "Active", + "inactive": "Inactive" }, "usergroups": { "searchGroups": "Search Groups...", @@ -1004,7 +1008,10 @@ "noUsers": "No users for this group", "errorSaving": "There was an error saving this group", "errorDelete": "There was an error deleting this group", - "confirmDeleteGroup": "Are you sure you want to delete this group?" + "confirmDeleteGroup": "Are you sure you want to delete this \"{title}\" group?", + "userGroupsFound": "{count, plural, =0 {0 Groups found} =1 {1 Group found} other {# Groups found}}", + "active": "Active", + "inactive": "Inactive" }, "share": { "title": "Share", @@ -2043,7 +2050,6 @@ "createContext": "Create context", "createMapFromContext": "Create map from this context", "viewResourceProperties": "Open properties", - "editResourceProperties": "Edit properties", "uploadImage": "Upload image", "removeThumbnail": "Remove thumbnail", "apply": "Apply", @@ -2054,6 +2060,7 @@ "filterMapsByContext": "Maps by context", "tags": "Tags", "deleteResource": "Delete", + "editResource": "Edit", "deleteResourceTitle": "Are you sure you want to delete this resource?", "deleteResourceDescription": "This resource and all linked resources will be deleted", "deleteResourceConfirm": "Delete",