Merge branch 'devel' into 10205-fix-high-cve-in-nanoid-and-image-size-libraries

This commit is contained in:
mr-anton-t
2026-08-11 12:37:59 +02:00
committed by GitHub
9 changed files with 157 additions and 51 deletions
@@ -17,7 +17,8 @@ import {
getObjectPropertyValueType,
type IObjectPropertyInfo,
} from '@cloudbeaver/core-sdk';
import { EMPTY_ARRAY, removeMetadataFromDataURL } from '@cloudbeaver/core-utils';
import { EMPTY_ARRAY, getTextFileReadingProcess, removeMetadataFromDataURL } from '@cloudbeaver/core-utils';
import { clsx } from '@dbeaver/ui-kit';
import { FieldCheckbox } from '../../FormControls/Checkboxes/FieldCheckbox.js';
import { Select } from '../../FormControls/Select.js';
@@ -30,6 +31,8 @@ import { Link } from '../../Link.js';
import { useTranslate } from '../../localization/useTranslate.js';
import { evaluate } from '../evaluate.js';
import { SAVED_VALUE_INDICATOR } from '../../SAVED_VALUE_INDICATOR.js';
import { UploadArea } from '../../UploadArea.js';
import { Button } from '../../Button.js';
interface RenderFieldProps {
property: IObjectPropertyInfo;
@@ -204,6 +207,46 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
);
}
if (controlType === 'uploadable-textarea' && state && property.id) {
async function handleFileUpload(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0];
if (!file) {
throw new Error('File is not found');
}
const process = getTextFileReadingProcess(file);
const result = await process.promise;
if (state && property.id) {
state[property.id] = result;
}
}
return (
<div className="tw:flex tw:flex-col tw:gap-2">
<Textarea
required={required}
title={state[property.id]}
labelTooltip={property.description || property.displayName}
placeholder={placeholder}
name={property.id}
state={state}
disabled={disabled}
readOnly={readonly}
className={clsx('tw:flex-0!', className)}
>
{property.displayName ?? ''}
</Textarea>
<UploadArea disabled={disabled || readonly} reset onChange={handleFileUpload}>
<Button className="tw:w-max" tag="div" disabled={disabled || readonly} variant="secondary">
{translate('ui_file')}
</Button>
</UploadArea>
</div>
);
}
if (controlType === 'file' && state) {
return (
<InputFileTextContent
@@ -7,10 +7,12 @@
*/
import type { IObjectPropertyInfo } from './IObjectPropertyInfo.js';
export type ObjectPropertyType = 'checkbox' | 'selector' | 'link' | 'textarea' | 'file' | 'input';
export type ObjectPropertyType = 'checkbox' | 'selector' | 'link' | 'textarea' | 'file' | 'input' | 'uploadable-textarea';
export function getObjectPropertyType(property: IObjectPropertyInfo): ObjectPropertyType {
const dataType = property.dataType?.toLowerCase();
const isTextarea = dataType === 'string' && property.length === 'MULTILINE';
const isFile = property.features.includes('file');
if (dataType === 'boolean') {
return 'checkbox';
@@ -18,9 +20,11 @@ export function getObjectPropertyType(property: IObjectPropertyInfo): ObjectProp
return 'selector';
} else if (property.features.includes('href')) {
return 'link';
} else if (dataType === 'string' && property.length === 'MULTILINE') {
} else if (isTextarea && isFile) {
return 'uploadable-textarea';
} else if (isTextarea) {
return 'textarea';
} else if (property.features.includes('file')) {
} else if (isFile) {
return 'file';
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dial
import { CreateTeamService } from './Teams/TeamsTable/CreateTeamService.js';
import { EUsersAdministrationSub, UsersAdministrationNavigationService } from './UsersAdministrationNavigationService.js';
import { CreateUserService } from './UsersTable/CreateUserService.js';
import type { IUserFilters } from './UsersTable/Filters/useUsersTableFilters.js';
const UserCredentialsList = React.lazy(async () => {
const { UserCredentialsList } = await import('./UsersTable/UserCredentialsList.js');
@@ -33,15 +34,25 @@ const UsersAdministration = React.lazy(async () => {
return { default: UsersAdministration };
});
const UsersTableFilterButton = React.lazy(async () => {
const { UsersTableFilterButton } = await import('./UsersTable/Filters/UsersTableFilterButton.js');
return { default: UsersTableFilterButton };
});
export interface IUserDetailsInfoProps {
user: AdminUser;
}
export interface IUsersActionButtonProps {
filters: IUserFilters;
}
@injectable(() => [AdministrationItemService, CreateUserService, TeamsResource, CreateTeamService, UsersResource, CommonDialogService])
export class UsersAdministrationService extends Bootstrap {
readonly tabsContainer: TabsContainer;
readonly userDetailsInfoPlaceholder: PlaceholderContainer<IUserDetailsInfoProps>;
readonly informationPlaceholder: PlaceholderContainer;
readonly actionButtonsPlaceholder: PlaceholderContainer<IUsersActionButtonProps>;
administrationItem!: IAdministrationItem;
constructor(
@@ -56,6 +67,7 @@ export class UsersAdministrationService extends Bootstrap {
this.userDetailsInfoPlaceholder = new PlaceholderContainer();
this.tabsContainer = new TabsContainer('Access Control');
this.informationPlaceholder = new PlaceholderContainer();
this.actionButtonsPlaceholder = new PlaceholderContainer();
}
override register(): void {
@@ -83,6 +95,7 @@ export class UsersAdministrationService extends Bootstrap {
getDrawerComponent: () => UsersDrawerItem,
});
this.userDetailsInfoPlaceholder.add(UserCredentialsList, 0);
this.actionButtonsPlaceholder.add(UsersTableFilterButton, 0);
}
private async handleDeactivate(sub: EUsersAdministrationSub) {
@@ -0,0 +1,29 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { clsx, IconButton, type IconButtonProps } from '@dbeaver/ui-kit';
const BUTTON_BOX_CLASS_NAME = clsx(
'theme-form-element-radius theme-background-surface theme-text-on-surface theme theme-border-color-background',
'tw:flex tw:items-center tw:justify-center tw:box-border tw:h-8 tw:border-2',
);
const BUTTON_CLASS_NAME = 'tw:relative! tw:box-border! tw:bg-inherit! tw:cursor-pointer! tw:w-[28px]! tw:h-full! tw:p-1! tw:hover:opacity-80!';
export interface IFiltersActionButtonProps extends IconButtonProps {
active?: boolean;
}
export const FiltersActionButton = observer<IFiltersActionButtonProps>(function FiltersActionButton({ active, className, size = 'small', ...rest }) {
return (
<div className={BUTTON_BOX_CLASS_NAME}>
<IconButton className={clsx(BUTTON_CLASS_NAME, active && 'tw:bg-(--theme-secondary)!', className)} size={size} {...rest} />
</div>
);
});
@@ -0,0 +1,32 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { useContext } from 'react';
import { IconOrImage, useTranslate } from '@cloudbeaver/core-blocks';
import type { IUsersActionButtonProps } from '../../UsersAdministrationService.js';
import { FiltersActionButton } from './FiltersActionButton.js';
import { UsersTableFiltersOpenContext } from './UsersTableFiltersOpenContext.js';
export const UsersTableFilterButton = observer<IUsersActionButtonProps>(function UsersTableFilterButton() {
const translate = useTranslate();
const openContext = useContext(UsersTableFiltersOpenContext);
if (!openContext) {
throw new Error('UsersTableFilterButton must be rendered within UsersTableFiltersOpenContext');
}
const label = translate('authentication_administration_users_filters_filter_label');
return (
<FiltersActionButton active={openContext.open} aria-label={label} title={label} onClick={openContext.onToggle}>
<IconOrImage className="tw:w-full tw:h-full" icon="filter" />
</FiltersActionButton>
);
});
@@ -1,11 +1,3 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
.filterContainer {
display: flex;
gap: 12px;
@@ -16,31 +8,7 @@
}
.actions {
composes: theme-form-element-radius theme-background-surface theme-text-on-surface theme theme-border-color-background from global;
box-sizing: border-box;
border: 2px solid;
height: 32px;
}
.button {
position: relative;
box-sizing: border-box;
background: inherit;
cursor: pointer;
width: 28px;
height: 100%;
padding: 4px;
&:hover {
opacity: 0.8;
}
}
.buttonActive {
background: var(--theme-secondary);
}
.iconOrImage {
width: 100%;
height: 100%;
display: flex;
align-items: center;
gap: 8px;
}
@@ -6,14 +6,16 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { Filter, Group, IconOrImage, Loader, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { Filter, Group, Loader, Placeholder, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { UsersAdministrationService } from '../../UsersAdministrationService.js';
import styles from './UsersTableFilters.module.css';
import { UsersTableFiltersDetails } from './UsersTableFiltersDetails.js';
import { UsersTableFiltersOpenContext } from './UsersTableFiltersOpenContext.js';
import { type IUserFilters } from './useUsersTableFilters.js';
import { IconButton } from '@dbeaver/ui-kit';
interface Props {
filters: IUserFilters;
@@ -22,8 +24,11 @@ interface Props {
export const UsersTableFilters = observer<Props>(function UsersTableFilters({ filters }) {
const translate = useTranslate();
const style = useS(styles);
const usersAdministrationService = useService(UsersAdministrationService);
const [open, setOpen] = useState(false);
const onToggle = useCallback(() => setOpen(open => !open), []);
const openContext = useMemo(() => ({ open, onToggle }), [open, onToggle]);
return (
<Group parent compact gap>
@@ -35,14 +40,9 @@ export const UsersTableFilters = observer<Props>(function UsersTableFilters({ fi
onChange={filters.setSearch}
/>
<div className={s(style, { actions: true })}>
<IconButton
className={s(style, { button: true, buttonActive: open })}
size="small"
aria-label={translate('authentication_administration_users_filters_filter_label')}
onClick={() => setOpen(!open)}
>
<IconOrImage className={s(style, { iconOrImage: true })} icon="filter" />
</IconButton>
<UsersTableFiltersOpenContext.Provider value={openContext}>
<Placeholder container={usersAdministrationService.actionButtonsPlaceholder} filters={filters} />
</UsersTableFiltersOpenContext.Provider>
</div>
</div>
@@ -0,0 +1,15 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { createContext } from 'react';
export interface IUsersTableFiltersOpenContext {
open: boolean;
onToggle: () => void;
}
export const UsersTableFiltersOpenContext = createContext<IUsersTableFiltersOpenContext | null>(null);
@@ -22,3 +22,5 @@ export * from './AdministrationUsersManagementService.js';
export * from './externalUserProviderStatusContext.js';
export * from './Administration/Users/Teams/TeamsForm/Options/getTeamOptionsFormPart.js';
export * from './Administration/Users/UsersTable/UsersTableOptionsPanelService.js';
export * from './Administration/Users/UsersTable/Filters/useUsersTableFilters.js';
export * from './Administration/Users/UsersTable/Filters/FiltersActionButton.js';