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 ();
- }
-
- 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",