Merge branch 'devel' into CB-4356-create-smart-tabs-switchingng-for-forms

This commit is contained in:
s.teleshev
2023-12-13 14:01:42 +01:00
56 changed files with 239 additions and 326 deletions
@@ -18,7 +18,7 @@ Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
org.jkiss.bundle.graphql.java;visibility:=reexport,
org.jkiss.bundle.apache.dbcp,
com.google.gson;visibility:=reexport,
jakarta.servlet-api;bundle-version="[4.0.0,4.0.0]";visibility:=reexport
jakarta.servlet-api;visibility:=reexport
Export-Package: io.cloudbeaver,
io.cloudbeaver.auth,
io.cloudbeaver.auth.provider,
@@ -77,9 +77,9 @@ import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import org.jkiss.utils.CommonUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.lang.reflect.InvocationTargetException;
import java.time.Instant;
import java.util.*;
@@ -19,7 +19,7 @@ package io.cloudbeaver.service;
import org.jkiss.dbeaver.DBException;
import javax.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServlet;
public interface DBWServletContext {
void addServlet(String servletId, HttpServlet servlet, String mapping) throws DBException;
@@ -19,8 +19,8 @@ package io.cloudbeaver.service;
import io.cloudbeaver.model.session.WebSession;
import org.jkiss.dbeaver.DBException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
@@ -33,9 +33,9 @@ import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
@@ -20,9 +20,9 @@ import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWServletHandler;
import org.jkiss.dbeaver.DBException;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.Servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
@@ -20,8 +20,8 @@ import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWSessionHandler;
import org.jkiss.dbeaver.DBException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public abstract class AbstractActionSessionHandler implements DBWSessionHandler {
@@ -38,10 +38,10 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -22,7 +22,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.jkiss.dbeaver.DBException;
import javax.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServlet;
public class CBJettyServletContext implements DBWServletContext {
private final ServletContextHandler contextHandler;
@@ -19,9 +19,9 @@ package io.cloudbeaver.server.jetty;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.SessionCookieConfig;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.SessionCookieConfig;
public class CBServerContextListener implements ServletContextListener {
@@ -5,10 +5,10 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -47,10 +47,10 @@ import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = "/")
public class CBStaticServlet extends DefaultServlet {
@@ -7,10 +7,10 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -29,7 +29,7 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.security.exception.SMAccessTokenExpiredException;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
@@ -18,9 +18,9 @@ package io.cloudbeaver.service;
import org.jkiss.dbeaver.DBException;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.Servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
@@ -34,8 +34,8 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.utils.ArrayUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -9,10 +9,10 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;
@@ -26,9 +26,9 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.rm.RMConstants;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -31,8 +31,8 @@ import io.cloudbeaver.service.WebServiceBindingBase;
import io.cloudbeaver.service.core.impl.WebServiceCore;
import io.cloudbeaver.service.session.WebSessionManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -60,8 +60,8 @@ import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -38,9 +38,9 @@ import org.jkiss.utils.CommonUtils;
import java.util.*;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
/**
* Web session manager
@@ -34,11 +34,11 @@ import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@MultipartConfig
public class WebSQLFileLoaderServlet extends WebServiceServletBase {
@@ -11,12 +11,12 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
@@ -12,8 +12,8 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.dbeaver.utils.MimeTypes;
import org.jkiss.utils.IOUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
@@ -43,8 +43,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class RPSessionHandler implements DBWSessionHandler {
@@ -23,9 +23,9 @@ import io.cloudbeaver.utils.WebAppUtils;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.Servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LocalServletHandler extends AbstractActionServletHandler {
@@ -28,8 +28,8 @@ import org.jkiss.dbeaver.tools.transfer.registry.DataTransferRegistry;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
@@ -30,11 +30,11 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
import javax.servlet.MultipartConfigElement;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
@@ -33,6 +33,7 @@ import type { IAuthCredentials } from './IAuthCredentials';
const NEW_USER_SYMBOL = Symbol('new-user');
export type AdminUser = AdminUserInfoFragment;
export type AdminUserOrigin = AdminUserInfoFragment['origins'][number];
type AdminUserNew = AdminUser & { [NEW_USER_SYMBOL]: boolean };
export type UserResourceIncludes = Omit<GetUsersListQueryVariables, 'userId' | 'page' | 'filter'>;
@@ -3,6 +3,7 @@
flex-direction: row;
align-content: baseline;
position: relative;
box-sizing: border-box;
flex-wrap: wrap;
flex: 1 1 100%;
@@ -17,5 +17,5 @@ export function filterContainerFakeProps<T extends IContainerProps>(props: T): O
export function getContainerProps<T extends IContainerProps>(props: T): IContainerProps {
const { hideEmpty, flexStart, flexEnd, baseline, center, vertical, wrap, overflow, parent, gap, grid, dense, compact, ...rest } = props;
return { hideEmpty, flexStart, flexEnd, baseline, center, vertical, wrap, overflow, parent, gap, grid, dense, compact, ...getLayoutProps(props) };
return { hideEmpty, flexStart, flexEnd, baseline, center, vertical, wrap, overflow, parent, gap, grid, dense, compact, ...getLayoutProps(rest) };
}
@@ -13,6 +13,7 @@ import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
import { getLayoutProps } from '../../Containers/filterLayoutFakeProps';
import type { ILayoutSizeProps } from '../../Containers/ILayoutSizeProps';
import elementsSizeStyles from '../../Containers/shared/ElementsSize.m.css';
import { useTranslate } from '../../localization/useTranslate';
import { s } from '../../s';
import { TextPlaceholder } from '../../TextPlaceholder';
import { useS } from '../../useS';
@@ -30,6 +31,7 @@ interface ObjectPropertyFormProps extends ILayoutSizeProps {
autoHide?: boolean;
showRememberTip?: boolean;
hideEmptyPlaceholder?: boolean;
emptyPlaceholder?: string;
canShowPassword?: boolean;
isSaved?: (property: ObjectPropertyInfo) => boolean;
geLayoutSize?: (property: ObjectPropertyInfo) => ILayoutSizeProps;
@@ -48,12 +50,14 @@ export const ObjectPropertyInfoForm = observer<ObjectPropertyFormProps>(function
autoHide,
showRememberTip,
hideEmptyPlaceholder,
emptyPlaceholder = 'core_blocks_object_property_info_form_empty_placeholder',
canShowPassword,
isSaved,
geLayoutSize,
onFocus,
...rest
}) {
const translate = useTranslate();
const layoutProps = getLayoutProps(rest);
const handleFocus = useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
@@ -67,7 +71,7 @@ export const ObjectPropertyInfoForm = observer<ObjectPropertyFormProps>(function
const sizeStyles = useS(elementsSizeStyles);
if (properties.length === 0 && !hideEmptyPlaceholder) {
return <TextPlaceholder>Properties empty</TextPlaceholder>;
return <TextPlaceholder>{translate(emptyPlaceholder)}</TextPlaceholder>;
}
return (
@@ -7,6 +7,7 @@
*/
import { s } from '../s';
import { useS } from '../useS';
import tableStyles from './Table.m.css';
import style from './TableColumnHeader.m.css';
interface Props {
@@ -25,7 +26,7 @@ export const TableColumnHeader: React.FC<React.PropsWithChildren<Props>> = funct
className,
children,
}) {
const styles = useS(style);
const styles = useS(style, tableStyles);
return (
<th title={title} className={s(styles, { min, centerContent, columnHeader: true }, className)}>
@@ -5,10 +5,15 @@
padding: 0 16px;
transition: padding ease-in-out 0.24s;
&.nowrap,
&.ellipsis {
white-space: nowrap;
}
&.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 0;
}
&.centerContent > .tdFlex {
@@ -26,12 +26,16 @@ type Props = {
ellipsis?: boolean;
flex?: boolean;
expand?: boolean;
nowrap?: boolean;
onClick?: () => void;
onDoubleClick?: () => void;
} & React.DetailedHTMLProps<React.TdHTMLAttributes<HTMLTableCellElement>, HTMLTableCellElement>;
export const TableColumnValue = observer<Props, HTMLTableCellElement>(
forwardRef(function TableColumnValue({ align, children, centerContent, ellipsis, flex, expand, className, onClick, onDoubleClick, ...rest }, ref) {
forwardRef(function TableColumnValue(
{ align, children, centerContent, ellipsis, flex, expand, nowrap, className, onClick, onDoubleClick, ...rest },
ref,
) {
const tableContext = useContext(TableContext);
const context = useContext(TableItemContext);
const props = useObjectRef({ onClick, onDoubleClick });
@@ -67,7 +71,7 @@ export const TableColumnValue = observer<Props, HTMLTableCellElement>(
<td
ref={ref}
align={align}
className={s(styles, { centerContent, ellipsis, cell: true }, className)}
className={s(styles, { centerContent, ellipsis, nowrap, cell: true }, className)}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
{...rest}
@@ -4,6 +4,7 @@ export default [
['core_block_properties_table_filter_name', 'Filter'],
['core_block_properties_table_add', 'Add property'],
['core_blocks_properties_table_item_reset', 'Reset to default'],
['core_blocks_object_property_info_form_empty_placeholder', 'Properties empty'],
['core_blocks_properties_table_item_remove', 'Remove property'],
['combobox_no_results_placeholder', 'No results'],
['core_blocks_exception_message_error_title', 'Unable to display content'],
@@ -3,5 +3,6 @@ export default [
['core_block_properties_table_value', 'Valore'],
['core_block_properties_table_add', 'Aggiungi proprietà'],
['core_blocks_properties_table_item_reset', 'Reset to default'],
['core_blocks_object_property_info_form_empty_placeholder', 'Properties empty'],
['core_blocks_properties_table_item_remove', 'Remove property'],
];
@@ -4,6 +4,7 @@ export default [
['core_block_properties_table_filter_name', 'Фильтр'],
['core_block_properties_table_add', 'Добавить параметр'],
['core_blocks_properties_table_item_reset', 'Вернуть дефолтное значение'],
['core_blocks_object_property_info_form_empty_placeholder', 'Параметры отсутствуют'],
['core_blocks_properties_table_item_remove', 'Удалить параметр'],
['combobox_no_results_placeholder', 'Нет совпадений'],
];
@@ -42,6 +42,7 @@ export default [
['ui_add', 'Add'],
['ui_refresh', 'Refresh'],
['ui_data_saving_error', 'Save error'],
['ui_data_remove_confirmation', 'Remove confirmation'],
['ui_data_delete_confirmation', 'Delete confirmation'],
['ui_no_matches_placeholder', 'Your search returned no matches.'],
['ui_information', 'Information'],
@@ -39,6 +39,7 @@ export default [
['ui_add', 'Aggiungi'],
['ui_refresh', 'Aggiorna'],
['ui_data_saving_error', 'Errore di salvataggio'],
['ui_data_remove_confirmation', 'Remove confirmation'],
['ui_data_delete_confirmation', 'Conferma di eliminazione'],
['ui_no_matches_placeholder', 'La tua ricerca non ha prodotto risultati.'],
['ui_information', 'Informazioni'],
@@ -39,6 +39,7 @@ export default [
['ui_refresh', 'Обновить'],
['ui_data_saving_error', 'Ошибка при сохранении изменений'],
['ui_no_matches_placeholder', 'По вашему запросу ничего не найдено.'],
['ui_data_remove_confirmation', 'Подтвердите удаление'],
['ui_data_delete_confirmation', 'Подтвердите удаление'],
['ui_information', 'Информация'],
['ui_clipboard', 'Буфер обмена'],
@@ -39,6 +39,7 @@ export default [
['ui_add', '添加'],
['ui_refresh', '刷新'],
['ui_data_saving_error', '保存出错'],
['ui_data_remove_confirmation', 'Remove confirmation'],
['ui_data_delete_confirmation', '删除确认'],
['ui_no_matches_placeholder', '您的搜索没有返回匹配项。'],
['ui_information', '信息'],
@@ -67,7 +67,7 @@ export const AuthConfiguration = observer<Props>(function AuthConfiguration({ co
<TableColumnValue title={configuration.displayName} expand ellipsis>
{configuration.displayName}
</TableColumnValue>
<TableColumnValue>{configuration.providerId}</TableColumnValue>
<TableColumnValue nowrap>{configuration.providerId}</TableColumnValue>
<TableColumnValue title={configuration.description} ellipsis>
{configuration.description || ''}
</TableColumnValue>
@@ -31,9 +31,9 @@ export const AuthConfigurationsTable = observer<Props>(function AuthConfiguratio
<TableColumnHeader min />
<TableColumnHeader min />
<TableColumnHeader>{translate('administration_identity_providers_provider_configuration_name')}</TableColumnHeader>
<TableColumnHeader>{translate('administration_identity_providers_provider')}</TableColumnHeader>
<TableColumnHeader min>{translate('administration_identity_providers_provider')}</TableColumnHeader>
<TableColumnHeader>{translate('administration_identity_providers_provider_configuration_description')}</TableColumnHeader>
<TableColumnHeader>{translate('administration_identity_providers_provider_configuration_disabled')}</TableColumnHeader>
<TableColumnHeader min>{translate('administration_identity_providers_provider_configuration_disabled')}</TableColumnHeader>
<TableColumnHeader />
</TableHeader>
<TableBody>
@@ -6,162 +6,118 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { Fragment } from 'react';
import { UsersResource } from '@cloudbeaver/core-authentication';
import { AdminUserOrigin, UsersResource } from '@cloudbeaver/core-authentication';
import {
Button,
ColoredContainer,
ExceptionMessage,
Combobox,
ConfirmationDialog,
Container,
Group,
IAutoLoadable,
Loader,
GroupItem,
ObjectPropertyInfoForm,
TextPlaceholder,
useAutoLoad,
useObjectRef,
useResource,
useTranslate,
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import type { AdminUserInfo, ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { FormMode, TabContainerPanelComponent, useTab, useTabState } from '@cloudbeaver/core-ui';
import type { UserFormProps } from '../AdministrationUserFormService';
import { getUserFormOriginTabId } from './getUserFormOriginTabId';
interface IInnerState extends IAutoLoadable {
state: IState;
user: AdminUserInfo | undefined;
}
interface IState {
properties: ObjectPropertyInfo[];
state: Record<string, any>;
loading: boolean;
loaded: boolean;
exception: Error | null;
selectedOrigin: string;
}
const empty: any[] = [];
export const UserFormOriginInfoPanel: TabContainerPanelComponent<UserFormProps> = observer(function UserFormOriginInfoPanel({
tabId,
formState: { mode, state },
}) {
const translate = useTranslate();
const usersResource = useService(UsersResource);
const localState = useTabState<IState>(() => ({
origin: null,
properties: [],
state: {},
loading: false,
loaded: false,
exception: null,
}));
const editing = mode === FormMode.Edit;
const userInfo = useResource(UserFormOriginInfoPanel, UsersResource, state.userId, { active: editing });
let origin = userInfo.data?.origins.find(origin => getUserFormOriginTabId('origin', origin) === tabId);
if (!origin) {
origin = userInfo.data?.origins[0];
}
const loadableState = useObjectRef<IInnerState>(
() => ({
get exception(): Error | null {
return this.state.exception;
},
isError(): boolean {
return !!this.state.exception;
},
isLoaded(): boolean {
return this.state.loaded;
},
isLoading(): boolean {
return this.state.loading;
},
async load(reload = false) {
if ((this.state.loaded && !reload) || this.state.loading || !this.user) {
return;
}
this.state.loading = true;
this.state.exception = null;
try {
usersResource.markOutdated(this.user.userId);
const userOrigin = await usersResource.load(this.user.userId, ['customIncludeOriginDetails']);
let origin = userOrigin.origins.find(origin => getUserFormOriginTabId('origin', origin) === tabId);
if (!origin) {
origin = this.user.origins[0];
}
const propertiesState = {} as Record<string, any>;
for (const property of origin.details!) {
propertiesState[property.id!] = property.value;
}
this.state.properties = origin.details!;
this.state.state = propertiesState;
this.state.loaded = true;
} catch (error: any) {
this.state.exception = error;
} finally {
this.state.loading = false;
}
},
async reload() {
await this.load();
},
}),
const localState = useTabState<IState>(() => ({
selectedOrigin: '0',
}));
const userInfoLoader = useResource(
UserFormOriginInfoPanel,
UsersResource,
{ key: state.userId, includes: ['customIncludeOriginDetails'] },
{
state: localState,
user: userInfo.data as AdminUserInfo | undefined,
active: editing,
},
['reload', 'load', 'isLoaded', 'isLoading', 'isError'],
);
const commonDialogService = useService(CommonDialogService);
const notificationService = useService(NotificationService);
const origins = userInfoLoader.data?.origins ?? [];
const origin: AdminUserOrigin | undefined = origins[localState.selectedOrigin as any];
const { selected } = useTab(tabId);
useAutoLoad(UserFormOriginInfoPanel, loadableState, selected);
if (!selected) {
return null;
}
if (localState.loading) {
return (
<ColoredContainer parent>
<Group large>
<Loader key="static" />
</Group>
</ColoredContainer>
);
if (!origin && origins.length > 0) {
localState.selectedOrigin = '0';
}
if (localState.exception) {
return (
<ColoredContainer parent>
<Group large>
<ExceptionMessage exception={localState.exception} onRetry={() => loadableState.reload?.()} />
</Group>
</ColoredContainer>
);
}
async function deleteHandler() {
const result = await commonDialogService.open(ConfirmationDialog, {
title: 'ui_data_delete_confirmation',
message: translate('authentication_administration_user_delete_credentials_confirmation_message', undefined, {
originName: origin?.displayName,
userId: state.userId,
}),
confirmActionText: 'ui_delete',
});
if (!origin || (localState.loaded && localState.properties.length === 0)) {
return (
<ColoredContainer parent>
<Group large>
<TextPlaceholder>{translate('authentication_administration_user_origin_empty')}</TextPlaceholder>
</Group>
</ColoredContainer>
);
if (result !== DialogueStateResult.Rejected) {
try {
await userInfoLoader.resource.deleteCredentials(state.userId!, origin!.type!);
notificationService.logSuccess({ title: 'authentication_administration_user_delete_credentials_success' });
} catch (exception: any) {
notificationService.logException(exception, 'authentication_administration_user_delete_credentials_error');
}
}
}
return (
<ColoredContainer>
<Group gap large>
<ObjectPropertyInfoForm properties={localState.properties} state={localState.state} readOnly small autoHide />
<ColoredContainer gap>
<Group gap medium overflow>
<Combobox
state={localState}
name="selectedOrigin"
items={origins}
keySelector={(o, i) => String(i)}
valueSelector={origin => origin.displayName}
tiny
>
{translate('authentication_administration_user_auth_method')}
</Combobox>
{origins.length === 0 && <GroupItem>{translate('authentication_administration_user_auth_methods_empty')}</GroupItem>}
{origin && (
<Fragment>
<Container gap>
<ObjectPropertyInfoForm
properties={origin.details || empty}
emptyPlaceholder="authentication_administration_user_auth_method_no_details"
readOnly
small
autoHide
/>
</Container>
<GroupItem>
<Button type="button" mod={['outlined']} onClick={deleteHandler}>
{translate('ui_delete')}
</Button>
</GroupItem>
</Fragment>
)}
</Group>
</ColoredContainer>
);
@@ -8,26 +8,16 @@
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { UsersResource } from '@cloudbeaver/core-authentication';
import { Translate, useResource, useStyles } from '@cloudbeaver/core-blocks';
import { FormMode, Tab, TabContainerTabComponent, TabTitle } from '@cloudbeaver/core-ui';
import { Translate, useStyles } from '@cloudbeaver/core-blocks';
import { Tab, TabContainerTabComponent, TabTitle } from '@cloudbeaver/core-ui';
import type { UserFormProps } from '../AdministrationUserFormService';
import { getUserFormOriginTabId } from './getUserFormOriginTabId';
export const UserFormOriginInfoTab: TabContainerTabComponent<UserFormProps> = observer(function UserFormOriginInfoTab({
tabId,
formState: { mode, state },
style,
...rest
}) {
const editing = mode === FormMode.Edit;
const userInfo = useResource(UserFormOriginInfoTab, UsersResource, state.userId, { active: editing });
const origin = userInfo.data?.origins.find(origin => getUserFormOriginTabId('origin', origin) === tabId);
export const UserFormOriginInfoTab: TabContainerTabComponent<UserFormProps> = observer(function UserFormOriginInfoTab({ tabId, style, ...rest }) {
return styled(useStyles(style))(
<Tab {...rest} tabId={tabId} style={style}>
<TabTitle>
<Translate token={origin?.displayName || 'Origin'} />
<Translate token="authentication_administration_user_auth_methods" />
</TabTitle>
</Tab>,
);
@@ -13,7 +13,6 @@ import { FormMode } from '@cloudbeaver/core-ui';
import { AdministrationUserFormService } from '../AdministrationUserFormService';
import { DATA_CONTEXT_USER_FORM_INFO_PART } from '../Info/DATA_CONTEXT_USER_FORM_INFO_PART';
import { getUserFormOriginTabId } from './getUserFormOriginTabId';
const UserFormOriginInfoPanel = React.lazy(async () => {
const { UserFormOriginInfoPanel } = await import('./UserFormOriginInfoPanel');
@@ -35,19 +34,6 @@ export class UserFormOriginPartBootstrap extends Bootstrap {
this.administrationUserFormService.parts.add({
key: 'origin',
order: 2,
generator: (tabId, props) => {
const userFormInfoPart = props?.formState.dataContext.get(DATA_CONTEXT_USER_FORM_INFO_PART);
if (props?.formState?.mode === FormMode.Edit && userFormInfoPart?.initialState.userId) {
const user = this.usersResource.get(userFormInfoPart.initialState.userId);
const origins = user?.origins.filter(origin => origin.type !== AUTH_PROVIDER_LOCAL_ID);
if (origins && origins.length > 0) {
return origins.map(origin => getUserFormOriginTabId(tabId, origin));
}
}
return ['origin'];
},
isHidden: (tabId, props) => {
const userFormInfoPart = props?.formState.dataContext.get(DATA_CONTEXT_USER_FORM_INFO_PART);
if (props?.formState?.mode === FormMode.Edit && userFormInfoPart?.initialState.userId) {
@@ -34,6 +34,9 @@ const styles = css`
TableColumnValue[|gap] {
gap: 16px;
}
TableColumnValue[|overflow] {
overflow: auto !important;
}
`;
interface Props {
@@ -95,7 +98,7 @@ export const User = observer<Props>(function User({ user, displayAuthRole, selec
onChange={handleEnabledCheckboxChange}
/>
</TableColumnValue>
<TableColumnValue flex {...use({ gap: true })}>
<TableColumnValue flex {...use({ gap: true, overflow: true })} ellipsis>
<Loader suspense small inline hideMessage>
<Placeholder container={usersAdministrationService.userDetailsInfoPlaceholder} user={user} />
</Loader>
@@ -6,30 +6,6 @@
* you may not use this file except in compliance with the License.
*/
.container {
display: flex;
justify-content: center;
align-items: center;
width: 24px;
height: 46px;
cursor: pointer;
}
.container:hover {
& .icon {
display: block;
}
& .originIcon {
display: none;
}
}
.icon {
display: none;
width: 16px;
}
.staticImage {
width: 24px;
height: 24px;
@@ -8,11 +8,8 @@
import { observer } from 'mobx-react-lite';
import { Fragment } from 'react';
import { AUTH_PROVIDER_LOCAL_ID, UsersResource } from '@cloudbeaver/core-authentication';
import { ConfirmationDialog, Icon, PlaceholderComponent, s, StaticImage, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication';
import { PlaceholderComponent, s, StaticImage, useS, useTranslate } from '@cloudbeaver/core-blocks';
import type { ObjectOrigin } from '@cloudbeaver/core-sdk';
import type { IUserDetailsInfoProps } from '../UsersAdministrationService';
@@ -35,45 +32,10 @@ export const UserCredentials = observer<IUserCredentialsProps>(function UserCred
});
export const UserCredentialsList: PlaceholderComponent<IUserDetailsInfoProps> = observer(function UserCredentialsList({ user }) {
const translate = useTranslate();
const usersResource = useService(UsersResource);
const commonDialogService = useService(CommonDialogService);
const notificationService = useService(NotificationService);
const styles = useS(style);
async function onDelete(originId: string, originName: string) {
const state = await commonDialogService.open(ConfirmationDialog, {
title: 'ui_data_delete_confirmation',
message: translate('authentication_administration_user_remove_credentials_confirmation_message', undefined, {
originName,
userId: user.userId,
}),
confirmActionText: 'ui_delete',
});
if (state !== DialogueStateResult.Rejected) {
try {
await usersResource.deleteCredentials(user.userId, originId);
notificationService.logSuccess({ title: 'authentication_administration_user_remove_credentials_success' });
} catch (exception: any) {
notificationService.logException(exception, 'authentication_administration_user_remove_credentials_error');
}
}
}
return (
<Fragment key="user-credentials-list">
{user.origins.map(origin => (
<div
key={origin.type + origin.subType}
title={origin.displayName}
className={s(styles, { container: true })}
onClick={() => onDelete(origin.type, origin.displayName)}
>
<UserCredentials className={s(styles, { originIcon: true })} origin={origin} />
<Icon className={s(styles, { icon: true })} name="reject" viewBox="0 0 16 16" />
</div>
<UserCredentials key={origin.type + origin.subType} origin={origin} />
))}
</Fragment>
);
@@ -6,6 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { AuthRolesResource } from '@cloudbeaver/core-authentication';
import { ColoredContainer, Container, Group, Placeholder, useAutoLoad, useResource } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
@@ -38,12 +39,16 @@ export const UsersPage = observer<Props>(function UsersPage({ param }) {
const userManagementDisabled = administrationUsersManagementService.externalUserProviderEnabled;
return (
<ColoredContainer vertical wrap gap parent>
<Group box keepSize>
<ColoredContainer vertical wrap gap parent maximum>
<Group keepSize box maximum>
<UsersAdministrationToolsPanel onUpdate={table.update} />
</Group>
<Container overflow gap>
<Group keepSize box maximum>
<UsersTableFilters filters={filters} />
</Group>
<Container overflow gap maximum>
{create && createUserService.state && !userManagementDisabled && (
<Group box>
<CreateUser state={createUserService.state} onCancel={createUserService.cancelCreate} />
@@ -52,10 +57,6 @@ export const UsersPage = observer<Props>(function UsersPage({ param }) {
<Placeholder container={createUserService.toolsContainer} param={param} />
<Group box>
<UsersTableFilters filters={filters} />
</Group>
<Group boxNoOverflow>
<UsersTable
users={table.users}
@@ -54,8 +54,8 @@ export const UsersTable = observer<Props>(function UsersTable({
<TableColumnHeader>{translate('authentication_user_name')}</TableColumnHeader>
{displayAuthRole && <TableColumnHeader>{translate('authentication_user_role')}</TableColumnHeader>}
<TableColumnHeader>{translate('authentication_user_team')}</TableColumnHeader>
<TableColumnHeader>{translate('authentication_user_enabled')}</TableColumnHeader>
<TableColumnHeader>{translate('authentication_user_credentials')}</TableColumnHeader>
<TableColumnHeader min>{translate('authentication_user_enabled')}</TableColumnHeader>
<TableColumnHeader>{translate('authentication_administration_user_auth_methods')}</TableColumnHeader>
</TableHeader>
<TableBody>
{users.map(user => (
@@ -12,6 +12,10 @@ export default [
['authentication_administration_user_origin_empty', 'No available details'],
['authentication_administration_user_info', 'Info'],
['authentication_administration_user_local', 'Local user'],
['authentication_administration_user_auth_method', 'Auth Method'],
['authentication_administration_user_auth_methods', 'Auth Methods'],
['authentication_administration_user_auth_methods_empty', 'No available auth methods'],
['authentication_administration_user_auth_method_no_details', 'No details available'],
['authentication_administration_item', 'Access Management'],
['authentication_administration_item_users', 'Users'],
['authentication_administration_item_metaParameters', 'Meta Parameters'],
@@ -50,11 +54,11 @@ export default [
'Deleting this account will permanently remove all associated user data from the system. Please confirm you want to proceed with deletion of "{arg:username}" user.',
],
['authentication_administration_user_remove_credentials_error', 'Failed to remove user credentials'],
['authentication_administration_user_remove_credentials_success', 'User credentials were removed'],
['authentication_administration_user_delete_credentials_error', 'Failed to remove user credentials'],
['authentication_administration_user_delete_credentials_success', 'User credentials were removed'],
[
'authentication_administration_user_remove_credentials_confirmation_message',
'Are you sure you want to delete "{arg:originName}" credentials from "{arg:userId}"?',
'authentication_administration_user_delete_credentials_confirmation_message',
'Are you sure you want to delete "{arg:originName}" auth method from "{arg:userId}"?',
],
['administration_configuration_wizard_configuration_admin', 'Administrator Credentials'],
@@ -11,6 +11,10 @@ export default [
['authentication_administration_user_origin_empty', 'Nessun dettaglio disponibile'],
['authentication_administration_user_info', 'Info'],
['authentication_administration_user_local', 'Local user'],
['authentication_administration_user_auth_method', 'Auth Method'],
['authentication_administration_user_auth_methods', 'Auth Methods'],
['authentication_administration_user_auth_methods_empty', 'No available auth methods'],
['authentication_administration_user_auth_method_no_details', 'No details available'],
['authentication_administration_item', 'Utenti'],
['authentication_administration_tools_add_tooltip', 'Create new user'],
['authentication_administration_tools_refresh_tooltip', 'Aggiorna la lista utenti'],
@@ -46,11 +50,11 @@ export default [
'Deleting this account will permanently remove all associated user data from the system. Please confirm you want to proceed with deletion of "{arg:username}" user.',
],
['authentication_administration_user_remove_credentials_error', 'Failed to remove user credentials'],
['authentication_administration_user_remove_credentials_success', 'User credentials were removed'],
['authentication_administration_user_delete_credentials_error', 'Failed to remove user credentials'],
['authentication_administration_user_delete_credentials_success', 'User credentials were removed'],
[
'authentication_administration_user_remove_credentials_confirmation_message',
'Are you sure you want to delete "{arg:originName}" credentials from "{arg:userId}"?',
'authentication_administration_user_delete_credentials_confirmation_message',
'Are you sure you want to delete "{arg:originName}" auth method from "{arg:userId}"?',
],
['administration_teams_team_info_created', 'Team created'],
@@ -12,6 +12,10 @@ export default [
['authentication_administration_user_origin_empty', 'Дополнительная информация не доступна'],
['authentication_administration_user_info', 'Общее'],
['authentication_administration_user_local', 'Локальный пользователь'],
['authentication_administration_user_auth_method', 'Способ входа'],
['authentication_administration_user_auth_methods', 'Способы входа'],
['authentication_administration_user_auth_methods_empty', 'Нет доступных способов входа'],
['authentication_administration_user_auth_method_no_details', 'Дополнительная информация не доступна'],
['authentication_administration_item', 'Управление доступом'],
['authentication_administration_item_users', 'Пользователи'],
['authentication_administration_item_metaParameters', 'Свойства Пользователей'],
@@ -50,11 +54,11 @@ export default [
'Удаление этой учетной записи навсегда уберет все связанные с ней пользовательские данные из системы. Пожалуйста, подтвердите, что вы хотите произвести удаление пользователя "{arg:username}"',
],
['authentication_administration_user_remove_credentials_error', 'Не удалось удалить учетные данные пользователя'],
['authentication_administration_user_remove_credentials_success', 'Учетные данные пользователя удалены'],
['authentication_administration_user_delete_credentials_error', 'Не удалось удалить учетные данные пользователя'],
['authentication_administration_user_delete_credentials_success', 'Учетные данные пользователя удалены'],
[
'authentication_administration_user_remove_credentials_confirmation_message',
'Вы уверены что хотите удалить "{arg:originName}" учетные данные у "{arg:userId}"?',
'authentication_administration_user_delete_credentials_confirmation_message',
'Вы уверены что хотите удалить "{arg:originName}" способ авторизации у "{arg:userId}"?',
],
['administration_configuration_wizard_configuration_admin', 'Учетные данные администратора'],
@@ -10,6 +10,10 @@ export default [
['authentication_administration_user_connections_empty', '没有可用连接'],
['authentication_administration_user_origin_empty', '没有可用详情'],
['authentication_administration_user_info', '信息'],
['authentication_administration_user_auth_method', 'Auth Method'],
['authentication_administration_user_auth_methods', 'Auth Methods'],
['authentication_administration_user_auth_methods_empty', 'No available auth methods'],
['authentication_administration_user_auth_method_no_details', 'No details available'],
['authentication_administration_user_local', 'Local user'],
['authentication_administration_item', '访问管理'],
['authentication_administration_item_users', '用户'],
@@ -36,11 +40,11 @@ export default [
['authentication_administration_users_disable_user_fail', 'Failed to disable user'],
['authentication_administration_users_disable_user_success', 'User disabled'],
['authentication_administration_user_remove_credentials_error', 'Failed to remove user credentials'],
['authentication_administration_user_remove_credentials_success', 'User credentials were removed'],
['authentication_administration_user_delete_credentials_error', 'Failed to remove user credentials'],
['authentication_administration_user_delete_credentials_success', 'User credentials were removed'],
[
'authentication_administration_user_remove_credentials_confirmation_message',
'Are you sure you want to delete "{arg:originName}" credentials from "{arg:userId}"?',
'authentication_administration_user_delete_credentials_confirmation_message',
'Are you sure you want to delete "{arg:originName}" auth method from "{arg:userId}"?',
],
['administration_configuration_wizard_configuration_admin', '管理员凭据'],