mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
refactor(core-connections): CB-1037 change Filter state managment
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
@@ -64,13 +63,7 @@ const innerInputStyle = css`
|
||||
}
|
||||
`;
|
||||
|
||||
export interface IFilterState {
|
||||
filterValue: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
state?: IFilterState;
|
||||
onFilter?: (filter: string) => void;
|
||||
interface BaseProps {
|
||||
toggleMode?: boolean;
|
||||
onToggle?: (status: boolean) => void;
|
||||
placeholder?: string;
|
||||
@@ -78,8 +71,24 @@ interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Filter: React.FC<Props> = observer(function Filter({
|
||||
type ControlledProps = BaseProps & {
|
||||
name?: string;
|
||||
value?: string;
|
||||
state?: never;
|
||||
onFilter?: (value: string, name?: string) => void;
|
||||
};
|
||||
|
||||
type ObjectsProps<TKey extends keyof TState, TState> = BaseProps & {
|
||||
name: TKey;
|
||||
state: TState;
|
||||
onFilter?: (value: TState[TKey], name: TKey) => void;
|
||||
value?: never;
|
||||
};
|
||||
|
||||
export const Filter: React.FC<ControlledProps | ObjectsProps<any, any>> = observer(function Filter({
|
||||
state,
|
||||
name,
|
||||
value: valueControlled,
|
||||
onFilter,
|
||||
toggleMode,
|
||||
onToggle,
|
||||
@@ -89,26 +98,34 @@ export const Filter: React.FC<Props> = observer(function Filter({
|
||||
}) {
|
||||
const [inputRef] = useFocus<HTMLInputElement>({});
|
||||
const [toggled, setToggled] = useState(!toggleMode);
|
||||
const [filterState] = useState<IFilterState>(() => state || observable({ filterValue: '' }));
|
||||
|
||||
const filter = useCallback((value: string) => {
|
||||
filterState.filterValue = value;
|
||||
if (onFilter) {
|
||||
onFilter(value);
|
||||
const filter = useCallback((value: string | number, name?: string) => {
|
||||
value = String(value).trim();
|
||||
|
||||
if (state && name) {
|
||||
state[name] = value;
|
||||
}
|
||||
}, [onFilter, filterState]);
|
||||
|
||||
if (onFilter) {
|
||||
onFilter(value, name);
|
||||
}
|
||||
}, [onFilter, state]);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
setToggled(prev => {
|
||||
if (prev) {
|
||||
filter('');
|
||||
}
|
||||
if (onToggle) {
|
||||
onToggle(!prev);
|
||||
}
|
||||
return !prev;
|
||||
});
|
||||
}, [onToggle, filter]);
|
||||
if (!toggleMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (toggled) {
|
||||
filter('');
|
||||
}
|
||||
|
||||
setToggled(!toggled);
|
||||
|
||||
if (onToggle) {
|
||||
onToggle(!toggled);
|
||||
}
|
||||
}, [toggleMode, toggled, onToggle, filter]);
|
||||
|
||||
const onKeyDown = useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter' || event.keyCode === 13) {
|
||||
@@ -131,7 +148,11 @@ export const Filter: React.FC<Props> = observer(function Filter({
|
||||
}
|
||||
}, [toggled, toggleMode, inputRef]);
|
||||
|
||||
useEffect(() => () => { filter(''); }, []);
|
||||
let value: any = valueControlled;
|
||||
|
||||
if (state && name !== undefined && name in state) {
|
||||
value = state[name];
|
||||
}
|
||||
|
||||
return styled(useStyles(styles, toggleMode && toggleModeButtonStyle))(
|
||||
<filter-container
|
||||
@@ -145,15 +166,16 @@ export const Filter: React.FC<Props> = observer(function Filter({
|
||||
style={innerInputStyle}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
value={filterState.filterValue}
|
||||
name={name}
|
||||
value={value}
|
||||
onKeyDown={onKeyDown}
|
||||
onChange={value => filter(String(value).trim())}
|
||||
onChange={filter}
|
||||
{...use({ toggled })}
|
||||
/>
|
||||
<IconButton
|
||||
name='search'
|
||||
disabled={disabled}
|
||||
onClick={toggleMode ? toggle : undefined}
|
||||
onClick={toggle}
|
||||
{...use({ toggled })}
|
||||
/>
|
||||
</filter-container>
|
||||
|
||||
+18
-11
@@ -23,7 +23,7 @@ import {
|
||||
Group,
|
||||
IconOrImage,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { isCloudConnection } from '../../Administration/ConnectionsResource';
|
||||
@@ -57,7 +57,7 @@ const styles = css`
|
||||
`;
|
||||
|
||||
interface IInfoItem {
|
||||
text: string;
|
||||
text: TLocalizationToken;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
@@ -81,23 +81,30 @@ export const ConnectionAccess: TabContainerPanelComponent<IConnectionFormProps>
|
||||
|
||||
const grantedUsers = useMemo(() => computed(() => users.resource.values
|
||||
.filter(user => state.grantedSubjects.includes(user.userId))
|
||||
), [state.grantedSubjects, users.resource.values]);
|
||||
), [state.grantedSubjects, users.resource]);
|
||||
|
||||
const grantedRoles = useMemo(() => computed(() => roles.resource.values
|
||||
.filter(role => state.grantedSubjects.includes(role.roleId))
|
||||
), [state.grantedSubjects, roles.resource.values]);
|
||||
), [state.grantedSubjects, roles.resource]);
|
||||
|
||||
const { selected } = useTab(tabId, load);
|
||||
const loading = users.isLoading() || roles.isLoading() || state.loading;
|
||||
const cloud = formState.info ? isCloudConnection(formState.info) : false;
|
||||
const disabled = loading || !state.loaded || formState.disabled || cloud;
|
||||
const infoItem: IInfoItem = {
|
||||
text: translate('connections_connection_access_save_reminder'),
|
||||
icon: '/icons/info_icon.svg',
|
||||
};
|
||||
let infoItem: IInfoItem | null = null;
|
||||
|
||||
if (unsaved) {
|
||||
infoItem = {
|
||||
text: 'connections_connection_access_save_reminder',
|
||||
icon: '/icons/info_icon.svg',
|
||||
};
|
||||
}
|
||||
|
||||
if (cloud) {
|
||||
infoItem.text = translate('connections_connection_access_cloud_placeholder');
|
||||
infoItem = {
|
||||
text: 'connections_connection_access_cloud_placeholder',
|
||||
icon: '/icons/info_icon.svg',
|
||||
};
|
||||
}
|
||||
|
||||
if (!selected) {
|
||||
@@ -114,10 +121,10 @@ export const ConnectionAccess: TabContainerPanelComponent<IConnectionFormProps>
|
||||
</Group>
|
||||
) : (
|
||||
<>
|
||||
{(unsaved || cloud) && (
|
||||
{infoItem && (
|
||||
<info-item>
|
||||
<IconOrImage icon={infoItem.icon} />
|
||||
{infoItem.text}
|
||||
{translate(infoItem.text)}
|
||||
</info-item>
|
||||
)}
|
||||
<ConnectionAccessGrantedList
|
||||
|
||||
+13
-24
@@ -19,14 +19,13 @@ import {
|
||||
BASE_CONTAINERS_STYLES,
|
||||
Group,
|
||||
Button,
|
||||
IFilterState,
|
||||
useObjectRef
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
|
||||
import type { AdminRoleInfo, AdminUserInfoFragment } from '@cloudbeaver/core-sdk';
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ConnectionAccessTableHeader } from './ConnectionAccessTableHeader/ConnectionAccessTableHeader';
|
||||
import { ConnectionAccessTableHeader, IFilterState } from './ConnectionAccessTableHeader/ConnectionAccessTableHeader';
|
||||
import { ConnectionAccessTableInnerHeader } from './ConnectionAccessTableHeader/ConnectionAccessTableInnerHeader';
|
||||
import { ConnectionAccessTableItem } from './ConnectionAccessTableitem';
|
||||
import { getFilteredRoles, getFilteredUsers } from './getFilteredSubjects';
|
||||
@@ -43,9 +42,6 @@ const styles = composes(
|
||||
position: relative;
|
||||
overflow: auto !important;
|
||||
}
|
||||
Table {
|
||||
flex: 1;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -69,43 +65,36 @@ export const ConnectionAccessGrantedList: React.FC<Props> = observer(function Co
|
||||
const translate = useTranslate();
|
||||
const [selectedSubjects] = useState<Map<any, boolean>>(() => observable(new Map()));
|
||||
const [filterState] = useState<IFilterState>(() => observable({ filterValue: '' }));
|
||||
const subjectsSelected = useMemo(() => computed(
|
||||
() => Array.from(selectedSubjects.values()).some(Boolean)
|
||||
const selectedList = useMemo(() => computed(
|
||||
() => Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key)
|
||||
), [selectedSubjects]);
|
||||
|
||||
const revoke = useCallback(() => {
|
||||
const subjectsToRevoke = [];
|
||||
for (const [subject, value] of selectedSubjects) {
|
||||
if (value) {
|
||||
subjectsToRevoke.push(subject);
|
||||
}
|
||||
}
|
||||
|
||||
props.onRevoke(subjectsToRevoke);
|
||||
props.onRevoke(selectedList.get());
|
||||
selectedSubjects.clear();
|
||||
}, []);
|
||||
|
||||
const roles = useMemo(() => computed(() => getFilteredRoles(
|
||||
grantedRoles, filterState.filterValue
|
||||
)), [filterState.filterValue, grantedRoles]);
|
||||
)), [filterState, grantedRoles]);
|
||||
|
||||
const users = useMemo(() => computed(() => getFilteredUsers(
|
||||
grantedUsers, filterState.filterValue
|
||||
)), [filterState.filterValue, grantedUsers]);
|
||||
)), [filterState, grantedUsers]);
|
||||
|
||||
let tableInfoText: string = translate('connections_connection_access_admin_info');
|
||||
let tableInfoText: TLocalizationToken = 'connections_connection_access_admin_info';
|
||||
if (!roles.get().length && !users.get().length) {
|
||||
if (filterState.filterValue) {
|
||||
tableInfoText = translate('connections_connection_access_filter_no_result');
|
||||
tableInfoText = 'connections_connection_access_filter_no_result';
|
||||
} else {
|
||||
tableInfoText = translate('connections_connection_access_empty_table_placeholder');
|
||||
tableInfoText = 'connections_connection_access_empty_table_placeholder';
|
||||
}
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<Group box medium>
|
||||
<ConnectionAccessTableHeader filter={filterState} disabled={disabled}>
|
||||
<Button disabled={disabled || !subjectsSelected.get()} mod={['outlined']} onClick={revoke}>{translate('connections_connection_access_revoke')}</Button>
|
||||
<ConnectionAccessTableHeader filterState={filterState} disabled={disabled}>
|
||||
<Button disabled={disabled || !selectedList.get().length} mod={['outlined']} onClick={revoke}>{translate('connections_connection_access_revoke')}</Button>
|
||||
<Button disabled={disabled} mod={['raised']} onClick={props.onEdit}>{translate('connections_connection_access_edit')}</Button>
|
||||
</ConnectionAccessTableHeader>
|
||||
<Table selectedItems={selectedSubjects}>
|
||||
@@ -113,7 +102,7 @@ export const ConnectionAccessGrantedList: React.FC<Props> = observer(function Co
|
||||
<TableBody>
|
||||
<TableItem item='tableInfo'>
|
||||
<TableColumnValue colSpan={5}>
|
||||
{tableInfoText}
|
||||
{translate(tableInfoText)}
|
||||
</TableColumnValue>
|
||||
</TableItem>
|
||||
{roles.get().map(role => (
|
||||
|
||||
+9
-21
@@ -19,14 +19,13 @@ import {
|
||||
BASE_CONTAINERS_STYLES,
|
||||
Group,
|
||||
Button,
|
||||
IFilterState,
|
||||
useObjectRef
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import type { AdminRoleInfo, AdminUserInfoFragment } from '@cloudbeaver/core-sdk';
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ConnectionAccessTableHeader } from './ConnectionAccessTableHeader/ConnectionAccessTableHeader';
|
||||
import { ConnectionAccessTableHeader, IFilterState } from './ConnectionAccessTableHeader/ConnectionAccessTableHeader';
|
||||
import { ConnectionAccessTableInnerHeader } from './ConnectionAccessTableHeader/ConnectionAccessTableInnerHeader';
|
||||
import { ConnectionAccessTableItem } from './ConnectionAccessTableitem';
|
||||
import { getFilteredRoles, getFilteredUsers } from './getFilteredSubjects';
|
||||
@@ -43,9 +42,6 @@ const styles = composes(
|
||||
position: relative;
|
||||
overflow: auto !important;
|
||||
}
|
||||
Table {
|
||||
flex: 1;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -69,35 +65,27 @@ export const ConnectionAccessList: React.FC<Props> = observer(function Connectio
|
||||
const translate = useTranslate();
|
||||
const [selectedSubjects] = useState<Map<any, boolean>>(() => observable(new Map()));
|
||||
const [filterState] = useState<IFilterState>(() => observable({ filterValue: '' }));
|
||||
const subjectsSelected = useMemo(() => computed(
|
||||
() => Array.from(selectedSubjects.values()).some(Boolean)
|
||||
const selectedList = useMemo(() => computed(
|
||||
() => Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key)
|
||||
), [selectedSubjects]);
|
||||
|
||||
const grant = useCallback(() => {
|
||||
const subjectsToGrant = [];
|
||||
|
||||
for (const [subject, value] of selectedSubjects) {
|
||||
if (value) {
|
||||
subjectsToGrant.push(subject);
|
||||
}
|
||||
}
|
||||
|
||||
props.onGrant(subjectsToGrant);
|
||||
props.onGrant(selectedList.get());
|
||||
selectedSubjects.clear();
|
||||
}, []);
|
||||
|
||||
const roles = useMemo(() => computed(() => getFilteredRoles(
|
||||
roleList, filterState.filterValue
|
||||
)), [filterState.filterValue, roleList]);
|
||||
)), [filterState, roleList]);
|
||||
|
||||
const users = useMemo(() => computed(() => getFilteredUsers(
|
||||
userList, filterState.filterValue
|
||||
)), [filterState.filterValue, userList]);
|
||||
)), [filterState, userList]);
|
||||
|
||||
return styled(style)(
|
||||
<Group box medium>
|
||||
<ConnectionAccessTableHeader filter={filterState} disabled={disabled}>
|
||||
<Button disabled={disabled || !subjectsSelected.get()} mod={['raised']} onClick={grant}>{translate('connections_connection_access_grant')}</Button>
|
||||
<ConnectionAccessTableHeader filterState={filterState} disabled={disabled}>
|
||||
<Button disabled={disabled || !selectedList.get().length} mod={['raised']} onClick={grant}>{translate('connections_connection_access_grant')}</Button>
|
||||
</ConnectionAccessTableHeader>
|
||||
<Table selectedItems={selectedSubjects}>
|
||||
<ConnectionAccessTableInnerHeader />
|
||||
@@ -116,7 +104,7 @@ export const ConnectionAccessList: React.FC<Props> = observer(function Connectio
|
||||
name={role.roleName || ''}
|
||||
description={role.description}
|
||||
icon='/icons/role.svg'
|
||||
iconTooltip={translate('connections_connection_access_role_tooltip')}
|
||||
iconTooltip='connections_connection_access_role_tooltip'
|
||||
disabled={disabled || grantedSubjects.includes(role.roleId)}
|
||||
/>
|
||||
))}
|
||||
|
||||
+13
-6
@@ -9,12 +9,16 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Filter, IFilterState } from '@cloudbeaver/core-blocks';
|
||||
import { Filter } from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { composes, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
export interface IFilterState {
|
||||
filterValue: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
filter: IFilterState;
|
||||
filterState: IFilterState;
|
||||
disabled: boolean;
|
||||
className?: string;
|
||||
}
|
||||
@@ -29,7 +33,6 @@ const styles = composes(
|
||||
buttons {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
grid-gap: 16px;
|
||||
}
|
||||
header {
|
||||
overflow: hidden;
|
||||
@@ -41,17 +44,21 @@ const styles = composes(
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
grid-gap: 16px;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
export const ConnectionAccessTableHeader: React.FC<Props> = observer(function ConnectionAccessTableHeader({ filter, disabled, className, children }) {
|
||||
export const ConnectionAccessTableHeader: React.FC<Props> = observer(function ConnectionAccessTableHeader({ filterState, disabled, className, children }) {
|
||||
const translate = useTranslate();
|
||||
return styled(useStyles(styles))(
|
||||
<header className={className}>
|
||||
<Filter disabled={disabled} placeholder={translate('connections_connection_access_filter_placeholder')} state={filter} />
|
||||
<Filter
|
||||
disabled={disabled}
|
||||
placeholder={translate('connections_connection_access_filter_placeholder')}
|
||||
name='filterValue'
|
||||
state={filterState}
|
||||
/>
|
||||
<buttons>
|
||||
{children}
|
||||
</buttons>
|
||||
|
||||
+4
-2
@@ -10,13 +10,14 @@ import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { StaticImage, TableColumnValue, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks';
|
||||
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
|
||||
|
||||
interface Props {
|
||||
id: any;
|
||||
name: string;
|
||||
icon: string;
|
||||
disabled: boolean;
|
||||
iconTooltip?: string;
|
||||
iconTooltip?: TLocalizationToken;
|
||||
description?: string;
|
||||
className?: string;
|
||||
}
|
||||
@@ -31,6 +32,7 @@ const style = css`
|
||||
export const ConnectionAccessTableItem: React.FC<Props> = observer(function ConnectionAccessTableItem({
|
||||
id, name, description, icon, iconTooltip, disabled, className,
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
return styled(style)(
|
||||
<TableItem
|
||||
item={id}
|
||||
@@ -41,7 +43,7 @@ export const ConnectionAccessTableItem: React.FC<Props> = observer(function Conn
|
||||
<TableColumnValue centerContent flex>
|
||||
<TableItemSelect disabled={disabled} />
|
||||
</TableColumnValue>
|
||||
<TableColumnValue><StaticImage icon={icon} title={iconTooltip} /></TableColumnValue>
|
||||
<TableColumnValue><StaticImage icon={icon} title={translate(iconTooltip)} /></TableColumnValue>
|
||||
<TableColumnValue>{name}</TableColumnValue>
|
||||
<TableColumnValue>{description}</TableColumnValue>
|
||||
</TableItem>
|
||||
|
||||
+1
-7
@@ -32,13 +32,7 @@ export function useConnectionAccessState(connection: DatabaseConnectionFragment
|
||||
};
|
||||
|
||||
const revoke = (subjectIds: string[]): void => {
|
||||
const newGrantedSubjects = [];
|
||||
for (const subjectId of state.grantedSubjects) {
|
||||
if (!subjectIds.includes(subjectId)) {
|
||||
newGrantedSubjects.push(subjectId);
|
||||
}
|
||||
}
|
||||
state.grantedSubjects = newGrantedSubjects;
|
||||
state.grantedSubjects = state.grantedSubjects.filter(subject => !subjectIds.includes(subject));
|
||||
};
|
||||
|
||||
const grant = (subjectIds: string[]): void => {
|
||||
|
||||
@@ -32,13 +32,13 @@ export default [
|
||||
['connections_connection_address', 'Address'],
|
||||
['connections_connection_name', 'Connection name'],
|
||||
['connections_connection_access_user_or_role_name', 'User or Role name'],
|
||||
['connections_connection_access_save_reminder', 'You have unsaved changes. Click save to save all permission changes.'],
|
||||
['connections_connection_access_save_reminder', 'You have unsaved changes. Click Save to save all permission changes.'],
|
||||
['connections_connection_access_user_tooltip', 'User'],
|
||||
['connections_connection_access_role_tooltip', 'Role'],
|
||||
['connections_connection_access_revoke', 'Revoke'],
|
||||
['connections_connection_access_grant', 'Grant'],
|
||||
['connections_connection_access_edit', 'Edit'],
|
||||
['connections_connection_access_cloud_placeholder', 'Cloud connections are visible to all users. Access management can be configured in AWS console.'],
|
||||
['connections_connection_access_cloud_placeholder', 'Cloud connections are visible to all users. Access management can be configured in "AWS Management Console".'],
|
||||
['connections_connection_access_empty_table_placeholder', 'There are no items yet. Press edit button to grant role or user.'],
|
||||
['connections_connection_access_filter_placeholder', 'Search for user or role name'],
|
||||
['connections_connection_access_filter_no_result', 'No results have been found.'],
|
||||
|
||||
@@ -29,14 +29,14 @@ export default [
|
||||
['connections_connection_address', 'Адрес'],
|
||||
['connections_connection_name', 'Название подключения'],
|
||||
['connections_connection_access_user_or_role_name', 'Имя пользователя или роли'],
|
||||
['connections_connection_access_save_reminder', 'У вас есть несохраненные изменения. Нажмите кнопку сохранить, чтобы применить изменения в настройках доступа.'],
|
||||
['connections_connection_access_save_reminder', 'У вас есть несохраненные изменения. Нажмите кнопку Сохранить, чтобы применить изменения в настройках доступа.'],
|
||||
['connections_connection_access_user_tooltip', 'Пользователь'],
|
||||
['connections_connection_access_role_tooltip', 'Роль'],
|
||||
['connections_connection_access_revoke', 'Отозвать'],
|
||||
['connections_connection_access_grant', 'Дать доступ'],
|
||||
['connections_connection_access_revoke', 'Запретить'],
|
||||
['connections_connection_access_grant', 'Разрешить'],
|
||||
['connections_connection_access_edit', 'Изменить'],
|
||||
['connections_connection_access_cloud_placeholder', 'Облачные подключения видны всем пользователям. Вы можете настроить доступ в AWS консоли.'],
|
||||
['connections_connection_access_empty_table_placeholder', 'Вы еще ничего не добавили. Нажмите кнопку редактировать, чтобы добавить роль или пользователя.'],
|
||||
['connections_connection_access_cloud_placeholder', 'Облачные подключения видны всем пользователям. Вы можете настроить доступ в "Консоль Управления AWS".'],
|
||||
['connections_connection_access_empty_table_placeholder', 'Вы еще ничего не добавили. Нажмите кнопку Изменить, чтобы добавить роль или пользователя.'],
|
||||
['connections_connection_access_filter_placeholder', 'Поиск по имени пользователя или роли'],
|
||||
['connections_connection_access_filter_no_result', 'По вашему запросу ничего не найдено.'],
|
||||
['connections_connection_access_admin_info', 'Администраторы видят все подключения.'],
|
||||
|
||||
Reference in New Issue
Block a user