diff --git a/src/containers/Header/Header.tsx b/src/containers/Header/Header.tsx index 172f94c7ef..ecbf83e654 100644 --- a/src/containers/Header/Header.tsx +++ b/src/containers/Header/Header.tsx @@ -28,7 +28,7 @@ import {clustersApi} from '../../store/reducers/clusters/clusters'; import {tenantApi} from '../../store/reducers/tenant/tenant'; import {uiFactory} from '../../uiFactory/uiFactory'; import {cn} from '../../utils/cn'; -import {DEVELOPER_UI_TITLE} from '../../utils/constants'; +import {DEVELOPER_UI_TITLE, MONITORING_UI_TITLE} from '../../utils/constants'; import {createDeveloperUIInternalPageHref} from '../../utils/developerUI/developerUI'; import {useTypedSelector} from '../../utils/hooks'; import { @@ -56,7 +56,7 @@ function Header() { const isMetaDatabasesAvailable = useDatabasesAvailable(); - const {title: clusterTitle} = useClusterBaseInfo(); + const {title: clusterTitle, monitoring} = useClusterBaseInfo(); const database = useDatabaseFromQuery(); @@ -93,6 +93,16 @@ function Header() { const {currentData: databaseData, isLoading: isDatabaseDataLoading} = tenantApi.useGetTenantInfoQuery(params); + const monitoringLinkUrl = + monitoring && uiFactory.getMonitoringLink && databaseData?.Name && databaseData?.Type + ? uiFactory.getMonitoringLink({ + monitoring, + clusterName, + dbName: databaseData.Name, + dbType: databaseData.Type, + }) + : null; + const breadcrumbItems = React.useMemo(() => { let options = { ...pageBreadcrumbsOptions, @@ -128,6 +138,15 @@ function Header() { } if (isDatabasePage && database) { + if (monitoringLinkUrl) { + elements.push( + , + ); + } + elements.push( + )} + {renderName()} - - {links.map(({title, url, icon}) => ( - - ))} - + {links.length > 0 && ( + + {links.map(({title, url, icon}) => ( + + ))} + + )} {!isServerless && } diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json b/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json index 9ec4807cc0..c51694d47c 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json @@ -1,4 +1,5 @@ { + "action_open-monitoring": "Monitoring", "top-nodes.empty-data": "No such nodes", "title_top-nodes-load": "Top nodes by load", "title_top-nodes-pool": "Top nodes by pools usage", diff --git a/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx b/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx index c481f81516..d3331f0c61 100644 --- a/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx +++ b/src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx @@ -14,6 +14,7 @@ import {selectIsDirty, selectUserInput} from '../../../../store/reducers/query/q import {schemaApi} from '../../../../store/reducers/schema/schema'; import {tableSchemaDataApi} from '../../../../store/reducers/tableSchemaData'; import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema'; +import {uiFactory} from '../../../../uiFactory/uiFactory'; import {valueIsDefined} from '../../../../utils'; import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; import {getConfirmation} from '../../../../utils/hooks/withConfirmation/useChangeInputWithConfirmation'; @@ -144,6 +145,7 @@ export function SchemaTree(props: SchemaTreeProps) { getConnectToDBDialog, schemaData: actionsSchemaData, isSchemaDataLoading: isActionsDataFetching, + hasMonitoring: typeof uiFactory.renderMonitoring === 'function', }, databaseFullPath, database, diff --git a/src/containers/Tenant/i18n/en.json b/src/containers/Tenant/i18n/en.json index 371eb83583..07d30799a9 100644 --- a/src/containers/Tenant/i18n/en.json +++ b/src/containers/Tenant/i18n/en.json @@ -28,6 +28,7 @@ "actions.connectToDB": "Connect to DB", "actions.dropIndex": "Drop index", "actions.openPreview": "Open preview", + "actions.openMonitoring": "Monitoring", "actions.createTable": "Create table...", "actions.createExternalTable": "Create external table...", "actions.createTopic": "Create topic...", diff --git a/src/containers/Tenant/utils/schemaActions.tsx b/src/containers/Tenant/utils/schemaActions.tsx index 1ad121c38e..135b237092 100644 --- a/src/containers/Tenant/utils/schemaActions.tsx +++ b/src/containers/Tenant/utils/schemaActions.tsx @@ -1,12 +1,16 @@ -import {CirclePlus, Copy, PlugConnection} from '@gravity-ui/icons'; +import {CirclePlus, Copy, DisplayPulse, PlugConnection} from '@gravity-ui/icons'; import {Flex, Spin} from '@gravity-ui/uikit'; import copy from 'copy-to-clipboard'; import type {NavigationTreeNodeType} from 'ydb-ui-components'; import type {SnippetParams} from '../../../components/ConnectToDB/types'; import type {AppDispatch} from '../../../store'; -import {TENANT_PAGES_IDS, TENANT_QUERY_TABS_ID} from '../../../store/reducers/tenant/constants'; -import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant'; +import { + TENANT_DIAGNOSTICS_TABS_IDS, + TENANT_PAGES_IDS, + TENANT_QUERY_TABS_ID, +} from '../../../store/reducers/tenant/constants'; +import {setDiagnosticsTab, setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant'; import createToast from '../../../utils/createToast'; import {insertSnippetToEditor} from '../../../utils/monaco/insertSnippet'; import {transformPath} from '../ObjectSummary/transformPath'; @@ -48,6 +52,7 @@ interface ActionsAdditionalParams { getConnectToDBDialog?: (params: SnippetParams) => Promise; schemaData?: SchemaData[]; isSchemaDataLoading?: boolean; + hasMonitoring?: boolean; } interface BindActionParams { @@ -98,6 +103,11 @@ const bindActions = ( } : undefined, getConnectToDBDialog: () => getConnectToDBDialog?.({database: params.database}), + openMonitoring: () => { + dispatch(setTenantPage(TENANT_PAGES_IDS.diagnostics)); + dispatch(setDiagnosticsTab(TENANT_DIAGNOSTICS_TABS_IDS.monitoring)); + setActivePath(params.path); + }, createTable: inputQuery(createTableTemplate), createColumnTable: inputQuery(createColumnTableTemplate), createAsyncReplication: inputQuery(createAsyncReplicationTemplate), @@ -190,6 +200,11 @@ export const getActions = action: actions.getConnectToDBDialog, iconStart: , }; + const monitoringItem = { + text: i18n('actions.openMonitoring'), + action: actions.openMonitoring, + iconStart: , + }; const createEntitiesSet = [ {text: i18n('actions.createTable'), action: actions.createTable}, @@ -216,10 +231,14 @@ export const getActions = }, ], }; - const DB_SET: ActionsSet = [[copyItem, connectToDBItem], createEntitiesSet]; + let DB_SET: ActionsSet = [[copyItem, connectToDBItem], createEntitiesSet]; const DIR_SET: ActionsSet = [[copyItem], createEntitiesSet]; + if (additionalEffects.hasMonitoring) { + DB_SET = [[copyItem, connectToDBItem, monitoringItem], createEntitiesSet]; + } + if (actions.createDirectory) { const createDirectoryItem = { text: i18n('actions.createDirectory'), diff --git a/src/routes.ts b/src/routes.ts index b94f0a7874..b18fc44ced 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import type {Location} from 'history'; import isEmpty from 'lodash/isEmpty'; -import {compile} from 'path-to-regexp'; +import {compile, match} from 'path-to-regexp'; import qs from 'qs'; import type {QueryParamConfig} from 'use-query-params'; import {StringParam} from 'use-query-params'; @@ -210,9 +210,11 @@ export function useTabletPagePath() { } export function checkIsClustersPage(pathname: string) { - return pathname.endsWith(routes.clusters); + const matchFn = match(routes.clusters); + return Boolean(matchFn(pathname)); } export function checkIsTenantPage(pathname: string) { - return pathname.endsWith(routes.tenant); + const matchFn = match(routes.tenant); + return Boolean(matchFn(pathname)); } diff --git a/src/store/reducers/tenant/constants.ts b/src/store/reducers/tenant/constants.ts index 9b3ae84100..2efa6be3b1 100644 --- a/src/store/reducers/tenant/constants.ts +++ b/src/store/reducers/tenant/constants.ts @@ -30,6 +30,7 @@ export const TENANT_DIAGNOSTICS_TABS_IDS = { operations: 'operations', access: 'access', backups: 'backups', + monitoring: 'monitoring', } as const; export const TENANT_SUMMARY_TABS_IDS = { diff --git a/src/uiFactory/types.ts b/src/uiFactory/types.ts index afa4f586d7..ed04f72635 100644 --- a/src/uiFactory/types.ts +++ b/src/uiFactory/types.ts @@ -9,8 +9,9 @@ import type { import type {ClusterInfo} from '../store/reducers/cluster/cluster'; import type {IssuesTree} from '../store/reducers/healthcheckInfo/types'; import type {PreparedTenant} from '../store/reducers/tenants/types'; -import type {ClusterLink, DatabaseLink} from '../types/additionalProps'; +import type {AdditionalTenantsProps, ClusterLink, DatabaseLink} from '../types/additionalProps'; import type {MetaBaseClusterInfo} from '../types/api/meta'; +import type {EPathSubType, EPathType} from '../types/api/schema/schema'; import type {ETenantType} from '../types/api/tenant'; import type {GetLogsLink} from '../utils/logs'; import type {GetMonitoringClusterLink, GetMonitoringLink} from '../utils/monitoring'; @@ -35,6 +36,7 @@ export interface UIFactory { renderBackups?: RenderBackups; renderEvents?: RenderEvents; + renderMonitoring?: RenderMonitoring; clusterOrDatabaseAccessError?: Partial; healthcheck: { @@ -85,3 +87,13 @@ export type RenderBackups = (props: { export type RenderEvents = (props: { scrollContainerRef: React.RefObject; }) => React.ReactNode; + +export type RenderMonitoring = (props: { + type?: EPathType; + subType?: EPathSubType; + database: string; + path: string; + databaseFullPath?: string; + additionalTenantProps?: AdditionalTenantsProps; + scrollContainerRef: React.RefObject; +}) => React.ReactNode; diff --git a/src/utils/additionalProps.ts b/src/utils/additionalProps.ts index 71fc11edce..47f877fe02 100644 --- a/src/utils/additionalProps.ts +++ b/src/utils/additionalProps.ts @@ -36,3 +36,28 @@ export function getDatabaseLinks( return links; } + +export function getInfoTabLinks( + additionalProps?: AdditionalTenantsProps, + name?: string, + type?: ETenantType, +) { + if (!additionalProps) { + return []; + } + + const links: DatabaseLink[] = []; + + if (additionalProps.getLogsLink) { + const link = additionalProps.getLogsLink(name); + if (link) { + links.push({title: i18n('field_logs-link'), url: link, icon: FileText}); + } + } + + if (additionalProps.getLinks) { + links.push(...additionalProps.getLinks(name, type)); + } + + return links; +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a41e27e201..24f5567db7 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -106,6 +106,7 @@ export const QUERY_TECHNICAL_MARK = '/*UI-QUERY-EXCLUDE*/'; // ==== Titles ==== export const DEVELOPER_UI_TITLE = 'Developer UI'; +export const MONITORING_UI_TITLE = 'Monitoring'; export const CLUSTER_DEFAULT_TITLE = 'Cluster'; export const TENANT_DEFAULT_TITLE = 'Database';