diff --git a/src/shared/schema/auth/types/users.ts b/src/shared/schema/auth/types/users.ts index de59d3b383..87d4dc7a04 100644 --- a/src/shared/schema/auth/types/users.ts +++ b/src/shared/schema/auth/types/users.ts @@ -91,11 +91,11 @@ export interface GetUsersListArgs { roles?: `${UserRole}`[]; } -export interface GetUserList extends UserProfile { +export interface ListUser extends UserProfile { providerId: string | null; } export interface GetUsersListResponse { nextPageToken?: string; - users: GetUserList[]; + users: ListUser[]; } diff --git a/src/ui/datalens/index.tsx b/src/ui/datalens/index.tsx index 55af6a0344..8ecb76829e 100644 --- a/src/ui/datalens/index.tsx +++ b/src/ui/datalens/index.tsx @@ -59,6 +59,8 @@ const DatalensPageView = () => { return ( }> + {DL.AUTH_ENABLED && } + { component={CollectionsNavigtaionPage} /> - {DL.AUTH_ENABLED && } - diff --git a/src/ui/datalens/pages/ServiceSettingsPage/ServiceSettingsPage.tsx b/src/ui/datalens/pages/ServiceSettingsPage/ServiceSettingsPage.tsx index da080594a0..5afd017857 100644 --- a/src/ui/datalens/pages/ServiceSettingsPage/ServiceSettingsPage.tsx +++ b/src/ui/datalens/pages/ServiceSettingsPage/ServiceSettingsPage.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {ServiceSettings} from 'units/main/containers/ServiceSettings/ServiceSettings'; +import {App as ServiceSettingsApp} from 'ui/units/service-settings/containers/App/App'; -const ServiceSettingsPage = () => ; +const ServiceSettingsPage = () => ; export default ServiceSettingsPage; diff --git a/src/ui/index.ts b/src/ui/index.ts index b2babc17d5..2bdba6cd8f 100644 --- a/src/ui/index.ts +++ b/src/ui/index.ts @@ -22,6 +22,7 @@ import type {WizardGlobalState} from 'units/wizard/reducers'; import type {WorkbooksState} from 'units/workbooks/store/reducers'; import type {CollectionsNavigationState} from './units/collections-navigation/store/reducers'; +import type {ServiceSettingsState} from './units/service-settings/store/typings/serviceSettings'; export {default as ActionPanel} from './components/ActionPanel/ActionPanel'; export {default as Utils} from './utils'; @@ -77,4 +78,5 @@ export type DatalensGlobalState = { editHistory: EditHistoryState; iamAccessDialog: IamAccessDialogState; auth: AuthState; + serviceSettings: ServiceSettingsState; }; diff --git a/src/ui/units/auth/components/UserRoleLabel/UserRoleLabel.tsx b/src/ui/units/auth/components/UserRoleLabel/UserRoleLabel.tsx index 47687e224d..6d41e3884d 100644 --- a/src/ui/units/auth/components/UserRoleLabel/UserRoleLabel.tsx +++ b/src/ui/units/auth/components/UserRoleLabel/UserRoleLabel.tsx @@ -3,7 +3,8 @@ import React from 'react'; import type {LabelProps} from '@gravity-ui/uikit'; import {Label} from '@gravity-ui/uikit'; import {UserRole} from 'shared/components/auth/constants/role'; -import {getCapitalizedStr} from 'ui/utils/stringUtils'; + +import {getRoleByKey} from '../../utils/userProfile'; const labelThemeByUserRole: Record<`${UserRole}`, LabelProps['theme']> = { [UserRole.Editor]: 'info', @@ -20,7 +21,3 @@ interface UserRoleLabelProps { export function UserRoleLabel({role}: UserRoleLabelProps) { return ; } - -function getRoleByKey(role: `${UserRole}`) { - return getCapitalizedStr(role.replace('datalens.', '')); -} diff --git a/src/ui/units/auth/utils/userProfile.ts b/src/ui/units/auth/utils/userProfile.ts new file mode 100644 index 0000000000..12fd202d95 --- /dev/null +++ b/src/ui/units/auth/utils/userProfile.ts @@ -0,0 +1,6 @@ +import type {UserRole} from 'shared/components/auth/constants/role'; +import {getCapitalizedStr} from 'ui/utils/stringUtils'; + +export function getRoleByKey(role: `${UserRole}`) { + return getCapitalizedStr(role.replace('datalens.', '')); +} diff --git a/src/ui/units/main/containers/ServiceSettings/ServiceSettings.scss b/src/ui/units/main/containers/ServiceSettings/ServiceSettings.scss deleted file mode 100644 index 9004e13915..0000000000 --- a/src/ui/units/main/containers/ServiceSettings/ServiceSettings.scss +++ /dev/null @@ -1,81 +0,0 @@ -@mixin header($fontSize, $lineHeight) { - margin: 0; - font-weight: 500; - font-size: $fontSize; - line-height: $lineHeight; -} - -.dl-service-settings { - $baseClass: &; - - min-height: 100vh; - - &__content { - padding: 20px; - } - - &__header { - @include header(var(--g-text-body-3-font-size), var(--g-text-body-3-line-height)); - } - - &__sections { - display: grid; - grid-template-columns: repeat(1, auto); - row-gap: 60px; - width: 520px; - margin-top: 40px; - line-height: 18px; - } - - &__section-header { - @include header(var(--g-text-body-2-font-size), var(--g-text-body-2-line-height)); - } - - &__section-description { - margin: 10px 0 20px; - color: var(--g-color-text-secondary); - } - - &__section-rows { - display: grid; - grid-template-columns: repeat(1, 100%); - row-gap: 24px; - } - - &__section-row { - display: grid; - grid-template-columns: 200px auto; - } - - &__section-row-label { - display: flex; - flex-direction: column; - } - - &__section-row-label-part { - &:not(:first-child) { - margin-top: 2px; - } - - &_description { - color: var(--g-color-text-secondary); - } - } - - &__section-row-content { - display: grid; - grid-template-columns: repeat(1, 100%); - row-gap: 12px; - } - - &__button { - width: max-content; - } - - &__loader { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - } -} diff --git a/src/ui/units/main/containers/ServiceSettings/ServiceSettings.tsx b/src/ui/units/main/containers/ServiceSettings/ServiceSettings.tsx deleted file mode 100644 index 3bc5d0d6e4..0000000000 --- a/src/ui/units/main/containers/ServiceSettings/ServiceSettings.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -import block from 'bem-cn-lite'; -import {I18n} from 'i18n'; - -import {SectionColorPalettes} from './components/SectionColorPalettes/SectionColorPalettes'; - -import './ServiceSettings.scss'; - -const b = block('dl-service-settings'); -const i18n = I18n.keyset('main.service-settings.view'); - -export const ServiceSettings = () => { - return ( -
-
-

{i18n('label_header')}

-
- -
-
-
- ); -}; diff --git a/src/ui/units/main/containers/ServiceSettings/components/SectionColorPalettes/SectionColorPalettes.scss b/src/ui/units/main/containers/ServiceSettings/components/SectionColorPalettes/SectionColorPalettes.scss deleted file mode 100644 index 4503626d27..0000000000 --- a/src/ui/units/main/containers/ServiceSettings/components/SectionColorPalettes/SectionColorPalettes.scss +++ /dev/null @@ -1,18 +0,0 @@ -@import '~@gravity-ui/uikit/styles/mixins'; - -.dl-service-settings-color-palettes { - &__header { - margin: 0; - font-weight: 500; - font-size: var(--g-text-body-2-font-size); - line-height: var(--g-text-body-2-line-height); - } - - &__content { - flex-direction: column; - } - - &__description { - margin: 10px 0 20px; - } -} diff --git a/src/ui/units/main/containers/ServiceSettings/components/SectionColorPalettes/SectionColorPalettes.tsx b/src/ui/units/main/containers/ServiceSettings/components/SectionColorPalettes/SectionColorPalettes.tsx deleted file mode 100644 index 15b8b3cc28..0000000000 --- a/src/ui/units/main/containers/ServiceSettings/components/SectionColorPalettes/SectionColorPalettes.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -import block from 'bem-cn-lite'; -import ColorPaletteEditorContainer from 'components/ColorPaletteEditorContainer/ColorPaletteEditorContainer'; -import {I18n} from 'i18n'; - -import './SectionColorPalettes.scss'; - -const b = block('dl-service-settings-migration-tenant'); -const i18n = I18n.keyset('main.service-settings.view'); - -export const SectionColorPalettes = () => { - return ( -
-

{i18n('section_color-palettes')}

-
-
- -
-
-
- ); -}; diff --git a/src/ui/units/service-settings/components/SectionColorPalettes/SectionColorPalettes.scss b/src/ui/units/service-settings/components/SectionColorPalettes/SectionColorPalettes.scss new file mode 100644 index 0000000000..f47031cfc5 --- /dev/null +++ b/src/ui/units/service-settings/components/SectionColorPalettes/SectionColorPalettes.scss @@ -0,0 +1,17 @@ +.service-settings-color-palettes { + &__header { + margin: 0; + } + + &__content { + flex-direction: column; + + &_tab { + margin-top: var(--g-spacing-6); + } + } + + &__description { + margin: var(--g-spacing-3) 0 var(--g-spacing-4); + } +} diff --git a/src/ui/units/service-settings/components/SectionColorPalettes/SectionColorPalettes.tsx b/src/ui/units/service-settings/components/SectionColorPalettes/SectionColorPalettes.tsx new file mode 100644 index 0000000000..9a1614cc05 --- /dev/null +++ b/src/ui/units/service-settings/components/SectionColorPalettes/SectionColorPalettes.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import {Text} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import ColorPaletteEditorContainer from 'components/ColorPaletteEditorContainer/ColorPaletteEditorContainer'; +import {I18n} from 'i18n'; +import {DL} from 'ui/constants'; + +import './SectionColorPalettes.scss'; + +const b = block('service-settings-color-palettes'); +// const i18n = I18n.keyset('service-settings.main.view'); +const i18n = I18n.keyset('main.service-settings.view'); + +const SectionColorPalettes = () => { + const isTabContent = DL.AUTH_ENABLED && DL.IS_NATIVE_AUTH_ADMIN; + + return ( +
+ {!isTabContent && ( + + {i18n('section_color-palettes')} + + )} +
+
+ +
+
+
+ ); +}; + +export default SectionColorPalettes; diff --git a/src/ui/units/service-settings/components/UsersList/LabelsList/LabelsList.scss b/src/ui/units/service-settings/components/UsersList/LabelsList/LabelsList.scss new file mode 100644 index 0000000000..834281cf34 --- /dev/null +++ b/src/ui/units/service-settings/components/UsersList/LabelsList/LabelsList.scss @@ -0,0 +1,32 @@ +.users-list-labels-list { + $class: &; + + &__item { + margin-right: var(--g-spacing-2); + } + + &__button { + flex-shrink: 0; + margin-right: var(--g-spacing-2); + } + + &__popup { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: var(--g-spacing-2) var(--g-spacing-1) var(--g-spacing-2) var(--g-spacing-2); + + #{$class}__item { + margin: var(--g-spacing-2) 0; + } + } + + &__popup-content { + max-height: 200px; + overflow-y: auto; + display: flex; + gap: var(--g-spacing-2); + flex-direction: column; + padding-right: var(--g-spacing-1); + } +} diff --git a/src/ui/units/service-settings/components/UsersList/LabelsList/LabelsList.tsx b/src/ui/units/service-settings/components/UsersList/LabelsList/LabelsList.tsx new file mode 100644 index 0000000000..1aad0aa379 --- /dev/null +++ b/src/ui/units/service-settings/components/UsersList/LabelsList/LabelsList.tsx @@ -0,0 +1,83 @@ +import React from 'react'; + +import type {LabelProps} from '@gravity-ui/uikit'; +import {Flex, Label, Popup} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import type {UserRole} from 'shared/components/auth/constants/role'; +import {UserRoleLabel} from 'ui/units/auth/components/UserRoleLabel/UserRoleLabel'; + +import './LabelsList.scss'; + +const b = block('users-list-labels-list'); + +export type LabelsListProps = { + items: `${UserRole}`[]; + countVisibleElements: number; + buttonTheme?: LabelProps['theme']; +}; + +export const LabelsList = ({ + items, + countVisibleElements = 1, + buttonTheme = 'normal', +}: LabelsListProps) => { + const [open, setOpen] = React.useState(false); + const anchorRef = React.useRef(null); + + const toggleRolesPopup = React.useCallback( + (event: React.MouseEvent) => { + event.stopPropagation(); + setOpen(!open); + }, + [open], + ); + + const handlePopupClose = React.useCallback(() => { + setOpen(false); + }, []); + + const renderList = () => { + const visibleItems = items.slice(0, countVisibleElements); + const hiddenItems = items.slice(countVisibleElements); + + const buttonText = `+${items.length - countVisibleElements}`; + + return ( + + {visibleItems.map((item) => ( + + ))} + + +
+
+ {hiddenItems.map((item) => ( + + ))} +
+
+
+
+ ); + }; + + return ( + + {items.length > countVisibleElements + ? renderList() + : items.map((item) => )} + + ); +}; diff --git a/src/ui/units/service-settings/components/UsersList/UsersFilters/UsersFilters.scss b/src/ui/units/service-settings/components/UsersList/UsersFilters/UsersFilters.scss new file mode 100644 index 0000000000..89e4d7dadc --- /dev/null +++ b/src/ui/units/service-settings/components/UsersList/UsersFilters/UsersFilters.scss @@ -0,0 +1,9 @@ +$filterBaseWidth: 220px; + +.service-settings-users-list-filters { + padding-right: var(--g-spacing-2); + + &__filter { + width: $filterBaseWidth; + } +} diff --git a/src/ui/units/service-settings/components/UsersList/UsersFilters/UsersFilters.tsx b/src/ui/units/service-settings/components/UsersList/UsersFilters/UsersFilters.tsx new file mode 100644 index 0000000000..78205f831f --- /dev/null +++ b/src/ui/units/service-settings/components/UsersList/UsersFilters/UsersFilters.tsx @@ -0,0 +1,67 @@ +import React from 'react'; + +import {Flex, Select, TextInput} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +// import {I18n} from 'i18n'; +import debounce from 'lodash/debounce'; +import type {UserRole} from 'shared/components/auth/constants/role'; +import {registry} from 'ui/registry'; +import {getRoleByKey} from 'ui/units/auth/utils/userProfile'; + +import {BASE_USER_FILTERS} from '../constants'; + +import './UsersFilters.scss'; + +const b = block('service-settings-users-list-filters'); +// const i18n = I18n.keyset('service-settings.users-list.view'); + +const {getUsersRoles} = registry.auth.functions.getAll(); + +const ROLES_OPTIONS = Object.values(getUsersRoles()).map((key) => ({ + value: key, + content: getRoleByKey(key), +})); + +const UPDATE_FILTERS_TIMEOUT = 500; + +type UsersFilterProps = {onChange: (filterName: string, filterValue: string | string[]) => void}; + +export const UsersFilter = ({onChange}: UsersFilterProps) => { + const [search, setSearch] = React.useState(''); + const [roles, setRole] = React.useState([]); + + const sendUpdatedFilters = React.useMemo( + () => debounce((name, value) => onChange(name, value), UPDATE_FILTERS_TIMEOUT), + [onChange], + ); + + const handleSearchChange = (value: string) => { + setSearch(value); + sendUpdatedFilters(BASE_USER_FILTERS.FILTER_STRING, value); + }; + + const handleRoleChange = (value: string[]) => { + setRole(value as UserRole[]); + sendUpdatedFilters(BASE_USER_FILTERS.ROLES, value); + }; + + return ( + + +