diff --git a/src/containers/App/App.tsx b/src/containers/App/App.tsx index 66ef5dda03..dbaa5f5ede 100644 --- a/src/containers/App/App.tsx +++ b/src/containers/App/App.tsx @@ -3,33 +3,54 @@ import React from 'react'; import type {Store} from '@reduxjs/toolkit'; import type {History} from 'history'; import {Helmet} from 'react-helmet-async'; -import {connect} from 'react-redux'; import {componentsRegistry} from '../../components/ComponentsProvider/componentsRegistry'; -import type {RootState} from '../../store'; +import {useTypedSelector} from '../../utils/hooks'; import ReduxTooltip from '../ReduxTooltip/ReduxTooltip'; import type {YDBEmbeddedUISettings} from '../UserSettings/settings'; +import {useAppTitle} from './AppTitleContext'; import ContentWrapper, {Content} from './Content'; import {NavigationWrapper} from './NavigationWrapper'; import {Providers} from './Providers'; import './App.scss'; +const defaultAppTitle = 'YDB Monitoring'; + export interface AppProps { store: Store; history: History; - singleClusterMode: boolean; userSettings?: YDBEmbeddedUISettings; children?: React.ReactNode; + appTitle?: string; } -function App({store, history, singleClusterMode, children, userSettings}: AppProps) { +function App({store, history, children, userSettings, appTitle = defaultAppTitle}: AppProps) { const ChatPanel = componentsRegistry.get('ChatPanel'); return ( - - + + {children} + {ChatPanel && } + + + ); +} + +function AppContent({ + userSettings, + children, +}: { + userSettings?: YDBEmbeddedUISettings; + children?: React.ReactNode; +}) { + const {appTitle} = useAppTitle(); + const singleClusterMode = useTypedSelector((state) => state.singleClusterMode); + + return ( + + - {ChatPanel && } - - + ); } -function mapStateToProps(state: RootState) { - return { - singleClusterMode: state.singleClusterMode, - }; -} - -export default connect(mapStateToProps)(App); +export default App; diff --git a/src/containers/App/AppTitleContext.tsx b/src/containers/App/AppTitleContext.tsx new file mode 100644 index 0000000000..cbf71c3c84 --- /dev/null +++ b/src/containers/App/AppTitleContext.tsx @@ -0,0 +1,24 @@ +import React, {createContext, useContext} from 'react'; + +interface AppTitleContextType { + appTitle: string; +} + +const AppTitleContext = createContext(undefined); + +interface AppTitleProviderProps { + appTitle: string; + children: React.ReactNode; +} + +export function AppTitleProvider({appTitle, children}: AppTitleProviderProps) { + return {children}; +} + +export function useAppTitle(): AppTitleContextType { + const context = useContext(AppTitleContext); + if (context === undefined) { + throw new Error('useAppTitle must be used within an AppTitleProvider'); + } + return context; +} diff --git a/src/containers/App/Providers.tsx b/src/containers/App/Providers.tsx index e12fa7c78e..3ad9a7b4b7 100644 --- a/src/containers/App/Providers.tsx +++ b/src/containers/App/Providers.tsx @@ -17,11 +17,14 @@ import {THEME_KEY} from '../../utils/constants'; import {toaster} from '../../utils/createToast'; import {useSetting} from '../../utils/hooks'; +import {AppTitleProvider} from './AppTitleContext'; + interface ProvidersProps { store: Store; history: History; componentsRegistry?: ComponentsRegistry; children: React.ReactNode; + appTitle: string; } export function Providers({ @@ -29,20 +32,23 @@ export function Providers({ history, componentsRegistry = defaultComponentsRegistry, children, + appTitle, }: ProvidersProps) { return ( - - - - {children} - - - - + + + + + {children} + + + + + diff --git a/src/containers/AppWithClusters/AppWithClusters.tsx b/src/containers/AppWithClusters/AppWithClusters.tsx index c1442b5549..c76b6e5a2c 100644 --- a/src/containers/AppWithClusters/AppWithClusters.tsx +++ b/src/containers/AppWithClusters/AppWithClusters.tsx @@ -15,11 +15,18 @@ export interface AppWithClustersProps { history: History; userSettings?: YDBEmbeddedUISettings; children?: React.ReactNode; + appTitle?: string; } -export function AppWithClusters({store, history, userSettings, children}: AppWithClustersProps) { +export function AppWithClusters({ + store, + history, + userSettings, + appTitle, + children, +}: AppWithClustersProps) { return ( - + {({component}) => { return ( diff --git a/src/containers/Cluster/Cluster.tsx b/src/containers/Cluster/Cluster.tsx index f9994d3992..a72d9fcfdf 100644 --- a/src/containers/Cluster/Cluster.tsx +++ b/src/containers/Cluster/Cluster.tsx @@ -30,6 +30,7 @@ import type { import {EFlag} from '../../types/api/enums'; import {cn} from '../../utils/cn'; import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../utils/hooks'; +import {useAppTitle} from '../App/AppTitleContext'; import {Nodes} from '../Nodes/Nodes'; import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {TabletsTable} from '../Tablets/TabletsTable'; @@ -127,11 +128,13 @@ export function Cluster({ [activeTabId, actualClusterTabs], ); + const {appTitle} = useAppTitle(); + return (
{activeTab ? {activeTab.title} : null} diff --git a/src/containers/Node/Node.tsx b/src/containers/Node/Node.tsx index dca54cfcc8..491c005d05 100644 --- a/src/containers/Node/Node.tsx +++ b/src/containers/Node/Node.tsx @@ -22,6 +22,7 @@ import {nodeApi} from '../../store/reducers/node/node'; import type {PreparedNode} from '../../store/reducers/node/types'; import {cn} from '../../utils/cn'; import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks'; +import {useAppTitle} from '../App/AppTitleContext'; import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {Tablets} from '../Tablets/Tablets'; @@ -121,12 +122,10 @@ interface NodePageHelmetProps { } function NodePageHelmet({node, activeTabTitle}: NodePageHelmetProps) { + const {appTitle} = useAppTitle(); const host = node?.Host ? node.Host : i18n('node'); return ( - + {activeTabTitle} ); diff --git a/src/containers/PDiskPage/PDiskPage.tsx b/src/containers/PDiskPage/PDiskPage.tsx index 0492bef98f..abdcaaf626 100644 --- a/src/containers/PDiskPage/PDiskPage.tsx +++ b/src/containers/PDiskPage/PDiskPage.tsx @@ -25,6 +25,7 @@ import {cn} from '../../utils/cn'; import {getPDiskId, getSeverityColor} from '../../utils/disks/helpers'; import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks'; import {useIsUserAllowedToMakeChanges} from '../../utils/hooks/useIsUserAllowedToMakeChanges'; +import {useAppTitle} from '../App/AppTitleContext'; import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {DecommissionButton} from './DecommissionButton/DecommissionButton'; @@ -134,6 +135,8 @@ export function PDiskPage() { } }; + const {appTitle} = useAppTitle(); + const renderHelmet = () => { const pDiskPagePart = pDiskId ? `${pDiskPageKeyset('pdisk')} ${pDiskId}` @@ -143,8 +146,8 @@ export function PDiskPage() { return ( ); }; diff --git a/src/containers/StorageGroupPage/StorageGroupPage.tsx b/src/containers/StorageGroupPage/StorageGroupPage.tsx index 46426f2715..ea2da9f38b 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.tsx +++ b/src/containers/StorageGroupPage/StorageGroupPage.tsx @@ -19,6 +19,7 @@ import {EFlag} from '../../types/api/enums'; import {valueIsDefined} from '../../utils'; import {cn} from '../../utils/cn'; import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks'; +import {useAppTitle} from '../App/AppTitleContext'; import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {storageGroupPageKeyset} from './i18n'; @@ -53,6 +54,7 @@ export function StorageGroupPage() { const storageGroupData = groupQuery.data?.groups?.[0]; const loading = groupQuery.isFetching && storageGroupData === undefined; + const {appTitle} = useAppTitle(); const renderHelmet = () => { const pageTitle = groupId @@ -61,8 +63,8 @@ export function StorageGroupPage() { return ( ); }; diff --git a/src/containers/Tenant/Tenant.tsx b/src/containers/Tenant/Tenant.tsx index 0c1bb8b717..c0e6319e35 100644 --- a/src/containers/Tenant/Tenant.tsx +++ b/src/containers/Tenant/Tenant.tsx @@ -13,6 +13,7 @@ import {cn} from '../../utils/cn'; import {DEFAULT_IS_TENANT_SUMMARY_COLLAPSED, DEFAULT_SIZE_TENANT_KEY} from '../../utils/constants'; import {useTypedDispatch, useTypedSelector} from '../../utils/hooks'; import {isAccessError} from '../../utils/response'; +import {useAppTitle} from '../App/AppTitleContext'; import ObjectGeneral from './ObjectGeneral/ObjectGeneral'; import {ObjectSummary} from './ObjectSummary/ObjectSummary'; @@ -116,11 +117,12 @@ export function Tenant(props: TenantProps) { } const title = path || i18n('page.title'); + const {appTitle} = useAppTitle(); return (
diff --git a/src/containers/VDiskPage/VDiskPage.tsx b/src/containers/VDiskPage/VDiskPage.tsx index 35155c5fa2..2f6d40db10 100644 --- a/src/containers/VDiskPage/VDiskPage.tsx +++ b/src/containers/VDiskPage/VDiskPage.tsx @@ -26,6 +26,7 @@ import {cn} from '../../utils/cn'; import {getSeverityColor, getVDiskSlotBasedId} from '../../utils/disks/helpers'; import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks'; import {useIsUserAllowedToMakeChanges} from '../../utils/hooks/useIsUserAllowedToMakeChanges'; +import {useAppTitle} from '../App/AppTitleContext'; import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {VDiskTablets} from './VDiskTablets'; @@ -144,6 +145,8 @@ export function VDiskPage() { ); }; + const {appTitle} = useAppTitle(); + const renderHelmet = () => { const vDiskPagePart = vDiskSlotId ? `${vDiskPageKeyset('vdisk')} ${vDiskSlotId}` @@ -157,8 +160,8 @@ export function VDiskPage() { return ( ); };