Skip to content
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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');
Expand All @@ -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(
Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
});
Original file line number Diff line number Diff line change
@@ -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>
);
});
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>
Expand All @@ -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>

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@
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';

Check failure on line 25 in webapp/packages/plugin-authentication-administration/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './Administration/Users/UsersTable/Filters/FiltersActionButton.js';

Check failure on line 26 in webapp/packages/plugin-authentication-administration/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
Loading