From 52a8c0bcd1611bef55f7efb224f60ca3925290df Mon Sep 17 00:00:00 2001 From: shivani170 Date: Tue, 4 Nov 2025 10:36:24 +0530 Subject: [PATCH 1/9] feat: observability functionality added wip --- .../observability/Customer/CustomerList.tsx | 51 ++++ .../Customer/CustomerListCellComponent.tsx | 70 +++++ .../observability/Customer/Customers.tsx | 117 ++++++++ .../observability/Customer/index.ts | 1 + .../observability/MetricsInfoCard.tsx | 82 ++++++ .../observability/ObservabilityIcon.tsx | 25 ++ .../observability/ObservabilityRouter.tsx | 55 ++++ src/components/observability/Overview.tsx | 81 ++++++ .../ProjectObservability/Project.tsx | 160 +++++++++++ .../ProjectObservability/ProjectList.tsx | 53 ++++ .../ProjectListCellComponent.tsx | 77 ++++++ .../ProjectObservability/ProjectOverview.tsx | 53 ++++ .../ProjectObservability/index.ts | 1 + .../observability/SingleVMOverview.tsx | 175 ++++++++++++ .../observability/VMObservability/VM.tsx | 162 ++++++++++++ .../observability/VMObservability/VMList.tsx | 51 ++++ .../VMObservability/VMListCellComponent.tsx | 77 ++++++ .../VMObservability/VMOverview.tsx | 90 +++++++ .../observability/VMObservability/index.ts | 1 + src/components/observability/constants.ts | 250 ++++++++++++++++++ src/components/observability/index.ts | 1 + src/components/observability/service.ts | 136 ++++++++++ src/components/observability/styles.scss | 45 ++++ src/components/observability/types.ts | 99 +++++++ src/components/observability/utils.tsx | 52 ++++ 25 files changed, 1965 insertions(+) create mode 100644 src/components/observability/Customer/CustomerList.tsx create mode 100644 src/components/observability/Customer/CustomerListCellComponent.tsx create mode 100644 src/components/observability/Customer/Customers.tsx create mode 100644 src/components/observability/Customer/index.ts create mode 100644 src/components/observability/MetricsInfoCard.tsx create mode 100644 src/components/observability/ObservabilityIcon.tsx create mode 100644 src/components/observability/ObservabilityRouter.tsx create mode 100644 src/components/observability/Overview.tsx create mode 100644 src/components/observability/ProjectObservability/Project.tsx create mode 100644 src/components/observability/ProjectObservability/ProjectList.tsx create mode 100644 src/components/observability/ProjectObservability/ProjectListCellComponent.tsx create mode 100644 src/components/observability/ProjectObservability/ProjectOverview.tsx create mode 100644 src/components/observability/ProjectObservability/index.ts create mode 100644 src/components/observability/SingleVMOverview.tsx create mode 100644 src/components/observability/VMObservability/VM.tsx create mode 100644 src/components/observability/VMObservability/VMList.tsx create mode 100644 src/components/observability/VMObservability/VMListCellComponent.tsx create mode 100644 src/components/observability/VMObservability/VMOverview.tsx create mode 100644 src/components/observability/VMObservability/index.ts create mode 100644 src/components/observability/constants.ts create mode 100644 src/components/observability/index.ts create mode 100644 src/components/observability/service.ts create mode 100644 src/components/observability/styles.scss create mode 100644 src/components/observability/types.ts create mode 100644 src/components/observability/utils.tsx diff --git a/src/components/observability/Customer/CustomerList.tsx b/src/components/observability/Customer/CustomerList.tsx new file mode 100644 index 0000000000..0df426c294 --- /dev/null +++ b/src/components/observability/Customer/CustomerList.tsx @@ -0,0 +1,51 @@ +import { useMemo } from 'react' + +import { FiltersTypeEnum, PaginationEnum, Table, useAsync } from '@devtron-labs/devtron-fe-common-lib' + +import { CUSTOMER_TABLE_COLUMN } from '../constants' +import { getCustomerListData } from '../service' +import { CustomerObservabilityDTO, CustomerTableProps } from '../types' + +export const CustomerList = () => { + // ASYNC CALLS + const [isFetching, customerData] = useAsync(() => getCustomerListData(), []) + + // CONFIGS + const rows = useMemo( + () => + (customerData || []).map((data) => ({ + id: `observe_project_${data.id.toString()}`, + data, + })), + [customerData], + ) + + const filter: CustomerTableProps['filter'] = ( + rowData: { id: string; data: CustomerObservabilityDTO }, + filterData: { searchKey: string }, + ) => rowData.data.name.toLowerCase().includes(filterData.searchKey.toLowerCase()) + + return ( +
+ + id="table__customer-list" + loading={isFetching} + stylesConfig={{ showSeparatorBetweenRows: true }} + columns={CUSTOMER_TABLE_COLUMN} + rows={rows} + filtersVariant={FiltersTypeEnum.STATE} + emptyStateConfig={{ + noRowsConfig: { + title: 'No resources found', + subTitle: `No resources found in this cluster for upgrade compatibility check`, + }, + }} + filter={filter} + additionalFilterProps={{ + initialSortKey: 'name', + }} + paginationVariant={PaginationEnum.PAGINATED} + /> +
+ ) +} diff --git a/src/components/observability/Customer/CustomerListCellComponent.tsx b/src/components/observability/Customer/CustomerListCellComponent.tsx new file mode 100644 index 0000000000..71584b72ce --- /dev/null +++ b/src/components/observability/Customer/CustomerListCellComponent.tsx @@ -0,0 +1,70 @@ +import { FunctionComponent, useEffect, useRef } from 'react' +import { Link, useRouteMatch } from 'react-router-dom' + +import { + FiltersTypeEnum, + TableCellComponentProps, + TableSignalEnum, + Tooltip, +} from '@devtron-labs/devtron-fe-common-lib/dist' + +import { CustomerObservabilityDTO, ProjectListFields } from '../types' + +export const CustomerListCellComponent: FunctionComponent< + TableCellComponentProps +> = ({ + field, + row: { + data: { id, name, status, project, totalVms, activeVms, healthStatus }, + }, + isRowActive, + signals, +}: TableCellComponentProps) => { + const linkRef = useRef(null) + const match = useRouteMatch() + + useEffect(() => { + const handleEnter = ({ detail: { activeRowData } }) => { + if (activeRowData.data.id === id) { + linkRef.current?.click() + } + } + + if (isRowActive) { + signals.addEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) + } + + return () => { + signals.removeEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) + } + }, [isRowActive]) + + switch (field) { + case ProjectListFields.PROJECT_NAME: + return ( + + + {name} + + + ) + case ProjectListFields.STATUS: + return {status} + case ProjectListFields.PROJECTS: + return {project} + case ProjectListFields.TOTAL_VMS: + return {totalVms} + case ProjectListFields.ACTIVE_VMS: + return {activeVms} + case ProjectListFields.HEALTH_STATUS: + return ( +
+ + {healthStatus} + +
+ ) + default: + return null + } +} diff --git a/src/components/observability/Customer/Customers.tsx b/src/components/observability/Customer/Customers.tsx new file mode 100644 index 0000000000..866b5179f3 --- /dev/null +++ b/src/components/observability/Customer/Customers.tsx @@ -0,0 +1,117 @@ +import { useEffect, useState } from 'react' + +import { + BreadCrumb, + BreadcrumbText, + ComponentSizeType, + handleUTCTime, + PageHeader, + SearchBar, + useBreadcrumb, +} from '@devtron-labs/devtron-fe-common-lib' + +import ObservabilityIconComponent from '../ObservabilityIcon' +import { CustomerList } from './CustomerList' + +let interval +const Customers = () => { + const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') + const [isDataSyncing, setDataSyncing] = useState(false) + const [syncListData, setSyncListData] = useState() + // TODO: Remove later + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [fetchingExternalApps, setFetchingExternalApps] = useState(false) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [selectedTabIndex, setSelectedTabIndex] = useState(0) + const renderDataSyncingText = () => Syncing + useEffect(() => { + if (isDataSyncing) { + setLastDataSyncTimeString(renderDataSyncingText) + } else { + const _lastDataSyncTime = Date() + setLastDataSyncTimeString(`Last synced ${handleUTCTime(_lastDataSyncTime, true)}`) + interval = setInterval(() => { + setLastDataSyncTimeString(`Last synced ${handleUTCTime(_lastDataSyncTime, true)}`) + }, 1000) + } + return () => { + if (interval) { + clearInterval(interval) + } + } + }, [isDataSyncing]) + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const updateDataSyncing = (loading: boolean): void => { + setDataSyncing(loading) + } + + const syncNow = (): void => { + setSyncListData(!syncListData) + } + + const renderLastSyncComponent = () => ( +
+ {lastDataSyncTimeString && ( + <> + {lastDataSyncTimeString} + {!isDataSyncing && ( + <> +   + + + )} + + )} + {fetchingExternalApps && renderDataSyncingText()} +
+ ) + + const { breadcrumbs } = useBreadcrumb({ + alias: { + observability: { + component: , + linked: true, + }, + customers: { + component: , + linked: false, + }, + }, + }) + const renderBreadcrumbs = () => + const searchKey = '' + const handleSearch = () => {} + return ( +
+ +
+
+ +
+ {renderLastSyncComponent()} +
+ + +
+ ) +} + +export default Customers diff --git a/src/components/observability/Customer/index.ts b/src/components/observability/Customer/index.ts new file mode 100644 index 0000000000..67c2f5def6 --- /dev/null +++ b/src/components/observability/Customer/index.ts @@ -0,0 +1 @@ +export { default as Customers } from './Customers' diff --git a/src/components/observability/MetricsInfoCard.tsx b/src/components/observability/MetricsInfoCard.tsx new file mode 100644 index 0000000000..5f3ac6579c --- /dev/null +++ b/src/components/observability/MetricsInfoCard.tsx @@ -0,0 +1,82 @@ +import { useState } from 'react' + +import { + Button, + ButtonStyleType, + ButtonVariantType, + ConditionalWrap, + Icon, + IconName, + motion, + noop, + Tooltip, +} from '@devtron-labs/devtron-fe-common-lib' + +export const MetricsInfoCard = ({ + dataTestId, + metricTitle, + metricValue, + metricUnit, + valueOutOf, + iconName, + redirectionLink, + tooltipContent, +}: { + dataTestId: string + metricTitle: string + metricValue: string + metricUnit?: string + valueOutOf?: string + iconName: IconName + redirectionLink?: string + tooltipContent?: string +}) => { + const [isHovering, setIsHovering] = useState(false) + + const handleHoverStart = () => setIsHovering(true) + const handleHoverEnd = () => setIsHovering(false) + + return ( + + +
+
+ + + {metricTitle} + + +
+ {metricValue} + {valueOutOf && ( + / {valueOutOf} + )} + {metricUnit && ( + {metricUnit} + )} +
+
+
+ {isHovering ? ( +
+
+
+
+ ) +} diff --git a/src/components/observability/ObservabilityIcon.tsx b/src/components/observability/ObservabilityIcon.tsx new file mode 100644 index 0000000000..4437780867 --- /dev/null +++ b/src/components/observability/ObservabilityIcon.tsx @@ -0,0 +1,25 @@ +import { + Button, + ButtonComponentType, + ButtonVariantType, + ComponentSizeType, + Icon, + URLS, +} from '@devtron-labs/devtron-fe-common-lib' + +const ObservabilityIconComponent = () => ( + + + )} + + )} + {fetchingExternalApps && renderDataSyncingText()} + + ) + + const renderProjectTabs = () => ( +
+
+
+ +
+
+
+ + + + + + +
+
+ ) + + const { breadcrumbs } = useBreadcrumb( + { + alias: { + observability: { + component: , + linked: true, + }, + ':projectId': { + component: , + linked: false, + }, + }, + }, + [], + ) + const renderBreadcrumbs = () => + const searchKey = '' + const handleSearch = () => {} + return ( +
+ +
+ +
+ {renderProjectTabs()} +
+ ) +} + +export default Project diff --git a/src/components/observability/ProjectObservability/ProjectList.tsx b/src/components/observability/ProjectObservability/ProjectList.tsx new file mode 100644 index 0000000000..6a29b7153b --- /dev/null +++ b/src/components/observability/ProjectObservability/ProjectList.tsx @@ -0,0 +1,53 @@ +import { useMemo } from 'react' + +import { FiltersTypeEnum, PaginationEnum, Table, useAsync } from '@devtron-labs/devtron-fe-common-lib' + +import { PROJECT_TABLE_COLUMNS } from '../constants' +import { getProjectList } from '../service' +import { ObservabilityProject, ProjectTableProps } from '../types' + +const ProjectList = () => { + // ASYNC CALLS + const [isFetching, projectData] = useAsync(() => getProjectList(), []) + + // CONFIGS + const rows = useMemo( + () => + (projectData || []).map((data) => ({ + id: `observe_project_${data.id.toString()}`, + data, + })), + [projectData], + ) + + const filter: ProjectTableProps['filter'] = ( + rowData: { id: string; data: ObservabilityProject }, + filterData: { searchKey: string }, + ) => rowData.data.name.toLowerCase().includes(filterData.searchKey.toLowerCase()) + + return ( +
+ + id="table__customer-list" + loading={isFetching} + stylesConfig={{ showSeparatorBetweenRows: true }} + columns={PROJECT_TABLE_COLUMNS} + rows={rows} + filtersVariant={FiltersTypeEnum.STATE} + paginationVariant={PaginationEnum.NOT_PAGINATED} + emptyStateConfig={{ + noRowsConfig: { + title: 'No resources found', + subTitle: `No resources found in this cluster for upgrade compatibility check`, + }, + }} + filter={filter} + additionalFilterProps={{ + initialSortKey: 'name', + }} + /> +
+ ) +} + +export default ProjectList diff --git a/src/components/observability/ProjectObservability/ProjectListCellComponent.tsx b/src/components/observability/ProjectObservability/ProjectListCellComponent.tsx new file mode 100644 index 0000000000..b594c833f9 --- /dev/null +++ b/src/components/observability/ProjectObservability/ProjectListCellComponent.tsx @@ -0,0 +1,77 @@ +import { FunctionComponent, useEffect, useRef } from 'react' +import { Link, useRouteMatch } from 'react-router-dom' + +import { + FiltersTypeEnum, + TableCellComponentProps, + TableSignalEnum, + Tooltip, +} from '@devtron-labs/devtron-fe-common-lib/dist' + +import { ObservabilityProject, ProjectListFields } from '../types' + +export const ProjectListCellComponent: FunctionComponent< + TableCellComponentProps +> = ({ + field, + row: { + data: { id, name, description, status, totalVms, activeVms, healthStatus }, + }, + isRowActive, + signals, +}: TableCellComponentProps) => { + const linkRef = useRef(null) + + const match = useRouteMatch() + + useEffect(() => { + const handleEnter = ({ detail: { activeRowData } }) => { + if (activeRowData.data.id === id) { + linkRef.current?.click() + } + } + + if (isRowActive) { + signals.addEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) + } + + return () => { + signals.removeEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) + } + }, [isRowActive]) + + switch (field) { + case ProjectListFields.PROJECT_NAME: + return ( + + + {name} + + + ) + case ProjectListFields.PROJECT_DESCRIPTION: + return {description} + case ProjectListFields.STATUS: + return {status} + case ProjectListFields.TOTAL_VMS: + return {totalVms} + case ProjectListFields.ACTIVE_VMS: + return ( +
+ + {activeVms} + +
+ ) + case ProjectListFields.HEALTH_STATUS: + return ( +
+ + {healthStatus} + +
+ ) + default: + return null + } +} diff --git a/src/components/observability/ProjectObservability/ProjectOverview.tsx b/src/components/observability/ProjectObservability/ProjectOverview.tsx new file mode 100644 index 0000000000..7a541525bb --- /dev/null +++ b/src/components/observability/ProjectObservability/ProjectOverview.tsx @@ -0,0 +1,53 @@ +import { GenericSectionErrorState } from '@devtron-labs/devtron-fe-common-lib' + +import { MetricsInfoCard } from '../MetricsInfoCard' +import { GlanceMetricsKeys } from '../types' +import { MetricsInfoLoadingCard, useGetGlanceConfig } from '../utils' + +export const ProjectOverview = () => { + const { isFetching, data, isError, refetch } = useGetGlanceConfig() + console.log(data) + + const renderBody = () => { + if (isFetching) { + return ( +
+ {Object.keys(GlanceMetricsKeys).map((key) => ( + + ))} +
+ ) + } + + if (isError) { + return ( + + ) + } + // alert(JSON.stringify(data)) + return ( +
+ {data.map((value) => ( + + ))} +
+ ) + } + + return ( +
+
+
+

At a Glance

+
+
+
{renderBody()}
+
+ ) +} + +export default ProjectOverview diff --git a/src/components/observability/ProjectObservability/index.ts b/src/components/observability/ProjectObservability/index.ts new file mode 100644 index 0000000000..02e1b0e063 --- /dev/null +++ b/src/components/observability/ProjectObservability/index.ts @@ -0,0 +1 @@ +export { default as ProjectObservability } from './Project' diff --git a/src/components/observability/SingleVMOverview.tsx b/src/components/observability/SingleVMOverview.tsx new file mode 100644 index 0000000000..722e13d38a --- /dev/null +++ b/src/components/observability/SingleVMOverview.tsx @@ -0,0 +1,175 @@ +import { useEffect, useState } from 'react' + +import { + BreadCrumb, + BreadcrumbText, + GenericSectionErrorState, + handleUTCTime, + PageHeader, + useBreadcrumb, +} from '@devtron-labs/devtron-fe-common-lib/dist' + +import { MetricsInfoCard } from './MetricsInfoCard' +import ObservabilityIconComponent from './ObservabilityIcon' +import { GlanceMetricsKeys } from './types' +import { MetricsInfoLoadingCard, useGetGlanceConfig } from './utils' + +import './styles.scss' + +let interval +const SingleVMOverview = () => { + const { isFetching, data, isError, refetch } = useGetGlanceConfig() + console.log(data) + + const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') + const [isDataSyncing, setDataSyncing] = useState(false) + const [syncListData, setSyncListData] = useState() + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [fetchingExternalApps, setFetchingExternalApps] = useState(false) + const renderDataSyncingText = () => Syncing + useEffect(() => { + if (isDataSyncing) { + setLastDataSyncTimeString(renderDataSyncingText) + } else { + const _lastDataSyncTime = Date() + setLastDataSyncTimeString(`Last synced ${handleUTCTime(_lastDataSyncTime, true)}`) + interval = setInterval(() => { + setLastDataSyncTimeString(`Last synced ${handleUTCTime(_lastDataSyncTime, true)}`) + }, 1000) + } + return () => { + if (interval) { + clearInterval(interval) + } + } + }, [isDataSyncing]) + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const updateDataSyncing = (loading: boolean): void => { + setDataSyncing(loading) + } + + const { breadcrumbs } = useBreadcrumb({ + alias: { + observability: { + component: , + linked: true, + }, + customer: { + component: , + linked: false, + }, + }, + }) + + const renderBody = () => { + if (isFetching) { + return ( +
+ {Object.keys(GlanceMetricsKeys).map((key) => ( + + ))} +
+ ) + } + + if (isError) { + return ( + + ) + } + // alert(JSON.stringify(data)) + return ( +
+ {data.map((value) => ( + + ))} +
+ ) + } + + const renderBreadcrumbs = () => + + const syncNow = (): void => { + setSyncListData(!syncListData) + } + + const renderLastSyncComponent = () => ( +
+ {lastDataSyncTimeString && ( + <> + {lastDataSyncTimeString} + {!isDataSyncing && ( + <> +   + + + )} + + )} + {fetchingExternalApps && renderDataSyncingText()} +
+ ) + return ( +
+ +
+ {renderLastSyncComponent()} + +
+
+

At a Glance

+
+
+
{renderBody()}
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+
+ ) +} + +export default SingleVMOverview diff --git a/src/components/observability/VMObservability/VM.tsx b/src/components/observability/VMObservability/VM.tsx new file mode 100644 index 0000000000..d6396f490b --- /dev/null +++ b/src/components/observability/VMObservability/VM.tsx @@ -0,0 +1,162 @@ +import { useEffect, useState } from 'react' +import { Redirect, Route, useRouteMatch } from 'react-router-dom' + +import { + BreadCrumb, + BreadcrumbText, + ComponentSizeType, + handleUTCTime, + PageHeader, + SearchBar, + TabGroup, + TabProps, + useBreadcrumb, +} from '@devtron-labs/devtron-fe-common-lib/dist' + +import ObservabilityIconComponent from '../ObservabilityIcon' +import VMList from './VMList' +import { VMOverview } from './VMOverview' + +import '../styles.scss' + +let interval + +const VM = () => { + const match = useRouteMatch() + const { breadcrumbs } = useBreadcrumb( + { + alias: { + observability: { + component: , + linked: true, + }, + ':projects': { + component: , + linked: false, + }, + }, + }, + [], + ) + + const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') + const [isDataSyncing, setDataSyncing] = useState(false) + const [syncListData, setSyncListData] = useState() + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [fetchingExternalApps, setFetchingExternalApps] = useState(false) + const renderDataSyncingText = () => Syncing + useEffect(() => { + if (isDataSyncing) { + setLastDataSyncTimeString(renderDataSyncingText) + } else { + const _lastDataSyncTime = Date() + setLastDataSyncTimeString(`Last synced ${handleUTCTime(_lastDataSyncTime, true)}`) + interval = setInterval(() => { + setLastDataSyncTimeString(`Last synced ${handleUTCTime(_lastDataSyncTime, true)}`) + }, 1000) + } + return () => { + if (interval) { + clearInterval(interval) + } + } + }, [isDataSyncing]) + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const updateDataSyncing = (loading: boolean): void => { + setDataSyncing(loading) + } + const tabs: TabProps[] = [ + { + id: 'vm_overview', + label: 'Overview', + tabType: 'navLink', + props: { + to: `${match.url}/overview`, + }, + }, + { + id: 'vm_list', + label: 'VMs', + tabType: 'navLink', + props: { + to: `${match.url}/vms`, + }, + }, + ] + + const syncNow = (): void => { + setSyncListData(!syncListData) + } + + const renderVMTabs = () => { + const rightComponent = ( +
+ {lastDataSyncTimeString && ( + <> + {lastDataSyncTimeString} + {!isDataSyncing && ( + <> +   + + + )} + + )} + {fetchingExternalApps && renderDataSyncingText()} +
+ ) + + return ( +
+
+
+ +
+
+
+ + + + + + + + +
+
+ ) + } + + const renderBreadcrumbs = () => + const searchKey = '' + const handleSearch = () => {} + return ( +
+ +
+ +
+ {renderVMTabs()} +
+ ) +} + +export default VM diff --git a/src/components/observability/VMObservability/VMList.tsx b/src/components/observability/VMObservability/VMList.tsx new file mode 100644 index 0000000000..b00d730403 --- /dev/null +++ b/src/components/observability/VMObservability/VMList.tsx @@ -0,0 +1,51 @@ +import { useMemo } from 'react' + +import { FiltersTypeEnum, PaginationEnum, Table, useAsync } from '@devtron-labs/devtron-fe-common-lib' + +import { VM_TABLE_COLUMNS } from '../constants' +import { getVMList } from '../service' +import { ObservabilityVM, VMTableProps } from '../types' + +const VMList = () => { + // ASYNC CALLS + const [isFetching, vmData] = useAsync(() => getVMList(), []) + + // CONFIGS + const rows = useMemo( + () => + (vmData || []).map((data) => ({ + id: `observe_vm_${data.id.toString()}`, + data, + })), + [vmData], + ) + + const filter: VMTableProps['filter'] = (rowData, filterData) => + rowData.data.name.includes(filterData.searchKey.toLowerCase()) + + return ( +
+ + id="table__vm-list" + loading={isFetching} + stylesConfig={{ showSeparatorBetweenRows: true }} + columns={VM_TABLE_COLUMNS} + rows={rows} + filtersVariant={FiltersTypeEnum.STATE} + paginationVariant={PaginationEnum.NOT_PAGINATED} + emptyStateConfig={{ + noRowsConfig: { + title: 'No resources found', + subTitle: `No resources found in this cluster for upgrade compatibility check`, + }, + }} + filter={filter} + additionalFilterProps={{ + initialSortKey: 'name', + }} + /> +
+ ) +} + +export default VMList diff --git a/src/components/observability/VMObservability/VMListCellComponent.tsx b/src/components/observability/VMObservability/VMListCellComponent.tsx new file mode 100644 index 0000000000..4ab2bb7b27 --- /dev/null +++ b/src/components/observability/VMObservability/VMListCellComponent.tsx @@ -0,0 +1,77 @@ +import { FunctionComponent, useEffect, useRef } from 'react' +import { Link, useRouteMatch } from 'react-router-dom' + +import { + FiltersTypeEnum, + TableCellComponentProps, + TableSignalEnum, + Tooltip, +} from '@devtron-labs/devtron-fe-common-lib/dist' + +import { ObservabilityVM, VMListFields } from '../types' + +export const VMListCellComponent: FunctionComponent< + TableCellComponentProps +> = ({ + field, + row: { + data: { id, name, ipAddress, status, cpu, memory, disk }, + }, + isRowActive, + signals, +}: TableCellComponentProps) => { + const linkRef = useRef(null) + + const match = useRouteMatch() + + useEffect(() => { + const handleEnter = ({ detail: { activeRowData } }) => { + if (activeRowData.data.id === id) { + linkRef.current?.click() + } + } + + if (isRowActive) { + signals.addEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) + } + + return () => { + signals.removeEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) + } + }, [isRowActive]) + + switch (field) { + case VMListFields.VM_NAME: + return ( + + + {name} + + + ) + case VMListFields.VM_IPADDRESS: + return {ipAddress} + case VMListFields.VM_STATUS: + return {status} + case VMListFields.VM_CPU: + return {cpu} + case VMListFields.VM_MEMORY: + return ( +
+ + {memory} + +
+ ) + case VMListFields.VM_DISK: + return ( +
+ + {disk} + +
+ ) + default: + return null + } +} diff --git a/src/components/observability/VMObservability/VMOverview.tsx b/src/components/observability/VMObservability/VMOverview.tsx new file mode 100644 index 0000000000..832c8b4c9a --- /dev/null +++ b/src/components/observability/VMObservability/VMOverview.tsx @@ -0,0 +1,90 @@ +import { GenericSectionErrorState } from '@devtron-labs/devtron-fe-common-lib' + +import { MetricsInfoCard } from '../MetricsInfoCard' +// import ObservabilityIconComponent from './ObservabilityIcon' +import { GlanceMetricsKeys } from '../types' +import { MetricsInfoLoadingCard, useGetGlanceConfig } from '../utils' + +import '../styles.scss' + +export const VMOverview = () => { + const { isFetching, data, isError, refetch } = useGetGlanceConfig() + console.log(data) + + const renderBody = () => { + if (isFetching) { + return ( +
+ {Object.keys(GlanceMetricsKeys).map((key) => ( + + ))} +
+ ) + } + + if (isError) { + return ( + + ) + } + // alert(JSON.stringify(data)) + return ( +
+ {data.map((value) => ( + + ))} +
+ ) + } + + return ( +
+
+
+

At a Glance

+
+
+
{renderBody()}
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+ ) +} + +export default VMOverview diff --git a/src/components/observability/VMObservability/index.ts b/src/components/observability/VMObservability/index.ts new file mode 100644 index 0000000000..3a44bfda27 --- /dev/null +++ b/src/components/observability/VMObservability/index.ts @@ -0,0 +1 @@ +export { default as VMObservability } from './VM' diff --git a/src/components/observability/constants.ts b/src/components/observability/constants.ts new file mode 100644 index 0000000000..dadb2f9aa2 --- /dev/null +++ b/src/components/observability/constants.ts @@ -0,0 +1,250 @@ +import { + numberComparatorBySortOrder, + SegmentedControlProps, + stringComparatorBySortOrder, +} from '@devtron-labs/devtron-fe-common-lib' + +import { CustomerListCellComponent } from './Customer/CustomerListCellComponent' +import { ProjectListCellComponent } from './ProjectObservability/ProjectListCellComponent' +import { VMListCellComponent } from './VMObservability/VMListCellComponent' +import { CustomerTableProps, MetricsInfoCardProps, ProjectTableProps, TabDetailsSegment, VMTableProps } from './types' + +export enum ObservabilityGlanceMetricKeys { + PROJECTS = 'totalProjects', + TOTAL_VMS = 'totalVms', + RUNNING_VMS = 'runningVms', + HEALTH_STATUS = 'healthStatus', + TOTAL_CLUSTER = 'totalClusters', + TOTAL_CUSTOMERS = 'totalCustomers', +} + +export const GLANCE_METRICS_CARDS_CONFIG: Partial> = { + [ObservabilityGlanceMetricKeys.TOTAL_CUSTOMERS]: { + iconName: 'ic-users', + metricTitle: 'Total Customers', + tooltipContent: 'Number of all Customers', + }, + [ObservabilityGlanceMetricKeys.PROJECTS]: { + iconName: 'ic-bg-cluster', + metricTitle: 'Projects', + tooltipContent: 'Number of all projects', + }, + [ObservabilityGlanceMetricKeys.TOTAL_VMS]: { + iconName: 'ic-bg-cluster', + metricTitle: 'Total VMs', + tooltipContent: 'Number of all Vms', + }, + + [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: { + iconName: 'ic-check', + metricTitle: 'Status', + tooltipContent: 'Percentage of all health status', + }, +} + +export const TAB_DETAILS_SEGMENTS: SegmentedControlProps['segments'] = [ + { + label: 'Overview', + value: TabDetailsSegment.OVERVIEW, + }, + { + label: 'Projects', + value: TabDetailsSegment.PROJECTS, + }, +] + +export const CUSTOMER_TABLE_COLUMN: CustomerTableProps['columns'] = [ + { + field: 'icon', + size: { + fixed: 24, + }, + CellComponent: CustomerListCellComponent, + }, + { + field: 'name', + label: 'Customer', + size: { + fixed: 250, + }, + CellComponent: CustomerListCellComponent, + isSortable: true, + comparator: stringComparatorBySortOrder, + } as CustomerTableProps['columns'][0], + { + field: 'status', + label: 'Status', + size: { + fixed: 250, + }, + CellComponent: CustomerListCellComponent, + isSortable: true, + comparator: stringComparatorBySortOrder, + }, + { + field: 'project', + label: 'Projects', + size: { + fixed: 250, + }, + CellComponent: CustomerListCellComponent, + isSortable: true, + comparator: numberComparatorBySortOrder, + }, + { + field: 'totalVms', + label: 'Total VMs', + size: { + fixed: 150, + }, + CellComponent: CustomerListCellComponent, + isSortable: true, + comparator: numberComparatorBySortOrder, + }, + + { + field: 'activeVms', + label: 'Active VMs', + size: { + fixed: 150, + }, + CellComponent: CustomerListCellComponent, + isSortable: true, + comparator: numberComparatorBySortOrder, + }, + + { + field: 'healthStatus', + label: 'Health', + size: { + fixed: 250, + }, + CellComponent: CustomerListCellComponent, + isSortable: true, + comparator: stringComparatorBySortOrder, + }, +] + +export const PROJECT_TABLE_COLUMNS: ProjectTableProps['columns'] = [ + { + field: 'name', + label: 'Project name', + size: { + fixed: 250, + }, + isSortable: true, + comparator: stringComparatorBySortOrder, + CellComponent: ProjectListCellComponent, + }, + { + field: 'description', + label: 'Description', + size: { + fixed: 250, + }, + isSortable: true, + comparator: stringComparatorBySortOrder, + CellComponent: ProjectListCellComponent, + }, + { + field: 'status', + label: 'Status', + size: { + fixed: 250, + }, + isSortable: true, + comparator: stringComparatorBySortOrder, + CellComponent: ProjectListCellComponent, + }, + { + field: 'totalVms', + label: 'Total VM', + size: { + fixed: 200, + }, + isSortable: true, + comparator: numberComparatorBySortOrder, + CellComponent: ProjectListCellComponent, + }, + { + field: 'activeVms', + label: 'Active VM', + size: { + fixed: 200, + }, + isSortable: true, + comparator: numberComparatorBySortOrder, + CellComponent: ProjectListCellComponent, + }, + { + field: 'healthStatus', + label: 'Health Status', + size: { + fixed: 200, + }, + CellComponent: ProjectListCellComponent, + }, +] + +export const VM_TABLE_COLUMNS: VMTableProps['columns'] = [ + { + field: 'name', + label: 'VM name', + size: { + fixed: 250, + }, + isSortable: true, + comparator: stringComparatorBySortOrder, + CellComponent: VMListCellComponent, + }, + { + field: 'status', + label: 'Status', + size: { + fixed: 250, + }, + isSortable: true, + comparator: stringComparatorBySortOrder, + CellComponent: VMListCellComponent, + }, + { + field: 'ipAddress', + label: 'IP Address', + size: { + fixed: 250, + }, + isSortable: true, + comparator: stringComparatorBySortOrder, + CellComponent: VMListCellComponent, + }, + { + field: 'cpu', + label: 'CPU', + size: { + fixed: 200, + }, + isSortable: true, + comparator: numberComparatorBySortOrder, + CellComponent: VMListCellComponent, + }, + { + field: 'memory', + label: 'Memory', + size: { + fixed: 200, + }, + isSortable: true, + comparator: numberComparatorBySortOrder, + CellComponent: VMListCellComponent, + }, + { + field: 'disk', + label: 'Disk', + size: { + fixed: 200, + }, + isSortable: true, + comparator: numberComparatorBySortOrder, + CellComponent: VMListCellComponent, + }, +] diff --git a/src/components/observability/index.ts b/src/components/observability/index.ts new file mode 100644 index 0000000000..d1371ea92b --- /dev/null +++ b/src/components/observability/index.ts @@ -0,0 +1 @@ +export { default as ObservabilityRouter } from './ObservabilityRouter' diff --git a/src/components/observability/service.ts b/src/components/observability/service.ts new file mode 100644 index 0000000000..25a4c6ca9d --- /dev/null +++ b/src/components/observability/service.ts @@ -0,0 +1,136 @@ +import { ObservabilityGlanceMetricKeys } from './constants' +import { CustomerObservabilityDTO, ObservabilityOverviewDTO } from './types' + +export const getObservabilityData: () => Promise> = () => + Promise.resolve({ + [ObservabilityGlanceMetricKeys.TOTAL_CUSTOMERS]: 10, + [ObservabilityGlanceMetricKeys.TOTAL_VMS]: 20, + [ObservabilityGlanceMetricKeys.PROJECTS]: 30, + [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: 50, + // [ObservabilityGlanceMetricKeys.RUNNING_VMS]: 40, + }) + +export const getProjectList: () => Promise = () => + Promise.resolve([ + { + id: 1, + name: 'Project-1', + description: 'Description of Project-1', + status: 'Active', + totalVms: 40, + activeVms: 50, + healthStatus: '80%', + }, + { + id: 2, + name: 'Project-2', + description: 'Description of Project-2', + status: 'Active', + totalVms: 40, + activeVms: 50, + healthStatus: '80%', + }, + { + id: 3, + name: 'Project-3', + description: 'Description of Project-3', + status: 'Active', + totalVms: 40, + activeVms: 50, + healthStatus: '80%', + }, + { + id: 4, + name: 'Project-4', + description: 'Description of Project-4', + status: 'Active', + totalVms: 40, + activeVms: 50, + healthStatus: '80%', + }, + { + id: 5, + name: 'Project-5', + description: 'Description of Project-5', + status: 'Active', + totalVms: 40, + activeVms: 50, + healthStatus: '80%', + }, + ]) + +export const getVMList: () => Promise = () => + Promise.resolve([ + { + id: 1, + name: 'PIS-Web-Server-01', + ipAddress: '192.168.1.101', + status: 'running', + cpu: 40, + memory: 50, + disk: 80, + }, + { + id: 2, + name: 'PIS-DB-Server-01', + ipAddress: '192.168.1.102', + status: 'running', + cpu: 40, + memory: 50, + disk: 80, + }, + ]) + +export const getCustomerListData: () => Promise = () => + Promise.resolve([ + { + id: 1, + name: 'Customer1', + status: 'ACTIVE', + project: 2, + totalVms: 14, + healthStatus: '80%', + activeVms: 2, + }, + { + id: 2, + name: 'Customer2', + status: 'INACTIVE', + project: 34, + totalVms: 4, + healthStatus: '20%', + activeVms: 1, + }, + ]) + +export const getProjectOverViewCards: () => Promise = () => + Promise.resolve([ + { + tooltipContent: '', + dataTestId: 'cpu_id', + metricValue: '16', + metricTitle: 'CPU', + iconName: 'ic-bg-cpu', + }, + { + tooltipContent: '', + dataTestId: 'disk_id', + metricValue: '400', + metricTitle: 'DISK', + iconName: 'ic-bg-cpu', + }, + { + tooltipContent: '', + dataTestId: 'memory_id', + metricValue: '1000', + metricTitle: 'MEMORY', + iconName: 'ic-bg-cpu', + }, + { + tooltipContent: '', + dataTestId: 'running_id', + metricValue: '10', + metricTitle: 'RUNNING VMs', + iconName: 'ic-bg-cpu', + }, + ]) diff --git a/src/components/observability/styles.scss b/src/components/observability/styles.scss new file mode 100644 index 0000000000..d2bb782c8d --- /dev/null +++ b/src/components/observability/styles.scss @@ -0,0 +1,45 @@ +.observability-overview { +flexbox-col dc__gap-32 bg__secondary p-20 + .cards-wrapper { + grid-template-columns: repeat(3, 1fr); + column-gap: 8px; + } + + .capacity-table-row { + grid-template-columns: 150px 1fr 1fr; + } + + .at-a-glance { + grid-template-columns: 1fr 1fr 1fr; + } + + .observability-table-wrapper { + + // Apply to every first child up to 4 levels deep + > :first-child, + > :first-child> :first-child, + > :first-child> :first-child> :first-child, + > :first-child> :first-child> :first-child> :first-child { + overflow: visible !important; + } + } + + .search-filter-section { + padding: 12px 20px; + display: flex; + flex-direction: row; + justify-content: space-between; + background-color: var(--bg-primary); + z-index: var(--filter-menu-index); + } + + .glance-cards-wrapper { + grid-template-columns: repeat(4, 1fr); + column-gap: 8px; + } + + .workflow-overview-cards-wrapper { + grid-template-columns: repeat(4, 1fr); + gap: 8px; + } +} diff --git a/src/components/observability/types.ts b/src/components/observability/types.ts new file mode 100644 index 0000000000..2c00c18e89 --- /dev/null +++ b/src/components/observability/types.ts @@ -0,0 +1,99 @@ +import { FiltersTypeEnum, IconName, TableProps } from '@devtron-labs/devtron-fe-common-lib/dist' + +import { ObservabilityGlanceMetricKeys } from './constants' + +export enum GlanceMetricsKeys { + REACHABLE_CUSTOMERS = 'customers', + TOTAL_PROJECTS = 'Projects', + TOTAL_VMs = 'vms', + HEALTH_STATUS = 'healthStatus', +} + +export type ObservabilityStatus = 'ACTIVE' | 'INACTIVE' + +export interface BaseObservability { + id: number + name: string + status: ObservabilityStatus +} + +export interface CustomerObservabilityDTO extends BaseObservability { + project: number + totalVms: number + activeVms: number + healthStatus: string +} + +export type CustomerTableProps = TableProps + +export interface ObservabilityProject + extends BaseObservability, + Pick { + description: string +} + +export type ProjectTableProps = TableProps + +export interface ObservabilityVM extends BaseObservability { + ipAddress: string + cpu: number + memory: number + disk: number +} + +export type VMTableProps = TableProps + +export enum ProjectListFields { + PROJECT_ID = 'id', + PROJECT_NAME = 'name', + PROJECT_DESCRIPTION = 'description', + STATUS = 'status', + TOTAL_VMS = 'totalVms', + ACTIVE_VMS = 'activeVms', + HEALTH_STATUS = 'healthStatus', + PROJECTS = 'projects', +} + +export enum VMListFields { + VM_ID = 'id', + VM_NAME = 'name', + VM_IPADDRESS = 'ipAddress', + VM_STATUS = 'status', + VM_CPU = 'cpu', + VM_MEMORY = 'memory', + VM_DISK = 'disk', +} + +export enum ObservabilityFilters { + customer = 'customer', + project = 'project', + vm = 'vm', +} + +export enum TabDetailsSegment { + 'OVERVIEW' = 'Overview', + 'PROJECTS' = 'Projects', +} + +export interface TabDetailsSearchParams { + tab: TabDetailsSegment +} + +export interface ObservabilityOverviewDTO { + [ObservabilityGlanceMetricKeys.TOTAL_CLUSTER]: number + [ObservabilityGlanceMetricKeys.TOTAL_VMS]: number + [ObservabilityGlanceMetricKeys.PROJECTS]: number + [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: number + [ObservabilityGlanceMetricKeys.RUNNING_VMS]: number +} + +export interface MetricsInfoCardProps { + iconName: IconName + metricTitle: string + dataTestId?: string + metricValue?: string + metricUnit?: string + valueOutOf?: string + tooltipContent?: string + redirectionLink?: string +} diff --git a/src/components/observability/utils.tsx b/src/components/observability/utils.tsx new file mode 100644 index 0000000000..61eab2434a --- /dev/null +++ b/src/components/observability/utils.tsx @@ -0,0 +1,52 @@ +import { useQuery } from '@devtron-labs/devtron-fe-common-lib' + +import { GLANCE_METRICS_CARDS_CONFIG, ObservabilityGlanceMetricKeys } from './constants' +import { getObservabilityData } from './service' +import { MetricsInfoCardProps, ObservabilityOverviewDTO, TabDetailsSearchParams, TabDetailsSegment } from './types' + +// Will be removing while importing to dashboard +export const MetricsInfoLoadingCard = () => ( +
+
+
+ + +
+
+
+
+) + +export const getObservabilityGlanceConfig = (result: Partial) => + Object.entries(GLANCE_METRICS_CARDS_CONFIG).map( + ([key, config]: [ObservabilityGlanceMetricKeys, MetricsInfoCardProps]) => { + const entry = result?.[key] + const isNumber = typeof entry === 'number' + const metricValue = isNumber ? entry : (entry as any)?.value + const metricTitle = config?.metricTitle + + return { + ...config, + dataTestId: key, + metricValue, + metricTitle, + } + }, + ) + +export const useGetGlanceConfig = () => + useQuery({ + queryKey: ['observabilityGlanceConfig'], + // queryFn: () => getProjectOverViewCards(), // Mukesh has to update this + queryFn: async () => ({ + code: 200, + status: 'SUCCESS', + result: await getObservabilityData(), + }), + // queryFn: () => get(ROUTES.OBSERVABILITY_OVERVIEW), // Will be replacing later + select: ({ result }) => getObservabilityGlanceConfig(result), + }) + +export const parseChartDetailsSearchParams = (searchParams: URLSearchParams): TabDetailsSearchParams => ({ + tab: (searchParams.get('tab') as TabDetailsSegment) || TabDetailsSegment.OVERVIEW, +}) From 600663fd8b62d012121741500f224e2d90722af3 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Tue, 4 Nov 2025 13:20:00 +0530 Subject: [PATCH 2/9] feat: observability functionality added wip --- .../common/navigation/Navigation.tsx | 12 ++++- .../common/navigation/NavigationRoutes.tsx | 4 ++ .../observability/Customer/Customers.tsx | 39 ++++++++++++++- src/components/observability/Overview.tsx | 48 +++++++++++++++---- 4 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/components/common/navigation/Navigation.tsx b/src/components/common/navigation/Navigation.tsx index 2fc2259364..0ca6c8ccc8 100644 --- a/src/components/common/navigation/Navigation.tsx +++ b/src/components/common/navigation/Navigation.tsx @@ -17,7 +17,7 @@ import React, { Component } from 'react' import { NavLink, RouteComponentProps } from 'react-router-dom' import ReactGA from 'react-ga4' -import { URLS as CommonURLS, Icon, MainContext } from '@devtron-labs/devtron-fe-common-lib' +import { URLS as CommonURLS, Icon, MainContext, URLS as CommonUrls } from '@devtron-labs/devtron-fe-common-lib' import { ModuleNameMap, MODULE_STATUS_POLLING_INTERVAL, @@ -96,6 +96,16 @@ const NavigationList: NavigationListItemType[] = [ markAsBeta: false, isAvailableInDesktop: true, }, + { + title: 'Observability', + dataTestId: 'click-on-observability', + type: 'link', + icon: 'ic-binoculars', + href: CommonUrls.OBSERVABILITY, + isAvailableInEA: false, + markAsBeta: false, + isAvailableInDesktop: true, + }, { title: 'Resource Watcher', dataTestId: 'click-on-resource-watcher', diff --git a/src/components/common/navigation/NavigationRoutes.tsx b/src/components/common/navigation/NavigationRoutes.tsx index b9d174013d..ce85976fd2 100644 --- a/src/components/common/navigation/NavigationRoutes.tsx +++ b/src/components/common/navigation/NavigationRoutes.tsx @@ -104,6 +104,7 @@ const OnboardingGuide = lazy(() => import('../../onboardingGuide/OnboardingGuide const DevtronStackManager = lazy(() => import('../../v2/devtronStackManager/DevtronStackManager')) const AppGroupRoute = lazy(() => import('../../ApplicationGroup/AppGroupRoute')) const Jobs = lazy(() => import('../../Jobs/Jobs')) +const Observability = lazy(() => import('../../observability/ObservabilityRouter')) const ResourceWatcherRouter = importComponentFromFELibrary('ResourceWatcherRouter') const SoftwareDistributionHub = importComponentFromFELibrary('SoftwareDistributionHub', null, 'function') @@ -549,6 +550,9 @@ const NavigationRoutes = ({ reloadVersionConfig }: Readonly } />, + + + , ...(!window._env_.HIDE_RESOURCE_WATCHER && ResourceWatcherRouter ? [ diff --git a/src/components/observability/Customer/Customers.tsx b/src/components/observability/Customer/Customers.tsx index 866b5179f3..92d392af33 100644 --- a/src/components/observability/Customer/Customers.tsx +++ b/src/components/observability/Customer/Customers.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react' +import { useRouteMatch } from 'react-router-dom' import { BreadCrumb, @@ -7,6 +8,7 @@ import { handleUTCTime, PageHeader, SearchBar, + TabGroup, useBreadcrumb, } from '@devtron-labs/devtron-fe-common-lib' @@ -15,6 +17,7 @@ import { CustomerList } from './CustomerList' let interval const Customers = () => { + const { path } = useRouteMatch() const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') const [isDataSyncing, setDataSyncing] = useState(false) const [syncListData, setSyncListData] = useState() @@ -46,6 +49,31 @@ const Customers = () => { setDataSyncing(loading) } + const getObservabilityTabs = () => ( + + ) + const syncNow = (): void => { setSyncListData(!syncListData) } @@ -87,11 +115,20 @@ const Customers = () => { }, }) const renderBreadcrumbs = () => + + const renderPageHeader = () => ( + + ) const searchKey = '' const handleSearch = () => {} return (
- + {renderPageHeader()}
{ const { isFetching, data, isError, refetch } = useGetGlanceConfig() - console.log(data) const { breadcrumbs } = useBreadcrumb({ alias: { observability: { @@ -29,7 +31,40 @@ export const Overview = () => { }, }, }) + const { path } = useRouteMatch() + const getObservabilityTabs = () => ( + + ) const renderBreadcrumbs = () => + const renderPageHeader = () => ( + + ) const renderBody = () => { if (isFetching) { @@ -43,13 +78,7 @@ export const Overview = () => { } if (isError) { - return ( - - ) + return } return ( @@ -63,8 +92,7 @@ export const Overview = () => { return (
- - + {renderPageHeader()}
From 83e85c223c7c7029caa1ba1dd1ac99e06521d3f1 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Tue, 4 Nov 2025 13:33:45 +0530 Subject: [PATCH 3/9] feat: replaced all customer with tenants --- src/components/observability/Customer/Customers.tsx | 10 +++++----- src/components/observability/Overview.tsx | 6 +++--- .../observability/ProjectObservability/Project.tsx | 6 ++---- src/components/observability/constants.ts | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/components/observability/Customer/Customers.tsx b/src/components/observability/Customer/Customers.tsx index 92d392af33..adf4947da8 100644 --- a/src/components/observability/Customer/Customers.tsx +++ b/src/components/observability/Customer/Customers.tsx @@ -57,16 +57,16 @@ const Customers = () => { label: 'Overview', tabType: 'navLink', props: { - to: `${path.replace('customers', 'overview')}`, + to: `${path.replace('tenants', 'overview')}`, }, }, { - id: 'customers', + id: 'tenants', label: 'Tenants', tabType: 'navLink', props: { to: `${path}`, - 'data-testid': 'customers', + 'data-testid': 'tenants', }, }, ]} @@ -108,8 +108,8 @@ const Customers = () => { component: , linked: true, }, - customers: { - component: , + tenants: { + component: , linked: false, }, }, diff --git a/src/components/observability/Overview.tsx b/src/components/observability/Overview.tsx index fc1a8a5a9f..13a86b5290 100644 --- a/src/components/observability/Overview.tsx +++ b/src/components/observability/Overview.tsx @@ -44,12 +44,12 @@ export const Overview = () => { }, }, { - id: 'customers', + id: 'tenants', label: 'Tenants', tabType: 'navLink', props: { - to: `${path.replace('overview', 'customers')}`, - 'data-testid': 'customers', + to: `${path.replace('overview', 'tenants')}`, + 'data-testid': 'tenants', }, }, ]} diff --git a/src/components/observability/ProjectObservability/Project.tsx b/src/components/observability/ProjectObservability/Project.tsx index 8e6aaea96c..6e199b7439 100644 --- a/src/components/observability/ProjectObservability/Project.tsx +++ b/src/components/observability/ProjectObservability/Project.tsx @@ -47,8 +47,6 @@ const Project = () => { } }, [isDataSyncing]) - console.log('match', match) - // eslint-disable-next-line @typescript-eslint/no-unused-vars const updateDataSyncing = (loading: boolean): void => { setDataSyncing(loading) @@ -103,7 +101,7 @@ const Project = () => { const renderProjectTabs = () => (
-
+
@@ -139,7 +137,7 @@ const Project = () => { return (
-
+
Date: Tue, 4 Nov 2025 14:16:40 +0530 Subject: [PATCH 4/9] chore: CSS fixes --- .../Customer/CustomerListCellComponent.tsx | 44 +++++++++++++------ .../ProjectListCellComponent.tsx | 14 +++--- src/components/observability/constants.ts | 2 +- src/components/observability/service.ts | 8 ++-- src/components/observability/types.ts | 6 ++- 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/components/observability/Customer/CustomerListCellComponent.tsx b/src/components/observability/Customer/CustomerListCellComponent.tsx index 71584b72ce..c9d632eed7 100644 --- a/src/components/observability/Customer/CustomerListCellComponent.tsx +++ b/src/components/observability/Customer/CustomerListCellComponent.tsx @@ -3,19 +3,20 @@ import { Link, useRouteMatch } from 'react-router-dom' import { FiltersTypeEnum, + Icon, TableCellComponentProps, TableSignalEnum, Tooltip, } from '@devtron-labs/devtron-fe-common-lib/dist' -import { CustomerObservabilityDTO, ProjectListFields } from '../types' +import { CustomerObservabilityDTO, ObservabilityListFields } from '../types' export const CustomerListCellComponent: FunctionComponent< TableCellComponentProps > = ({ field, row: { - data: { id, name, status, project, totalVms, activeVms, healthStatus }, + data: { id, name, status, projects, totalVms, activeVms, healthStatus, icon }, }, isRowActive, signals, @@ -40,25 +41,40 @@ export const CustomerListCellComponent: FunctionComponent< }, [isRowActive]) switch (field) { - case ProjectListFields.PROJECT_NAME: + case ObservabilityListFields.ICON: return ( - + + + + ) + case ObservabilityListFields.PROJECT_NAME: + return ( + {name} ) - case ProjectListFields.STATUS: - return {status} - case ProjectListFields.PROJECTS: - return {project} - case ProjectListFields.TOTAL_VMS: - return {totalVms} - case ProjectListFields.ACTIVE_VMS: - return {activeVms} - case ProjectListFields.HEALTH_STATUS: + case ObservabilityListFields.STATUS: + return ( + + + {status} + + ) + case ObservabilityListFields.PROJECTS: + return {projects} + case ObservabilityListFields.TOTAL_VMS: + return {totalVms} + case ObservabilityListFields.ACTIVE_VMS: + return {activeVms} + case ObservabilityListFields.HEALTH_STATUS: return ( -
+
{healthStatus} diff --git a/src/components/observability/ProjectObservability/ProjectListCellComponent.tsx b/src/components/observability/ProjectObservability/ProjectListCellComponent.tsx index b594c833f9..891bfd060c 100644 --- a/src/components/observability/ProjectObservability/ProjectListCellComponent.tsx +++ b/src/components/observability/ProjectObservability/ProjectListCellComponent.tsx @@ -8,7 +8,7 @@ import { Tooltip, } from '@devtron-labs/devtron-fe-common-lib/dist' -import { ObservabilityProject, ProjectListFields } from '../types' +import { ObservabilityListFields, ObservabilityProject } from '../types' export const ProjectListCellComponent: FunctionComponent< TableCellComponentProps @@ -41,7 +41,7 @@ export const ProjectListCellComponent: FunctionComponent< }, [isRowActive]) switch (field) { - case ProjectListFields.PROJECT_NAME: + case ObservabilityListFields.PROJECT_NAME: return ( @@ -49,13 +49,13 @@ export const ProjectListCellComponent: FunctionComponent< ) - case ProjectListFields.PROJECT_DESCRIPTION: + case ObservabilityListFields.PROJECT_DESCRIPTION: return {description} - case ProjectListFields.STATUS: + case ObservabilityListFields.STATUS: return {status} - case ProjectListFields.TOTAL_VMS: + case ObservabilityListFields.TOTAL_VMS: return {totalVms} - case ProjectListFields.ACTIVE_VMS: + case ObservabilityListFields.ACTIVE_VMS: return (
@@ -63,7 +63,7 @@ export const ProjectListCellComponent: FunctionComponent<
) - case ProjectListFields.HEALTH_STATUS: + case ObservabilityListFields.HEALTH_STATUS: return (
diff --git a/src/components/observability/constants.ts b/src/components/observability/constants.ts index 1dedee3123..76a7b0622f 100644 --- a/src/components/observability/constants.ts +++ b/src/components/observability/constants.ts @@ -82,7 +82,7 @@ export const CUSTOMER_TABLE_COLUMN: CustomerTableProps['columns'] = [ comparator: stringComparatorBySortOrder, }, { - field: 'project', + field: 'projects', label: 'Projects', size: { fixed: 250, diff --git a/src/components/observability/service.ts b/src/components/observability/service.ts index 25a4c6ca9d..9c7e866715 100644 --- a/src/components/observability/service.ts +++ b/src/components/observability/service.ts @@ -87,21 +87,23 @@ export const getCustomerListData: () => Promise = () id: 1, name: 'Customer1', status: 'ACTIVE', - project: 2, + projects: 2, totalVms: 14, healthStatus: '80%', activeVms: 2, + icon: 'ic-devtron', }, { id: 2, name: 'Customer2', status: 'INACTIVE', - project: 34, + projects: 34, totalVms: 4, healthStatus: '20%', activeVms: 1, + icon: 'ic-helm', }, - ]) + ] as unknown as CustomerObservabilityDTO[]) export const getProjectOverViewCards: () => Promise = () => Promise.resolve([ diff --git a/src/components/observability/types.ts b/src/components/observability/types.ts index 2c00c18e89..2628309632 100644 --- a/src/components/observability/types.ts +++ b/src/components/observability/types.ts @@ -18,10 +18,11 @@ export interface BaseObservability { } export interface CustomerObservabilityDTO extends BaseObservability { - project: number + projects: number totalVms: number activeVms: number healthStatus: string + icon: string } export type CustomerTableProps = TableProps @@ -43,7 +44,7 @@ export interface ObservabilityVM extends BaseObservability { export type VMTableProps = TableProps -export enum ProjectListFields { +export enum ObservabilityListFields { PROJECT_ID = 'id', PROJECT_NAME = 'name', PROJECT_DESCRIPTION = 'description', @@ -52,6 +53,7 @@ export enum ProjectListFields { ACTIVE_VMS = 'activeVms', HEALTH_STATUS = 'healthStatus', PROJECTS = 'projects', + ICON = 'icon', } export enum VMListFields { From 86bca594ed0344c321ea517e0a1893f6fa6920e8 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Thu, 6 Nov 2025 16:39:07 +0530 Subject: [PATCH 5/9] chore: bar chart in overview page added --- .../observability/Metrics/BarMetrics.tsx | 25 +++++++ .../observability/MetricsInfoCard.tsx | 14 +--- src/components/observability/Overview.tsx | 11 +-- .../ProjectObservability/ProjectOverview.tsx | 3 +- .../observability/SingleVMOverview.tsx | 3 +- .../VMObservability/VMOverview.tsx | 2 +- src/components/observability/constants.ts | 14 ++++ src/components/observability/service.ts | 67 +++++++++++++++--- src/components/observability/styles.scss | 4 +- src/components/observability/types.ts | 42 ++++++++--- src/components/observability/utils.tsx | 69 +++++++++++++++++-- 11 files changed, 209 insertions(+), 45 deletions(-) create mode 100644 src/components/observability/Metrics/BarMetrics.tsx diff --git a/src/components/observability/Metrics/BarMetrics.tsx b/src/components/observability/Metrics/BarMetrics.tsx new file mode 100644 index 0000000000..620683f0c2 --- /dev/null +++ b/src/components/observability/Metrics/BarMetrics.tsx @@ -0,0 +1,25 @@ +import { Tooltip } from '@devtron-labs/devtron-fe-common-lib/dist' + +import { BarMetricsProps } from '../types' +import { CPUCapacityCellComponent, DiskCapacityCellComponent, MemoryCapacityCellComponent } from '../utils' + +export const BarMetrics = ({ data }: BarMetricsProps) => ( +
+
+ TENANTS NAME + CPU + MEMORY + DISK +
+ {data?.map(({ disk, name, memory, cpu }) => ( +
+ + {name} + + + + +
+ ))} +
+) diff --git a/src/components/observability/MetricsInfoCard.tsx b/src/components/observability/MetricsInfoCard.tsx index 5f3ac6579c..6643f139cb 100644 --- a/src/components/observability/MetricsInfoCard.tsx +++ b/src/components/observability/MetricsInfoCard.tsx @@ -6,12 +6,13 @@ import { ButtonVariantType, ConditionalWrap, Icon, - IconName, motion, noop, Tooltip, } from '@devtron-labs/devtron-fe-common-lib' +import { MetricsInfoCardProps } from './types' + export const MetricsInfoCard = ({ dataTestId, metricTitle, @@ -21,16 +22,7 @@ export const MetricsInfoCard = ({ iconName, redirectionLink, tooltipContent, -}: { - dataTestId: string - metricTitle: string - metricValue: string - metricUnit?: string - valueOutOf?: string - iconName: IconName - redirectionLink?: string - tooltipContent?: string -}) => { +}: MetricsInfoCardProps) => { const [isHovering, setIsHovering] = useState(false) const handleHoverStart = () => setIsHovering(true) diff --git a/src/components/observability/Overview.tsx b/src/components/observability/Overview.tsx index 13a86b5290..69d56208ea 100644 --- a/src/components/observability/Overview.tsx +++ b/src/components/observability/Overview.tsx @@ -9,6 +9,7 @@ import { useBreadcrumb, } from '@devtron-labs/devtron-fe-common-lib' +import { BarMetrics } from './Metrics/BarMetrics' import { MetricsInfoCard } from './MetricsInfoCard' import ObservabilityIconComponent from './ObservabilityIcon' import { GlanceMetricsKeys } from './types' @@ -66,7 +67,7 @@ export const Overview = () => { /> ) - const renderBody = () => { + const renderGlanceConfig = () => { if (isFetching) { return (
@@ -83,9 +84,7 @@ export const Overview = () => { return (
- {data.map((config) => ( - - ))} + {data?.glanceConfig.map((config) => )}
) } @@ -98,10 +97,12 @@ export const Overview = () => {

At a Glance

- {renderBody()} + {renderGlanceConfig()}

Observability Metrics

+ +
diff --git a/src/components/observability/ProjectObservability/ProjectOverview.tsx b/src/components/observability/ProjectObservability/ProjectOverview.tsx index 7a541525bb..3c60d4ebfb 100644 --- a/src/components/observability/ProjectObservability/ProjectOverview.tsx +++ b/src/components/observability/ProjectObservability/ProjectOverview.tsx @@ -6,7 +6,6 @@ import { MetricsInfoLoadingCard, useGetGlanceConfig } from '../utils' export const ProjectOverview = () => { const { isFetching, data, isError, refetch } = useGetGlanceConfig() - console.log(data) const renderBody = () => { if (isFetching) { @@ -31,7 +30,7 @@ export const ProjectOverview = () => { // alert(JSON.stringify(data)) return (
- {data.map((value) => ( + {data.glanceConfig.map((value) => ( ))}
diff --git a/src/components/observability/SingleVMOverview.tsx b/src/components/observability/SingleVMOverview.tsx index 722e13d38a..eb073753b2 100644 --- a/src/components/observability/SingleVMOverview.tsx +++ b/src/components/observability/SingleVMOverview.tsx @@ -19,7 +19,6 @@ import './styles.scss' let interval const SingleVMOverview = () => { const { isFetching, data, isError, refetch } = useGetGlanceConfig() - console.log(data) const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') const [isDataSyncing, setDataSyncing] = useState(false) @@ -85,7 +84,7 @@ const SingleVMOverview = () => { // alert(JSON.stringify(data)) return (
- {data.map((value) => ( + {data.glanceConfig.map((value) => ( ))}
diff --git a/src/components/observability/VMObservability/VMOverview.tsx b/src/components/observability/VMObservability/VMOverview.tsx index 832c8b4c9a..45536bf3e9 100644 --- a/src/components/observability/VMObservability/VMOverview.tsx +++ b/src/components/observability/VMObservability/VMOverview.tsx @@ -34,7 +34,7 @@ export const VMOverview = () => { // alert(JSON.stringify(data)) return (
- {data.map((value) => ( + {data.glanceConfig.map((value) => ( ))}
diff --git a/src/components/observability/constants.ts b/src/components/observability/constants.ts index 76a7b0622f..a7c4812676 100644 --- a/src/components/observability/constants.ts +++ b/src/components/observability/constants.ts @@ -248,3 +248,17 @@ export const VM_TABLE_COLUMNS: VMTableProps['columns'] = [ CellComponent: VMListCellComponent, }, ] + +export enum ObservabilityUtilizationKeys { + VM_RUNNING_STATUS = 'vmRUnningStatus', + CPU_UTILIZATION = 'cpuUtilization', + MEMORY_UTILIZATION = 'memoryUtilization', + DISK_UTILIZATION = 'diskUtilization', +} + +export const OBSERVABILITY_UTILIZATION_CARDS_TITLE: Record = { + [ObservabilityUtilizationKeys.VM_RUNNING_STATUS]: 'VMS Running Status', + [ObservabilityUtilizationKeys.CPU_UTILIZATION]: 'CPU Utilization', + [ObservabilityUtilizationKeys.MEMORY_UTILIZATION]: 'Memory Utilization', + [ObservabilityUtilizationKeys.DISK_UTILIZATION]: 'Disk Utilization', +} diff --git a/src/components/observability/service.ts b/src/components/observability/service.ts index 9c7e866715..bc4790eb94 100644 --- a/src/components/observability/service.ts +++ b/src/components/observability/service.ts @@ -1,13 +1,46 @@ import { ObservabilityGlanceMetricKeys } from './constants' import { CustomerObservabilityDTO, ObservabilityOverviewDTO } from './types' -export const getObservabilityData: () => Promise> = () => +export const getObservabilityData: () => Promise = () => Promise.resolve({ - [ObservabilityGlanceMetricKeys.TOTAL_CUSTOMERS]: 10, - [ObservabilityGlanceMetricKeys.TOTAL_VMS]: 20, - [ObservabilityGlanceMetricKeys.PROJECTS]: 30, - [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: 50, - // [ObservabilityGlanceMetricKeys.RUNNING_VMS]: 40, + glanceConfig: { + [ObservabilityGlanceMetricKeys.TOTAL_CUSTOMERS]: 4, + [ObservabilityGlanceMetricKeys.TOTAL_VMS]: 20, + [ObservabilityGlanceMetricKeys.PROJECTS]: 2, + [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: 50, + [ObservabilityGlanceMetricKeys.TOTAL_CLUSTER]: 2, + [ObservabilityGlanceMetricKeys.RUNNING_VMS]: 40, + }, + metrics: [ + { + id: 1, + name: 'Tenants1', + cpu: { capacity: 36, utilization: '10.75' }, + memory: { capacity: 141, utilization: '22.75' }, + disk: { capacity: 50, utilization: '32.75' }, + }, + { + id: 2, + name: 'Tenant2', + cpu: { capacity: 36, utilization: '20.75' }, + memory: { capacity: 121, utilization: '40.75' }, + disk: { capacity: 50, utilization: '60.75' }, + }, + { + id: 3, + name: 'Tenants3', + cpu: { capacity: 36, utilization: '6.75' }, + memory: { capacity: 141, utilization: '2.75' }, + disk: { capacity: 50, utilization: '22.75' }, + }, + { + id: 4, + name: 'Tenants4', + cpu: { capacity: 36, utilization: '6.75' }, + memory: { capacity: 141, utilization: '22.75' }, + disk: { capacity: 50, utilization: '22.75' }, + }, + ], }) export const getProjectList: () => Promise = () => @@ -85,7 +118,7 @@ export const getCustomerListData: () => Promise = () Promise.resolve([ { id: 1, - name: 'Customer1', + name: 'Tenants1', status: 'ACTIVE', projects: 2, totalVms: 14, @@ -95,7 +128,7 @@ export const getCustomerListData: () => Promise = () }, { id: 2, - name: 'Customer2', + name: 'Tenant2', status: 'INACTIVE', projects: 34, totalVms: 4, @@ -103,6 +136,24 @@ export const getCustomerListData: () => Promise = () activeVms: 1, icon: 'ic-helm', }, + { + id: 3, + name: 'Tenants3', + status: 'INACTIVE', + project: 34, + totalVms: 4, + healthStatus: '20%', + activeVms: 1, + }, + { + id: 4, + name: 'tenants4', + status: 'ACTIVE', + project: 34, + totalVms: 4, + healthStatus: '30%', + activeVms: 1, + }, ] as unknown as CustomerObservabilityDTO[]) export const getProjectOverViewCards: () => Promise = () => diff --git a/src/components/observability/styles.scss b/src/components/observability/styles.scss index d2bb782c8d..020850c9f3 100644 --- a/src/components/observability/styles.scss +++ b/src/components/observability/styles.scss @@ -5,8 +5,8 @@ flexbox-col dc__gap-32 bg__secondary p-20 column-gap: 8px; } - .capacity-table-row { - grid-template-columns: 150px 1fr 1fr; + .bar-chart-table-row { + grid-template-columns: 150px 1fr 1fr 1fr; } .at-a-glance { diff --git a/src/components/observability/types.ts b/src/components/observability/types.ts index 2628309632..6f52b5bbed 100644 --- a/src/components/observability/types.ts +++ b/src/components/observability/types.ts @@ -81,14 +81,6 @@ export interface TabDetailsSearchParams { tab: TabDetailsSegment } -export interface ObservabilityOverviewDTO { - [ObservabilityGlanceMetricKeys.TOTAL_CLUSTER]: number - [ObservabilityGlanceMetricKeys.TOTAL_VMS]: number - [ObservabilityGlanceMetricKeys.PROJECTS]: number - [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: number - [ObservabilityGlanceMetricKeys.RUNNING_VMS]: number -} - export interface MetricsInfoCardProps { iconName: IconName metricTitle: string @@ -99,3 +91,37 @@ export interface MetricsInfoCardProps { tooltipContent?: string redirectionLink?: string } + +export interface GlanceConfigDTO { + [ObservabilityGlanceMetricKeys.TOTAL_CLUSTER]: number + [ObservabilityGlanceMetricKeys.TOTAL_VMS]: number + [ObservabilityGlanceMetricKeys.PROJECTS]: number + [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: number + [ObservabilityGlanceMetricKeys.RUNNING_VMS]: number +} + +export interface CPUMemoryDiskUtilization { + capacity: number + utilization: string +} + +export interface ObservabilityMetricsDTO { + id: number + name: string + cpu: CPUMemoryDiskUtilization + memory: CPUMemoryDiskUtilization + disk: CPUMemoryDiskUtilization +} + +export interface ObservabilityOverviewDTO { + glanceConfig: Partial + metrics: ObservabilityMetricsDTO[] +} + +export interface BarMetricsProps { + data: ObservabilityOverviewDTO['metrics'] +} + +export interface ResourceCapacityDistributionTypes extends CPUMemoryDiskUtilization { + bgColor?: string +} diff --git a/src/components/observability/utils.tsx b/src/components/observability/utils.tsx index 61eab2434a..16e7c7a5d9 100644 --- a/src/components/observability/utils.tsx +++ b/src/components/observability/utils.tsx @@ -1,8 +1,15 @@ -import { useQuery } from '@devtron-labs/devtron-fe-common-lib' +import { SelectPickerOptionType, Tooltip, useQuery } from '@devtron-labs/devtron-fe-common-lib' import { GLANCE_METRICS_CARDS_CONFIG, ObservabilityGlanceMetricKeys } from './constants' import { getObservabilityData } from './service' -import { MetricsInfoCardProps, ObservabilityOverviewDTO, TabDetailsSearchParams, TabDetailsSegment } from './types' +import { + MetricsInfoCardProps, + ObservabilityMetricsDTO, + ObservabilityOverviewDTO, + ResourceCapacityDistributionTypes, + TabDetailsSearchParams, + TabDetailsSegment, +} from './types' // Will be removing while importing to dashboard export const MetricsInfoLoadingCard = () => ( @@ -17,22 +24,27 @@ export const MetricsInfoLoadingCard = () => (
) -export const getObservabilityGlanceConfig = (result: Partial) => - Object.entries(GLANCE_METRICS_CARDS_CONFIG).map( +export const getObservabilityGlanceConfig = (result: Partial) => { + const glanceConfig = Object.entries(GLANCE_METRICS_CARDS_CONFIG).map( ([key, config]: [ObservabilityGlanceMetricKeys, MetricsInfoCardProps]) => { - const entry = result?.[key] + const entry = result.glanceConfig?.[key] const isNumber = typeof entry === 'number' const metricValue = isNumber ? entry : (entry as any)?.value const metricTitle = config?.metricTitle return { ...config, - dataTestId: key, + dataTestId: key as string, metricValue, metricTitle, } }, ) + return { + metrics: result?.metrics, + glanceConfig, + } +} export const useGetGlanceConfig = () => useQuery({ @@ -50,3 +62,48 @@ export const useGetGlanceConfig = () => export const parseChartDetailsSearchParams = (searchParams: URLSearchParams): TabDetailsSearchParams => ({ tab: (searchParams.get('tab') as TabDetailsSegment) || TabDetailsSegment.OVERVIEW, }) + +const AllocatedResource = ({ label, value }: SelectPickerOptionType) => ( +
+ {label} + {value} +
+) + +export const ResourceAllocationBar = ({ + capacity, + utilization, + bgColor = 'B500', +}: ResourceCapacityDistributionTypes & { bgColor? }) => ( +
+
+ {/* Fill div up to utilisation percentage */} + +
+ +
+
+ +
+ +
+
+) + +export const CPUCapacityCellComponent = ({ cpu }: Pick) => ( + +) + +export const MemoryCapacityCellComponent = ({ memory }: Pick) => ( + +) + +export const DiskCapacityCellComponent = ({ disk }: Pick) => ( + +) From 93dc3c45af841c8eebd842133dc0a531184c03a7 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Thu, 6 Nov 2025 17:17:56 +0530 Subject: [PATCH 6/9] fix: tenant page break fix --- src/components/observability/service.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/observability/service.ts b/src/components/observability/service.ts index bc4790eb94..e5c4953f9a 100644 --- a/src/components/observability/service.ts +++ b/src/components/observability/service.ts @@ -6,10 +6,10 @@ export const getObservabilityData: () => Promise = () glanceConfig: { [ObservabilityGlanceMetricKeys.TOTAL_CUSTOMERS]: 4, [ObservabilityGlanceMetricKeys.TOTAL_VMS]: 20, - [ObservabilityGlanceMetricKeys.PROJECTS]: 2, + [ObservabilityGlanceMetricKeys.PROJECTS]: 5, [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: 50, [ObservabilityGlanceMetricKeys.TOTAL_CLUSTER]: 2, - [ObservabilityGlanceMetricKeys.RUNNING_VMS]: 40, + [ObservabilityGlanceMetricKeys.RUNNING_VMS]: 2, }, metrics: [ { @@ -144,6 +144,7 @@ export const getCustomerListData: () => Promise = () totalVms: 4, healthStatus: '20%', activeVms: 1, + icon: 'ic-helm', }, { id: 4, @@ -153,6 +154,7 @@ export const getCustomerListData: () => Promise = () totalVms: 4, healthStatus: '30%', activeVms: 1, + icon: 'ic-devtron', }, ] as unknown as CustomerObservabilityDTO[]) From 8344ac871a2c5583e95d30dae3887435ed4afa03 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Thu, 6 Nov 2025 17:56:40 +0530 Subject: [PATCH 7/9] chore: css fixes --- .../observability/Metrics/BarMetrics.tsx | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/components/observability/Metrics/BarMetrics.tsx b/src/components/observability/Metrics/BarMetrics.tsx index 620683f0c2..c1818a4868 100644 --- a/src/components/observability/Metrics/BarMetrics.tsx +++ b/src/components/observability/Metrics/BarMetrics.tsx @@ -4,22 +4,27 @@ import { BarMetricsProps } from '../types' import { CPUCapacityCellComponent, DiskCapacityCellComponent, MemoryCapacityCellComponent } from '../utils' export const BarMetrics = ({ data }: BarMetricsProps) => ( -
-
- TENANTS NAME - CPU - MEMORY - DISK +
+
+ Tenants Capacity & Resource Allocation
- {data?.map(({ disk, name, memory, cpu }) => ( -
- - {name} - - - - +
+
+ TENANTS NAME + CPU + MEMORY + DISK
- ))} + {data?.map(({ disk, name, memory, cpu }) => ( +
+ + {name} + + + + +
+ ))} +
) From f263775561f90288cf2927fb516e88d3170186ff Mon Sep 17 00:00:00 2001 From: shivani170 Date: Mon, 10 Nov 2025 12:03:02 +0530 Subject: [PATCH 8/9] code refactoring --- .../observability/Customer/Customers.tsx | 26 +-- .../Metrics/ObservabilityGraphMetrics.tsx | 165 ++++++++++++++++++ .../observability/ObservabilityRouter.tsx | 18 +- src/components/observability/Overview.tsx | 68 ++------ .../ProjectObservability/Project.tsx | 93 ++-------- .../ProjectObservability/ProjectList.tsx | 76 +++++--- .../ProjectObservability/ProjectOverview.tsx | 52 ------ .../observability/VMObservability/VM.tsx | 107 +++++------- .../observability/VMObservability/VMList.tsx | 76 +++++--- .../VMObservability/VMOverview.tsx | 90 ---------- src/components/observability/types.ts | 6 + src/components/observability/utils.tsx | 150 +++++++++++++++- src/css/base.scss | 2 + 13 files changed, 511 insertions(+), 418 deletions(-) create mode 100644 src/components/observability/Metrics/ObservabilityGraphMetrics.tsx delete mode 100644 src/components/observability/ProjectObservability/ProjectOverview.tsx delete mode 100644 src/components/observability/VMObservability/VMOverview.tsx diff --git a/src/components/observability/Customer/Customers.tsx b/src/components/observability/Customer/Customers.tsx index adf4947da8..3c5c1a83cf 100644 --- a/src/components/observability/Customer/Customers.tsx +++ b/src/components/observability/Customer/Customers.tsx @@ -3,7 +3,6 @@ import { useRouteMatch } from 'react-router-dom' import { BreadCrumb, - BreadcrumbText, ComponentSizeType, handleUTCTime, PageHeader, @@ -12,12 +11,12 @@ import { useBreadcrumb, } from '@devtron-labs/devtron-fe-common-lib' -import ObservabilityIconComponent from '../ObservabilityIcon' +import { getBreadCrumbObj } from '../utils' import { CustomerList } from './CustomerList' let interval const Customers = () => { - const { path } = useRouteMatch() + const { url, path } = useRouteMatch() const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') const [isDataSyncing, setDataSyncing] = useState(false) const [syncListData, setSyncListData] = useState() @@ -102,27 +101,12 @@ const Customers = () => {
) - const { breadcrumbs } = useBreadcrumb({ - alias: { - observability: { - component: , - linked: true, - }, - tenants: { - component: , - linked: false, - }, - }, - }) + const { breadcrumbs } = useBreadcrumb(getBreadCrumbObj('project', url)) + const renderBreadcrumbs = () => const renderPageHeader = () => ( - + ) const searchKey = '' const handleSearch = () => {} diff --git a/src/components/observability/Metrics/ObservabilityGraphMetrics.tsx b/src/components/observability/Metrics/ObservabilityGraphMetrics.tsx new file mode 100644 index 0000000000..7b5ccb5008 --- /dev/null +++ b/src/components/observability/Metrics/ObservabilityGraphMetrics.tsx @@ -0,0 +1,165 @@ +import { useEffect, useState } from 'react' +import Tippy from '@tippyjs/react' +import moment, { Moment } from 'moment' + +import { useTheme } from '@devtron-labs/devtron-fe-common-lib/dist' + +import { + CalendarFocusInput, + CalendarFocusInputType, + ChartType, +} from '@Components/app/details/appDetails/appDetails.type' +import { APP_METRICS_CALENDAR_INPUT_DATE_FORMAT } from '@Components/app/details/appDetails/constants' +import { GraphModalProps } from '@Components/app/details/appDetails/GraphsModal' +import { AppInfo, getCalendarValue, getIframeSrc } from '@Components/app/details/appDetails/utils' + +import { ReactComponent as Fullscreen } from '../../../assets/icons/ic-fullscreen-2.svg' +import { DatePickerType2 as DateRangePicker } from '../../common' + +export const ObservabilityGraphMetrics = () => { + const { appTheme } = useTheme() + const [calendar, setDateRange] = useState<{ startDate: Moment; endDate: Moment }>({ + startDate: moment().subtract(5, 'minute'), + endDate: moment(), + }) + const [calendarInputs, setCalendarInput] = useState<{ startDate: string; endDate: string }>({ + startDate: 'now-5m', + endDate: 'now', + }) + const [focusedInput, setFocusedInput] = useState(CalendarFocusInput.StartDate) + const [calendarValue, setCalendarValue] = useState('') + const [graphs, setGraphs] = useState({ + cpu: '', + ram: '', + }) + + const getIframeSrcWrapper: GraphModalProps['getIframeSrcWrapper'] = (params) => + getIframeSrc({ + ...params, + grafanaTheme: appTheme, + }) + + const appInfo: AppInfo = { + appId: 740, + envId: 36, + dataSourceName: '', + newPodHash: '', + k8sVersion: '', + } + + function getNewGraphs(): void { + const cpu = getIframeSrcWrapper({ + appInfo, + chartName: ChartType.Cpu, + calendarInputs, + tab: 'aggregate', + isLegendRequired: true, + }) + const ram = getIframeSrcWrapper({ + appInfo, + chartName: ChartType.Ram, + calendarInputs, + tab: 'aggregate', + isLegendRequired: true, + }) + setGraphs({ + cpu, + ram, + }) + } + + useEffect(() => { + getNewGraphs() + }, [calendarValue, appTheme]) + + const handlePredefinedRange = (start: Moment, end: Moment, startStr: string): void => { + setDateRange({ + startDate: start, + endDate: end, + }) + setCalendarInput({ + startDate: startStr, + endDate: 'now', + }) + const str = getCalendarValue(startStr, 'now') + setCalendarValue(str) + } + + const handleDatesChange = ({ startDate, endDate }): void => { + setDateRange({ + startDate, + endDate, + }) + setCalendarInput({ + startDate: startDate?.format(APP_METRICS_CALENDAR_INPUT_DATE_FORMAT), + endDate: endDate?.format(APP_METRICS_CALENDAR_INPUT_DATE_FORMAT) || '', + }) + } + + const handleDateInput = (key: CalendarFocusInputType, value: string): void => { + setCalendarInput({ + ...calendarInputs, + [key]: value, + }) + } + + const handleFocusChange = (_focusedInput): void => { + setFocusedInput(_focusedInput || CalendarFocusInput.StartDate) + } + + const handleApply = (): void => { + const str = getCalendarValue(calendarInputs.startDate, calendarInputs.endDate) + setCalendarValue(str) + } + + return ( +
+
+ Observability Metrics +
+ +
+
+
+
+ CPU Usage + +
+ +
+
+
+