From e4d467d3dc668f93c6cfa7cd1dc247d12ca53eab Mon Sep 17 00:00:00 2001 From: Oleg Date: Thu, 5 Sep 2024 01:29:16 +0300 Subject: [PATCH] UI improvements (#1658) --- frontend/src/App/slice.ts | 28 +---- frontend/src/App/types.ts | 2 +- frontend/src/assets/icons/dark-theme.svg | 3 - frontend/src/assets/icons/light-theme.svg | 3 - frontend/src/assets/icons/system-theme.svg | 3 - frontend/src/assets/icons/theme.svg | 3 + frontend/src/layouts/AppLayout/hooks.ts | 26 +++++ .../src/layouts/AppLayout/index.module.scss | 50 +++++++- frontend/src/layouts/AppLayout/index.tsx | 37 ++---- frontend/src/layouts/AppLayout/themeIcons.tsx | 23 ++++ frontend/src/libs/fleet.ts | 9 +- frontend/src/libs/run.ts | 10 +- frontend/src/locale/en.json | 12 +- .../pages/Fleets/AdministrationList/hooks.tsx | 31 ++++- .../pages/Fleets/AdministrationList/index.tsx | 110 +++++++++++------- frontend/src/pages/Fleets/List/hooks.tsx | 58 ++++++++- frontend/src/pages/Fleets/List/index.tsx | 55 ++++++++- .../src/pages/Fleets/List/styles.module.scss | 10 ++ .../pages/Runs/AdministrationList/index.tsx | 1 + .../src/pages/Runs/Details/Logs/index.tsx | 4 +- .../src/pages/Runs/List/hooks/useFilters.ts | 15 ++- frontend/src/pages/Runs/MainList/index.tsx | 1 + frontend/src/pages/User/Add/index.tsx | 2 +- .../src/pages/User/Details/Billing/index.tsx | 2 +- .../User/Details/CreditsHistory/Add/index.tsx | 2 +- .../src/pages/User/Details/Settings/index.tsx | 12 +- frontend/src/pages/User/List/index.tsx | 6 +- 27 files changed, 373 insertions(+), 145 deletions(-) delete mode 100644 frontend/src/assets/icons/dark-theme.svg delete mode 100644 frontend/src/assets/icons/light-theme.svg delete mode 100644 frontend/src/assets/icons/system-theme.svg create mode 100644 frontend/src/assets/icons/theme.svg create mode 100644 frontend/src/layouts/AppLayout/themeIcons.tsx diff --git a/frontend/src/App/slice.ts b/frontend/src/App/slice.ts index b36d251b2..8b3ba243c 100644 --- a/frontend/src/App/slice.ts +++ b/frontend/src/App/slice.ts @@ -11,7 +11,6 @@ const getInitialState = (): IAppState => { let authData = null; let storageData = null; let activeMode = getThemeMode(); - let selectedMode: IAppState['systemMode'] = 'system'; try { storageData = localStorage.getItem(AUTH_DATA_STORAGE_KEY); @@ -22,9 +21,8 @@ const getInitialState = (): IAppState => { try { const modeStorageData = localStorage.getItem(MODE_STORAGE_KEY); - if (modeStorageData) { + if (modeStorageData && JSON.parse(modeStorageData)) { activeMode = modeStorageData as Mode; - selectedMode = modeStorageData as Mode; } } catch (e) { console.log(e); @@ -38,7 +36,7 @@ const getInitialState = (): IAppState => { authData, userData: null, breadcrumbs: null, - systemMode: selectedMode, + systemMode: activeMode, toolsPanelState: { isOpen: false, @@ -75,17 +73,11 @@ export const appSlice = createSlice({ } }, - setSystemMode: (state, action: PayloadAction) => { - state.systemMode = action.payload ?? 'system'; - - applyMode(action.payload ?? getThemeMode()); - + setSystemMode: (state, action: PayloadAction) => { + state.systemMode = action.payload; + applyMode(action.payload); try { - if (action.payload) { - localStorage.setItem(MODE_STORAGE_KEY, action.payload); - } else { - localStorage.removeItem(MODE_STORAGE_KEY); - } + localStorage.setItem(MODE_STORAGE_KEY, action.payload); } catch (e) { console.log(e); } @@ -168,12 +160,4 @@ export const selectToolsPanelState = (state: RootState) => state.app.toolsPanelS export const selectHelpPanelContent = (state: RootState) => state.app.helpPanel.content; export const selectTutorialPanel = (state: RootState) => state.app.tutorialPanel; export const selectSystemMode = (state: RootState) => state.app.systemMode; - -export const selectAppliedThemeMode = (state: RootState): Mode => { - if (state.app.systemMode === 'system') { - return getThemeMode(); - } - - return state.app.systemMode; -}; export default appSlice.reducer; diff --git a/frontend/src/App/types.ts b/frontend/src/App/types.ts index 10580cfee..ecdfa2cfd 100644 --- a/frontend/src/App/types.ts +++ b/frontend/src/App/types.ts @@ -21,7 +21,7 @@ export interface IAppState { userData: IUser | null; authData: IUserAuthData | null; breadcrumbs: TBreadcrumb[] | null; - systemMode: Mode | 'system'; + systemMode: Mode; toolsPanelState: { isOpen: boolean; tab: ToolsTabs; diff --git a/frontend/src/assets/icons/dark-theme.svg b/frontend/src/assets/icons/dark-theme.svg deleted file mode 100644 index 90d0773b6..000000000 --- a/frontend/src/assets/icons/dark-theme.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/src/assets/icons/light-theme.svg b/frontend/src/assets/icons/light-theme.svg deleted file mode 100644 index f0d639815..000000000 --- a/frontend/src/assets/icons/light-theme.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/src/assets/icons/system-theme.svg b/frontend/src/assets/icons/system-theme.svg deleted file mode 100644 index 07c112f53..000000000 --- a/frontend/src/assets/icons/system-theme.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/src/assets/icons/theme.svg b/frontend/src/assets/icons/theme.svg new file mode 100644 index 000000000..b3c4d3b3b --- /dev/null +++ b/frontend/src/assets/icons/theme.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/layouts/AppLayout/hooks.ts b/frontend/src/layouts/AppLayout/hooks.ts index ab0d61b6d..bc6c30772 100644 --- a/frontend/src/layouts/AppLayout/hooks.ts +++ b/frontend/src/layouts/AppLayout/hooks.ts @@ -13,6 +13,9 @@ import { GlobalUserRole } from 'types'; import { selectUserName } from 'App/slice'; import { useCheckAvailableProjectPermission } from 'pages/Project/hooks/useCheckAvailableProjectPermission'; +import { DISCORD_URL, DOCS_URL } from '../../consts'; +import { goToUrl } from '../../libs'; + export const useSideNavigation = () => { const { t } = useTranslation(); const userName = useAppSelector(selectUserName) ?? ''; @@ -83,6 +86,29 @@ export const useSideNavigation = () => { title: t('navigation.account'), items: userSettingsLinks, }, + + { type: 'divider' }, + + { + type: 'section-group', + title: t('navigation.resources'), + items: [ + { + type: 'link', + text: t('common.docs'), + external: true, + href: DOCS_URL, + // onClick: () => goToUrl(DOCS_URL, true), + }, + { + type: 'link', + text: t('common.discord'), + external: true, + href: DISCORD_URL, + onClick: () => goToUrl(DISCORD_URL, true), + }, + ], + }, ].filter(Boolean) as SideNavigationProps['items']; const activeHref = useMemo(() => { diff --git a/frontend/src/layouts/AppLayout/index.module.scss b/frontend/src/layouts/AppLayout/index.module.scss index f3220898f..7c12f909a 100644 --- a/frontend/src/layouts/AppLayout/index.module.scss +++ b/frontend/src/layouts/AppLayout/index.module.scss @@ -4,6 +4,14 @@ .b-page-header { .awsui-context-top-navigation { border-bottom: 1px solid awsui.$color-border-divider-default; + + [class*="awsui_utility-type-button"] { + [class*="awsui_link"][href="theme-button"] { + [class^="awsui_icon"] { + width: 48px !important; + } + } + } } } } @@ -38,10 +46,42 @@ } .themeIcon { - stroke: none !important; - fill: currentColor !important; - transform: scale(1.5); - path { - stroke: none !important; + display: flex; + align-items: center; + width: 48px; + gap: 6px; + + .switcher { + position: relative; + flex-shrink: 0; + width: 24px; + height: 16px; + border-radius: 8px; + background-color: awsui.$color-background-layout-toggle-default; + transition: background-color .2s ease; + + &::before { + content: ""; + position: absolute; + top: 2px; + left: 0; + transform: translateX(2px); + width: 12px; + height: 12px; + border-radius: 50%; + background-color: awsui.$color-foreground-control-default; + transition: transform .2s ease; + } + + &.on { + background-color: awsui.$color-background-control-checked; + &::before { + transform: translateX(10px); + } + } + } + + .icon { + flex-shrink: 0; } } diff --git a/frontend/src/layouts/AppLayout/index.tsx b/frontend/src/layouts/AppLayout/index.tsx index bec3817a7..8f6c6c614 100644 --- a/frontend/src/layouts/AppLayout/index.tsx +++ b/frontend/src/layouts/AppLayout/index.tsx @@ -37,13 +37,11 @@ import { import { AnnotationContext } from './AnnotationContext'; import { useProjectDropdown, useSideNavigation } from './hooks'; import { TallyComponent } from './Tally'; +import { DarkThemeIcon, LightThemeIcon } from './themeIcons'; import { TutorialPanel } from './TutorialPanel'; import { ToolsTabs } from 'App/types'; -import { ReactComponent as DarkThemeIcon } from 'assets/icons/dark-theme.svg'; -import { ReactComponent as LightThemeIcon } from 'assets/icons/light-theme.svg'; -import { ReactComponent as SystemThemeIcon } from 'assets/icons/system-theme.svg'; import logo from 'assets/images/logo.svg'; import styles from './index.module.scss'; @@ -55,10 +53,9 @@ const HeaderPortal = ({ children }: PortalProps) => { return null; }; -const THEME_ICON_MAP: Record = { +const THEME_ICON_MAP: Record = { [Mode.Dark]: DarkThemeIcon, [Mode.Light]: LightThemeIcon, - system: SystemThemeIcon, }; const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { @@ -76,7 +73,12 @@ const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { const onFollowHandler: SideNavigationProps['onFollow'] = (event) => { event.preventDefault(); - navigate(event.detail.href); + + if (event.detail.external) { + goToUrl(event.detail.href, true); + } else { + navigate(event.detail.href); + } }; const renderBreadcrumbs = () => { @@ -124,14 +126,11 @@ const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { event.preventDefault(); switch (systemMode) { - case 'system': - dispatch(setSystemMode(Mode.Light)); - return; case Mode.Light: dispatch(setSystemMode(Mode.Dark)); return; default: - dispatch(setSystemMode(null)); + dispatch(setSystemMode(Mode.Light)); } }; @@ -155,16 +154,10 @@ const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { onClick: toggleTutorialPanel, }, { + href: 'theme-button', type: 'button', - text: t('common.docs'), - external: true, - onClick: () => goToUrl(DOCS_URL, true), - }, - { - type: 'button', - text: t('common.discord'), - external: true, - onClick: () => goToUrl(DISCORD_URL, true), + iconSvg: , + onClick: onChangeSystemModeToggle, }, isAvailableProjectDropdown && { type: 'menu-dropdown', @@ -173,12 +166,6 @@ const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { items: projectsDropdownList, onItemFollow: onFollowProject, }, - { - type: 'button', - iconSvg: , - onClick: onChangeSystemModeToggle, - }, - { 'data-class': 'user-menu', type: 'menu-dropdown', diff --git a/frontend/src/layouts/AppLayout/themeIcons.tsx b/frontend/src/layouts/AppLayout/themeIcons.tsx new file mode 100644 index 000000000..214ef9b27 --- /dev/null +++ b/frontend/src/layouts/AppLayout/themeIcons.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import cn from 'classnames'; + +import { ReactComponent as ThemeIcon } from 'assets/icons/theme.svg'; +import styles from './index.module.scss'; + +export const DarkThemeIcon: React.FC = () => { + return ( +
+
+ +
+ ); +}; + +export const LightThemeIcon: React.FC = () => { + return ( +
+
+ +
+ ); +}; diff --git a/frontend/src/libs/fleet.ts b/frontend/src/libs/fleet.ts index 89867a7ed..e8f4ee71d 100644 --- a/frontend/src/libs/fleet.ts +++ b/frontend/src/libs/fleet.ts @@ -3,19 +3,18 @@ import { StatusIndicatorProps } from '@cloudscape-design/components'; export const getStatusIconType = (status: IInstance['status']): StatusIndicatorProps['type'] => { switch (status) { case 'pending': + case 'creating': return 'pending'; case 'terminated': return 'stopped'; - case 'creating': - case 'starting': - case 'provisioning': case 'terminating': - return 'loading'; + case 'provisioning': + case 'starting': case 'busy': return 'in-progress'; case 'idle': return 'success'; default: - return 'stopped'; + console.error(new Error('Undefined fleet status')); } }; diff --git a/frontend/src/libs/run.ts b/frontend/src/libs/run.ts index bb34b2522..000a82ee7 100644 --- a/frontend/src/libs/run.ts +++ b/frontend/src/libs/run.ts @@ -9,19 +9,19 @@ export const getStatusIconType = (status: IRun['status']): StatusIndicatorProps[ return 'error'; case 'aborted': case 'terminated': - return 'stopped'; case 'done': - return 'success'; + return 'stopped'; case 'running': + return 'success'; case 'terminating': + case 'pulling': + case 'provisioning': return 'in-progress'; case 'submitted': case 'pending': - case 'pulling': - case 'provisioning': return 'pending'; default: - return 'stopped'; + console.error(new Error('Undefined run status')); } }; diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json index 15ad4f047..239c8404b 100644 --- a/frontend/src/locale/en.json +++ b/frontend/src/locale/en.json @@ -65,8 +65,9 @@ "administration": "Administration", "users": "Users", "user_settings": "User settings", - "account": "Account", - "billing": "Billing" + "account": "User", + "billing": "Billing", + "resources": "Resources" }, "backend": { @@ -409,6 +410,7 @@ "fleets": { "fleet": "Fleet", + "fleet_placeholder": "Filtering by fleet", "fleet_name": "Fleet name", "total_instances": "Number of instances", "default": "Default pool", @@ -417,7 +419,7 @@ "nomatch_message_title": "No matches", "nomatch_message_text": "We can't find a match.", "nomatch_message_button_label": "Clear filter", - "active_only": "Active Fleets", + "active_only": "Active Instances", "instances": { "title": "Instances", "empty_message_title": "No instances", @@ -457,8 +459,8 @@ "token_description": "Specify use your personal access token", "global_role": "Global role", "email": "Email", - "account": "Account", - "account_settings": "Account settings", + "account": "User", + "account_settings": "User settings", "settings": "Settings", "create": { "page_title": "Create user", diff --git a/frontend/src/pages/Fleets/AdministrationList/hooks.tsx b/frontend/src/pages/Fleets/AdministrationList/hooks.tsx index 1458880ee..396787c1e 100644 --- a/frontend/src/pages/Fleets/AdministrationList/hooks.tsx +++ b/frontend/src/pages/Fleets/AdministrationList/hooks.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { format } from 'date-fns'; @@ -7,6 +7,10 @@ import { Icon, StatusIndicator, TableProps } from 'components'; import { DATE_TIME_FORMAT } from 'consts'; import { getStatusIconType } from 'libs/fleet'; +import { SelectCSDProps } from '../../../components'; +import { useLocalStorageState } from '../../../hooks/useLocalStorageState'; +import { useGetProjectsQuery } from '../../../services/project'; + export const useColumnsDefinitions = () => { const { t } = useTranslation(); @@ -66,10 +70,20 @@ export const useColumnsDefinitions = () => { }; export const useFilters = () => { - const [onlyActive, setOnlyActive] = useState(false); + const [onlyActive, setOnlyActive] = useLocalStorageState('administration-fleet-list-is-active', false); + const [selectedProject, setSelectedProject] = useState(null); + + const { data: projectsData } = useGetProjectsQuery(); + + const projectOptions = useMemo(() => { + if (!projectsData?.length) return []; + + return projectsData.map((project) => ({ label: project.project_name, value: project.project_name })); + }, [projectsData]); const clearFilters = () => { setOnlyActive(false); + setSelectedProject(null); }; const filteringFunction = useCallback<(pool: IPoolListItem) => boolean>( @@ -79,7 +93,16 @@ export const useFilters = () => { [onlyActive], ); - const isDisabledClearFilter = !onlyActive; + const isDisabledClearFilter = !selectedProject && !onlyActive; - return { onlyActive, setOnlyActive, filteringFunction, clearFilters, isDisabledClearFilter } as const; + return { + projectOptions, + selectedProject, + setSelectedProject, + onlyActive, + setOnlyActive, + filteringFunction, + clearFilters, + isDisabledClearFilter, + } as const; }; diff --git a/frontend/src/pages/Fleets/AdministrationList/index.tsx b/frontend/src/pages/Fleets/AdministrationList/index.tsx index 6b126860a..b54785982 100644 --- a/frontend/src/pages/Fleets/AdministrationList/index.tsx +++ b/frontend/src/pages/Fleets/AdministrationList/index.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Container, ContentLayout, DetailsHeader, Header, Loader, Pagination, Table, Toggle } from 'components'; +import { Button, FormField, Header, Pagination, SelectCSD, Table, Toggle } from 'components'; import { DEFAULT_TABLE_PAGE_SIZE } from 'consts'; import { useCollection } from 'hooks'; @@ -18,7 +18,15 @@ export const AdministrationFleetsList: React.FC = () => { const [pagesCount, setPagesCount] = useState(1); const [disabledNext, setDisabledNext] = useState(false); - const { onlyActive, setOnlyActive, isDisabledClearFilter, clearFilters } = useFilters(); + const { + onlyActive, + setOnlyActive, + isDisabledClearFilter, + clearFilters, + projectOptions, + selectedProject, + setSelectedProject, + } = useFilters(); const [getPools, { isLoading, isFetching }] = useLazyGetPoolsInstancesQuery(); const isDisabledPagination = isLoading || isFetching || data.length === 0; @@ -26,6 +34,7 @@ export const AdministrationFleetsList: React.FC = () => { const getPoolsRequest = (params?: Partial) => { return getPools({ only_active: onlyActive, + project_name: selectedProject?.value, limit: DEFAULT_TABLE_PAGE_SIZE, ...params, }).unwrap(); @@ -37,7 +46,7 @@ export const AdministrationFleetsList: React.FC = () => { setDisabledNext(false); setData(result); }); - }, [onlyActive]); + }, [onlyActive, selectedProject?.value]); const { columns } = useColumnsDefinitions(); const { renderEmptyMessage, renderNoMatchMessage } = useEmptyMessages(); @@ -105,47 +114,60 @@ export const AdministrationFleetsList: React.FC = () => { }; return ( - }> - {isLoading && ( - - - - )} - - {!isLoading && ( - {t('fleets.instances.title')}} - filter={ -
-
- setOnlyActive(detail.checked)} checked={onlyActive}> - {t('fleets.active_only')} - -
- - -
- } - pagination={ - - } +
+ {t('navigation.fleets')} + + } + filter={ +
+
+ + { + setSelectedProject(event.detail.selectedOption); + }} + placeholder={t('projects.run.project_placeholder')} + expandToViewport={true} + filteringType="auto" + /> + +
+ +
+ setOnlyActive(detail.checked)} checked={onlyActive}> + {t('fleets.active_only')} + +
+ +
+ +
+
+ } + pagination={ + - )} - + } + /> ); }; diff --git a/frontend/src/pages/Fleets/List/hooks.tsx b/frontend/src/pages/Fleets/List/hooks.tsx index 4c8224c6c..4fd26c37d 100644 --- a/frontend/src/pages/Fleets/List/hooks.tsx +++ b/frontend/src/pages/Fleets/List/hooks.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { format } from 'date-fns'; @@ -7,6 +7,9 @@ import { ListEmptyMessage, StatusIndicator, TableProps } from 'components'; import { DATE_TIME_FORMAT } from 'consts'; import { getStatusIconType } from 'libs/fleet'; +import { SelectCSDProps } from '../../../components'; +import { useLocalStorageState } from '../../../hooks/useLocalStorageState'; + import { TFleetInstance } from './types'; export const useColumnsDefinitions = () => { @@ -20,6 +23,11 @@ export const useColumnsDefinitions = () => { // {item.name} item.fleetName, }, + { + id: 'instance', + header: `${t('fleets.instances.instance_name')}`, + cell: (item) => item?.instance_type?.name, + }, { id: 'backend', header: `${t('fleets.instances.backend')}`, @@ -68,3 +76,51 @@ export const useEmptyMessages = () => { return { renderEmptyMessage, renderNoMatchMessage } as const; }; + +export const useFilters = ({ fleets }: { fleets?: IFleet[] }) => { + const [onlyActive, setOnlyActive] = useLocalStorageState('administration-fleet-list-is-active', false); + const [selectedFleet, setSelectedFleet] = useState(null); + + const fleetOptions = useMemo(() => { + if (!fleets?.length) return []; + + return fleets.map((fleet) => ({ label: fleet.name, value: fleet.name })); + }, [fleets]); + + const clearFilters = () => { + setOnlyActive(false); + setSelectedFleet(null); + }; + + const filteringFunction = useCallback<(instance: TFleetInstance) => boolean>( + (instance: TFleetInstance) => { + let isMatch = true; + + if (selectedFleet) { + isMatch = isMatch && instance.fleetName === selectedFleet.value; + } + + if (onlyActive) { + isMatch = + isMatch && + ['creating', 'starting', 'provisioning', 'terminating', 'busy', 'idle'].includes(instance.status ?? ''); + } + + return isMatch; + }, + [onlyActive, selectedFleet], + ); + + const isDisabledClearFilter = !selectedFleet && !onlyActive; + + return { + onlyActive, + setOnlyActive, + selectedFleet, + setSelectedFleet, + fleetOptions, + isDisabledClearFilter, + clearFilters, + filteringFunction, + } as const; +}; diff --git a/frontend/src/pages/Fleets/List/index.tsx b/frontend/src/pages/Fleets/List/index.tsx index f7b2a5599..c03051ab9 100644 --- a/frontend/src/pages/Fleets/List/index.tsx +++ b/frontend/src/pages/Fleets/List/index.tsx @@ -1,16 +1,18 @@ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { Header, Pagination, Table } from 'components'; +import { Button, FormField, Header, Pagination, SelectCSD, Table, Toggle } from 'components'; import { useProjectDropdown } from 'layouts/AppLayout/hooks'; import { useCollection } from 'hooks'; import { useGetFleetsQuery } from 'services/fleet'; -import { useColumnsDefinitions, useEmptyMessages } from './hooks'; +import { useColumnsDefinitions, useEmptyMessages, useFilters } from './hooks'; import { TFleetInstance } from './types'; +import styles from './styles.module.scss'; + export const FleetsList: React.FC = () => { const { t } = useTranslation(); const { selectedProject } = useProjectDropdown(); @@ -20,6 +22,17 @@ export const FleetsList: React.FC = () => { const { columns } = useColumnsDefinitions(); const { renderEmptyMessage, renderNoMatchMessage } = useEmptyMessages(); + const { + fleetOptions, + selectedFleet, + setSelectedFleet, + setOnlyActive, + onlyActive, + isDisabledClearFilter, + clearFilters, + filteringFunction, + } = useFilters({ fleets: data }); + const fleetInstances = useMemo(() => { if (!data) return []; @@ -46,6 +59,7 @@ export const FleetsList: React.FC = () => { filtering: { empty: renderEmptyMessage(), noMatch: renderNoMatchMessage(), + filteringFunction, }, pagination: { pageSize: 20 }, selection: {}, @@ -61,6 +75,41 @@ export const FleetsList: React.FC = () => { loadingText={t('common.loading')} stickyHeader={true} header={
{t('navigation.fleets')}
} + filter={ +
+
+ + { + setSelectedFleet(event.detail.selectedOption); + }} + placeholder={t('fleets.fleet_placeholder')} + expandToViewport={true} + filteringType="auto" + /> + +
+ +
+ setOnlyActive(detail.checked)} + checked={onlyActive} + > + {t('fleets.active_only')} + +
+ +
+ +
+
+ } pagination={} /> ); diff --git a/frontend/src/pages/Fleets/List/styles.module.scss b/frontend/src/pages/Fleets/List/styles.module.scss index c334dc75a..a6917fc62 100644 --- a/frontend/src/pages/Fleets/List/styles.module.scss +++ b/frontend/src/pages/Fleets/List/styles.module.scss @@ -1,10 +1,20 @@ .filters { + --select-width: calc((688px - 3 * 20px) / 2); display: flex; flex-wrap: wrap; gap: 0 20px; + .select { + width: var(--select-width, 30%); + } + .activeOnly { display: flex; align-items: center; + padding-top: 26px; + } + + .clear { + padding-top: 26px; } } diff --git a/frontend/src/pages/Runs/AdministrationList/index.tsx b/frontend/src/pages/Runs/AdministrationList/index.tsx index e47945034..c499e531c 100644 --- a/frontend/src/pages/Runs/AdministrationList/index.tsx +++ b/frontend/src/pages/Runs/AdministrationList/index.tsx @@ -39,6 +39,7 @@ export const AdministrationList: React.FC = () => { repoSearchKey: 'repo', projectSearchKey: 'project', userSearchKey: 'user', + localStorePrefix: 'administration-run-list-page', }); const [getRuns, { isLoading, isFetching }] = useLazyGetRunsQuery(); diff --git a/frontend/src/pages/Runs/Details/Logs/index.tsx b/frontend/src/pages/Runs/Details/Logs/index.tsx index 7533970a5..6d4135edd 100644 --- a/frontend/src/pages/Runs/Details/Logs/index.tsx +++ b/frontend/src/pages/Runs/Details/Logs/index.tsx @@ -12,7 +12,7 @@ import { useAppSelector } from 'hooks'; import { useGetProjectLogsQuery } from 'services/project'; import { useGetRunQuery } from 'services/run'; -import { selectAppliedThemeMode } from 'App/slice'; +import { selectSystemMode } from 'App/slice'; import { getJobSubmissionId } from './helpers'; @@ -29,7 +29,7 @@ export const Logs: React.FC = ({ className, ...props }) => { // @ts-ignore const paramProjectName: string = props.name ?? params.projectName ?? ''; const paramRunName = props.run_name ?? params.runName ?? ''; - const appliedTheme = useAppSelector(selectAppliedThemeMode); + const appliedTheme = useAppSelector(selectSystemMode); const terminalInstance = useRef(new Terminal()); diff --git a/frontend/src/pages/Runs/List/hooks/useFilters.ts b/frontend/src/pages/Runs/List/hooks/useFilters.ts index 9d44e7f95..0db17c2b7 100644 --- a/frontend/src/pages/Runs/List/hooks/useFilters.ts +++ b/frontend/src/pages/Runs/List/hooks/useFilters.ts @@ -6,19 +6,28 @@ import { SelectCSDProps } from 'components'; import { useGetProjectReposQuery, useGetProjectsQuery } from 'services/project'; import { useGetUserListQuery } from 'services/user'; +import { useLocalStorageState } from '../../../../hooks/useLocalStorageState'; + type Args = { + localStorePrefix: string; repoSearchKey?: string; projectSearchKey?: string; userSearchKey?: string; - selectedProject?: string; }; -export const useFilters = ({ repoSearchKey, projectSearchKey, userSearchKey, selectedProject: selectedProjectProp }: Args) => { + +export const useFilters = ({ + localStorePrefix, + repoSearchKey, + projectSearchKey, + userSearchKey, + selectedProject: selectedProjectProp, +}: Args) => { const [searchParams, setSearchParams] = useSearchParams(); const [selectedProject, setSelectedProject] = useState(null); const [selectedRepo, setSelectedRepo] = useState(null); const [selectedUser, setSelectedUser] = useState(null); - const [onlyActive, setOnlyActive] = useState(false); + const [onlyActive, setOnlyActive] = useLocalStorageState(`${localStorePrefix}-is-active`, false); useEffect(() => { setSelectedRepo(null); diff --git a/frontend/src/pages/Runs/MainList/index.tsx b/frontend/src/pages/Runs/MainList/index.tsx index 8727ef89f..513cc8abf 100644 --- a/frontend/src/pages/Runs/MainList/index.tsx +++ b/frontend/src/pages/Runs/MainList/index.tsx @@ -30,6 +30,7 @@ export const List: React.FC = () => { const { selectedRepo, setSelectedRepo, repoOptions, clearSelected, onlyActive, setOnlyActive } = useFilters({ repoSearchKey: 'repo', selectedProject: selectedProject ?? undefined, + localStorePrefix: 'main-run-list-page', }); const [getRuns, { isLoading, isFetching }] = useLazyGetRunsQuery(); diff --git a/frontend/src/pages/User/Add/index.tsx b/frontend/src/pages/User/Add/index.tsx index 0048f81c4..88453641f 100644 --- a/frontend/src/pages/User/Add/index.tsx +++ b/frontend/src/pages/User/Add/index.tsx @@ -18,7 +18,7 @@ export const UserAdd: React.FC = () => { useBreadcrumbs([ { - text: t('navigation.users'), + text: t('navigation.account'), href: ROUTES.USER.LIST, }, { diff --git a/frontend/src/pages/User/Details/Billing/index.tsx b/frontend/src/pages/User/Details/Billing/index.tsx index f9ffe4c4c..774807994 100644 --- a/frontend/src/pages/User/Details/Billing/index.tsx +++ b/frontend/src/pages/User/Details/Billing/index.tsx @@ -41,7 +41,7 @@ export const Billing: React.FC = () => { useBreadcrumbs([ { - text: t('navigation.users'), + text: t('navigation.account'), href: ROUTES.USER.LIST, }, { diff --git a/frontend/src/pages/User/Details/CreditsHistory/Add/index.tsx b/frontend/src/pages/User/Details/CreditsHistory/Add/index.tsx index 981246107..fd9e0381f 100644 --- a/frontend/src/pages/User/Details/CreditsHistory/Add/index.tsx +++ b/frontend/src/pages/User/Details/CreditsHistory/Add/index.tsx @@ -30,7 +30,7 @@ export const Add: React.FC = () => { useBreadcrumbs([ { - text: t('navigation.users'), + text: t('navigation.account'), href: ROUTES.USER.LIST, }, { diff --git a/frontend/src/pages/User/Details/Settings/index.tsx b/frontend/src/pages/User/Details/Settings/index.tsx index c97f3f1d1..52d98aa72 100644 --- a/frontend/src/pages/User/Details/Settings/index.tsx +++ b/frontend/src/pages/User/Details/Settings/index.tsx @@ -29,7 +29,7 @@ export const Settings: React.FC = () => { useBreadcrumbs([ { - text: t('navigation.users'), + text: t('navigation.account'), href: ROUTES.USER.LIST, }, { @@ -80,10 +80,12 @@ export const Settings: React.FC = () => { {/*
{data.user_name}
*/} {/**/} -
- {t('users.email')} -
{data.email ?? '-'}
-
+ {process.env.UI_VERSION !== 'enterprise' && ( +
+ {t('users.email')} +
{data.email ?? '-'}
+
+ )}
diff --git a/frontend/src/pages/User/List/index.tsx b/frontend/src/pages/User/List/index.tsx index 235aabc36..bbe904041 100644 --- a/frontend/src/pages/User/List/index.tsx +++ b/frontend/src/pages/User/List/index.tsx @@ -32,7 +32,7 @@ export const UserList: React.FC = () => { useBreadcrumbs([ { - text: t('navigation.users'), + text: t('navigation.account'), href: ROUTES.USER.LIST, }, ]); @@ -45,7 +45,7 @@ export const UserList: React.FC = () => { {item.username} ), }, - { + process.env.UI_VERSION !== 'enterprise' && { id: 'email', header: t('users.email'), cell: (item: IUser) => item.email ?? '-', @@ -55,7 +55,7 @@ export const UserList: React.FC = () => { header: t('users.global_role'), cell: (item: IUser) => t(`roles.${item.global_role}`), }, - ]; + ].filter(Boolean); const toggleDeleteConfirm = () => { setShowConfirmDelete((val) => !val);