From 64a66f7d2a053bd3e13070179f511a5ff4da800e Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Wed, 13 Oct 2021 13:37:52 +0300 Subject: [PATCH] feat(core-blocks): CB-1318 add "select all" logic to table --- .../packages/core-blocks/src/Table/Table.tsx | 45 +++++++++++++++-- .../core-blocks/src/Table/TableContext.tsx | 9 ++++ .../core-blocks/src/Table/TableItem.tsx | 3 ++ .../core-blocks/src/Table/TableSelect.tsx | 42 ++++++++++++++++ .../core-blocks/src/Table/getSelectedItems.ts | 19 +++++++ webapp/packages/core-blocks/src/index.ts | 2 + .../ConnectionAccessGrantedList.tsx | 39 +++++++-------- .../ConnectionAccess/ConnectionAccessList.tsx | 49 ++++++++++--------- .../ConnectionAccessTableInnerHeader.tsx | 9 ++-- .../ConnectionAccessTableItem.tsx | 21 +++++--- .../src/CommonDialog/RenameDialog.tsx | 2 +- .../core-localization/src/locales/en.ts | 2 + .../core-localization/src/locales/ru.ts | 2 + .../GrantedConnections/ConnectionList.tsx | 16 ++++-- .../GrantedConnectionsList.tsx | 16 +++--- .../GrantedConnectionsTableInnerHeader.tsx | 9 ++-- .../GrantedConnectionsTableItem.tsx | 20 +++++--- .../Roles/GrantedUsers/GrantedUserList.tsx | 48 ++++++++++-------- .../GrantedUsersTableInnerHeader.tsx | 10 ++-- .../GrantedUsers/GrantedUsersTableItem.tsx | 21 +++++--- .../Roles/GrantedUsers/UserList.tsx | 37 ++++++++------ .../plugin-authentication/src/locales/en.ts | 1 + .../plugin-authentication/src/locales/ru.ts | 1 + .../PublicConnectionFormService.ts | 2 +- 24 files changed, 300 insertions(+), 125 deletions(-) create mode 100644 webapp/packages/core-blocks/src/Table/TableSelect.tsx create mode 100644 webapp/packages/core-blocks/src/Table/getSelectedItems.ts diff --git a/webapp/packages/core-blocks/src/Table/Table.tsx b/webapp/packages/core-blocks/src/Table/Table.tsx index 462d805df1..5a9ba2e7d1 100644 --- a/webapp/packages/core-blocks/src/Table/Table.tsx +++ b/webapp/packages/core-blocks/src/Table/Table.tsx @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { observable, computed } from 'mobx'; +import { observable, computed, action } from 'mobx'; import { observer } from 'mobx-react-lite'; import { useState, useCallback, useMemo } from 'react'; import styled, { use } from 'reshadow'; @@ -14,23 +14,59 @@ import styled, { use } from 'reshadow'; import { useStyles } from '@cloudbeaver/core-theming'; import { useObjectRef } from '../useObjectRef'; -import { TableContext, ITableContext } from './TableContext'; +import { useObservableRef } from '../useObservableRef'; +import { TableContext, ITableContext, ITableState } from './TableContext'; interface Props { + keys?: any[]; selectedItems?: Map; expandedItems?: Map; size?: 'big'; className?: string; onSelect?: (item: any, state: boolean) => void; + isItemSelectable?: (item: any) => boolean; } export const Table = observer(function Table({ - selectedItems, expandedItems, className, size, children, onSelect, + keys, isItemSelectable, selectedItems, expandedItems, className, size, children, onSelect, }) { const props = useObjectRef({ onSelect }); const [selected] = useState>(() => selectedItems || observable(new Map())); const [expanded] = useState>(() => expandedItems || observable(new Map())); + + const state: ITableState = useObservableRef(() => ({ + get selectableItems() { + if (!this.keys) { + return []; + } + + if (!this.isItemSelectable) { + return this.keys; + } + + return this.keys.filter(this.isItemSelectable); + }, + get tableSelected() { + return this.selectableItems.length > 0 && this.selectableItems.every(item => selected.get(item)); + }, + selectTable() { + const tableSelected = this.tableSelected; + for (const item of this.selectableItems) { + selected.set(item, !tableSelected); + } + }, + + }), { + keys: observable.ref, + isItemSelectable: observable.ref, + selectableItems: computed, + tableSelected: computed, + selectTable: action.bound, + }, { keys, isItemSelectable }); + + const isExpanded = useMemo(() => computed(() => Array.from(expanded.values()).some(Boolean)), [expanded]); + const setItemSelect = useCallback((item: any, state: boolean) => { selected.set(item, state); if (props.onSelect) { @@ -40,11 +76,12 @@ export const Table = observer(function Table({ const setItemExpand = useCallback((item: any, state: boolean) => expanded.set(item, state), []); const clearSelection = useCallback(() => selected.clear(), []); const collapse = useCallback(() => expanded.clear(), []); - const isExpanded = useMemo(() => computed(() => Array.from(expanded.values()).some(Boolean)), [expanded]); const [context] = useState(() => ({ + state, selectedItems: selected, expandedItems: expanded, + isItemSelectable, setItemExpand, setItemSelect, clearSelection, diff --git a/webapp/packages/core-blocks/src/Table/TableContext.tsx b/webapp/packages/core-blocks/src/Table/TableContext.tsx index 93a4c4e16d..fbd735c8b2 100644 --- a/webapp/packages/core-blocks/src/Table/TableContext.tsx +++ b/webapp/packages/core-blocks/src/Table/TableContext.tsx @@ -8,7 +8,16 @@ import { createContext } from 'react'; +export interface ITableState { + keys?: any[]; + readonly selectableItems: any[]; + readonly tableSelected: boolean; + selectTable: () => void; + isItemSelectable?: (item: any) => boolean; +} + export interface ITableContext { + state: ITableState; selectedItems: Map; expandedItems: Map; setItemSelect: (item: any, state: boolean) => void; diff --git a/webapp/packages/core-blocks/src/Table/TableItem.tsx b/webapp/packages/core-blocks/src/Table/TableItem.tsx index 81cb2c9290..85ef15fe16 100644 --- a/webapp/packages/core-blocks/src/Table/TableItem.tsx +++ b/webapp/packages/core-blocks/src/Table/TableItem.tsx @@ -30,6 +30,7 @@ interface Props { selectOnItem?: boolean; selectDisabled?: boolean; disabled?: boolean; + title?: string; className?: string; onClick?: () => void; onDoubleClick?: () => void; @@ -42,6 +43,7 @@ export const TableItem = observer(function TableItem({ selectDisabled = false, disabled, children, + title, className, onClick, onDoubleClick, @@ -91,6 +93,7 @@ export const TableItem = observer(function TableItem({ return styled(styles)( (function TableSelect({ id, disabled, tooltip, className }) { + const tableContext = useContext(TableContext); + const translate = useTranslate(); + + if (!tableContext) { + throw new Error('Context must be provided'); + } + + return ( + + ); +}); diff --git a/webapp/packages/core-blocks/src/Table/getSelectedItems.ts b/webapp/packages/core-blocks/src/Table/getSelectedItems.ts new file mode 100644 index 0000000000..353bcc539b --- /dev/null +++ b/webapp/packages/core-blocks/src/Table/getSelectedItems.ts @@ -0,0 +1,19 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +export function getSelectedItems(items: Map): any[] { + const result: any[] = []; + + for (const [key, value] of items) { + if (value) { + result.push(key); + } + } + + return result; +} diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 663c1083ed..446ad9034c 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -53,6 +53,8 @@ export * from './Table/TableItemSelect'; export * from './Table/TableItemSeparator'; export * from './Table/useTable'; export * from './Table/TableState'; +export * from './Table/TableSelect'; +export * from './Table/getSelectedItems'; export * from './Tabs/ITab'; export * from './Tabs/TabContext'; diff --git a/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessGrantedList.tsx b/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessGrantedList.tsx index 53fc8016bb..93fd64a785 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessGrantedList.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessGrantedList.tsx @@ -6,9 +6,9 @@ * you may not use this file except in compliance with the License. */ -import { computed, observable } from 'mobx'; +import { observable } from 'mobx'; import { observer } from 'mobx-react-lite'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useState } from 'react'; import styled, { css } from 'reshadow'; import type { RoleInfo } from '@cloudbeaver/core-authentication'; @@ -20,7 +20,9 @@ import { BASE_CONTAINERS_STYLES, Group, Button, - useObjectRef + useObjectRef, + getComputed, + getSelectedItems } from '@cloudbeaver/core-blocks'; import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization'; import type { AdminUserInfoFragment } from '@cloudbeaver/core-sdk'; @@ -78,25 +80,20 @@ export const ConnectionAccessGrantedList = observer(function ConnectionAc const translate = useTranslate(); const [selectedSubjects] = useState>(() => observable(new Map())); const [filterState] = useState(() => observable({ filterValue: '' })); - const selectedList = useMemo(() => computed( - () => Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key) - ), [selectedSubjects]); + + const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v)); const revoke = useCallback(() => { - props.onRevoke(selectedList.get()); + props.onRevoke(getSelectedItems(selectedSubjects)); selectedSubjects.clear(); }, []); - const roles = useMemo(() => computed(() => getFilteredRoles( - grantedRoles, filterState.filterValue - )), [filterState, grantedRoles]); - - const users = useMemo(() => computed(() => getFilteredUsers( - grantedUsers, filterState.filterValue - )), [filterState, grantedUsers]); + const roles = getFilteredRoles(grantedRoles, filterState.filterValue); + const users = getFilteredUsers(grantedUsers, filterState.filterValue); + const keys = roles.map(role => role.roleId).concat(users.map(user => user.userId)); let tableInfoText: TLocalizationToken = 'connections_connection_access_admin_info'; - if (!roles.get().length && !users.get().length) { + if (!keys.length) { if (filterState.filterValue) { tableInfoText = 'ui_search_no_result_placeholder'; } else { @@ -108,34 +105,36 @@ export const ConnectionAccessGrantedList = observer(function ConnectionAc - + - - +
+ {translate(tableInfoText)} - {roles.get().map(role => ( + {roles.map(role => ( ))} - {users.get().map(user => ( + {users.map(user => ( (function ConnectionAccessLis const translate = useTranslate(); const [selectedSubjects] = useState>(() => observable(new Map())); const [filterState] = useState(() => observable({ filterValue: '' })); - const selectedList = useMemo(() => computed( - () => Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key) - ), [selectedSubjects]); + + const roles = getFilteredRoles(roleList, filterState.filterValue); + const users = getFilteredUsers(userList, filterState.filterValue); + const keys = roles.map(role => role.roleId).concat(users.map(user => user.userId)); + + const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v)); const grant = useCallback(() => { - props.onGrant(selectedList.get()); + props.onGrant(getSelectedItems(selectedSubjects)); selectedSubjects.clear(); }, []); - const roles = useMemo(() => computed(() => getFilteredRoles( - roleList, filterState.filterValue - )), [filterState, roleList]); - - const users = useMemo(() => computed(() => getFilteredUsers( - userList, filterState.filterValue - )), [filterState, userList]); - return styled(style)( - + -
- +
!grantedSubjects.includes(item)} + > + - {!roles.get().length && !users.get().length && filterState.filterValue && ( + {!keys.length && filterState.filterValue && ( {translate('ui_search_no_result_placeholder')} )} - {roles.get().map(role => ( + {roles.map(role => ( ))} - {users.get().map(user => ( + {users.map(user => ( ))} diff --git a/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessTableHeader/ConnectionAccessTableInnerHeader.tsx b/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessTableHeader/ConnectionAccessTableInnerHeader.tsx index 930c1bb277..91c73e07ad 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessTableHeader/ConnectionAccessTableInnerHeader.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessTableHeader/ConnectionAccessTableInnerHeader.tsx @@ -9,11 +9,12 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; -import { TableColumnHeader, TableHeader } from '@cloudbeaver/core-blocks'; +import { TableColumnHeader, TableHeader, TableSelect } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import { composes, useStyles } from '@cloudbeaver/core-theming'; interface Props { + disabled?: boolean; className?: string; } @@ -32,11 +33,13 @@ const styles = composes( ` ); -export const ConnectionAccessTableInnerHeader = observer(function ConnectionAccessTableInnerHeader({ className }) { +export const ConnectionAccessTableInnerHeader = observer(function ConnectionAccessTableInnerHeader({ disabled, className }) { const translate = useTranslate(); return styled(useStyles(styles))( - + + + {translate('connections_connection_access_user_or_role_name')} {translate('connections_connection_description')} diff --git a/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessTableItem.tsx b/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessTableItem.tsx index b8fe47f081..528529e6f7 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessTableItem.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/ConnectionAccess/ConnectionAccessTableItem.tsx @@ -7,17 +7,18 @@ */ import { observer } from 'mobx-react-lite'; +import { useContext } from 'react'; import styled, { css } from 'reshadow'; -import { StaticImage, TableColumnValue, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks'; -import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization'; +import { StaticImage, TableColumnValue, TableContext, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks'; interface Props { id: any; name: string; icon: string; disabled: boolean; - iconTooltip?: TLocalizationToken; + iconTooltip?: string; + tooltip?: string; description?: string; className?: string; } @@ -30,12 +31,20 @@ const style = css` `; export const ConnectionAccessTableItem = observer(function ConnectionAccessTableItem({ - id, name, description, icon, iconTooltip, disabled, className, + id, name, description, icon, iconTooltip, tooltip, disabled: tableDisabled, className, }) { - const translate = useTranslate(); + const tableContext = useContext(TableContext); + + if (!tableContext) { + throw new Error('Context must be provided'); + } + + const disabled = tableDisabled || tableContext.state.isItemSelectable?.(id) === false; + return styled(style)( (function ConnectionAcce - + {name} {description} diff --git a/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.tsx b/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.tsx index 16c867d77b..5823f486ff 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/RenameDialog.tsx @@ -75,7 +75,7 @@ export const RenameDialog: DialogComponent = functi mod={['unelevated']} onClick={() => resolveDialog(name)} > - + )} diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 771fb1063a..41aaebe732 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -45,6 +45,8 @@ export const defaultENLocale = [ ['ui_search_no_result_placeholder', 'No results have been found.'], ['ui_save_reminder', 'You have unsaved changes.'], ['ui_yes', 'Yes'], + ['ui_select_all', 'Select all'], + ['ui_reconnect', 'Reconnect'], ['root_permission_denied', 'You have no permissions'], ['app_root_session_expire_warning_title', 'Session is about to expire'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index 6afc6ffb5c..a5a38fe714 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -43,6 +43,8 @@ export const defaultRULocale = [ ['ui_search_no_result_placeholder', 'По вашему запросу ничего не найдено.'], ['ui_save_reminder', 'У вас есть несохраненные изменения.'], ['ui_yes', 'Да'], + ['ui_select_all', 'Выбрать все'], + ['ui_reconnect', 'Переподключиться'], ['root_permission_denied', 'Отказано в доступе'], ['app_root_session_expire_warning_title', 'Ваша сессия скоро закончится'], diff --git a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/ConnectionList.tsx b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/ConnectionList.tsx index 6617c5f3a6..523c73bfb1 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/ConnectionList.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/ConnectionList.tsx @@ -21,6 +21,7 @@ import { Button, useObjectRef, getComputed, + getSelectedItems, } from '@cloudbeaver/core-blocks'; import { DBDriverResource } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; @@ -85,12 +86,12 @@ export const ConnectionList = observer(function ConnectionList({ const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v)); const grant = useCallback(() => { - const selectedList = Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key); - props.onGrant(selectedList); + props.onGrant(getSelectedItems(selectedSubjects)); selectedSubjects.clear(); }, []); const connections = getFilteredConnections(connectionList, filterState.filterValue); + const keys = connections.map(connection => connection.id); return styled(style)( @@ -99,8 +100,12 @@ export const ConnectionList = observer(function ConnectionList({ -
- +
!grantedSubjects.includes(item)} + > + {!connections.length && filterState.filterValue && ( @@ -116,9 +121,10 @@ export const ConnectionList = observer(function ConnectionList({ key={connection.id} id={connection.id} name={connection.name} + tooltip={connection.name} host={`${connection.host || ''}${connection.host && connection.port ? ':' + connection.port : ''}`} icon={driver?.icon} - disabled={disabled || grantedSubjects.includes(connection.id)} + disabled={disabled} /> ); })} diff --git a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsList.tsx b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsList.tsx index 1372c0c311..c565ba3bdc 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsList.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsList.tsx @@ -19,7 +19,8 @@ import { BASE_CONTAINERS_STYLES, Group, Button, - useObjectRef + useObjectRef, + getSelectedItems } from '@cloudbeaver/core-blocks'; import { getComputed } from '@cloudbeaver/core-blocks'; import { DBDriverResource } from '@cloudbeaver/core-connections'; @@ -82,16 +83,16 @@ export const GrantedConnectionList = observer(function GrantedConnectionL const [selectedSubjects] = useState>(() => observable(new Map())); const [filterState] = useState(() => observable({ filterValue: '' })); + const connections = getFilteredConnections(grantedConnections, filterState.filterValue); + const keys = connections.map(connection => connection.id); + const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v)); const revoke = useCallback(() => { - const selectedList = Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key); - props.onRevoke(selectedList); + props.onRevoke(getSelectedItems(selectedSubjects)); selectedSubjects.clear(); }, []); - const connections = getFilteredConnections(grantedConnections, filterState.filterValue); - let tableInfoText: TLocalizationToken | null = null; if (!connections.length) { if (filterState.filterValue) { @@ -109,8 +110,8 @@ export const GrantedConnectionList = observer(function GrantedConnectionL -
- +
+ {tableInfoText && ( @@ -126,6 +127,7 @@ export const GrantedConnectionList = observer(function GrantedConnectionL key={connection.id} id={connection.id} name={connection.name} + tooltip={connection.name} host={`${connection.host || ''}${connection.port ? ':' + connection.port : ''}`} icon={driver?.icon} disabled={disabled} diff --git a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsTableHeader/GrantedConnectionsTableInnerHeader.tsx b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsTableHeader/GrantedConnectionsTableInnerHeader.tsx index 8ae80ac98b..b643bf96ee 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsTableHeader/GrantedConnectionsTableInnerHeader.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsTableHeader/GrantedConnectionsTableInnerHeader.tsx @@ -9,11 +9,12 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; -import { TableColumnHeader, TableHeader } from '@cloudbeaver/core-blocks'; +import { TableColumnHeader, TableHeader, TableSelect } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import { composes, useStyles } from '@cloudbeaver/core-theming'; interface Props { + disabled?: boolean; className?: string; } @@ -32,11 +33,13 @@ const styles = composes( ` ); -export const GrantedConnectionsTableInnerHeader = observer(function GrantedConnectionsTableInnerHeader({ className }) { +export const GrantedConnectionsTableInnerHeader = observer(function GrantedConnectionsTableInnerHeader({ disabled, className }) { const translate = useTranslate(); return styled(useStyles(styles))( - + + + {translate('connections_connection_name')} {translate('connections_connection_address')} diff --git a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsTableItem.tsx b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsTableItem.tsx index 97b7dca4c7..a995673feb 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsTableItem.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedConnections/GrantedConnectionsTableItem.tsx @@ -7,10 +7,10 @@ */ import { observer } from 'mobx-react-lite'; +import { useContext } from 'react'; import styled, { css } from 'reshadow'; -import { StaticImage, TableColumnValue, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks'; -import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization'; +import { StaticImage, TableColumnValue, TableContext, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks'; interface Props { id: any; @@ -18,7 +18,8 @@ interface Props { disabled: boolean; host?: string; icon?: string; - iconTooltip?: TLocalizationToken; + iconTooltip?: string; + tooltip?: string; className?: string; } @@ -30,13 +31,20 @@ const style = css` `; export const GrantedConnectionsTableItem = observer(function GrantedConnectionsTableItem({ - id, name, host, icon, iconTooltip, disabled, className, + id, name, host, icon, iconTooltip, tooltip, disabled: tableDisabled, className, }) { - const translate = useTranslate(); + const tableContext = useContext(TableContext); + + if (!tableContext) { + throw new Error('Context must be provided'); + } + + const disabled = tableDisabled || tableContext.state.isItemSelectable?.(id) === false; return styled(style)( (function GrantedConne - {icon && } + {icon && } {name} {host && host} diff --git a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUserList.tsx b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUserList.tsx index f53ef30b8d..a1d9a9433a 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUserList.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUserList.tsx @@ -21,7 +21,8 @@ import { Group, Button, useObjectRef, - getComputed + getComputed, + getSelectedItems } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization'; @@ -68,10 +69,7 @@ interface Props { } export const GrantedUserList = observer(function GrantedUserList({ - grantedUsers, - disabled, - onRevoke, - onEdit, + grantedUsers, disabled, onRevoke, onEdit, }) { const props = useObjectRef({ onRevoke, onEdit }); const style = useStyles(styles, BASE_CONTAINERS_STYLES); @@ -84,14 +82,14 @@ export const GrantedUserList = observer(function GrantedUserList({ const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v)); + const users = getFilteredUsers(grantedUsers, filterState.filterValue); + const keys = users.map(user => user.userId); + const revoke = useCallback(() => { - const selectedList = Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key); - props.onRevoke(selectedList); + props.onRevoke(getSelectedItems(selectedSubjects)); selectedSubjects.clear(); }, []); - const users = getFilteredUsers(grantedUsers, filterState.filterValue); - let tableInfoText: TLocalizationToken | null = null; if (!users.length) { if (filterState.filterValue) { @@ -109,8 +107,12 @@ export const GrantedUserList = observer(function GrantedUserList({ -
- +
!usersResource.isActiveUser(item)} + > + {tableInfoText && ( @@ -119,16 +121,20 @@ export const GrantedUserList = observer(function GrantedUserList({ )} - {users.map(user => ( - - ))} + {users.map(user => { + const activeUser = usersResource.isActiveUser(user.userId); + return ( + + ); + })}
diff --git a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUsersTableHeader/GrantedUsersTableInnerHeader.tsx b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUsersTableHeader/GrantedUsersTableInnerHeader.tsx index b0b7c7d852..97132373b3 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUsersTableHeader/GrantedUsersTableInnerHeader.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUsersTableHeader/GrantedUsersTableInnerHeader.tsx @@ -9,11 +9,12 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; -import { TableColumnHeader, TableHeader } from '@cloudbeaver/core-blocks'; +import { TableColumnHeader, TableHeader, TableSelect } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import { composes, useStyles } from '@cloudbeaver/core-theming'; interface Props { + disabled?: boolean; className?: string; } @@ -32,11 +33,14 @@ const styles = composes( ` ); -export const GrantedUsersTableInnerHeader = observer(function GrantedUsersTableInnerHeader({ className }) { +export const GrantedUsersTableInnerHeader = observer(function GrantedUsersTableInnerHeader({ disabled, className }) { const translate = useTranslate(); + return styled(useStyles(styles))( - + + + {translate('administration_roles_role_granted_users_user_id')} diff --git a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUsersTableItem.tsx b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUsersTableItem.tsx index 0ced640aba..22c7404195 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUsersTableItem.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/GrantedUsersTableItem.tsx @@ -7,17 +7,18 @@ */ import { observer } from 'mobx-react-lite'; +import { useContext } from 'react'; import styled, { css } from 'reshadow'; -import { StaticImage, TableColumnValue, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks'; -import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization'; +import { StaticImage, TableColumnValue, TableContext, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks'; interface Props { id: any; name: string; icon: string; disabled: boolean; - iconTooltip?: TLocalizationToken; + iconTooltip?: string; + tooltip?: string; className?: string; } @@ -29,12 +30,20 @@ const style = css` `; export const GrantedUsersTableItem = observer(function GrantedUsersTableItem({ - id, name, icon, iconTooltip, disabled, className, + id, name, icon, iconTooltip, tooltip, disabled: tableDisabled, className, }) { - const translate = useTranslate(); + const tableContext = useContext(TableContext); + + if (!tableContext) { + throw new Error('Context must be provided'); + } + + const disabled = tableDisabled || tableContext.state.isItemSelectable?.(id) === false; + return styled(style)( (function GrantedUsersTableI - + {name} ); diff --git a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/UserList.tsx b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/UserList.tsx index 81b61380e5..522c73be74 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/UserList.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Roles/GrantedUsers/UserList.tsx @@ -22,6 +22,7 @@ import { Button, useObjectRef, getComputed, + getSelectedItems, } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; @@ -84,14 +85,14 @@ export const UserList = observer(function UserList({ const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v)); + const users = getFilteredUsers(userList, filterState.filterValue); + const keys = users.map(user => user.userId); + const grant = useCallback(() => { - const selectedList = Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key); - props.onGrant(selectedList); + props.onGrant(getSelectedItems(selectedSubjects)); selectedSubjects.clear(); }, []); - const users = getFilteredUsers(userList, filterState.filterValue); - return styled(style)( @@ -99,8 +100,8 @@ export const UserList = observer(function UserList({ - - +
!grantedUsers.includes(item)}> + {!users.length && filterState.filterValue && ( @@ -109,16 +110,20 @@ export const UserList = observer(function UserList({ )} - {users.map(user => ( - - ))} + {users.map(user => { + const activeUser = usersResource.isActiveUser(user.userId); + return ( + + ); + })}
diff --git a/webapp/packages/plugin-authentication/src/locales/en.ts b/webapp/packages/plugin-authentication/src/locales/en.ts index d35cb5a4cf..6d9220f8a8 100644 --- a/webapp/packages/plugin-authentication/src/locales/en.ts +++ b/webapp/packages/plugin-authentication/src/locales/en.ts @@ -98,6 +98,7 @@ export default [ ['administration_roles_role_granted_users_user_id', 'User ID'], ['administration_roles_role_granted_users_user_name', 'User name'], ['administration_roles_role_granted_users_empty', 'No available users'], + ['administration_roles_role_granted_users_permission_denied', "You can't edit your own permissions"], ['administration_roles_role_granted_connections_tab_title', 'Connections'], ['administration_roles_role_granted_connections_search_placeholder', 'Search for connection name...'], diff --git a/webapp/packages/plugin-authentication/src/locales/ru.ts b/webapp/packages/plugin-authentication/src/locales/ru.ts index 59e7905261..2e0aff1fa3 100644 --- a/webapp/packages/plugin-authentication/src/locales/ru.ts +++ b/webapp/packages/plugin-authentication/src/locales/ru.ts @@ -86,6 +86,7 @@ export default [ ['administration_roles_role_granted_users_user_id', 'ID пользователя'], ['administration_roles_role_granted_users_user_name', 'Имя пользователя'], ['administration_roles_role_granted_users_empty', 'Нет доступных пользователей'], + ['administration_roles_role_granted_users_permission_denied', 'Вы не можете редактировать свои настройки доступа'], ['administration_roles_role_granted_connections_tab_title', 'Подключения'], ['administration_roles_role_granted_connections_search_placeholder', 'Поиск по названию подключения...'], diff --git a/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormService.ts b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormService.ts index cbb6274fd3..008d9b6717 100644 --- a/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormService.ts +++ b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormService.ts @@ -140,7 +140,7 @@ export class PublicConnectionFormService { const result = await this.commonDialogService.open(ConfirmationDialog, { title: 'connections_public_connection_edit_reconnect_title', message: 'connections_public_connection_edit_reconnect_message', - confirmActionText: 'ui_yes', + confirmActionText: 'ui_reconnect', }); if (result === DialogueStateResult.Rejected) {