Merge pull request #487 from dbeaver/fix/identity-providers

fix(plugin-authentication): CB-1241 cleanup
This commit is contained in:
Alexey
2021-09-27 17:28:49 +03:00
committed by GitHub
34 changed files with 408 additions and 412 deletions
@@ -0,0 +1,46 @@
/*
* 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.
*/
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
import { IconOrImage } from './IconOrImage';
export interface IInfoItem {
info: TLocalizationToken;
icon?: string;
}
interface Props extends IInfoItem {
className?: string;
}
const styles = css`
info-item {
display: flex;
align-items: center;
flex: 0 0 auto;
}
IconOrImage {
width: 24px;
height: 24px;
margin-right: 16px;
}
`;
export const InfoItem = observer<Props>(function InfoItem({ info, icon = '/icons/info_icon.svg', className }) {
const translate = useTranslate();
return styled(styles)(
<info-item className={className}>
<IconOrImage icon={icon} />
{translate(info)}
</info-item>
);
});
@@ -0,0 +1,74 @@
/*
* 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.
*/
import { action, computed, makeObservable, observable } from 'mobx';
type Key = string | string[];
export class TableState {
selected: Map<string, boolean>;
expanded: Map<string, boolean>;
get itemsSelected(): boolean {
return Array.from(this.selected.values()).some(v => v);
}
get selectedList(): string[] {
return Array
.from(this.selected)
.filter(([_, value]) => value)
.map(([key]) => key);
}
constructor() {
this.selected = new Map();
this.expanded = new Map();
makeObservable(this, {
selected: observable,
expanded: observable,
itemsSelected: computed,
selectedList: computed,
unselect: action,
unexpand: action,
});
}
unselect(key?: Key): Map<string, boolean> {
if (key === undefined) {
this.selected.clear();
} else {
if (typeof key === 'string') {
this.selected.delete(key);
} else {
for (const id of key) {
this.selected.delete(id);
}
}
}
return this.selected;
}
unexpand(key?: Key): Map<string, boolean> {
if (key === undefined) {
this.expanded.clear();
} else {
if (typeof key === 'string') {
this.expanded.delete(key);
} else {
for (const id of key) {
this.expanded.delete(id);
}
}
}
return this.expanded;
}
}
@@ -7,70 +7,11 @@
* you may not use this file except in compliance with the License.
*/
import { action, computed, observable } from 'mobx';
import { useState } from 'react';
import { useObservableRef } from '@cloudbeaver/core-blocks';
import { TableState } from './TableState';
type Key = string | string[];
export interface ITableState {
selected: Map<string, boolean>;
expanded: Map<string, boolean>;
readonly itemsSelected: boolean;
readonly selectedList: string[];
unselect: (key?: Key) => Map<string, boolean>;
unexpand: (key?: Key) => Map<string, boolean>;
}
export function useTable(): ITableState {
return useObservableRef<ITableState>(() => ({
selected: new Map(),
expanded: new Map(),
get selectedList() {
return Array
.from(this.selected)
.filter(([_, value]) => value)
.map(([key]) => key);
},
get itemsSelected() {
return Array.from(this.selected.values()).some(v => v);
},
unselect(key?: string | string[]) {
if (key === undefined) {
this.selected.clear();
} else {
if (typeof key === 'string') {
this.selected.delete(key);
} else {
for (const id of key) {
this.selected.delete(id);
}
}
}
return this.selected;
},
unexpand(key?: string | string[]) {
if (key === undefined) {
this.expanded.clear();
} else {
if (typeof key === 'string') {
this.expanded.delete(key);
} else {
for (const id of key) {
this.expanded.delete(id);
}
}
}
return this.expanded;
},
}), {
selected: observable,
expanded: observable,
selectedList: computed,
itemsSelected: computed,
unexpand: action.bound,
unselect: action.bound,
}, false);
export function useTable(): TableState {
const [table] = useState(() => new TableState());
return table;
}
+2
View File
@@ -52,6 +52,7 @@ export * from './Table/TableItemExpand';
export * from './Table/TableItemSelect';
export * from './Table/TableItemSeparator';
export * from './Table/useTable';
export * from './Table/TableState';
export * from './Tabs/ITab';
export * from './Tabs/TabContext';
@@ -139,6 +140,7 @@ export * from './IconButton';
export * from './IconOrImage';
export * from './StaticImage';
export * from './TextPlaceholder';
export * from './InfoItem';
export * from './useClickEvents';
export * from './useClipboard';
export * from './useDataResource';
@@ -21,8 +21,8 @@ import {
BASE_CONTAINERS_STYLES,
ColoredContainer,
Group,
IconOrImage,
Container,
InfoItem,
} from '@cloudbeaver/core-blocks';
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
import { useStyles } from '@cloudbeaver/core-theming';
@@ -44,35 +44,20 @@ const styles = css`
position: relative;
overflow: auto !important;
}
info-item {
display: flex;
align-items: center;
flex: 0 0 auto;
}
IconOrImage {
width: 24px;
height: 24px;
margin-right: 16px;
}
Loader {
z-index: 2;
}
`;
interface IInfoItem {
text: TLocalizationToken;
icon: string;
}
export const ConnectionAccess: TabContainerPanelComponent<IConnectionFormProps> = observer(function ConnectionAccess({
tabId,
state: formState,
}) {
const { state, edit, grant, revoke, load } = useConnectionAccessState(formState.info);
const state = useConnectionAccessState(formState.info);
const style = useStyles(styles, BASE_CONTAINERS_STYLES);
const translate = useTranslate();
const unsaved = state.initialGrantedSubjects.length !== state.grantedSubjects.length
|| state.initialGrantedSubjects.some(subject => !state.grantedSubjects.includes(subject));
const { selected } = useTab(tabId, state.load);
const users = useMapResource(UsersResource, null, {
onLoad: resource => { resource.loadAll(); },
@@ -83,39 +68,30 @@ export const ConnectionAccess: TabContainerPanelComponent<IConnectionFormProps>
});
const grantedUsers = useMemo(() => computed(() => users.resource.values
.filter(user => state.grantedSubjects.includes(user.userId))
), [state.grantedSubjects, users.resource]);
.filter(user => state.state.grantedSubjects.includes(user.userId))
), [state.state.grantedSubjects, users.resource]);
const grantedRoles = useMemo(() => computed(() => roles.resource.values
.filter(role => state.grantedSubjects.includes(role.roleId))
), [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;
let infoItem: IInfoItem | null = null;
if (unsaved) {
infoItem = {
text: 'connections_connection_access_save_reminder',
icon: '/icons/info_icon.svg',
};
}
if (cloud) {
infoItem = {
text: 'connections_connection_access_cloud_placeholder',
icon: '/icons/info_icon.svg',
};
}
.filter(role => state.state.grantedSubjects.includes(role.roleId))
), [state.state.grantedSubjects, roles.resource]);
if (!selected) {
return null;
}
const loading = users.isLoading() || roles.isLoading() || state.state.loading;
const cloud = formState.info ? isCloudConnection(formState.info) : false;
const disabled = loading || !state.state.loaded || formState.disabled || cloud;
let info: TLocalizationToken | null = null;
if (formState.mode === 'edit' && state.changed) {
info = 'ui_save_reminder';
} else if (cloud) {
info = 'cloud_connections_access_placeholder';
}
return styled(style)(
<Loader state={[users, roles, state]}>
<Loader state={[users, roles, state.state]}>
{() => styled(style)(
<ColoredContainer parent gap vertical>
{!users.resource.values.length && !roles.resource.values.length ? (
@@ -124,27 +100,22 @@ export const ConnectionAccess: TabContainerPanelComponent<IConnectionFormProps>
</Group>
) : (
<>
{infoItem && (
<info-item>
<IconOrImage icon={infoItem.icon} />
{translate(infoItem.text)}
</info-item>
)}
{info && <InfoItem info={info} />}
<Container gap overflow>
<ConnectionAccessGrantedList
grantedUsers={grantedUsers.get()}
grantedRoles={grantedRoles.get()}
disabled={disabled}
onEdit={edit}
onRevoke={revoke}
onEdit={state.edit}
onRevoke={state.revoke}
/>
{state.editing && (
{state.state.editing && (
<ConnectionAccessList
userList={users.resource.values}
roleList={roles.resource.values}
grantedSubjects={state.grantedSubjects}
grantedSubjects={state.state.grantedSubjects}
disabled={disabled}
onGrant={grant}
onGrant={state.grant}
/>
)}
</Container>
@@ -98,9 +98,9 @@ export const ConnectionAccessGrantedList = observer<Props>(function ConnectionAc
let tableInfoText: TLocalizationToken = 'connections_connection_access_admin_info';
if (!roles.get().length && !users.get().length) {
if (filterState.filterValue) {
tableInfoText = 'connections_connection_access_filter_no_result';
tableInfoText = 'ui_search_no_result_placeholder';
} else {
tableInfoText = 'connections_connection_access_empty_table_placeholder';
tableInfoText = 'ui_no_items_placeholder';
}
}
@@ -127,7 +127,7 @@ export const ConnectionAccessGrantedList = observer<Props>(function ConnectionAc
name={role.roleName || ''}
description={role.description}
icon='/icons/role.svg'
iconTooltip={translate('connections_connection_access_role_tooltip')}
iconTooltip={translate('authentication_role_icon_tooltip')}
disabled={disabled}
/>
))}
@@ -137,7 +137,7 @@ export const ConnectionAccessGrantedList = observer<Props>(function ConnectionAc
id={user.userId}
name={user.userId}
icon='/icons/user.svg'
iconTooltip={translate('connections_connection_access_user_tooltip')}
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled}
/>
))}
@@ -108,7 +108,7 @@ export const ConnectionAccessList = observer<Props>(function ConnectionAccessLis
{!roles.get().length && !users.get().length && filterState.filterValue && (
<TableItem item='tableInfo' selectDisabled>
<TableColumnValue colSpan={5}>
{translate('connections_connection_access_filter_no_result')}
{translate('ui_search_no_result_placeholder')}
</TableColumnValue>
</TableItem>
)}
@@ -119,7 +119,7 @@ export const ConnectionAccessList = observer<Props>(function ConnectionAccessLis
name={role.roleName || ''}
description={role.description}
icon='/icons/role.svg'
iconTooltip='connections_connection_access_role_tooltip'
iconTooltip='authentication_role_icon_tooltip'
disabled={disabled || grantedSubjects.includes(role.roleId)}
/>
))}
@@ -129,7 +129,7 @@ export const ConnectionAccessList = observer<Props>(function ConnectionAccessLis
id={user.userId}
name={user.userId}
icon='/icons/user.svg'
iconTooltip={translate('connections_connection_access_user_tooltip')}
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled || grantedSubjects.includes(user.userId)}
/>
))}
@@ -6,59 +6,67 @@
* you may not use this file except in compliance with the License.
*/
import { useObjectRef, useTabState } from '@cloudbeaver/core-blocks';
import { action, computed, observable } from 'mobx';
import { useObservableRef, useTabState } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import type { DatabaseConnectionFragment } from '@cloudbeaver/core-sdk';
import { isArraysEqual } from '@cloudbeaver/core-utils';
import { ConnectionsResource } from '../../Administration/ConnectionsResource';
import type { IConnectionAccessTabState } from './IConnectionAccessTabState';
interface IConnectionAccessState {
interface State {
state: IConnectionAccessTabState;
changed: boolean;
edit: () => void;
revoke: (subjectIds: string[]) => void;
grant: (subjectIds: string[]) => void;
edit: () => void;
load: () => Promise<void>;
}
export function useConnectionAccessState(connection: DatabaseConnectionFragment | undefined): IConnectionAccessState {
const connectionsResource = useService(ConnectionsResource);
export function useConnectionAccessState(connection: DatabaseConnectionFragment | undefined): Readonly<State> {
const resource = useService(ConnectionsResource);
const notificationService = useService(NotificationService);
const state = useTabState<IConnectionAccessTabState>();
const edit = () => {
state.editing = !state.editing;
};
const revoke = (subjectIds: string[]): void => {
state.grantedSubjects = state.grantedSubjects.filter(subject => !subjectIds.includes(subject));
};
const grant = (subjectIds: string[]): void => {
state.grantedSubjects.push(...subjectIds);
};
const load = async () => {
if (state.loaded) {
return;
}
try {
state.loading = true;
if (connection) {
const grantedSubjects = await connectionsResource.loadAccessSubjects(connection.id);
state.grantedSubjects = grantedSubjects.map(subject => subject.subjectId);
state.initialGrantedSubjects = state.grantedSubjects.slice();
return useObservableRef(() => ({
get changed() {
return !isArraysEqual(this.state.initialGrantedSubjects, this.state.grantedSubjects);
},
edit() {
this.state.editing = !this.state.editing;
},
revoke(subjectIds: string[]) {
this.state.grantedSubjects = this.state.grantedSubjects.filter(subject => !subjectIds.includes(subject));
},
grant(subjectIds: string[]) {
this.state.grantedSubjects.push(...subjectIds);
},
async load() {
if (this.state.loaded || this.state.loading) {
return;
}
state.loaded = true;
} catch (exception) {
notificationService.logException(exception, 'connections_connection_edit_access_load_failed');
}
state.loading = false;
};
try {
this.state.loading = true;
return useObjectRef({ state, revoke, grant, edit, load });
if (this.connection) {
const grantedSubjects = await this.resource.loadAccessSubjects(this.connection.id);
this.state.grantedSubjects = grantedSubjects.map(subject => subject.subjectId);
this.state.initialGrantedSubjects = this.state.grantedSubjects.slice();
}
this.state.loaded = true;
} catch (exception) {
this.notificationService.logException(exception, 'connections_connection_edit_access_load_failed');
} finally {
this.state.loading = false;
}
},
}),
{ state: observable.ref, changed: computed, edit: action.bound, revoke: action.bound, grant: action.bound },
{ state, connection, resource, notificationService },
['load']);
}
@@ -34,14 +34,8 @@ 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_user_tooltip', 'User'],
['connections_connection_access_role_tooltip', 'Role'],
['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 manage access settings.'],
['connections_connection_access_filter_placeholder', 'Search for user or role name'],
['connections_connection_access_filter_no_result', 'No results have been found.'],
['connections_connection_access_admin_info', 'Administators see all connections.'],
['connections_connection_access_admin_info', 'Administrators see all connections.'],
['connections_connection_description', 'Description'],
['connections_connection_driver', 'Driver'],
['connections_connection_host', 'Host'],
@@ -65,4 +59,6 @@ export default [
['connections_network_handler_ssh_tunnel_password', 'Password'],
['connections_driver_search_placeholder', 'Type driver name...'],
['connections_not_found', 'No database connections were found'],
['cloud_connections_access_placeholder', 'Cloud connections are visible to all users. Access management can be configured in "AWS Management Console".'],
];
@@ -34,16 +34,10 @@ export default [
['connections_connection_address', 'Indirizzo'],
['connections_connection_name', 'Nome della connessione'],
['connections_connection_access_user_or_role_name', 'Nome dell\'Utente o del ruolo'],
['connections_connection_access_save_reminder', 'Ci sono modifiche non salvate. Cliccare Salva per salvare tutte le modifiche ai permessi.'],
['connections_connection_access_user_tooltip', 'Utente'],
['connections_connection_access_role_tooltip', 'Ruolo'],
['connections_connection_access_revoke', 'Revoca'],
['connections_connection_access_grant', 'Permetti'],
['connections_connection_access_edit', 'Modifica'],
['connections_connection_access_cloud_placeholder', 'Connessioni Cloud sono visibili a tutti gli utenti. Il gestore degli accessi può essere configurato in "AWS Management Console".'],
['connections_connection_access_empty_table_placeholder', 'Non ci sono ancora elementi. Premi il pulsante Modifica per gestire le impostazioni d\'accesso.'],
['connections_connection_access_filter_placeholder', 'Cerca per utente o ruolo'],
['connections_connection_access_filter_no_result', 'Nessun risultato trovato.'],
['connections_connection_access_admin_info', 'Gli amministratori vedono tutte le connessioni.'],
['connections_connection_description', 'Descrizione'],
['connections_connection_driver', 'Driver'],
@@ -68,4 +62,6 @@ export default [
['connections_network_handler_ssh_tunnel_password', 'Password'],
['connections_driver_search_placeholder', 'Digita il nome del driver...'],
['connections_not_found', 'Nessuna connessione al database trovata'],
['cloud_connections_access_placeholder', 'Connessioni Cloud sono visibili a tutti gli utenti. Il gestore degli accessi può essere configurato in "AWS Management Console".'],
];
@@ -31,13 +31,7 @@ export default [
['connections_connection_address', 'Адрес'],
['connections_connection_name', 'Название подключения'],
['connections_connection_access_user_or_role_name', 'Имя пользователя или роли'],
['connections_connection_access_save_reminder', 'У вас есть несохраненные изменения. Нажмите кнопку Сохранить, чтобы применить изменения в настройках доступа.'],
['connections_connection_access_user_tooltip', 'Пользователь'],
['connections_connection_access_role_tooltip', 'Роль'],
['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', 'Администраторы видят все подключения.'],
['connections_connection_description', 'Описание'],
['connections_connection_driver', 'Драйвер'],
@@ -62,4 +56,6 @@ export default [
['connections_network_handler_ssh_tunnel_password', 'Пароль'],
['connections_driver_search_placeholder', 'Введите название драйвера...'],
['connections_not_found', 'Подключения к базам данных не найдены'],
['cloud_connections_access_placeholder', 'Облачные подключения видны всем пользователям. Вы можете настроить доступ в "Консоль Управления AWS".'],
];
@@ -41,6 +41,9 @@ export const defaultENLocale = [
['ui_rename_processing', 'Renaming...'],
['ui_name', 'Name'],
['ui_cant_delete_item', "This item can't be deleted"],
['ui_no_items_placeholder', 'There are no items yet.'],
['ui_search_no_result_placeholder', 'No results have been found.'],
['ui_save_reminder', 'You have unsaved changes.'],
['root_permission_denied', 'You have no permissions'],
['app_root_session_expire_warning_title', 'Session is about to expire'],
@@ -34,6 +34,10 @@ export const defaultITLocale = [
['ui_clipboard_access_denied_message', 'Devi autorizzare l\'accesso agli appunti per utilizzare alcune feature dell\'app'],
['ui_reveal_password', 'Mostra o nascondi la password'],
['ui_page_not_found', 'Pagina non trovata'],
['ui_no_items_placeholder', 'Non ci sono ancora elementi.'],
['ui_search_no_result_placeholder', 'Nessun risultato trovato.'],
['ui_save_reminder', 'Ci sono modifiche non salvate.'],
['root_permission_denied', 'Non hai i permessi'],
['app_root_session_expire_warning_title', 'La sessione sta per scadere'],
['app_root_session_expire_warning_message', 'La sessione scadrà in meno di 5 minuti. Per continuare a lavorare, chiudi questo popup.'],
@@ -39,6 +39,9 @@ export const defaultRULocale = [
['ui_rename_processing', 'Переименование...'],
['ui_name', 'Название'],
['ui_cant_delete_item', 'Этот элемент нельзя удалить'],
['ui_no_items_placeholder', 'Вы еще ничего не добавили.'],
['ui_search_no_result_placeholder', 'По вашему запросу ничего не найдено.'],
['ui_save_reminder', 'У вас есть несохраненные изменения.'],
['root_permission_denied', 'Отказано в доступе'],
['app_root_session_expire_warning_title', 'Ваша сессия скоро закончится'],
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
import styled, { css, use } from 'reshadow';
import { AdministrationItemContentComponent, ADMINISTRATION_TOOLS_PANEL_STYLES } from '@cloudbeaver/core-administration';
import { BASE_CONTAINERS_STYLES, ToolsAction, Loader, ToolsPanel, useTable } from '@cloudbeaver/core-blocks';
import { BASE_CONTAINERS_STYLES, ToolsAction, Loader, ToolsPanel } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
import { composes, useStyles } from '@cloudbeaver/core-theming';
@@ -51,8 +51,7 @@ export const AuthConfigurationsAdministration: AdministrationItemContentComponen
const style = useStyles(styles, ADMINISTRATION_TOOLS_PANEL_STYLES, BASE_CONTAINERS_STYLES);
const service = useService(CreateAuthConfigurationService);
const tableState = useTable();
const configurationsTableState = useConfigurationsTable(tableState);
const table = useConfigurationsTable();
return styled(style)(
<>
@@ -61,7 +60,7 @@ export const AuthConfigurationsAdministration: AdministrationItemContentComponen
title={translate('administration_identity_providers_add_tooltip')}
icon="add"
viewBox="0 0 24 24"
disabled={!!sub || configurationsTableState.processing}
disabled={!!sub || table.processing}
onClick={service.create}
>
{translate('ui_add')}
@@ -70,8 +69,8 @@ export const AuthConfigurationsAdministration: AdministrationItemContentComponen
title={translate('administration_identity_providers_refresh_tooltip')}
icon="refresh"
viewBox="0 0 24 24"
disabled={configurationsTableState.processing}
onClick={configurationsTableState.update}
disabled={table.processing}
onClick={table.update}
>
{translate('ui_refresh')}
</ToolsAction>
@@ -79,8 +78,8 @@ export const AuthConfigurationsAdministration: AdministrationItemContentComponen
title={translate('administration_identity_providers_delete_tooltip')}
icon="trash"
viewBox="0 0 24 24"
disabled={!tableState.itemsSelected || configurationsTableState.processing}
onClick={configurationsTableState.delete}
disabled={!table.tableState.itemsSelected || table.processing}
onClick={table.delete}
>
{translate('ui_delete')}
</ToolsAction>
@@ -94,11 +93,11 @@ export const AuthConfigurationsAdministration: AdministrationItemContentComponen
)}
<layout-grid-cell {...use({ span: 12 })}>
<AuthConfigurationsTable
configurations={configurationsTableState.configurations}
selectedItems={tableState.selected}
expandedItems={tableState.expanded}
configurations={table.configurations}
selectedItems={table.tableState.selected}
expandedItems={table.tableState.expanded}
/>
<Loader loading={configurationsTableState.processing} overlay />
<Loader loading={table.processing} overlay />
</layout-grid-cell>
</layout-grid-inner>
</layout-grid>
@@ -9,28 +9,28 @@
import { computed, observable } from 'mobx';
import { AuthConfigurationsResource, compareAuthConfigurations } from '@cloudbeaver/core-authentication';
import { ITableState, useObservableRef } from '@cloudbeaver/core-blocks';
import { TableState, useObservableRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { AdminAuthProviderConfiguration, resourceKeyList } from '@cloudbeaver/core-sdk';
interface State {
tableState: TableState;
processing: boolean;
tableState: ITableState;
configurations: AdminAuthProviderConfiguration[];
update: () => Promise<void>;
delete: () => Promise<void>;
}
export function useConfigurationsTable(tableState: ITableState): State {
export function useConfigurationsTable(): Readonly<State> {
const notificationService = useService(NotificationService);
const dialogService = useService(CommonDialogService);
const resource = useService(AuthConfigurationsResource);
return useObservableRef<State>(() => ({
tableState: new TableState(),
processing: false,
tableState,
get configurations() {
return resource.values.slice().sort(compareAuthConfigurations);
},
@@ -89,5 +89,5 @@ export function useConfigurationsTable(tableState: ITableState): State {
}), {
processing: observable.ref,
configurations: computed,
}, { tableState }, ['update', 'delete']);
}, false, ['update', 'delete']);
}
@@ -153,8 +153,6 @@ export const AuthConfigurationOptions: TabContainerPanelComponent<IAuthConfigura
title={state.config.signInLink}
disabled={state.disabled}
readOnly
tiny
fill
>
Sign in
</InputFieldNew>
@@ -164,8 +162,6 @@ export const AuthConfigurationOptions: TabContainerPanelComponent<IAuthConfigura
title={state.config.signOutLink}
disabled={state.disabled}
readOnly
tiny
fill
>
Sign out
</InputFieldNew>
@@ -63,15 +63,15 @@ const styles = composes(
interface Props {
connectionList: DatabaseConnectionFragment[];
grantedSubjects: string[];
onGrant: (subjectIds: string[]) => void;
disabled: boolean;
onGrant: (subjectIds: string[]) => void;
}
export const ConnectionList = observer<Props>(function ConnectionList({
connectionList,
grantedSubjects,
onGrant,
disabled,
onGrant,
}) {
const props = useObjectRef({ onGrant });
const style = useStyles(styles, BASE_CONTAINERS_STYLES);
@@ -105,7 +105,7 @@ export const ConnectionList = observer<Props>(function ConnectionList({
{!connections.length && filterState.filterValue && (
<TableItem item='tableInfo' selectDisabled>
<TableColumnValue colSpan={5}>
{translate('connections_connection_access_filter_no_result')}
{translate('ui_search_no_result_placeholder')}
</TableColumnValue>
</TableItem>
)}
@@ -13,7 +13,7 @@ import styled, { css } from 'reshadow';
import {
BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group,
IconOrImage, Loader, TabContainerPanelComponent,
InfoItem, Loader, TabContainerPanelComponent,
TextPlaceholder, useMapResource, useTab,
} from '@cloudbeaver/core-blocks';
import { ConnectionsResource, DBDriverResource, isCloudConnection } from '@cloudbeaver/core-connections';
@@ -36,26 +36,11 @@ const styles = css`
position: relative;
overflow: auto !important;
}
info-item {
display: flex;
align-items: center;
flex: 0 0 auto;
}
IconOrImage {
width: 24px;
height: 24px;
margin-right: 16px;
}
Loader {
z-index: 2;
}
`;
interface IInfoItem {
text: TLocalizationToken;
icon: string;
}
export const GrantedConnections: TabContainerPanelComponent<IRoleFormProps> = observer(function GrantedConnections({
tabId,
state: formState,
@@ -63,49 +48,40 @@ export const GrantedConnections: TabContainerPanelComponent<IRoleFormProps> = ob
const style = useStyles(BASE_CONTAINERS_STYLES, styles);
const translate = useTranslate();
const { state, edit, grant, load, revoke } = useGrantedConnections(formState.config, formState.mode);
const state = useGrantedConnections(formState.config, formState.mode);
const { selected } = useTab(tabId);
const dbDriverResource = useMapResource(DBDriverResource, selected ? 'all' : null);
const connections = useMapResource(ConnectionsResource, selected ? ConnectionsResource.keyAll : null);
const grantedConnections = useMemo(() => computed(() => connections.resource.values
.filter(connection => state.grantedSubjects.includes(connection.id))
), [state.grantedSubjects, connections.resource]);
.filter(connection => state.state.grantedSubjects.includes(connection.id))
), [state.state.grantedSubjects, connections.resource]);
useEffect(() => {
if (selected && !state.loaded) {
load();
if (selected && !state.state.loaded) {
state.load();
}
}, [selected, state.loaded, load]);
}, [selected, state.state.loaded]);
if (!selected) {
return null;
}
let infoItem: IInfoItem | null = null;
let info: TLocalizationToken | null = null;
const unsaved = (formState.mode === 'edit' && (state.initialGrantedSubjects.length !== state.grantedSubjects.length
|| state.initialGrantedSubjects.some(subject => !state.grantedSubjects.includes(subject))));
const cloudExists = connections.resource.values.some(isCloudConnection);
if (cloudExists) {
infoItem = {
text: 'connections_connection_access_cloud_placeholder',
icon: '/icons/info_icon.svg',
};
info = 'cloud_connections_access_placeholder';
}
if (unsaved) {
infoItem = {
text: 'connections_connection_access_save_reminder',
icon: '/icons/info_icon.svg',
};
if (formState.mode === 'edit' && state.changed) {
info = 'ui_save_reminder';
}
return styled(style)(
<Loader state={[connections, dbDriverResource, state]}>
<Loader state={[connections, dbDriverResource, state.state]}>
{() => styled(style)(
<ColoredContainer parent gap vertical>
{!connections.resource.values.length ? (
@@ -114,25 +90,20 @@ export const GrantedConnections: TabContainerPanelComponent<IRoleFormProps> = ob
</Group>
) : (
<>
{infoItem && (
<info-item>
<IconOrImage icon={infoItem.icon} />
{translate(infoItem.text)}
</info-item>
)}
{info && <InfoItem info={info} />}
<Container gap overflow>
<GrantedConnectionList
grantedConnections={grantedConnections.get()}
disabled={formState.disabled}
onEdit={edit}
onRevoke={revoke}
onEdit={state.edit}
onRevoke={state.revoke}
/>
{state.editing && (
{state.state.editing && (
<ConnectionList
connectionList={connections.resource.values}
grantedSubjects={state.grantedSubjects}
grantedSubjects={state.state.grantedSubjects}
disabled={formState.disabled}
onGrant={grant}
onGrant={state.grant}
/>
)}
</Container>
@@ -95,9 +95,9 @@ export const GrantedConnectionList = observer<Props>(function GrantedConnectionL
let tableInfoText: TLocalizationToken | null = null;
if (!connections.length) {
if (filterState.filterValue) {
tableInfoText = 'connections_connection_access_filter_no_result';
tableInfoText = 'ui_search_no_result_placeholder';
} else {
tableInfoText = 'connections_connection_access_empty_table_placeholder';
tableInfoText = 'ui_no_items_placeholder';
}
}
@@ -6,59 +6,67 @@
* you may not use this file except in compliance with the License.
*/
import { action, computed, observable } from 'mobx';
import { RoleInfo, RolesResource } from '@cloudbeaver/core-authentication';
import { useObjectRef, useTabState } from '@cloudbeaver/core-blocks';
import { useObservableRef, useTabState } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { isArraysEqual } from '@cloudbeaver/core-utils';
import type { RoleFormMode } from '../IRoleFormProps';
import type { IGrantedConnectionsTabState } from './IGrantedConnectionsTabState';
interface IConnectionAccessState {
interface State {
state: IGrantedConnectionsTabState;
changed: boolean;
edit: () => void;
revoke: (subjectIds: string[]) => void;
grant: (subjectIds: string[]) => void;
edit: () => void;
load: () => Promise<void>;
}
export function useGrantedConnections(role: RoleInfo, mode: RoleFormMode): IConnectionAccessState {
export function useGrantedConnections(role: RoleInfo, mode: RoleFormMode): Readonly<State> {
const resource = useService(RolesResource);
const notificationService = useService(NotificationService);
const state = useTabState<IGrantedConnectionsTabState>();
const edit = () => {
state.editing = !state.editing;
};
const revoke = (subjectIds: string[]): void => {
state.grantedSubjects = state.grantedSubjects.filter(subject => !subjectIds.includes(subject));
};
const grant = (subjectIds: string[]): void => {
state.grantedSubjects.push(...subjectIds);
};
const load = async () => {
if (state.loaded || state.loading) {
return;
}
try {
state.loading = true;
if (mode === 'edit') {
const grantInfo = await resource.getSubjectConnectionAccess(role.roleId);
state.grantedSubjects = grantInfo.map(subject => subject.connectionId);
state.initialGrantedSubjects = state.grantedSubjects.slice();
return useObservableRef(() => ({
get changed() {
return !isArraysEqual(this.state.initialGrantedSubjects, this.state.grantedSubjects);
},
edit() {
this.state.editing = !this.state.editing;
},
grant(subjectIds: string[]) {
this.state.grantedSubjects.push(...subjectIds);
},
revoke(subjectIds: string[]) {
this.state.grantedSubjects = this.state.grantedSubjects.filter(subject => !subjectIds.includes(subject));
},
async load() {
if (this.state.loaded || this.state.loading) {
return;
}
state.loaded = true;
} catch (exception) {
notificationService.logException(exception, `Error getting granted connections for "${role.roleId}"`);
}
state.loading = false;
};
try {
this.state.loading = true;
return useObjectRef({ state, revoke, grant, edit, load });
if (this.mode === 'edit') {
const grantInfo = await this.resource.getSubjectConnectionAccess(this.role.roleId);
this.state.grantedSubjects = grantInfo.map(subject => subject.connectionId);
this.state.initialGrantedSubjects = this.state.grantedSubjects.slice();
}
this.state.loaded = true;
} catch (exception) {
this.notificationService.logException(exception, `Error getting granted connections for "${this.role.roleId}"`);
} finally {
this.state.loading = false;
}
},
}),
{ state: observable.ref, changed: computed, grant: action.bound, revoke: action.bound, edit: action.bound },
{ state, role, mode, resource, notificationService },
['load']);
}
@@ -95,9 +95,9 @@ export const GrantedUserList = observer<Props>(function GrantedUserList({
let tableInfoText: TLocalizationToken | null = null;
if (!users.length) {
if (filterState.filterValue) {
tableInfoText = 'connections_connection_access_filter_no_result';
tableInfoText = 'ui_search_no_result_placeholder';
} else {
tableInfoText = 'connections_connection_access_empty_table_placeholder';
tableInfoText = 'ui_no_items_placeholder';
}
}
@@ -125,7 +125,7 @@ export const GrantedUserList = observer<Props>(function GrantedUserList({
id={user.userId}
name={`${user.userId}${usersResource.isActiveUser(user.userId) ? ' (you)' : ''}`}
icon='/icons/user.svg'
iconTooltip={translate('connections_connection_access_user_tooltip')}
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled}
/>
))}
@@ -14,10 +14,10 @@ import styled, { css } from 'reshadow';
import { UsersResource } from '@cloudbeaver/core-authentication';
import {
BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group,
IconOrImage, Loader, TabContainerPanelComponent,
InfoItem, Loader, TabContainerPanelComponent,
TextPlaceholder, useMapResource, useTab,
} from '@cloudbeaver/core-blocks';
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles } from '@cloudbeaver/core-theming';
import type { IRoleFormProps } from '../IRoleFormProps';
@@ -36,26 +36,11 @@ const styles = css`
position: relative;
overflow: auto !important;
}
info-item {
display: flex;
align-items: center;
flex: 0 0 auto;
}
IconOrImage {
width: 24px;
height: 24px;
margin-right: 16px;
}
Loader {
z-index: 2;
}
`;
interface IInfoItem {
text: TLocalizationToken;
icon: string;
}
export const GrantedUsers: TabContainerPanelComponent<IRoleFormProps> = observer(function GrantedUsers({
tabId,
state: formState,
@@ -63,40 +48,27 @@ export const GrantedUsers: TabContainerPanelComponent<IRoleFormProps> = observer
const style = useStyles(BASE_CONTAINERS_STYLES, styles);
const translate = useTranslate();
const { state, edit, grant, load, revoke } = useGrantedUsers(formState.config, formState.mode);
const state = useGrantedUsers(formState.config, formState.mode);
const { selected } = useTab(tabId);
const users = useMapResource(UsersResource, selected ? UsersResource.keyAll : null);
const grantedUsers = useMemo(() => computed(() => users.resource.values
.filter(user => state.grantedUsers.includes(user.userId))
), [state.grantedUsers, users.resource]);
.filter(user => state.state.grantedUsers.includes(user.userId))
), [state.state.grantedUsers, users.resource]);
useEffect(() => {
if (selected && !state.loaded) {
load();
if (selected && !state.state.loaded) {
state.load();
}
}, [selected, state.loaded, load]);
}, [selected, state.state.loaded]);
if (!selected) {
return null;
}
let infoItem: IInfoItem | null = null;
const unsaved = (formState.mode === 'edit' && (state.initialGrantedUsers.length !== state.grantedUsers.length
|| state.initialGrantedUsers.some(subject => !state.grantedUsers.includes(subject))));
if (unsaved) {
infoItem = {
text: 'connections_connection_access_save_reminder',
icon: '/icons/info_icon.svg',
};
}
return styled(style)(
<Loader state={[users, state]}>
<Loader state={[users, state.state]}>
{() => styled(style)(
<ColoredContainer parent gap vertical>
{!users.resource.values.length ? (
@@ -105,25 +77,20 @@ export const GrantedUsers: TabContainerPanelComponent<IRoleFormProps> = observer
</Group>
) : (
<>
{infoItem && (
<info-item>
<IconOrImage icon={infoItem.icon} />
{translate(infoItem.text)}
</info-item>
)}
{formState.mode === 'edit' && state.changed && <InfoItem info='ui_save_reminder' />}
<Container gap overflow>
<GrantedUserList
grantedUsers={grantedUsers.get()}
disabled={formState.disabled}
onEdit={edit}
onRevoke={revoke}
onEdit={state.edit}
onRevoke={state.revoke}
/>
{state.editing && (
{state.state.editing && (
<UserList
userList={users.resource.values}
grantedUsers={state.grantedUsers}
grantedUsers={state.state.grantedUsers}
disabled={formState.disabled}
onGrant={grant}
onGrant={state.grant}
/>
)}
</Container>
@@ -63,15 +63,15 @@ const styles = composes(
interface Props {
userList: AdminUserInfoFragment[];
grantedUsers: string[];
onGrant: (subjectIds: string[]) => void;
disabled: boolean;
onGrant: (subjectIds: string[]) => void;
}
export const UserList = observer<Props>(function UserList({
userList,
grantedUsers,
onGrant,
disabled,
onGrant,
}) {
const props = useObjectRef({ onGrant });
const style = useStyles(styles, BASE_CONTAINERS_STYLES);
@@ -105,7 +105,7 @@ export const UserList = observer<Props>(function UserList({
{!users.length && filterState.filterValue && (
<TableItem item='tableInfo' selectDisabled>
<TableColumnValue colSpan={5}>
{translate('connections_connection_access_filter_no_result')}
{translate('ui_search_no_result_placeholder')}
</TableColumnValue>
</TableItem>
)}
@@ -115,7 +115,7 @@ export const UserList = observer<Props>(function UserList({
id={user.userId}
name={`${user.userId}${usersResource.isActiveUser(user.userId) ? ' (you)' : ''}`}
icon='/icons/user.svg'
iconTooltip={translate('connections_connection_access_user_tooltip')}
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled || !!grantedUsers.includes(user.userId)}
/>
))}
@@ -6,60 +6,67 @@
* you may not use this file except in compliance with the License.
*/
import { action, computed, observable } from 'mobx';
import { RoleInfo, RolesResource } from '@cloudbeaver/core-authentication';
import { useObjectRef, useTabState } from '@cloudbeaver/core-blocks';
import { useObservableRef, useTabState } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { isArraysEqual } from '@cloudbeaver/core-utils';
import type { RoleFormMode } from '../IRoleFormProps';
import type { IGrantedUsersTabState } from './IGrantedUsersTabState';
interface IConnectionAccessState {
interface State {
state: IGrantedUsersTabState;
changed: boolean;
edit: () => void;
revoke: (subjectIds: string[]) => void;
grant: (subjectIds: string[]) => void;
edit: () => void;
load: () => Promise<void>;
}
export function useGrantedUsers(role: RoleInfo, mode: RoleFormMode): IConnectionAccessState {
export function useGrantedUsers(role: RoleInfo, mode: RoleFormMode): Readonly<State> {
const resource = useService(RolesResource);
const notificationService = useService(NotificationService);
const state = useTabState<IGrantedUsersTabState>();
const edit = () => {
state.editing = !state.editing;
};
const revoke = (subjectIds: string[]): void => {
state.grantedUsers = state.grantedUsers.filter(subject => !subjectIds.includes(subject));
};
const grant = (subjectIds: string[]): void => {
state.grantedUsers.push(...subjectIds);
};
const load = async () => {
if (state.loaded || state.loading) {
return;
}
try {
state.loading = true;
if (mode === 'edit') {
const grantedUsers = await resource.loadGrantedUsers(role.roleId);
state.grantedUsers = grantedUsers;
state.initialGrantedUsers = state.grantedUsers.slice();
return useObservableRef(() => ({
get changed() {
return !isArraysEqual(this.state.initialGrantedUsers, this.state.grantedUsers);
},
edit() {
this.state.editing = !this.state.editing;
},
revoke(subjectIds: string[]) {
this.state.grantedUsers = this.state.grantedUsers.filter(subject => !subjectIds.includes(subject));
},
grant(subjectIds: string[]) {
this.state.grantedUsers.push(...subjectIds);
},
async load() {
if (this.state.loaded || this.state.loading) {
return;
}
state.loaded = true;
} catch (exception) {
notificationService.logException(exception, "Can't load users info");
}
state.loading = false;
};
try {
this.state.loading = true;
return useObjectRef({ state, revoke, grant, edit, load });
if (this.mode === 'edit') {
const grantedUsers = await this.resource.loadGrantedUsers(this.role.roleId);
this.state.grantedUsers = grantedUsers;
this.state.initialGrantedUsers = this.state.grantedUsers.slice();
}
this.state.loaded = true;
} catch (exception) {
this.notificationService.logException(exception, "Can't load users info");
} finally {
this.state.loading = false;
}
},
}),
{ state: observable.ref, changed: computed, edit: action.bound, revoke: action.bound, grant: action.bound },
{ state, role, mode, resource, notificationService },
['load']);
}
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
import styled, { css, use } from 'reshadow';
import { AdministrationItemContentComponent, ADMINISTRATION_TOOLS_PANEL_STYLES } from '@cloudbeaver/core-administration';
import { BASE_CONTAINERS_STYLES, ToolsAction, Loader, ToolsPanel, useTable } from '@cloudbeaver/core-blocks';
import { BASE_CONTAINERS_STYLES, ToolsAction, Loader, ToolsPanel } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
import { composes, useStyles } from '@cloudbeaver/core-theming';
@@ -50,8 +50,7 @@ export const RolesAdministration: AdministrationItemContentComponent = observer(
const style = useStyles(styles, ADMINISTRATION_TOOLS_PANEL_STYLES, BASE_CONTAINERS_STYLES);
const service = useService(CreateRoleService);
const tableState = useTable();
const rolesTableState = useRolesTable(tableState);
const table = useRolesTable();
return styled(style)(
<>
@@ -60,7 +59,7 @@ export const RolesAdministration: AdministrationItemContentComponent = observer(
title={translate('administration_roles_add_tooltip')}
icon="add"
viewBox="0 0 24 24"
disabled={!!sub || rolesTableState.processing}
disabled={!!sub || table.processing}
onClick={service.create}
>
{translate('ui_add')}
@@ -69,8 +68,8 @@ export const RolesAdministration: AdministrationItemContentComponent = observer(
title={translate('administration_roles_refresh_tooltip')}
icon="refresh"
viewBox="0 0 24 24"
disabled={rolesTableState.processing}
onClick={rolesTableState.update}
disabled={table.processing}
onClick={table.update}
>
{translate('ui_refresh')}
</ToolsAction>
@@ -78,8 +77,8 @@ export const RolesAdministration: AdministrationItemContentComponent = observer(
title={translate('administration_roles_delete_tooltip')}
icon="trash"
viewBox="0 0 24 24"
disabled={!tableState.itemsSelected || rolesTableState.processing}
onClick={rolesTableState.delete}
disabled={!table.tableState.itemsSelected || table.processing}
onClick={table.delete}
>
{translate('ui_delete')}
</ToolsAction>
@@ -93,11 +92,11 @@ export const RolesAdministration: AdministrationItemContentComponent = observer(
)}
<layout-grid-cell {...use({ span: 12 })}>
<RolesTable
roles={rolesTableState.roles}
selectedItems={tableState.selected}
expandedItems={tableState.expanded}
roles={table.roles}
selectedItems={table.tableState.selected}
expandedItems={table.tableState.expanded}
/>
<Loader loading={false} overlay />
<Loader loading={table.processing} overlay />
</layout-grid-cell>
</layout-grid-inner>
</layout-grid>
@@ -9,28 +9,28 @@
import { computed, observable } from 'mobx';
import { compareRoles, RoleInfo, RolesResource } from '@cloudbeaver/core-authentication';
import { ITableState, useObservableRef } from '@cloudbeaver/core-blocks';
import { TableState, useObservableRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { resourceKeyList } from '@cloudbeaver/core-sdk';
interface State {
tableState: TableState;
processing: boolean;
tableState: ITableState;
roles: RoleInfo[];
update: () => Promise<void>;
delete: () => Promise<void>;
}
export function useRolesTable(tableState: ITableState): State {
export function useRolesTable(): Readonly<State> {
const notificationService = useService(NotificationService);
const dialogService = useService(CommonDialogService);
const resource = useService(RolesResource);
return useObservableRef<State>(() => ({
tableState: new TableState(),
processing: false,
tableState,
get roles() {
return resource.values.slice().sort(compareRoles);
},
@@ -85,5 +85,5 @@ export function useRolesTable(tableState: ITableState): State {
}), {
processing: observable.ref,
roles: computed,
}, { tableState }, ['update', 'delete']);
}, false, ['update', 'delete']);
}
@@ -96,7 +96,7 @@ export const ConnectionAccess: TabContainerPanelComponent<IUserFormProps> = obse
{cloudExists && (
<TableItem item='cloudInfo' selectDisabled>
<TableColumnValue colSpan={5}>
{translate('connections_connection_access_cloud_placeholder')}
{translate('cloud_connections_access_placeholder')}
</TableColumnValue>
</TableItem>
)}
@@ -34,7 +34,6 @@ const styles = composes(
layout-grid {
width: 100%;
overflow: auto;
flex: 1;
}
layout-grid-inner {
@@ -96,12 +95,14 @@ export const UsersAdministration: AdministrationItemContentComponent = observer(
</ToolsPanel>
<layout-grid>
<layout-grid-inner>
<layout-grid-cell {...use({ span: 12 })}>
{controller.isProvidersLoading ? <Loader /> : (
<>
{sub && service.user && (
{controller.isProvidersLoading ? <Loader /> : (
<>
{sub && service.user && (
<layout-grid-cell {...use({ span: 12 })}>
<CreateUser user={service.user} onCancel={service.cancelCreate} />
)}
</layout-grid-cell>
)}
<layout-grid-cell {...use({ span: 12 })}>
<UsersTable
users={controller.users}
selectedItems={controller.selectedItems}
@@ -109,9 +110,9 @@ export const UsersAdministration: AdministrationItemContentComponent = observer(
selectable={controller.isLocalProviderAvailable}
/>
<Loader loading={controller.isLoading} overlay />
</>
)}
</layout-grid-cell>
</layout-grid-cell>
</>
)}
</layout-grid-inner>
</layout-grid>
</>
@@ -44,6 +44,9 @@ export default [
['authentication_user_password_change_menu_title', 'Change local password'],
['authentication_user_password_change_success', 'Password was changed successfully'],
['authentication_user_password_change_dialog_submit', 'Change'],
['authentication_user_icon_tooltip', 'User'],
['authentication_role_icon_tooltip', 'Role'],
['administration_configuration_wizard_configuration_admin', 'Administrator Credentials'],
['administration_configuration_wizard_configuration_admin_name', 'Login'],
['administration_configuration_wizard_configuration_admin_password', 'Password'],
@@ -42,6 +42,9 @@ export default [
['authentication_user_password_change_menu_title', 'Modifica la password locale'],
['authentication_user_password_change_success', 'Password modificata con successo'],
['authentication_user_password_change_dialog_submit', 'Modifica'],
['authentication_user_icon_tooltip', 'Utente'],
['authentication_role_icon_tooltip', 'Ruolo'],
['administration_configuration_wizard_configuration_admin', 'Credenziali amministrative'],
['administration_configuration_wizard_configuration_admin_name', 'Login'],
['administration_configuration_wizard_configuration_admin_password', 'Password'],
@@ -42,6 +42,8 @@ export default [
['authentication_user_password_change_menu_title', 'Изменить локальный пароль'],
['authentication_user_password_change_success', 'Пароль успешно изменен'],
['authentication_user_password_change_dialog_submit', 'Изменить'],
['authentication_user_icon_tooltip', 'Пользователь'],
['authentication_role_icon_tooltip', 'Роль'],
['administration_identity_providers_tab_title', 'Провайдеры идентификации'],
['administration_identity_providers_provider', 'Провайдер'],
@@ -8,7 +8,7 @@
import { observer } from 'mobx-react-lite';
import { ITableState, ToolsPanel } from '@cloudbeaver/core-blocks';
import { TableState, ToolsPanel } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { ObjectPropertyTableFooterItem } from './ObjectPropertyTableFooterItem';
@@ -16,7 +16,7 @@ import { ObjectPropertyTableFooterService } from './ObjectPropertyTableFooterSer
interface Props {
nodeIds: string[];
tableState: ITableState;
tableState: TableState;
className?: string;
}
@@ -7,7 +7,7 @@
*/
import { ENodeFeature, getNodeName, NavNode, NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-app';
import type { ITableState } from '@cloudbeaver/core-blocks';
import type { TableState } from '@cloudbeaver/core-blocks';
import { injectable } from '@cloudbeaver/core-di';
import {
ContextMenuService, IMenuContext, IContextMenuItem, IMenuItem,
@@ -18,7 +18,7 @@ import { resourceKeyList } from '@cloudbeaver/core-sdk';
interface IObjectPropertyTableFooterContext {
nodeIds: string[];
tableState: ITableState;
tableState: TableState;
}
@injectable()
@@ -93,7 +93,7 @@ export class ObjectPropertyTableFooterService {
);
}
constructMenuWithContext(nodeIds: string[], tableState: ITableState): IMenuItem[] {
constructMenuWithContext(nodeIds: string[], tableState: TableState): IMenuItem[] {
const context: IMenuContext<IObjectPropertyTableFooterContext> = {
menuId: this.objectPropertyTableFooterToken,
contextType: ObjectPropertyTableFooterService.objectPropertyContextType,