From acbe2f1de9480ecdf912ece7ba62f051c213cb2b Mon Sep 17 00:00:00 2001 From: shivani170 Date: Thu, 9 Oct 2025 11:59:57 +0530 Subject: [PATCH 01/14] feat: folder structure for observability added --- src/components/Navigation/constants.ts | 21 ++++++++++++++++ src/components/Navigation/types.ts | 1 + .../common/navigation/NavigationRoutes.tsx | 7 ++++++ .../observability/ObservabilityRouter.tsx | 24 +++++++++++++++++++ src/components/observability/Overview.tsx | 1 + src/components/observability/VMList.tsx | 1 + src/components/observability/index.ts | 1 + 7 files changed, 56 insertions(+) create mode 100644 src/components/observability/ObservabilityRouter.tsx create mode 100644 src/components/observability/Overview.tsx create mode 100644 src/components/observability/VMList.tsx create mode 100644 src/components/observability/index.ts diff --git a/src/components/Navigation/constants.ts b/src/components/Navigation/constants.ts index cde53e7021..faa30c667e 100644 --- a/src/components/Navigation/constants.ts +++ b/src/components/Navigation/constants.ts @@ -281,6 +281,27 @@ export const NAVIGATION_LIST: NavigationGroupType[] = [ }, ], }, + { + id: 'observability', + title: 'Observability', + icon: 'ic-user-key', + items: [ + { + title: 'Overview', + dataTestId: 'observability-overview', + id: 'infrastructure-management-overview', + icon: 'ic-speedometer', + href: COMMON_URLS.OBSERVABILITY_OVERVIEW, + }, + { + title: 'VMs', + dataTestId: 'observability-vms', + id: 'observability-vms', + icon: 'ic-cluster', + href: COMMON_URLS.OBSERVABILITY_LIST, + }, + ], + }, { id: 'software-release-management', title: 'Software Release Management', diff --git a/src/components/Navigation/types.ts b/src/components/Navigation/types.ts index a8f017b64c..c0233d5971 100644 --- a/src/components/Navigation/types.ts +++ b/src/components/Navigation/types.ts @@ -15,6 +15,7 @@ import { ViewType } from '@Config/constants' export type NavigationRootItemID = | 'application-management' | 'infrastructure-management' + | 'observability' | 'software-release-management' | 'cost-visibility' | 'security-center' diff --git a/src/components/common/navigation/NavigationRoutes.tsx b/src/components/common/navigation/NavigationRoutes.tsx index 96cc03780b..2d8ceafb51 100644 --- a/src/components/common/navigation/NavigationRoutes.tsx +++ b/src/components/common/navigation/NavigationRoutes.tsx @@ -112,6 +112,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') @@ -560,6 +561,12 @@ const NavigationRoutes = ({ reloadVersionConfig }: Readonly + + + } diff --git a/src/components/observability/ObservabilityRouter.tsx b/src/components/observability/ObservabilityRouter.tsx new file mode 100644 index 0000000000..4d048d5f93 --- /dev/null +++ b/src/components/observability/ObservabilityRouter.tsx @@ -0,0 +1,24 @@ +import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom' + +import { Overview } from './Overview' +import { VMList } from './VMList' + +const ObservabilityRouter: React.FC = () => { + const { path } = useRouteMatch() + + return ( + + + + + + + + + + + + ) +} + +export default ObservabilityRouter diff --git a/src/components/observability/Overview.tsx b/src/components/observability/Overview.tsx new file mode 100644 index 0000000000..a39a994aeb --- /dev/null +++ b/src/components/observability/Overview.tsx @@ -0,0 +1 @@ +export const Overview = () =>
Overview
diff --git a/src/components/observability/VMList.tsx b/src/components/observability/VMList.tsx new file mode 100644 index 0000000000..5539c5f112 --- /dev/null +++ b/src/components/observability/VMList.tsx @@ -0,0 +1 @@ +export const VMList = () =>
VMList
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' From d28fda226d012f325b41b5892995f04bdc249a64 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Fri, 17 Oct 2025 13:13:41 +0530 Subject: [PATCH 02/14] chore: observability integration wip --- src/components/Navigation/constants.ts | 4 +- .../observability/MetricsInfoCard.tsx | 82 +++++++++++++++++++ .../observability/ObservabilityIcon.tsx | 25 ++++++ .../observability/ObservabilityRouter.tsx | 30 ++++--- src/components/observability/Overview.tsx | 78 +++++++++++++++++- src/components/observability/constants.ts | 32 ++++++++ src/components/observability/service.ts | 8 ++ src/components/observability/styles.scss | 16 ++++ src/components/observability/types.ts | 6 ++ src/components/observability/utils.tsx | 22 +++++ 10 files changed, 284 insertions(+), 19 deletions(-) create mode 100644 src/components/observability/MetricsInfoCard.tsx create mode 100644 src/components/observability/ObservabilityIcon.tsx create mode 100644 src/components/observability/constants.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/Navigation/constants.ts b/src/components/Navigation/constants.ts index f1dbcaf310..19a7d13603 100644 --- a/src/components/Navigation/constants.ts +++ b/src/components/Navigation/constants.ts @@ -285,7 +285,7 @@ export const NAVIGATION_LIST: NavigationGroupType[] = [ { id: 'observability', title: 'Observability', - icon: 'ic-user-key', + icon: 'ic-binoculars', items: [ { title: 'Overview', @@ -295,7 +295,7 @@ export const NAVIGATION_LIST: NavigationGroupType[] = [ href: COMMON_URLS.OBSERVABILITY_OVERVIEW, }, { - title: 'VMs', + title: 'Customers', dataTestId: 'observability-vms', id: 'observability-vms', icon: 'ic-cluster', diff --git a/src/components/observability/MetricsInfoCard.tsx b/src/components/observability/MetricsInfoCard.tsx new file mode 100644 index 0000000000..2a387ea105 --- /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 renderSegments = () => { + switch (tab) { + case TabDetailsSegment.OVERVIEW: + return ( + renderProjectOverview() + ) + case TabDetailsSegment.PROJECTS: + return renderProjectList() + default: + return null + } + } + + return ( +
+
+
+ {/* */} + +
+
+
+
+ {/* {selectedTabIndex == 0 && renderProjectOverview()} + {selectedTabIndex == 1 && renderProjectList()} */} + {renderSegments()} +
+
+
+ ) + } + + const { breadcrumbs } = useBreadcrumb({ + alias: { + observability: { + component: , + linked: true, + }, + customer: { + component: , + linked: false, + }, + }, + }) + const renderBreadcrumbs = () => + const searchKey = "" + const handleSearch = () => {} + return ( +
+ +
+ +
+ {renderProjectTabs()} +
+ ) +} + +export default Project; \ No newline at end of file diff --git a/src/components/observability/ProjectList.tsx b/src/components/observability/ProjectList.tsx new file mode 100644 index 0000000000..97410c1171 --- /dev/null +++ b/src/components/observability/ProjectList.tsx @@ -0,0 +1,182 @@ +import { FiltersTypeEnum, numberComparatorBySortOrder, PaginationEnum, stringComparatorBySortOrder, Table, TableCellComponentProps, TableSignalEnum, Tooltip, useAsync } from ".yalc/@devtron-labs/devtron-fe-common-lib/dist"; +import { FunctionComponent, useEffect, useMemo, useRef } from "react"; +import { ObservabilityProject, ProjectListFields, ProjectTableProps } from "./types"; +import { Link } from "react-router-dom"; +import { getProjectList } from "./service"; + +const ProjectList = () => { + + // ASYNC CALLS + const [isFetching, projectData, isError, refetch] = useAsync( + () => getProjectList(), + [], + ) + + // CONFIGS + const rows = useMemo( + () => + (projectData || []).map((data) => ({ + id: `observe_project_${data.id.toString()}`, + data, + })), + [projectData], + ) + + const filter: ProjectTableProps['filter'] = (rowData, filterData) => + rowData.data.name.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; + +export const ProjectListCellComponent: FunctionComponent< + TableCellComponentProps +> = ({ + field, + row: { + data: { id, name, description, status, totalVms, activeVms, healthStatus }, + }, + isRowActive, + signals, +}: TableCellComponentProps) => { + const linkRef = useRef(null) + + 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.PROJECT_STATUS: + return {status} + case ProjectListFields.TOTAL_VMS: + return {totalVms} + case ProjectListFields.ACTIVE_VMS: + return ( +
+ + {activeVms} + +
+ ) + case ProjectListFields.HEALTH_STATUS: + return ( +
+ + {healthStatus} + +
+ ) + default: + return null + } +} + +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 + } +] \ No newline at end of file diff --git a/src/components/observability/ProjectOverview.tsx b/src/components/observability/ProjectOverview.tsx new file mode 100644 index 0000000000..861a2cf6e2 --- /dev/null +++ b/src/components/observability/ProjectOverview.tsx @@ -0,0 +1,60 @@ +import { + BreadCrumb, + BreadcrumbText, + GenericSectionErrorState, + PageHeader, + useBreadcrumb, +} from '@devtron-labs/devtron-fe-common-lib' + +import ObservabilityIconComponent from './ObservabilityIcon' +import { GlanceMetricsKeys } from './types' +import { MetricsInfoLoadingCard, useGetGlanceConfig } from './utils' + +import './styles.scss' +import { MetricsInfoCard } from './MetricsInfoCard' + +export const ProjectOverview = () => { + const { isFetching, data, isError, refetch } = useGetGlanceConfig() + + const renderBody = () => { + if (isFetching) { + return ( +
+ {Object.keys(GlanceMetricsKeys).map((key) => ( + + ))} +
+ ) + } + + if (isError) { + return ( + + ) + } + // alert(JSON.stringify(data)) + return ( +
+ +
+ ) + } + + return ( +
+
+
+

At a Glance

+
+
+ {renderBody()} +
+ ) +} + + +export default ProjectOverview; \ No newline at end of file diff --git a/src/components/observability/VM.tsx b/src/components/observability/VM.tsx new file mode 100644 index 0000000000..d14d544554 --- /dev/null +++ b/src/components/observability/VM.tsx @@ -0,0 +1,150 @@ +import { useState,useEffect } from "react" +import { BreadCrumb, BreadcrumbText, ComponentSizeType, handleUTCTime, PageHeader, SearchBar, TabGroup, TabProps, useBreadcrumb } from ".yalc/@devtron-labs/devtron-fe-common-lib/dist" +import ObservabilityIconComponent from "./ObservabilityIcon" +import './styles.scss' +import VMOverview from "./VMOverview" +import VMList from "./VMList" +let interval +const VM = () => { + const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') + const [isDataSyncing, setDataSyncing] = useState(false) + const [syncListData, setSyncListData] = useState() + const [fetchingExternalApps, setFetchingExternalApps] = useState(false) + 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]) + + const updateDataSyncing = (loading: boolean): void => { + setDataSyncing(loading) + } + const tabs: TabProps = [ + { + id: 'vm_overview', + label: 'Overview', + tabType: 'button', + active: selectedTabIndex == 0, + props: { + onClick: () => { + setSelectedTabIndex(0) + }, + }, + }, + { + id: 'vm_list', + label: 'VMs', + tabType: 'button', + active: selectedTabIndex == 1, + props: { + onClick: () => { + setSelectedTabIndex(1) + }, + }, + } + ] + + const syncNow = (): void => { + setSyncListData(!syncListData) + } + + const renderVMOverview = () => { + return + } + + const renderVMList = () => { + return + } + + const renderVMTabs = () => { + const rightComponent = ( +
+ {lastDataSyncTimeString && + <> + {lastDataSyncTimeString} + {!isDataSyncing && ( + <> +   + + + )} + + } + {fetchingExternalApps && renderDataSyncingText()} +
+ ) + + return ( +
+
+
+ +
+
+
+
+ {selectedTabIndex == 0 && renderVMOverview()} + {selectedTabIndex == 1 && renderVMList()} +
+
+
+ ) + } + + const { breadcrumbs } = useBreadcrumb({ + alias: { + observability: { + component: , + linked: true, + }, + customer: { + component: , + linked: false, + }, + }, + }) + const renderBreadcrumbs = () => + const searchKey = "" + const handleSearch = () => {} + return ( +
+ +
+ +
+ {renderVMTabs()} +
+ ) +} + +export default VM; \ No newline at end of file diff --git a/src/components/observability/VMList.tsx b/src/components/observability/VMList.tsx index 5539c5f112..aba1d9ffbb 100644 --- a/src/components/observability/VMList.tsx +++ b/src/components/observability/VMList.tsx @@ -1 +1,185 @@ -export const VMList = () =>
VMList
+import { FiltersTypeEnum, numberComparatorBySortOrder, PaginationEnum, stringComparatorBySortOrder, Table, TableCellComponentProps, TableSignalEnum, Tooltip, useAsync } from ".yalc/@devtron-labs/devtron-fe-common-lib/dist"; +import { FunctionComponent, useEffect, useMemo, useRef } from "react"; +import { ObservabilityVM, VMListFields, VMTableProps } from "./types"; +import { Link } from "react-router-dom"; +import { getVMList } from "./service"; + +const VMList = () => { + + // ASYNC CALLS + const [isFetching, vmData, isError, refetch] = 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; + +export const VMListCellComponent: FunctionComponent< + TableCellComponentProps +> = ({ + field, + row: { + data: { id, name, ipAddress, status, cpu, memory, disk }, + }, + isRowActive, + signals, +}: TableCellComponentProps) => { + const linkRef = useRef(null) + + 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 + } +} + +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, + }, + comparator: numberComparatorBySortOrder, + CellComponent: VMListCellComponent + } +] \ No newline at end of file diff --git a/src/components/observability/VMOverview.tsx b/src/components/observability/VMOverview.tsx new file mode 100644 index 0000000000..3d85d1894b --- /dev/null +++ b/src/components/observability/VMOverview.tsx @@ -0,0 +1,60 @@ +import { + BreadCrumb, + BreadcrumbText, + GenericSectionErrorState, + PageHeader, + useBreadcrumb, +} from '@devtron-labs/devtron-fe-common-lib' + +import ObservabilityIconComponent from './ObservabilityIcon' +import { GlanceMetricsKeys } from './types' +import { MetricsInfoLoadingCard, useGetGlanceConfig } from './utils' + +import './styles.scss' +import { MetricsInfoCard } from './MetricsInfoCard' + +export const VMOverview = () => { + const { isFetching, data, isError, refetch } = useGetGlanceConfig() + + const renderBody = () => { + if (isFetching) { + return ( +
+ {Object.keys(GlanceMetricsKeys).map((key) => ( + + ))} +
+ ) + } + + if (isError) { + return ( + + ) + } + // alert(JSON.stringify(data)) + return ( +
+ +
+ ) + } + + return ( +
+
+
+

At a Glance

+
+
+ {renderBody()} +
+ ) +} + + +export default VMOverview; \ No newline at end of file diff --git a/src/components/observability/service.ts b/src/components/observability/service.ts index 716ce215cf..b5a8de4d1c 100644 --- a/src/components/observability/service.ts +++ b/src/components/observability/service.ts @@ -6,3 +6,72 @@ export const getObservabilityData: () => Promise = () => totalCustomers: 40, healthStatus: 50, }) + + 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 + }, + ]) \ No newline at end of file diff --git a/src/components/observability/styles.scss b/src/components/observability/styles.scss index 82c270eb33..a569c3f908 100644 --- a/src/components/observability/styles.scss +++ b/src/components/observability/styles.scss @@ -13,4 +13,23 @@ .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); } \ No newline at end of file diff --git a/src/components/observability/types.ts b/src/components/observability/types.ts index 4c850c835a..86a35d87d8 100644 --- a/src/components/observability/types.ts +++ b/src/components/observability/types.ts @@ -1,6 +1,76 @@ +import { FiltersTypeEnum, TableProps } from ".yalc/@devtron-labs/devtron-fe-common-lib/dist" + export enum GlanceMetricsKeys { REACHABLE_CUSTOMERS = 'customers', TOTAL_PROJECTS = 'Projects', TOTAL_VMs = 'vms', HEALTH_STATUS = 'healthStatus', } + + +export interface ObservabilityProject { + id: string, + name: string, + description: string, + status: string, + totalVms: number, + activeVms: number, + healthStatus: string +} + +export type ProjectTableProps = TableProps< + ObservabilityProject, + FiltersTypeEnum.STATE, + {} +> + +export enum ProjectListFields { + PROJECT_ID = 'id', + PROJECT_NAME = 'name', + PROJECT_DESCRIPTION = 'description', + PROJECT_STATUS = 'status', + TOTAL_VMS = 'totalVms', + ACTIVE_VMS = 'activeVms', + HEALTH_STATUS = 'healthStatus', +} + +export interface ObservabilityVM { + id: string, + name: string, + ipAddress: string, + status: string, + cpu: number, + memory: number, + disk: number +} + +export type VMTableProps = TableProps< + ObservabilityVM, + FiltersTypeEnum.STATE, + {} +> + +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 +} \ No newline at end of file diff --git a/src/config/routes.ts b/src/config/routes.ts index 0b5a8f54f9..715a3808e7 100644 --- a/src/config/routes.ts +++ b/src/config/routes.ts @@ -106,6 +106,8 @@ export const URLS = { HELM_APP_LIST: `${COMMON_URLS.APPLICATION_MANAGEMENT_APP}/list/h`, ARGO_APP_LIST: `${COMMON_URLS.APPLICATION_MANAGEMENT_APP}/list/a`, FLUX_APP_LIST: `${COMMON_URLS.APPLICATION_MANAGEMENT_APP}/list/f`, + PROJECT_OVERVIEW: `${COMMON_URLS.OBSERVABILITY_OVERVIEW}/project-overview`, + PROJECT_LIST: `${COMMON_URLS.OBSERVABILITY_OVERVIEW}/project-list`, BUILD: '/build', WEBHOOK_MODAL: 'webhook', MONITORING_DASHBOARD: 'monitoring-dashboard', From f811b701a06cb0cf8b981949e74502d873bcdbb3 Mon Sep 17 00:00:00 2001 From: Mukesh Date: Fri, 24 Oct 2025 10:07:22 +0530 Subject: [PATCH 04/14] segement control changes only for projects --- src/components/observability/Project.tsx | 17 ++--------------- src/components/observability/constants.ts | 14 +++++++++++++- src/components/observability/utils.tsx | 5 +++++ 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/components/observability/Project.tsx b/src/components/observability/Project.tsx index 8c91aa5f20..fe3198d913 100644 --- a/src/components/observability/Project.tsx +++ b/src/components/observability/Project.tsx @@ -6,6 +6,8 @@ import ObservabilityIconComponent from "./ObservabilityIcon" import ProjectOverview from "./ProjectOverview" import './styles.scss' import { TabDetailsSearchParams, TabDetailsSegment } from "./types" +import { parseChartDetailsSearchParams } from "./utils" +import { TAB_DETAILS_SEGMENTS } from "./constants" let interval const Project = () => { const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') @@ -71,21 +73,6 @@ const Project = () => { return } - const TAB_DETAILS_SEGMENTS: SegmentedControlProps['segments'] = [ - { - label: 'Overview', - value: TabDetailsSegment.OVERVIEW, - }, - { - label: 'Projects', - value: TabDetailsSegment.PROJECTS, - } -] - -const parseChartDetailsSearchParams = (searchParams: URLSearchParams): TabDetailsSearchParams => ({ - tab: (searchParams.get('tab') as TabDetailsSegment) || TabDetailsSegment.OVERVIEW, -}) - const { tab, updateSearchParams } = useUrlFilters({ parseSearchParams: parseChartDetailsSearchParams, }) diff --git a/src/components/observability/constants.ts b/src/components/observability/constants.ts index a9681f0f80..f0a1892809 100644 --- a/src/components/observability/constants.ts +++ b/src/components/observability/constants.ts @@ -1,4 +1,5 @@ -import { IconName } from '@devtron-labs/devtron-fe-common-lib' +import { IconName, SegmentedControlProps } from '@devtron-labs/devtron-fe-common-lib' +import { TabDetailsSegment } from './types' export enum GlanceMetricKeys { PROJECTS = 'projects', @@ -30,3 +31,14 @@ export const GLANCE_METRICS_CARDS_CONFIG: Record< metricTitle: 'Environments', }, } + +export const TAB_DETAILS_SEGMENTS: SegmentedControlProps['segments'] = [ + { + label: 'Overview', + value: TabDetailsSegment.OVERVIEW, + }, + { + label: 'Projects', + value: TabDetailsSegment.PROJECTS, + } +] diff --git a/src/components/observability/utils.tsx b/src/components/observability/utils.tsx index af14c3392e..fe934d565d 100644 --- a/src/components/observability/utils.tsx +++ b/src/components/observability/utils.tsx @@ -1,6 +1,7 @@ import { useQuery } from '@devtron-labs/devtron-fe-common-lib' import { getObservabilityData } from './service' +import { TabDetailsSearchParams, TabDetailsSegment } from './types' // Will be removing while importing to dashboard export const MetricsInfoLoadingCard = () => ( @@ -20,3 +21,7 @@ export const useGetGlanceConfig = () => queryKey: ['observabilityGlanceConfig'], queryFn: () => getObservabilityData(), }) + +export const parseChartDetailsSearchParams = (searchParams: URLSearchParams): TabDetailsSearchParams => ({ + tab: (searchParams.get('tab') as TabDetailsSegment) || TabDetailsSegment.OVERVIEW, +}) \ No newline at end of file From 5dd1c021f62475a955aff1744f5d448b2755ffec Mon Sep 17 00:00:00 2001 From: shivani170 Date: Mon, 27 Oct 2025 17:29:41 +0530 Subject: [PATCH 05/14] chore: observability navigation & type fixes --- src/components/Navigation/constants.ts | 2 +- .../common/navigation/NavigationRoutes.tsx | 5 +- src/components/observability/CustomerList.tsx | 51 +++++ .../CustomerListCellComponent.tsx | 69 ++++++ src/components/observability/Customers.tsx | 116 ++++++++++ .../observability/ObservabilityRouter.tsx | 32 +-- src/components/observability/Project.tsx | 188 ---------------- src/components/observability/ProjectList.tsx | 182 --------------- .../ProjectObservability/Project.tsx | 171 ++++++++++++++ .../ProjectObservability/ProjectList.tsx | 53 +++++ .../ProjectListCellComponent.tsx | 75 +++++++ .../ProjectObservability/ProjectOverview.tsx | 59 +++++ .../ProjectObservability/index.ts | 1 + .../observability/ProjectOverview.tsx | 60 ----- src/components/observability/VMList.tsx | 185 --------------- .../{ => VMObservability}/VM.tsx | 64 +++--- .../observability/VMObservability/VMList.tsx | 51 +++++ .../VMObservability/VMListCellComponent.tsx | 75 +++++++ .../VMObservability/VMOverview.tsx | 61 +++++ .../observability/VMObservability/index.ts | 1 + src/components/observability/VMOverview.tsx | 60 ----- src/components/observability/constants.ts | 211 +++++++++++++++++- src/components/observability/service.ts | 158 +++++++------ src/components/observability/styles.scss | 13 +- src/components/observability/types.ts | 66 +++--- src/components/observability/utils.tsx | 2 +- src/components/project/ProjectList.tsx | 1 - vite.config.mts | 2 +- 28 files changed, 1183 insertions(+), 831 deletions(-) create mode 100644 src/components/observability/CustomerList.tsx create mode 100644 src/components/observability/CustomerListCellComponent.tsx create mode 100644 src/components/observability/Customers.tsx delete mode 100644 src/components/observability/Project.tsx delete mode 100644 src/components/observability/ProjectList.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 delete mode 100644 src/components/observability/ProjectOverview.tsx delete mode 100644 src/components/observability/VMList.tsx rename src/components/observability/{ => VMObservability}/VM.tsx (81%) 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 delete mode 100644 src/components/observability/VMOverview.tsx diff --git a/src/components/Navigation/constants.ts b/src/components/Navigation/constants.ts index 19a7d13603..581af738b8 100644 --- a/src/components/Navigation/constants.ts +++ b/src/components/Navigation/constants.ts @@ -299,7 +299,7 @@ export const NAVIGATION_LIST: NavigationGroupType[] = [ dataTestId: 'observability-vms', id: 'observability-vms', icon: 'ic-cluster', - href: COMMON_URLS.OBSERVABILITY_LIST, + href: COMMON_URLS.OBSERVABILITY_CUSTOMER_LIST, }, ], }, diff --git a/src/components/common/navigation/NavigationRoutes.tsx b/src/components/common/navigation/NavigationRoutes.tsx index cbf810bbbd..4720926276 100644 --- a/src/components/common/navigation/NavigationRoutes.tsx +++ b/src/components/common/navigation/NavigationRoutes.tsx @@ -561,10 +561,7 @@ const NavigationRoutes = ({ reloadVersionConfig }: Readonly
- + { + // 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/CustomerListCellComponent.tsx b/src/components/observability/CustomerListCellComponent.tsx new file mode 100644 index 0000000000..eb4eaa8988 --- /dev/null +++ b/src/components/observability/CustomerListCellComponent.tsx @@ -0,0 +1,69 @@ +import { FunctionComponent, useEffect, useRef } from 'react' +import { Link } 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) + + 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/Customers.tsx b/src/components/observability/Customers.tsx new file mode 100644 index 0000000000..7a82fefc21 --- /dev/null +++ b/src/components/observability/Customers.tsx @@ -0,0 +1,116 @@ +import { useEffect, useState } from 'react' + +import { + BreadCrumb, + BreadcrumbText, + ComponentSizeType, + handleUTCTime, + PageHeader, + SearchBar, + useBreadcrumb, +} from '@devtron-labs/devtron-fe-common-lib' + +import { CustomerList } from './CustomerList' +import ObservabilityIconComponent from './ObservabilityIcon' + +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/ObservabilityRouter.tsx b/src/components/observability/ObservabilityRouter.tsx index d0bbd2d25b..928f83c7d7 100644 --- a/src/components/observability/ObservabilityRouter.tsx +++ b/src/components/observability/ObservabilityRouter.tsx @@ -1,23 +1,25 @@ -import { Redirect, Route, Switch } from 'react-router-dom' +import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom' import { URLS } from '@devtron-labs/devtron-fe-common-lib' -import Project from './Project' -import VMList from './VMList' -import { Overview } from './Overview' +import VM from './VMObservability/VM' +import Customers from './Customers' -const ObservabilityRouter: React.FC = () => ( - - - - +const ObservabilityRouter: React.FC = () => { + const { path } = useRouteMatch() + return ( + + + + - - - + + + - - -) + + + ) +} export default ObservabilityRouter diff --git a/src/components/observability/Project.tsx b/src/components/observability/Project.tsx deleted file mode 100644 index fe3198d913..0000000000 --- a/src/components/observability/Project.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { useState,useEffect } from "react" -import ProjectList from "./ProjectList" -import { BreadCrumb, BreadcrumbText, ComponentSizeType, handleUTCTime, PageHeader, SearchBar, SegmentedControl, SegmentedControlProps, TabGroup, TabProps, useBreadcrumb, useUrlFilters } from ".yalc/@devtron-labs/devtron-fe-common-lib/dist" -import { URLS } from "@Config/routes" -import ObservabilityIconComponent from "./ObservabilityIcon" -import ProjectOverview from "./ProjectOverview" -import './styles.scss' -import { TabDetailsSearchParams, TabDetailsSegment } from "./types" -import { parseChartDetailsSearchParams } from "./utils" -import { TAB_DETAILS_SEGMENTS } from "./constants" -let interval -const Project = () => { - const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') - const [isDataSyncing, setDataSyncing] = useState(false) - const [syncListData, setSyncListData] = useState() - const [fetchingExternalApps, setFetchingExternalApps] = useState(false) - 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]) - - const updateDataSyncing = (loading: boolean): void => { - setDataSyncing(loading) - } - const tabs: TabProps = [ - { - id: 'project_overview', - label: 'Overview', - tabType: 'button', - active: selectedTabIndex == 0, - props: { - onClick: () => { - setSelectedTabIndex(0) - }, - }, - }, - { - id: 'project_list', - label: 'Projects', - tabType: 'button', - active: selectedTabIndex == 1, - props: { - onClick: () => { - setSelectedTabIndex(1) - }, - }, - } - ] - - const syncNow = (): void => { - setSyncListData(!syncListData) - } - - const renderProjectOverview = () => { - return - } - - const renderProjectList = () => { - return - } - -const { tab, updateSearchParams } = useUrlFilters({ - parseSearchParams: parseChartDetailsSearchParams, - }) - - const handleSegmentChange: SegmentedControlProps['onChange'] = (selectedSegment) => { - const updatedTab = selectedSegment.value as TabDetailsSegment - - if (updatedTab === TabDetailsSegment.PROJECTS) { - renderProjectList() - } - - updateSearchParams({ tab: updatedTab }) - } - - const renderProjectTabs = () => { - const rightComponent = ( -
- {lastDataSyncTimeString && - <> - {lastDataSyncTimeString} - {!isDataSyncing && ( - <> -   - - - )} - - } - {fetchingExternalApps && renderDataSyncingText()} -
- ) - - const renderSegments = () => { - switch (tab) { - case TabDetailsSegment.OVERVIEW: - return ( - renderProjectOverview() - ) - case TabDetailsSegment.PROJECTS: - return renderProjectList() - default: - return null - } - } - - return ( -
-
-
- {/* */} - -
-
-
-
- {/* {selectedTabIndex == 0 && renderProjectOverview()} - {selectedTabIndex == 1 && renderProjectList()} */} - {renderSegments()} -
-
-
- ) - } - - const { breadcrumbs } = useBreadcrumb({ - alias: { - observability: { - component: , - linked: true, - }, - customer: { - component: , - linked: false, - }, - }, - }) - const renderBreadcrumbs = () => - const searchKey = "" - const handleSearch = () => {} - return ( -
- -
- -
- {renderProjectTabs()} -
- ) -} - -export default Project; \ No newline at end of file diff --git a/src/components/observability/ProjectList.tsx b/src/components/observability/ProjectList.tsx deleted file mode 100644 index 97410c1171..0000000000 --- a/src/components/observability/ProjectList.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { FiltersTypeEnum, numberComparatorBySortOrder, PaginationEnum, stringComparatorBySortOrder, Table, TableCellComponentProps, TableSignalEnum, Tooltip, useAsync } from ".yalc/@devtron-labs/devtron-fe-common-lib/dist"; -import { FunctionComponent, useEffect, useMemo, useRef } from "react"; -import { ObservabilityProject, ProjectListFields, ProjectTableProps } from "./types"; -import { Link } from "react-router-dom"; -import { getProjectList } from "./service"; - -const ProjectList = () => { - - // ASYNC CALLS - const [isFetching, projectData, isError, refetch] = useAsync( - () => getProjectList(), - [], - ) - - // CONFIGS - const rows = useMemo( - () => - (projectData || []).map((data) => ({ - id: `observe_project_${data.id.toString()}`, - data, - })), - [projectData], - ) - - const filter: ProjectTableProps['filter'] = (rowData, filterData) => - rowData.data.name.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; - -export const ProjectListCellComponent: FunctionComponent< - TableCellComponentProps -> = ({ - field, - row: { - data: { id, name, description, status, totalVms, activeVms, healthStatus }, - }, - isRowActive, - signals, -}: TableCellComponentProps) => { - const linkRef = useRef(null) - - 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.PROJECT_STATUS: - return {status} - case ProjectListFields.TOTAL_VMS: - return {totalVms} - case ProjectListFields.ACTIVE_VMS: - return ( -
- - {activeVms} - -
- ) - case ProjectListFields.HEALTH_STATUS: - return ( -
- - {healthStatus} - -
- ) - default: - return null - } -} - -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 - } -] \ No newline at end of file diff --git a/src/components/observability/ProjectObservability/Project.tsx b/src/components/observability/ProjectObservability/Project.tsx new file mode 100644 index 0000000000..b3b3000a43 --- /dev/null +++ b/src/components/observability/ProjectObservability/Project.tsx @@ -0,0 +1,171 @@ +import { useEffect, useState } from 'react' +import { useRouteMatch } from 'react-router-dom' + +import { + BreadCrumb, + BreadcrumbText, + ComponentSizeType, + handleUTCTime, + PageHeader, + SearchBar, + TabGroup, + TabProps, + URLS, + useBreadcrumb, +} from '@devtron-labs/devtron-fe-common-lib' + +import ObservabilityIconComponent from '../ObservabilityIcon' +import ProjectList from './ProjectList' +import { ProjectOverview } from './ProjectOverview' + +let interval +const Project = () => { + const match = useRouteMatch() + + 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]) + + console.log(match) + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const updateDataSyncing = (loading: boolean): void => { + setDataSyncing(loading) + } + const tabs: TabProps[] = [ + { + id: 'project_overview', + label: 'Overview', + tabType: 'link', + active: selectedTabIndex === 0, + props: { + to: `${match.url}/${URLS.OBSERVABILITY_OVERVIEW}`, + // onClick: () => { + // setSelectedTabIndex(0) + // }, + }, + }, + { + id: 'project_list', + label: 'Projects', + tabType: 'link', + active: selectedTabIndex === 1, + props: { + to: `${match.url}/projects`, + // onClick: () => { + // setSelectedTabIndex(1) + // }, + }, + }, + ] + + const syncNow = (): void => { + setSyncListData(!syncListData) + } + + const rightComponent = ( +
+ {lastDataSyncTimeString && ( + <> + {lastDataSyncTimeString} + {!isDataSyncing && ( + <> +   + + + )} + + )} + {fetchingExternalApps && renderDataSyncingText()} +
+ ) + + const renderProjectTabs = () => { + const renderSegments = () => { + if (selectedTabIndex === 0) { + return + } + + return + } + + return ( +
+
+
+ +
+
+
+
{renderSegments()}
+
+
+ ) + } + + const { breadcrumbs } = useBreadcrumb({ + alias: { + observability: { + component: , + linked: true, + }, + customer: { + 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..98d7d90087 --- /dev/null +++ b/src/components/observability/ProjectObservability/ProjectListCellComponent.tsx @@ -0,0 +1,75 @@ +import { FunctionComponent, useEffect, useRef } from 'react' +import { Link } 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) + + 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..cbbf347952 --- /dev/null +++ b/src/components/observability/ProjectObservability/ProjectOverview.tsx @@ -0,0 +1,59 @@ +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 ( +
+ +
+ ) + } + + 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/ProjectOverview.tsx b/src/components/observability/ProjectOverview.tsx deleted file mode 100644 index 861a2cf6e2..0000000000 --- a/src/components/observability/ProjectOverview.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { - BreadCrumb, - BreadcrumbText, - GenericSectionErrorState, - PageHeader, - useBreadcrumb, -} from '@devtron-labs/devtron-fe-common-lib' - -import ObservabilityIconComponent from './ObservabilityIcon' -import { GlanceMetricsKeys } from './types' -import { MetricsInfoLoadingCard, useGetGlanceConfig } from './utils' - -import './styles.scss' -import { MetricsInfoCard } from './MetricsInfoCard' - -export const ProjectOverview = () => { - const { isFetching, data, isError, refetch } = useGetGlanceConfig() - - const renderBody = () => { - if (isFetching) { - return ( -
- {Object.keys(GlanceMetricsKeys).map((key) => ( - - ))} -
- ) - } - - if (isError) { - return ( - - ) - } - // alert(JSON.stringify(data)) - return ( -
- -
- ) - } - - return ( -
-
-
-

At a Glance

-
-
- {renderBody()} -
- ) -} - - -export default ProjectOverview; \ No newline at end of file diff --git a/src/components/observability/VMList.tsx b/src/components/observability/VMList.tsx deleted file mode 100644 index aba1d9ffbb..0000000000 --- a/src/components/observability/VMList.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import { FiltersTypeEnum, numberComparatorBySortOrder, PaginationEnum, stringComparatorBySortOrder, Table, TableCellComponentProps, TableSignalEnum, Tooltip, useAsync } from ".yalc/@devtron-labs/devtron-fe-common-lib/dist"; -import { FunctionComponent, useEffect, useMemo, useRef } from "react"; -import { ObservabilityVM, VMListFields, VMTableProps } from "./types"; -import { Link } from "react-router-dom"; -import { getVMList } from "./service"; - -const VMList = () => { - - // ASYNC CALLS - const [isFetching, vmData, isError, refetch] = 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; - -export const VMListCellComponent: FunctionComponent< - TableCellComponentProps -> = ({ - field, - row: { - data: { id, name, ipAddress, status, cpu, memory, disk }, - }, - isRowActive, - signals, -}: TableCellComponentProps) => { - const linkRef = useRef(null) - - 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 - } -} - -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, - }, - comparator: numberComparatorBySortOrder, - CellComponent: VMListCellComponent - } -] \ No newline at end of file diff --git a/src/components/observability/VM.tsx b/src/components/observability/VMObservability/VM.tsx similarity index 81% rename from src/components/observability/VM.tsx rename to src/components/observability/VMObservability/VM.tsx index d14d544554..ac78a2fa2d 100644 --- a/src/components/observability/VM.tsx +++ b/src/components/observability/VMObservability/VM.tsx @@ -1,14 +1,29 @@ -import { useState,useEffect } from "react" -import { BreadCrumb, BreadcrumbText, ComponentSizeType, handleUTCTime, PageHeader, SearchBar, TabGroup, TabProps, useBreadcrumb } from ".yalc/@devtron-labs/devtron-fe-common-lib/dist" -import ObservabilityIconComponent from "./ObservabilityIcon" -import './styles.scss' -import VMOverview from "./VMOverview" -import VMList from "./VMList" +import { useEffect, useState } from 'react' + +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 [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 [selectedTabIndex, setSelectedTabIndex] = useState(0) const renderDataSyncingText = () => Syncing @@ -28,16 +43,17 @@ const VM = () => { } } }, [isDataSyncing]) - + + // eslint-disable-next-line @typescript-eslint/no-unused-vars const updateDataSyncing = (loading: boolean): void => { setDataSyncing(loading) } - const tabs: TabProps = [ + const tabs: TabProps[] = [ { id: 'vm_overview', label: 'Overview', tabType: 'button', - active: selectedTabIndex == 0, + active: selectedTabIndex === 0, props: { onClick: () => { setSelectedTabIndex(0) @@ -48,31 +64,27 @@ const VM = () => { id: 'vm_list', label: 'VMs', tabType: 'button', - active: selectedTabIndex == 1, + active: selectedTabIndex === 1, props: { onClick: () => { setSelectedTabIndex(1) }, }, - } + }, ] - + const syncNow = (): void => { setSyncListData(!syncListData) } - const renderVMOverview = () => { - return - } + const renderVMOverview = () => - const renderVMList = () => { - return - } + const renderVMList = () => const renderVMTabs = () => { const rightComponent = (
- {lastDataSyncTimeString && + {lastDataSyncTimeString && ( <> {lastDataSyncTimeString} {!isDataSyncing && ( @@ -89,22 +101,22 @@ const VM = () => { )} - } + )} {fetchingExternalApps && renderDataSyncingText()}
) - + return (
- +
- {selectedTabIndex == 0 && renderVMOverview()} - {selectedTabIndex == 1 && renderVMList()} + {selectedTabIndex === 0 && renderVMOverview()} + {selectedTabIndex === 1 && renderVMList()}
@@ -124,7 +136,7 @@ const VM = () => { }, }) const renderBreadcrumbs = () => - const searchKey = "" + const searchKey = '' const handleSearch = () => {} return (
@@ -147,4 +159,4 @@ const VM = () => { ) } -export default VM; \ No newline at end of file +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..1fc53cef30 --- /dev/null +++ b/src/components/observability/VMObservability/VMListCellComponent.tsx @@ -0,0 +1,75 @@ +import { FunctionComponent, useEffect, useRef } from 'react' +import { Link } 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) + + 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..2727eaccf1 --- /dev/null +++ b/src/components/observability/VMObservability/VMOverview.tsx @@ -0,0 +1,61 @@ +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, isError, refetch } = useGetGlanceConfig() + + const renderBody = () => { + if (isFetching) { + return ( +
+ {Object.keys(GlanceMetricsKeys).map((key) => ( + + ))} +
+ ) + } + + if (isError) { + return ( + + ) + } + // alert(JSON.stringify(data)) + return ( +
+ +
+ ) + } + + return ( +
+
+
+

At a Glance

+
+
+ {renderBody()} +
+ ) +} + +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/VMOverview.tsx b/src/components/observability/VMOverview.tsx deleted file mode 100644 index 3d85d1894b..0000000000 --- a/src/components/observability/VMOverview.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { - BreadCrumb, - BreadcrumbText, - GenericSectionErrorState, - PageHeader, - useBreadcrumb, -} from '@devtron-labs/devtron-fe-common-lib' - -import ObservabilityIconComponent from './ObservabilityIcon' -import { GlanceMetricsKeys } from './types' -import { MetricsInfoLoadingCard, useGetGlanceConfig } from './utils' - -import './styles.scss' -import { MetricsInfoCard } from './MetricsInfoCard' - -export const VMOverview = () => { - const { isFetching, data, isError, refetch } = useGetGlanceConfig() - - const renderBody = () => { - if (isFetching) { - return ( -
- {Object.keys(GlanceMetricsKeys).map((key) => ( - - ))} -
- ) - } - - if (isError) { - return ( - - ) - } - // alert(JSON.stringify(data)) - return ( -
- -
- ) - } - - return ( -
-
-
-

At a Glance

-
-
- {renderBody()} -
- ) -} - - -export default VMOverview; \ No newline at end of file diff --git a/src/components/observability/constants.ts b/src/components/observability/constants.ts index f0a1892809..73b3a461eb 100644 --- a/src/components/observability/constants.ts +++ b/src/components/observability/constants.ts @@ -1,5 +1,14 @@ -import { IconName, SegmentedControlProps } from '@devtron-labs/devtron-fe-common-lib' -import { TabDetailsSegment } from './types' +import { + IconName, + numberComparatorBySortOrder, + SegmentedControlProps, + stringComparatorBySortOrder, +} from '@devtron-labs/devtron-fe-common-lib' + +import { ProjectListCellComponent } from './ProjectObservability/ProjectListCellComponent' +import { VMListCellComponent } from './VMObservability/VMListCellComponent' +import { CustomerListCellComponent } from './CustomerListCellComponent' +import { CustomerTableProps, ProjectTableProps, TabDetailsSegment, VMTableProps } from './types' export enum GlanceMetricKeys { PROJECTS = 'projects', @@ -40,5 +49,201 @@ export const TAB_DETAILS_SEGMENTS: SegmentedControlProps['segments'] = [ { 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/service.ts b/src/components/observability/service.ts index b5a8de4d1c..1f828eff58 100644 --- a/src/components/observability/service.ts +++ b/src/components/observability/service.ts @@ -1,3 +1,5 @@ +import { CustomerObservabilityDTO } from './types' + export const getObservabilityData: () => Promise = () => Promise.resolve({ totalClusters: 10, @@ -7,71 +9,95 @@ export const getObservabilityData: () => Promise = () => healthStatus: 50, }) - 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 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 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 - }, - ]) \ No newline at end of file +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, + }, + ]) diff --git a/src/components/observability/styles.scss b/src/components/observability/styles.scss index a569c3f908..a87e223ec5 100644 --- a/src/components/observability/styles.scss +++ b/src/components/observability/styles.scss @@ -10,17 +10,18 @@ grid-template-columns: 150px 1fr 1fr; } - .at-a-glance { - grid-template-columns: 1fr 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 { + > :first-child> :first-child, + > :first-child> :first-child> :first-child, + > :first-child> :first-child> :first-child> :first-child { overflow: visible !important; } } diff --git a/src/components/observability/types.ts b/src/components/observability/types.ts index 86a35d87d8..f2cadb3a62 100644 --- a/src/components/observability/types.ts +++ b/src/components/observability/types.ts @@ -1,4 +1,4 @@ -import { FiltersTypeEnum, TableProps } from ".yalc/@devtron-labs/devtron-fe-common-lib/dist" +import { FiltersTypeEnum, TableProps } from '.yalc/@devtron-labs/devtron-fe-common-lib/dist' export enum GlanceMetricsKeys { REACHABLE_CUSTOMERS = 'customers', @@ -7,49 +7,51 @@ export enum GlanceMetricsKeys { HEALTH_STATUS = 'healthStatus', } +export type ObservabilityStatus = 'ACTIVE' | 'INACTIVE' -export interface ObservabilityProject { - id: string, - name: string, - description: string, - status: string, - totalVms: number, - activeVms: number, +export interface BaseObservability { + id: number + name: string + status: ObservabilityStatus +} + +export interface CustomerObservabilityDTO extends BaseObservability { + project: number + totalVms: number + activeVms: number healthStatus: string } -export type ProjectTableProps = TableProps< - ObservabilityProject, - FiltersTypeEnum.STATE, - {} -> +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', - PROJECT_STATUS = 'status', + STATUS = 'status', TOTAL_VMS = 'totalVms', ACTIVE_VMS = 'activeVms', HEALTH_STATUS = 'healthStatus', + PROJECTS = 'projects', } -export interface ObservabilityVM { - id: string, - name: string, - ipAddress: string, - status: string, - cpu: number, - memory: number, - disk: number -} - -export type VMTableProps = TableProps< - ObservabilityVM, - FiltersTypeEnum.STATE, - {} -> - export enum VMListFields { VM_ID = 'id', VM_NAME = 'name', @@ -68,9 +70,9 @@ export enum ObservabilityFilters { export enum TabDetailsSegment { 'OVERVIEW' = 'Overview', - 'PROJECTS' = 'Projects' + 'PROJECTS' = 'Projects', } export interface TabDetailsSearchParams { tab: TabDetailsSegment -} \ No newline at end of file +} diff --git a/src/components/observability/utils.tsx b/src/components/observability/utils.tsx index fe934d565d..382130f680 100644 --- a/src/components/observability/utils.tsx +++ b/src/components/observability/utils.tsx @@ -24,4 +24,4 @@ export const useGetGlanceConfig = () => export const parseChartDetailsSearchParams = (searchParams: URLSearchParams): TabDetailsSearchParams => ({ tab: (searchParams.get('tab') as TabDetailsSegment) || TabDetailsSegment.OVERVIEW, -}) \ No newline at end of file +}) diff --git a/src/components/project/ProjectList.tsx b/src/components/project/ProjectList.tsx index fd2b7dd8bc..799a63bbfc 100644 --- a/src/components/project/ProjectList.tsx +++ b/src/components/project/ProjectList.tsx @@ -20,7 +20,6 @@ import { Progressing, ErrorScreenManager, ErrorScreenNotAuthorized, - FeatureTitleWithInfo, ToastVariantType, ToastManager, PageHeader, diff --git a/vite.config.mts b/vite.config.mts index dcb785595b..e7d3de936c 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -31,7 +31,7 @@ import tsconfigPaths from 'vite-tsconfig-paths' import { compression, defineAlgorithm } from 'vite-plugin-compression2' const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";` -const TARGET_URL = 'https://preview.devtron.ai/' +const TARGET_URL = 'https://devtron-ent-7.devtron.info/' function reactVirtualized(): PluginOption { return { From edafff7f0a52c415545c941bb87b3b8d1a50f720 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Mon, 27 Oct 2025 18:04:13 +0530 Subject: [PATCH 06/14] chore: navigation wip --- .../observability/CustomerListCellComponent.tsx | 11 +++++++++-- src/components/observability/ObservabilityRouter.tsx | 11 ++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/observability/CustomerListCellComponent.tsx b/src/components/observability/CustomerListCellComponent.tsx index eb4eaa8988..3c73fb441c 100644 --- a/src/components/observability/CustomerListCellComponent.tsx +++ b/src/components/observability/CustomerListCellComponent.tsx @@ -1,8 +1,9 @@ import { FunctionComponent, useEffect, useRef } from 'react' -import { Link } from 'react-router-dom' +import { Link, useRouteMatch } from 'react-router-dom' import { FiltersTypeEnum, + getUrlWithSearchParams, TableCellComponentProps, TableSignalEnum, Tooltip, @@ -22,6 +23,8 @@ export const CustomerListCellComponent: FunctionComponent< }: TableCellComponentProps) => { const linkRef = useRef(null) + const match = useRouteMatch() + useEffect(() => { const handleEnter = ({ detail: { activeRowData } }) => { if (activeRowData.data.id === id) { @@ -41,7 +44,11 @@ export const CustomerListCellComponent: FunctionComponent< switch (field) { case ProjectListFields.PROJECT_NAME: return ( - + {name} diff --git a/src/components/observability/ObservabilityRouter.tsx b/src/components/observability/ObservabilityRouter.tsx index 928f83c7d7..4c8d660241 100644 --- a/src/components/observability/ObservabilityRouter.tsx +++ b/src/components/observability/ObservabilityRouter.tsx @@ -2,22 +2,27 @@ import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom' import { URLS } from '@devtron-labs/devtron-fe-common-lib' -import VM from './VMObservability/VM' +import Project from './ProjectObservability/Project' import Customers from './Customers' +import { Overview } from './Overview' const ObservabilityRouter: React.FC = () => { const { path } = useRouteMatch() return ( - + - + + + + + ) } From 97bf34aaaf79f1bc68240f96b3f1de21789e832e Mon Sep 17 00:00:00 2001 From: shivani170 Date: Tue, 28 Oct 2025 16:05:05 +0530 Subject: [PATCH 07/14] fix: navigation redirection fix for customer level --- .../{ => Customer}/CustomerList.tsx | 8 +-- .../CustomerListCellComponent.tsx | 12 ++--- .../{ => Customer}/Customers.tsx | 7 ++- .../observability/Customer/index.ts | 1 + .../observability/ObservabilityRouter.tsx | 9 +++- src/components/observability/Overview.tsx | 4 ++ .../ProjectObservability/Project.tsx | 54 +++++++------------ src/components/observability/constants.ts | 2 +- 8 files changed, 47 insertions(+), 50 deletions(-) rename src/components/observability/{ => Customer}/CustomerList.tsx (88%) rename src/components/observability/{ => Customer}/CustomerListCellComponent.tsx (88%) rename src/components/observability/{ => Customer}/Customers.tsx (95%) create mode 100644 src/components/observability/Customer/index.ts diff --git a/src/components/observability/CustomerList.tsx b/src/components/observability/Customer/CustomerList.tsx similarity index 88% rename from src/components/observability/CustomerList.tsx rename to src/components/observability/Customer/CustomerList.tsx index 0a016aeb7e..0df426c294 100644 --- a/src/components/observability/CustomerList.tsx +++ b/src/components/observability/Customer/CustomerList.tsx @@ -1,10 +1,10 @@ import { useMemo } from 'react' -import { FiltersTypeEnum, PaginationEnum, Table, useAsync } from '@devtron-labs/devtron-fe-common-lib/dist' +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' +import { CUSTOMER_TABLE_COLUMN } from '../constants' +import { getCustomerListData } from '../service' +import { CustomerObservabilityDTO, CustomerTableProps } from '../types' export const CustomerList = () => { // ASYNC CALLS diff --git a/src/components/observability/CustomerListCellComponent.tsx b/src/components/observability/Customer/CustomerListCellComponent.tsx similarity index 88% rename from src/components/observability/CustomerListCellComponent.tsx rename to src/components/observability/Customer/CustomerListCellComponent.tsx index 3c73fb441c..62a2d8ff3d 100644 --- a/src/components/observability/CustomerListCellComponent.tsx +++ b/src/components/observability/Customer/CustomerListCellComponent.tsx @@ -3,13 +3,12 @@ import { Link, useRouteMatch } from 'react-router-dom' import { FiltersTypeEnum, - getUrlWithSearchParams, TableCellComponentProps, TableSignalEnum, Tooltip, } from '@devtron-labs/devtron-fe-common-lib/dist' -import { CustomerObservabilityDTO, ProjectListFields } from './types' +import { CustomerObservabilityDTO, ProjectListFields } from '../types' export const CustomerListCellComponent: FunctionComponent< TableCellComponentProps @@ -22,9 +21,10 @@ export const CustomerListCellComponent: FunctionComponent< signals, }: TableCellComponentProps) => { const linkRef = useRef(null) - const match = useRouteMatch() + console.log(match) + useEffect(() => { const handleEnter = ({ detail: { activeRowData } }) => { if (activeRowData.data.id === id) { @@ -44,11 +44,7 @@ export const CustomerListCellComponent: FunctionComponent< switch (field) { case ProjectListFields.PROJECT_NAME: return ( - + {name} diff --git a/src/components/observability/Customers.tsx b/src/components/observability/Customer/Customers.tsx similarity index 95% rename from src/components/observability/Customers.tsx rename to src/components/observability/Customer/Customers.tsx index 7a82fefc21..430a762c9a 100644 --- a/src/components/observability/Customers.tsx +++ b/src/components/observability/Customer/Customers.tsx @@ -10,8 +10,8 @@ import { useBreadcrumb, } from '@devtron-labs/devtron-fe-common-lib' +import ObservabilityIconComponent from '../ObservabilityIcon' import { CustomerList } from './CustomerList' -import ObservabilityIconComponent from './ObservabilityIcon' let interval const Customers = () => { @@ -84,6 +84,10 @@ const Customers = () => { component: , linked: false, }, + ':customerId': { + component: , + linked: false, + }, }, }) const renderBreadcrumbs = () => @@ -108,6 +112,7 @@ const Customers = () => {
{renderLastSyncComponent()} + ) 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/ObservabilityRouter.tsx b/src/components/observability/ObservabilityRouter.tsx index 4c8d660241..bff07e8a76 100644 --- a/src/components/observability/ObservabilityRouter.tsx +++ b/src/components/observability/ObservabilityRouter.tsx @@ -2,8 +2,9 @@ import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom' import { URLS } from '@devtron-labs/devtron-fe-common-lib' +import Customers from './Customer/Customers' import Project from './ProjectObservability/Project' -import Customers from './Customers' +import { ProjectOverview } from './ProjectObservability/ProjectOverview' import { Overview } from './Overview' const ObservabilityRouter: React.FC = () => { @@ -18,10 +19,14 @@ const ObservabilityRouter: React.FC = () => {
- + + + + + ) diff --git a/src/components/observability/Overview.tsx b/src/components/observability/Overview.tsx index 8203ed936e..aeefc03f34 100644 --- a/src/components/observability/Overview.tsx +++ b/src/components/observability/Overview.tsx @@ -26,6 +26,10 @@ export const Overview = () => { component: , linked: false, }, + ':customerId': { + component: , + linked: false, + }, }, }) const renderBreadcrumbs = () => diff --git a/src/components/observability/ProjectObservability/Project.tsx b/src/components/observability/ProjectObservability/Project.tsx index b3b3000a43..ea3f42a3b8 100644 --- a/src/components/observability/ProjectObservability/Project.tsx +++ b/src/components/observability/ProjectObservability/Project.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { useRouteMatch } from 'react-router-dom' +import { Redirect, Route, useRouteMatch } from 'react-router-dom' import { BreadCrumb, @@ -10,7 +10,6 @@ import { SearchBar, TabGroup, TabProps, - URLS, useBreadcrumb, } from '@devtron-labs/devtron-fe-common-lib' @@ -28,8 +27,7 @@ const Project = () => { // 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) { @@ -48,8 +46,6 @@ const Project = () => { } }, [isDataSyncing]) - console.log(match) - // eslint-disable-next-line @typescript-eslint/no-unused-vars const updateDataSyncing = (loading: boolean): void => { setDataSyncing(loading) @@ -59,24 +55,16 @@ const Project = () => { id: 'project_overview', label: 'Overview', tabType: 'link', - active: selectedTabIndex === 0, props: { - to: `${match.url}/${URLS.OBSERVABILITY_OVERVIEW}`, - // onClick: () => { - // setSelectedTabIndex(0) - // }, + to: `${match.url}/overview`, }, }, { id: 'project_list', label: 'Projects', tabType: 'link', - active: selectedTabIndex === 1, props: { to: `${match.url}/projects`, - // onClick: () => { - // setSelectedTabIndex(1) - // }, }, }, ] @@ -109,28 +97,26 @@ const Project = () => { ) - const renderProjectTabs = () => { - const renderSegments = () => { - if (selectedTabIndex === 0) { - return - } - - return - } - - return ( -
-
-
- -
+ const renderProjectTabs = () => ( +
+
+
+
-
-
{renderSegments()}
+
+
+
+ + + + + + +
- ) - } +
+ ) const { breadcrumbs } = useBreadcrumb({ alias: { diff --git a/src/components/observability/constants.ts b/src/components/observability/constants.ts index 73b3a461eb..611a97d286 100644 --- a/src/components/observability/constants.ts +++ b/src/components/observability/constants.ts @@ -5,9 +5,9 @@ import { stringComparatorBySortOrder, } from '@devtron-labs/devtron-fe-common-lib' +import { CustomerListCellComponent } from './Customer/CustomerListCellComponent' import { ProjectListCellComponent } from './ProjectObservability/ProjectListCellComponent' import { VMListCellComponent } from './VMObservability/VMListCellComponent' -import { CustomerListCellComponent } from './CustomerListCellComponent' import { CustomerTableProps, ProjectTableProps, TabDetailsSegment, VMTableProps } from './types' export enum GlanceMetricKeys { From 9ad7118c2898ccfe60849d62335d6a2c88dc74a3 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Tue, 28 Oct 2025 17:20:07 +0530 Subject: [PATCH 08/14] fix: navigation for VM fixed --- .../Customer/CustomerListCellComponent.tsx | 4 +-- .../observability/Customer/Customers.tsx | 6 +--- .../observability/ObservabilityRouter.tsx | 19 +++++++++-- src/components/observability/Overview.tsx | 6 +--- .../ProjectObservability/Project.tsx | 5 ++- .../ProjectListCellComponent.tsx | 6 ++-- .../observability/VMObservability/VM.tsx | 34 +++++++++---------- .../VMObservability/VMListCellComponent.tsx | 6 ++-- 8 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/components/observability/Customer/CustomerListCellComponent.tsx b/src/components/observability/Customer/CustomerListCellComponent.tsx index 62a2d8ff3d..71584b72ce 100644 --- a/src/components/observability/Customer/CustomerListCellComponent.tsx +++ b/src/components/observability/Customer/CustomerListCellComponent.tsx @@ -23,8 +23,6 @@ export const CustomerListCellComponent: FunctionComponent< const linkRef = useRef(null) const match = useRouteMatch() - console.log(match) - useEffect(() => { const handleEnter = ({ detail: { activeRowData } }) => { if (activeRowData.data.id === id) { @@ -44,7 +42,7 @@ export const CustomerListCellComponent: FunctionComponent< switch (field) { case ProjectListFields.PROJECT_NAME: return ( - + {name} diff --git a/src/components/observability/Customer/Customers.tsx b/src/components/observability/Customer/Customers.tsx index 430a762c9a..866b5179f3 100644 --- a/src/components/observability/Customer/Customers.tsx +++ b/src/components/observability/Customer/Customers.tsx @@ -84,17 +84,13 @@ const Customers = () => { component: , linked: false, }, - ':customerId': { - component: , - linked: false, - }, }, }) const renderBreadcrumbs = () => const searchKey = '' const handleSearch = () => {} return ( -
+
diff --git a/src/components/observability/ObservabilityRouter.tsx b/src/components/observability/ObservabilityRouter.tsx index bff07e8a76..916bec6dae 100644 --- a/src/components/observability/ObservabilityRouter.tsx +++ b/src/components/observability/ObservabilityRouter.tsx @@ -5,6 +5,9 @@ import { URLS } from '@devtron-labs/devtron-fe-common-lib' import Customers from './Customer/Customers' import Project from './ProjectObservability/Project' import { ProjectOverview } from './ProjectObservability/ProjectOverview' +import VM from './VMObservability/VM' +import VMList from './VMObservability/VMList' +import { VMOverview } from './VMObservability/VMOverview' import { Overview } from './Overview' const ObservabilityRouter: React.FC = () => { @@ -19,15 +22,27 @@ const ObservabilityRouter: React.FC = () => { + + + + + + + + + + + + - + - + ) } diff --git a/src/components/observability/Overview.tsx b/src/components/observability/Overview.tsx index aeefc03f34..99e881e1bc 100644 --- a/src/components/observability/Overview.tsx +++ b/src/components/observability/Overview.tsx @@ -26,10 +26,6 @@ export const Overview = () => { component: , linked: false, }, - ':customerId': { - component: , - linked: false, - }, }, }) const renderBreadcrumbs = () => @@ -65,7 +61,7 @@ export const Overview = () => { } return ( -
+
diff --git a/src/components/observability/ProjectObservability/Project.tsx b/src/components/observability/ProjectObservability/Project.tsx index ea3f42a3b8..d1f16bd215 100644 --- a/src/components/observability/ProjectObservability/Project.tsx +++ b/src/components/observability/ProjectObservability/Project.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { Redirect, Route, useRouteMatch } from 'react-router-dom' +import { Route, useRouteMatch } from 'react-router-dom' import { BreadCrumb, @@ -112,7 +112,6 @@ const Project = () => { -
@@ -134,7 +133,7 @@ const Project = () => { const searchKey = '' const handleSearch = () => {} return ( -
+
) => { const linkRef = useRef(null) + const match = useRouteMatch() + useEffect(() => { const handleEnter = ({ detail: { activeRowData } }) => { if (activeRowData.data.id === id) { @@ -41,7 +43,7 @@ export const ProjectListCellComponent: FunctionComponent< switch (field) { case ProjectListFields.PROJECT_NAME: return ( - + {name} diff --git a/src/components/observability/VMObservability/VM.tsx b/src/components/observability/VMObservability/VM.tsx index ac78a2fa2d..05f7bed019 100644 --- a/src/components/observability/VMObservability/VM.tsx +++ b/src/components/observability/VMObservability/VM.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react' +import { Redirect, Route, useRouteMatch } from 'react-router-dom' import { BreadCrumb, @@ -20,12 +21,13 @@ import '../styles.scss' let interval const VM = () => { + const match = useRouteMatch() + 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 [selectedTabIndex, setSelectedTabIndex] = useState(0) const renderDataSyncingText = () => Syncing useEffect(() => { if (isDataSyncing) { @@ -52,23 +54,17 @@ const VM = () => { { id: 'vm_overview', label: 'Overview', - tabType: 'button', - active: selectedTabIndex === 0, + tabType: 'link', props: { - onClick: () => { - setSelectedTabIndex(0) - }, + to: `${match.url}/overview`, }, }, { id: 'vm_list', label: 'VMs', - tabType: 'button', - active: selectedTabIndex === 1, + tabType: 'link', props: { - onClick: () => { - setSelectedTabIndex(1) - }, + to: `${match.url}/vms`, }, }, ] @@ -77,10 +73,6 @@ const VM = () => { setSyncListData(!syncListData) } - const renderVMOverview = () => - - const renderVMList = () => - const renderVMTabs = () => { const rightComponent = (
@@ -115,8 +107,14 @@ const VM = () => {
- {selectedTabIndex === 0 && renderVMOverview()} - {selectedTabIndex === 1 && renderVMList()} + + + + + + + +
@@ -139,7 +137,7 @@ const VM = () => { const searchKey = '' const handleSearch = () => {} return ( -
+
) => { const linkRef = useRef(null) + const match = useRouteMatch() + useEffect(() => { const handleEnter = ({ detail: { activeRowData } }) => { if (activeRowData.data.id === id) { @@ -41,7 +43,7 @@ export const VMListCellComponent: FunctionComponent< switch (field) { case VMListFields.VM_NAME: return ( - + {name} From d63e2681dda60ed01f6712ef616d68fd21a3dbdc Mon Sep 17 00:00:00 2001 From: Mukesh Date: Wed, 29 Oct 2025 10:26:01 +0530 Subject: [PATCH 09/14] overview pages with tab style and individual vm overview --- .../observability/ObservabilityRouter.tsx | 8 +- .../ProjectObservability/Project.tsx | 6 +- .../ProjectObservability/ProjectOverview.tsx | 18 ++-- .../observability/SingleVMOverview.tsx | 59 +++++++++++ .../observability/VMObservability/VM.tsx | 6 +- .../VMObservability/VMListCellComponent.tsx | 97 ++++++++++--------- .../VMObservability/VMOverview.tsx | 21 ++-- src/components/observability/service.ts | 32 ++++++ src/components/observability/styles.scss | 45 +++++---- src/components/observability/types.ts | 15 ++- src/components/observability/utils.tsx | 4 +- 11 files changed, 212 insertions(+), 99 deletions(-) create mode 100644 src/components/observability/SingleVMOverview.tsx diff --git a/src/components/observability/ObservabilityRouter.tsx b/src/components/observability/ObservabilityRouter.tsx index 916bec6dae..4e2f0bc5fc 100644 --- a/src/components/observability/ObservabilityRouter.tsx +++ b/src/components/observability/ObservabilityRouter.tsx @@ -3,17 +3,23 @@ import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom' import { URLS } from '@devtron-labs/devtron-fe-common-lib' import Customers from './Customer/Customers' +import { Overview } from './Overview' import Project from './ProjectObservability/Project' import { ProjectOverview } from './ProjectObservability/ProjectOverview' +import SingleVMOverview from './SingleVMOverview' import VM from './VMObservability/VM' import VMList from './VMObservability/VMList' import { VMOverview } from './VMObservability/VMOverview' -import { Overview } from './Overview' const ObservabilityRouter: React.FC = () => { const { path } = useRouteMatch() return ( + + + + + diff --git a/src/components/observability/ProjectObservability/Project.tsx b/src/components/observability/ProjectObservability/Project.tsx index d1f16bd215..f5e575af13 100644 --- a/src/components/observability/ProjectObservability/Project.tsx +++ b/src/components/observability/ProjectObservability/Project.tsx @@ -54,7 +54,7 @@ const Project = () => { { id: 'project_overview', label: 'Overview', - tabType: 'link', + tabType: 'navLink', props: { to: `${match.url}/overview`, }, @@ -62,7 +62,7 @@ const Project = () => { { id: 'project_list', label: 'Projects', - tabType: 'link', + tabType: 'navLink', props: { to: `${match.url}/projects`, }, @@ -131,7 +131,7 @@ const Project = () => { }) const renderBreadcrumbs = () => const searchKey = '' - const handleSearch = () => {} + const handleSearch = () => { } return (
diff --git a/src/components/observability/ProjectObservability/ProjectOverview.tsx b/src/components/observability/ProjectObservability/ProjectOverview.tsx index cbbf347952..8634c2cd03 100644 --- a/src/components/observability/ProjectObservability/ProjectOverview.tsx +++ b/src/components/observability/ProjectObservability/ProjectOverview.tsx @@ -30,16 +30,10 @@ export const ProjectOverview = () => { } // alert(JSON.stringify(data)) return ( -
- +
+ {data.map((value) => { + return + })}
) } @@ -51,7 +45,9 @@ export const ProjectOverview = () => {

At a Glance

- {renderBody()} +
+ {renderBody()} +
) } diff --git a/src/components/observability/SingleVMOverview.tsx b/src/components/observability/SingleVMOverview.tsx new file mode 100644 index 0000000000..07fd59b456 --- /dev/null +++ b/src/components/observability/SingleVMOverview.tsx @@ -0,0 +1,59 @@ + + + +import { GenericSectionErrorState } from '.yalc/@devtron-labs/devtron-fe-common-lib/dist' +import { MetricsInfoCard } from './MetricsInfoCard' +import './styles.scss' +import { GlanceMetricsKeys } from './types' +import { MetricsInfoLoadingCard, useGetGlanceConfig } from './utils' + +let interval +const SingleVMOverview = () => { + 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 + })} +
+ ) + } + + return ( +
+
+
+

At a Glance

+
+
+
+ {renderBody()} +
+
+ ) +} + +export default SingleVMOverview; diff --git a/src/components/observability/VMObservability/VM.tsx b/src/components/observability/VMObservability/VM.tsx index 05f7bed019..3752d55668 100644 --- a/src/components/observability/VMObservability/VM.tsx +++ b/src/components/observability/VMObservability/VM.tsx @@ -54,7 +54,7 @@ const VM = () => { { id: 'vm_overview', label: 'Overview', - tabType: 'link', + tabType: 'navLink', props: { to: `${match.url}/overview`, }, @@ -62,7 +62,7 @@ const VM = () => { { id: 'vm_list', label: 'VMs', - tabType: 'link', + tabType: 'navLink', props: { to: `${match.url}/vms`, }, @@ -135,7 +135,7 @@ const VM = () => { }) const renderBreadcrumbs = () => const searchKey = '' - const handleSearch = () => {} + const handleSearch = () => { } return (
diff --git a/src/components/observability/VMObservability/VMListCellComponent.tsx b/src/components/observability/VMObservability/VMListCellComponent.tsx index 4ab2bb7b27..6539767313 100644 --- a/src/components/observability/VMObservability/VMListCellComponent.tsx +++ b/src/components/observability/VMObservability/VMListCellComponent.tsx @@ -20,58 +20,59 @@ export const VMListCellComponent: FunctionComponent< isRowActive, signals, }: TableCellComponentProps) => { - const linkRef = useRef(null) + const linkRef = useRef(null) - const match = useRouteMatch() + const match = useRouteMatch() - useEffect(() => { - const handleEnter = ({ detail: { activeRowData } }) => { - if (activeRowData.data.id === id) { - linkRef.current?.click() + useEffect(() => { + const handleEnter = ({ detail: { activeRowData } }) => { + if (activeRowData.data.id === id) { + linkRef.current?.click() + } } - } - if (isRowActive) { - signals.addEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) - } + if (isRowActive) { + signals.addEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) + } - return () => { - signals.removeEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) - } - }, [isRowActive]) + 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 + switch (field) { + case VMListFields.VM_NAME: + debugger; + 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 index 2727eaccf1..c077d7480f 100644 --- a/src/components/observability/VMObservability/VMOverview.tsx +++ b/src/components/observability/VMObservability/VMOverview.tsx @@ -8,7 +8,8 @@ import { MetricsInfoLoadingCard, useGetGlanceConfig } from '../utils' import '../styles.scss' export const VMOverview = () => { - const { isFetching, isError, refetch } = useGetGlanceConfig() + const { isFetching, data, isError, refetch } = useGetGlanceConfig() + console.log(data) const renderBody = () => { if (isFetching) { @@ -32,16 +33,10 @@ export const VMOverview = () => { } // alert(JSON.stringify(data)) return ( -
- +
+ {data.map((value) => { + return + })}
) } @@ -53,7 +48,9 @@ export const VMOverview = () => {

At a Glance

- {renderBody()} +
+ {renderBody()} +
) } diff --git a/src/components/observability/service.ts b/src/components/observability/service.ts index 1f828eff58..6580c54543 100644 --- a/src/components/observability/service.ts +++ b/src/components/observability/service.ts @@ -101,3 +101,35 @@ export const getCustomerListData: () => Promise = () 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 index a87e223ec5..5b454bdeab 100644 --- a/src/components/observability/styles.scss +++ b/src/components/observability/styles.scss @@ -13,24 +13,35 @@ .at-a-glance { grid-template-columns: 1fr 1fr 1fr; } -} -.observability-table-wrapper { + .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; + // 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); -} \ No newline at end of file + .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); + grid-template-rows: repeat(2, 1fr); + gap: 8px; + } +} diff --git a/src/components/observability/types.ts b/src/components/observability/types.ts index f2cadb3a62..446d839c3d 100644 --- a/src/components/observability/types.ts +++ b/src/components/observability/types.ts @@ -1,4 +1,4 @@ -import { FiltersTypeEnum, TableProps } from '.yalc/@devtron-labs/devtron-fe-common-lib/dist' +import { FiltersTypeEnum, IconName, TableProps } from '.yalc/@devtron-labs/devtron-fe-common-lib/dist' export enum GlanceMetricsKeys { REACHABLE_CUSTOMERS = 'customers', @@ -26,7 +26,7 @@ export type CustomerTableProps = TableProps { + Pick { description: string } @@ -76,3 +76,14 @@ export enum TabDetailsSegment { export interface TabDetailsSearchParams { tab: TabDetailsSegment } + +export interface MetricsInfoCardProps { + dataTestId: string + metricTitle: string + metricValue: string + metricUnit?: string + valueOutOf?: string + iconName: IconName + tooltipContent: string + redirectionLink?: string +} diff --git a/src/components/observability/utils.tsx b/src/components/observability/utils.tsx index 382130f680..342ce1bd33 100644 --- a/src/components/observability/utils.tsx +++ b/src/components/observability/utils.tsx @@ -1,6 +1,6 @@ import { useQuery } from '@devtron-labs/devtron-fe-common-lib' -import { getObservabilityData } from './service' +import { getProjectOverViewCards } from './service' import { TabDetailsSearchParams, TabDetailsSegment } from './types' // Will be removing while importing to dashboard @@ -19,7 +19,7 @@ export const MetricsInfoLoadingCard = () => ( export const useGetGlanceConfig = () => useQuery({ queryKey: ['observabilityGlanceConfig'], - queryFn: () => getObservabilityData(), + queryFn: () => getProjectOverViewCards(), }) export const parseChartDetailsSearchParams = (searchParams: URLSearchParams): TabDetailsSearchParams => ({ From 15a81f17ebf8021448d50a74a0a71aed3ff05bd3 Mon Sep 17 00:00:00 2001 From: Mukesh Date: Wed, 29 Oct 2025 11:16:51 +0530 Subject: [PATCH 10/14] overview pages with tab style and individual vm overview --- .../observability/SingleVMOverview.tsx | 103 ++++++++++++++++-- .../VMObservability/VMListCellComponent.tsx | 1 - .../VMObservability/VMOverview.tsx | 36 +++++- 3 files changed, 130 insertions(+), 10 deletions(-) diff --git a/src/components/observability/SingleVMOverview.tsx b/src/components/observability/SingleVMOverview.tsx index 07fd59b456..3037e9c30a 100644 --- a/src/components/observability/SingleVMOverview.tsx +++ b/src/components/observability/SingleVMOverview.tsx @@ -1,7 +1,17 @@ +import { useEffect, useState } from 'react' +import { useRouteMatch } from 'react-router-dom' +import { + BreadCrumb, + BreadcrumbText, + GenericSectionErrorState, + handleUTCTime, + PageHeader, + useBreadcrumb +} from '@devtron-labs/devtron-fe-common-lib/dist' +import ObservabilityIconComponent from './ObservabilityIcon' -import { GenericSectionErrorState } from '.yalc/@devtron-labs/devtron-fe-common-lib/dist' import { MetricsInfoCard } from './MetricsInfoCard' import './styles.scss' import { GlanceMetricsKeys } from './types' @@ -11,6 +21,42 @@ let interval const SingleVMOverview = () => { const { isFetching, data, isError, refetch } = useGetGlanceConfig() console.log(data) + const match = useRouteMatch() + + const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') + const [isDataSyncing, setDataSyncing] = useState(false) + const [syncListData, setSyncListData] = useState() + 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]) + + const { breadcrumbs } = useBreadcrumb({ + alias: { + observability: { + component: , + linked: true, + }, + customer: { + component: , + linked: false, + }, + }, + }) const renderBody = () => { if (isFetching) { @@ -42,17 +88,58 @@ const SingleVMOverview = () => { ) } + const renderBreadcrumbs = () => + const searchKey = '' + const handleSearch = () => { } return ( -
-
-
-

At a Glance

+
+ +
+
+
+

At a Glance

+
+
+
+ {renderBody()} +
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
-
-
- {renderBody()}
+ ) } diff --git a/src/components/observability/VMObservability/VMListCellComponent.tsx b/src/components/observability/VMObservability/VMListCellComponent.tsx index 6539767313..622b46073d 100644 --- a/src/components/observability/VMObservability/VMListCellComponent.tsx +++ b/src/components/observability/VMObservability/VMListCellComponent.tsx @@ -42,7 +42,6 @@ export const VMListCellComponent: FunctionComponent< switch (field) { case VMListFields.VM_NAME: - debugger; return ( diff --git a/src/components/observability/VMObservability/VMOverview.tsx b/src/components/observability/VMObservability/VMOverview.tsx index c077d7480f..f732950e16 100644 --- a/src/components/observability/VMObservability/VMOverview.tsx +++ b/src/components/observability/VMObservability/VMOverview.tsx @@ -42,7 +42,7 @@ export const VMOverview = () => { } return ( -
+

At a Glance

@@ -51,6 +51,40 @@ export const VMOverview = () => {
{renderBody()}
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
+
+ CPU +
+
+ +
+
+
) } From 6cb4313e717d0d0737febf36f6a0b03b658b5068 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Wed, 29 Oct 2025 16:25:09 +0530 Subject: [PATCH 11/14] customer overview glance --- .../observability/MetricsInfoCard.tsx | 2 +- src/components/observability/Overview.tsx | 7 ++- .../VMObservability/VMOverview.tsx | 18 +++--- src/components/observability/constants.ts | 45 ++++++++------- src/components/observability/service.ts | 57 ++++++++++--------- src/components/observability/types.ts | 22 +++++-- src/components/observability/utils.tsx | 31 +++++++++- 7 files changed, 109 insertions(+), 73 deletions(-) diff --git a/src/components/observability/MetricsInfoCard.tsx b/src/components/observability/MetricsInfoCard.tsx index 2a387ea105..5f3ac6579c 100644 --- a/src/components/observability/MetricsInfoCard.tsx +++ b/src/components/observability/MetricsInfoCard.tsx @@ -29,7 +29,7 @@ export const MetricsInfoCard = ({ valueOutOf?: string iconName: IconName redirectionLink?: string - tooltipContent: string + tooltipContent?: string }) => { const [isHovering, setIsHovering] = useState(false) diff --git a/src/components/observability/Overview.tsx b/src/components/observability/Overview.tsx index 99e881e1bc..1c4d8e6dd1 100644 --- a/src/components/observability/Overview.tsx +++ b/src/components/observability/Overview.tsx @@ -6,6 +6,7 @@ import { useBreadcrumb, } from '@devtron-labs/devtron-fe-common-lib' +import { MetricsInfoCard } from './MetricsInfoCard' import ObservabilityIconComponent from './ObservabilityIcon' import { GlanceMetricsKeys } from './types' import { MetricsInfoLoadingCard, useGetGlanceConfig } from './utils' @@ -53,9 +54,9 @@ export const Overview = () => { return (
- {/* {data.map(({ metricTitle }) => ( - // - {/* ))} */} + {data.map((config) => ( + + ))}
) } diff --git a/src/components/observability/VMObservability/VMOverview.tsx b/src/components/observability/VMObservability/VMOverview.tsx index f732950e16..52ffe77a64 100644 --- a/src/components/observability/VMObservability/VMOverview.tsx +++ b/src/components/observability/VMObservability/VMOverview.tsx @@ -34,9 +34,9 @@ export const VMOverview = () => { // alert(JSON.stringify(data)) return (
- {data.map((value) => { - return - })} + {data.map((value) => ( + + ))}
) } @@ -48,16 +48,14 @@ export const VMOverview = () => {

At a Glance

-
- {renderBody()} -
+
{renderBody()}
CPU
- +
@@ -65,7 +63,7 @@ export const VMOverview = () => { CPU
- +
@@ -73,7 +71,7 @@ export const VMOverview = () => { CPU
- +
@@ -81,7 +79,7 @@ export const VMOverview = () => { CPU
- +
diff --git a/src/components/observability/constants.ts b/src/components/observability/constants.ts index 611a97d286..7f5b5114de 100644 --- a/src/components/observability/constants.ts +++ b/src/components/observability/constants.ts @@ -1,5 +1,4 @@ import { - IconName, numberComparatorBySortOrder, SegmentedControlProps, stringComparatorBySortOrder, @@ -8,36 +7,38 @@ import { import { CustomerListCellComponent } from './Customer/CustomerListCellComponent' import { ProjectListCellComponent } from './ProjectObservability/ProjectListCellComponent' import { VMListCellComponent } from './VMObservability/VMListCellComponent' -import { CustomerTableProps, ProjectTableProps, TabDetailsSegment, VMTableProps } from './types' +import { CustomerTableProps, MetricsInfoCardProps, ProjectTableProps, TabDetailsSegment, VMTableProps } from './types' -export enum GlanceMetricKeys { - PROJECTS = 'projects', +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: Record< - GlanceMetricKeys, - { - iconName: IconName - metricTitle: string - } -> = { - [GlanceMetricKeys.PROJECTS]: { + +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-project', metricTitle: 'Projects', + tooltipContent: 'Number of all projects', }, - [GlanceMetricKeys.TOTAL_VMS]: { - iconName: 'ic-devtron-app', - metricTitle: 'Devtron Applications', + [ObservabilityGlanceMetricKeys.TOTAL_VMS]: { + iconName: 'ic-bg-cluster', + metricTitle: 'Total VMs', + tooltipContent: 'Number of all Vms', }, - [GlanceMetricKeys.RUNNING_VMS]: { - iconName: 'ic-helm-app', - metricTitle: 'Helm Applications', - }, - [GlanceMetricKeys.HEALTH_STATUS]: { - iconName: 'ic-bg-environment', - metricTitle: 'Environments', + + [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: { + iconName: 'ic-check', + metricTitle: 'Status', + tooltipContent: 'Percentage of all health status', }, } diff --git a/src/components/observability/service.ts b/src/components/observability/service.ts index 6580c54543..b5255f1395 100644 --- a/src/components/observability/service.ts +++ b/src/components/observability/service.ts @@ -1,12 +1,13 @@ -import { CustomerObservabilityDTO } from './types' +import { ObservabilityGlanceMetricKeys } from './constants' +import { CustomerObservabilityDTO, ObservabilityOverviewDTO } from './types' -export const getObservabilityData: () => Promise = () => +export const getObservabilityData: () => Promise> = () => Promise.resolve({ - totalClusters: 10, - totalVMs: 20, - totalProjects: 30, - totalCustomers: 40, - healthStatus: 50, + [ObservabilityGlanceMetricKeys.TOTAL_CUSTOMERS]: 10, + [ObservabilityGlanceMetricKeys.TOTAL_VMS]: 20, + [ObservabilityGlanceMetricKeys.PROJECTS]: 30, + [ObservabilityGlanceMetricKeys.HEALTH_STATUS]: 50, + [ObservabilityGlanceMetricKeys.RUNNING_VMS]: 40, }) export const getProjectList: () => Promise = () => @@ -105,31 +106,31 @@ export const getCustomerListData: () => Promise = () export const getProjectOverViewCards: () => Promise = () => Promise.resolve([ { - tooltipContent: "", - dataTestId: "cpu_id", - metricValue: "16", - metricTitle: "CPU", - iconName: "ic-bg-cpu" + 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: 'disk_id', + metricValue: '400', + metricTitle: 'DISK', + iconName: 'ic-bg-cpu', }, { - tooltipContent: "", - dataTestId: "memory_id", - metricValue: "1000", - metricTitle: "MEMORY", - 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" - } + tooltipContent: '', + dataTestId: 'running_id', + metricValue: '10', + metricTitle: 'RUNNING VMs', + iconName: 'ic-bg-cpu', + }, ]) diff --git a/src/components/observability/types.ts b/src/components/observability/types.ts index 446d839c3d..2c00c18e89 100644 --- a/src/components/observability/types.ts +++ b/src/components/observability/types.ts @@ -1,4 +1,6 @@ -import { FiltersTypeEnum, IconName, TableProps } from '.yalc/@devtron-labs/devtron-fe-common-lib/dist' +import { FiltersTypeEnum, IconName, TableProps } from '@devtron-labs/devtron-fe-common-lib/dist' + +import { ObservabilityGlanceMetricKeys } from './constants' export enum GlanceMetricsKeys { REACHABLE_CUSTOMERS = 'customers', @@ -26,7 +28,7 @@ export type CustomerTableProps = TableProps { + Pick { description: string } @@ -77,13 +79,21 @@ 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 { - dataTestId: string + iconName: IconName metricTitle: string - metricValue: string + dataTestId?: string + metricValue?: string metricUnit?: string valueOutOf?: string - iconName: IconName - tooltipContent: string + tooltipContent?: string redirectionLink?: string } diff --git a/src/components/observability/utils.tsx b/src/components/observability/utils.tsx index 342ce1bd33..61eab2434a 100644 --- a/src/components/observability/utils.tsx +++ b/src/components/observability/utils.tsx @@ -1,7 +1,8 @@ import { useQuery } from '@devtron-labs/devtron-fe-common-lib' -import { getProjectOverViewCards } from './service' -import { TabDetailsSearchParams, TabDetailsSegment } from './types' +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 = () => ( @@ -16,10 +17,34 @@ 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(), + // 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 => ({ From 744d3703cf024eb2cb51621f5d2d44cf377e2f27 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Mon, 3 Nov 2025 15:38:24 +0530 Subject: [PATCH 12/14] chore: css fixes --- .../observability/ObservabilityRouter.tsx | 5 +- .../ProjectObservability/Project.tsx | 28 +++++--- .../observability/SingleVMOverview.tsx | 69 +++++++++++++------ .../observability/VMObservability/VM.tsx | 30 ++++---- 4 files changed, 85 insertions(+), 47 deletions(-) diff --git a/src/components/observability/ObservabilityRouter.tsx b/src/components/observability/ObservabilityRouter.tsx index 4e2f0bc5fc..e21d721165 100644 --- a/src/components/observability/ObservabilityRouter.tsx +++ b/src/components/observability/ObservabilityRouter.tsx @@ -3,19 +3,18 @@ import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom' import { URLS } from '@devtron-labs/devtron-fe-common-lib' import Customers from './Customer/Customers' -import { Overview } from './Overview' import Project from './ProjectObservability/Project' import { ProjectOverview } from './ProjectObservability/ProjectOverview' -import SingleVMOverview from './SingleVMOverview' import VM from './VMObservability/VM' import VMList from './VMObservability/VMList' import { VMOverview } from './VMObservability/VMOverview' +import { Overview } from './Overview' +import SingleVMOverview from './SingleVMOverview' const ObservabilityRouter: React.FC = () => { const { path } = useRouteMatch() return ( - diff --git a/src/components/observability/ProjectObservability/Project.tsx b/src/components/observability/ProjectObservability/Project.tsx index f5e575af13..2f5f00763d 100644 --- a/src/components/observability/ProjectObservability/Project.tsx +++ b/src/components/observability/ProjectObservability/Project.tsx @@ -24,6 +24,7 @@ const Project = () => { 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) @@ -46,6 +47,8 @@ const Project = () => { } }, [isDataSyncing]) + console.log('match', match) + // eslint-disable-next-line @typescript-eslint/no-unused-vars const updateDataSyncing = (loading: boolean): void => { setDataSyncing(loading) @@ -117,21 +120,24 @@ const Project = () => {
) - const { breadcrumbs } = useBreadcrumb({ - alias: { - observability: { - component: , - linked: true, - }, - customer: { - component: , - linked: false, + const { breadcrumbs } = useBreadcrumb( + { + alias: { + observability: { + component: , + linked: true, + }, + ':customerId': { + component: , + linked: false, + }, }, }, - }) + [], + ) const renderBreadcrumbs = () => const searchKey = '' - const handleSearch = () => { } + const handleSearch = () => {} return (
diff --git a/src/components/observability/SingleVMOverview.tsx b/src/components/observability/SingleVMOverview.tsx index 3037e9c30a..722e13d38a 100644 --- a/src/components/observability/SingleVMOverview.tsx +++ b/src/components/observability/SingleVMOverview.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react' -import { useRouteMatch } from 'react-router-dom' import { BreadCrumb, @@ -7,25 +6,25 @@ import { GenericSectionErrorState, handleUTCTime, PageHeader, - useBreadcrumb + useBreadcrumb, } from '@devtron-labs/devtron-fe-common-lib/dist' -import ObservabilityIconComponent from './ObservabilityIcon' - import { MetricsInfoCard } from './MetricsInfoCard' -import './styles.scss' +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 match = useRouteMatch() 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(() => { @@ -45,6 +44,11 @@ const SingleVMOverview = () => { } }, [isDataSyncing]) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const updateDataSyncing = (loading: boolean): void => { + setDataSyncing(loading) + } + const { breadcrumbs } = useBreadcrumb({ alias: { observability: { @@ -81,35 +85,61 @@ const SingleVMOverview = () => { // alert(JSON.stringify(data)) return (
- {data.map((value) => { - return - })} + {data.map((value) => ( + + ))}
) } const renderBreadcrumbs = () => - const searchKey = '' - const handleSearch = () => { } + + const syncNow = (): void => { + setSyncListData(!syncListData) + } + + const renderLastSyncComponent = () => ( +
+ {lastDataSyncTimeString && ( + <> + {lastDataSyncTimeString} + {!isDataSyncing && ( + <> +   + + + )} + + )} + {fetchingExternalApps && renderDataSyncingText()} +
+ ) return (
+ {renderLastSyncComponent()} +

At a Glance

-
- {renderBody()} -
+
{renderBody()}
CPU
- +
@@ -117,7 +147,7 @@ const SingleVMOverview = () => { CPU
- +
@@ -125,7 +155,7 @@ const SingleVMOverview = () => { CPU
- +
@@ -133,14 +163,13 @@ const SingleVMOverview = () => { CPU
- +
- ) } -export default SingleVMOverview; +export default SingleVMOverview diff --git a/src/components/observability/VMObservability/VM.tsx b/src/components/observability/VMObservability/VM.tsx index 3752d55668..b58eb8a5df 100644 --- a/src/components/observability/VMObservability/VM.tsx +++ b/src/components/observability/VMObservability/VM.tsx @@ -20,8 +20,24 @@ import { VMOverview } from './VMOverview' import '../styles.scss' let interval + const VM = () => { const match = useRouteMatch() + const { breadcrumbs } = useBreadcrumb( + { + alias: { + observability: { + component: , + linked: true, + }, + customerId: { + component: , + linked: false, + }, + }, + }, + [], + ) const [lastDataSyncTimeString, setLastDataSyncTimeString] = useState('') const [isDataSyncing, setDataSyncing] = useState(false) @@ -121,21 +137,9 @@ const VM = () => { ) } - const { breadcrumbs } = useBreadcrumb({ - alias: { - observability: { - component: , - linked: true, - }, - customer: { - component: , - linked: false, - }, - }, - }) const renderBreadcrumbs = () => const searchKey = '' - const handleSearch = () => { } + const handleSearch = () => {} return (
From 10b719fdcf5fefacab2f73f92d4d109e21a0aca8 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Tue, 4 Nov 2025 09:52:06 +0530 Subject: [PATCH 13/14] chore: css fixes --- src/components/observability/Overview.tsx | 13 ++- .../ProjectObservability/Project.tsx | 20 ++-- .../observability/VMObservability/VM.tsx | 24 +++-- .../VMObservability/VMListCellComponent.tsx | 96 +++++++++---------- .../VMObservability/VMOverview.tsx | 8 +- src/components/observability/service.ts | 2 +- src/components/observability/styles.scss | 4 +- 7 files changed, 82 insertions(+), 85 deletions(-) diff --git a/src/components/observability/Overview.tsx b/src/components/observability/Overview.tsx index 1c4d8e6dd1..46afc12883 100644 --- a/src/components/observability/Overview.tsx +++ b/src/components/observability/Overview.tsx @@ -62,16 +62,19 @@ export const Overview = () => { } return ( -
+
-
-
-
+
+
+

At a Glance

+ {renderBody()} +
+
+

Observability Metrics

- {renderBody()}
) diff --git a/src/components/observability/ProjectObservability/Project.tsx b/src/components/observability/ProjectObservability/Project.tsx index 2f5f00763d..8e6aaea96c 100644 --- a/src/components/observability/ProjectObservability/Project.tsx +++ b/src/components/observability/ProjectObservability/Project.tsx @@ -103,19 +103,17 @@ const Project = () => { const renderProjectTabs = () => (
-
+
-
- - - - - - -
+ + + + + +
) @@ -127,8 +125,8 @@ const Project = () => { component: , linked: true, }, - ':customerId': { - component: , + ':projectId': { + component: , linked: false, }, }, diff --git a/src/components/observability/VMObservability/VM.tsx b/src/components/observability/VMObservability/VM.tsx index b58eb8a5df..d6396f490b 100644 --- a/src/components/observability/VMObservability/VM.tsx +++ b/src/components/observability/VMObservability/VM.tsx @@ -30,8 +30,8 @@ const VM = () => { component: , linked: true, }, - customerId: { - component: , + ':projects': { + component: , linked: false, }, }, @@ -91,7 +91,7 @@ const VM = () => { const renderVMTabs = () => { const rightComponent = ( -
+
{lastDataSyncTimeString && ( <> {lastDataSyncTimeString} @@ -117,21 +117,19 @@ const VM = () => { return (
-
+
-
- - - - - - + + + + + + - -
+
) diff --git a/src/components/observability/VMObservability/VMListCellComponent.tsx b/src/components/observability/VMObservability/VMListCellComponent.tsx index 622b46073d..4ab2bb7b27 100644 --- a/src/components/observability/VMObservability/VMListCellComponent.tsx +++ b/src/components/observability/VMObservability/VMListCellComponent.tsx @@ -20,58 +20,58 @@ export const VMListCellComponent: FunctionComponent< isRowActive, signals, }: TableCellComponentProps) => { - const linkRef = useRef(null) + const linkRef = useRef(null) - const match = useRouteMatch() + const match = useRouteMatch() - useEffect(() => { - const handleEnter = ({ detail: { activeRowData } }) => { - if (activeRowData.data.id === id) { - linkRef.current?.click() - } - } - - if (isRowActive) { - signals.addEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) + useEffect(() => { + const handleEnter = ({ detail: { activeRowData } }) => { + if (activeRowData.data.id === id) { + linkRef.current?.click() } + } - return () => { - signals.removeEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) - } - }, [isRowActive]) + if (isRowActive) { + signals.addEventListener(TableSignalEnum.ENTER_PRESSED, handleEnter) + } - 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 + 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 index 52ffe77a64..832c8b4c9a 100644 --- a/src/components/observability/VMObservability/VMOverview.tsx +++ b/src/components/observability/VMObservability/VMOverview.tsx @@ -55,7 +55,7 @@ export const VMOverview = () => { CPU
- +
@@ -63,7 +63,7 @@ export const VMOverview = () => { CPU
- +
@@ -71,7 +71,7 @@ export const VMOverview = () => { CPU
- +
@@ -79,7 +79,7 @@ export const VMOverview = () => { CPU
- +
diff --git a/src/components/observability/service.ts b/src/components/observability/service.ts index b5255f1395..25a4c6ca9d 100644 --- a/src/components/observability/service.ts +++ b/src/components/observability/service.ts @@ -7,7 +7,7 @@ export const getObservabilityData: () => Promise Promise = () => diff --git a/src/components/observability/styles.scss b/src/components/observability/styles.scss index 5b454bdeab..d2bb782c8d 100644 --- a/src/components/observability/styles.scss +++ b/src/components/observability/styles.scss @@ -1,6 +1,5 @@ .observability-overview { - grid-template-columns: 10fr 3fr; - +flexbox-col dc__gap-32 bg__secondary p-20 .cards-wrapper { grid-template-columns: repeat(3, 1fr); column-gap: 8px; @@ -41,7 +40,6 @@ .workflow-overview-cards-wrapper { grid-template-columns: repeat(4, 1fr); - grid-template-rows: repeat(2, 1fr); gap: 8px; } } From c91ac73d844495ec1c8b73917eaee5e59805e212 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Thu, 6 Nov 2025 16:39:07 +0530 Subject: [PATCH 14/14] chore: bar chart in overview page added --- .../observability/Metrics/BarMetrics.tsx | 25 ++++++ .../observability/MetricsInfoCard.tsx | 14 +--- src/components/observability/Overview.tsx | 12 +-- .../ProjectObservability/ProjectOverview.tsx | 10 +-- .../observability/SingleVMOverview.tsx | 2 +- .../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 | 80 +++++++++++++++++-- 11 files changed, 223 insertions(+), 49 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 46afc12883..0517016a9f 100644 --- a/src/components/observability/Overview.tsx +++ b/src/components/observability/Overview.tsx @@ -6,6 +6,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' @@ -16,7 +17,6 @@ import './styles.scss' export const Overview = () => { const { isFetching, data, isError, refetch } = useGetGlanceConfig() - console.log(data) const { breadcrumbs } = useBreadcrumb({ alias: { observability: { @@ -31,7 +31,7 @@ export const Overview = () => { }) const renderBreadcrumbs = () => - const renderBody = () => { + const renderGlanceConfig = () => { if (isFetching) { return (
@@ -54,9 +54,7 @@ export const Overview = () => { return (
- {data.map((config) => ( - - ))} + {data?.glanceConfig.map((config) => )}
) } @@ -70,10 +68,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 8634c2cd03..d9954eb0e3 100644 --- a/src/components/observability/ProjectObservability/ProjectOverview.tsx +++ b/src/components/observability/ProjectObservability/ProjectOverview.tsx @@ -31,9 +31,9 @@ export const ProjectOverview = () => { // alert(JSON.stringify(data)) return (
- {data.map((value) => { - return - })} + {data.glanceConfig.map((value) => ( + + ))}
) } @@ -45,9 +45,7 @@ export const ProjectOverview = () => {

At a Glance

-
- {renderBody()} -
+
{renderBody()}
) } diff --git a/src/components/observability/SingleVMOverview.tsx b/src/components/observability/SingleVMOverview.tsx index 722e13d38a..940d97277a 100644 --- a/src/components/observability/SingleVMOverview.tsx +++ b/src/components/observability/SingleVMOverview.tsx @@ -85,7 +85,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 7f5b5114de..f4a6e2fd9b 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 25a4c6ca9d..617ad01efd 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', project: 2, totalVms: 14, @@ -94,13 +127,31 @@ export const getCustomerListData: () => Promise = () }, { id: 2, - name: 'Customer2', + name: 'Tenant2', + status: 'INACTIVE', + project: 34, + totalVms: 4, + healthStatus: '20%', + activeVms: 1, + }, + { + 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, + }, ]) 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 2c00c18e89..a59d59541c 100644 --- a/src/components/observability/types.ts +++ b/src/components/observability/types.ts @@ -79,14 +79,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 @@ -97,3 +89,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..0423bb068f 100644 --- a/src/components/observability/utils.tsx +++ b/src/components/observability/utils.tsx @@ -1,8 +1,22 @@ -import { useQuery } from '@devtron-labs/devtron-fe-common-lib' +import { + CHART_COLORS, + ChartColorKey, + SelectPickerOptionType, + Tooltip, + useQuery, + useTheme, +} 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 +31,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 +69,52 @@ 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 = 'SkyBlue500', +}: ResourceCapacityDistributionTypes & { bgColor?: ChartColorKey }) => { + const { appTheme } = useTheme() + + return ( +
+
+ {/* Fill div up to utilisation percentage */} + +
+ +
+
+ +
+ +
+
+ ) +} + +export const CPUCapacityCellComponent = ({ cpu }: Pick) => ( + +) + +export const MemoryCapacityCellComponent = ({ memory }: Pick) => ( + +) + +export const DiskCapacityCellComponent = ({ disk }: Pick) => ( + +)