From e4f81b69b6b3ac07845cefd79fe1ef1134cf5570 Mon Sep 17 00:00:00 2001 From: Arun Jain Date: Fri, 5 Dec 2025 11:59:22 +0530 Subject: [PATCH 1/6] feat: Add Observability navigation and route, and refactor calendar and date utilities to use the common library. --- .../app/details/appDetails/AppMetrics.tsx | 10 +++--- .../app/details/appDetails/GraphsModal.tsx | 8 ++--- .../app/details/appDetails/appDetails.type.ts | 6 ---- .../app/details/appDetails/utils.tsx | 35 +------------------ .../common/navigation/Navigation.tsx | 8 +++++ .../common/navigation/NavigationRoutes.tsx | 7 ++++ 6 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/components/app/details/appDetails/AppMetrics.tsx b/src/components/app/details/appDetails/AppMetrics.tsx index a180623f67..bb6f4ef81b 100644 --- a/src/components/app/details/appDetails/AppMetrics.tsx +++ b/src/components/app/details/appDetails/AppMetrics.tsx @@ -16,6 +16,7 @@ import React, { useState, useEffect } from 'react' import { + CalendarFocusInputType, ComponentSizeType, DatePickerRangeController, DocLink, @@ -26,11 +27,12 @@ import { useAsync, useMainContext, useTheme, + getCalendarValue, } from '@devtron-labs/devtron-fe-common-lib' import { useParams, Link, NavLink } from 'react-router-dom' import moment, { Moment } from 'moment' import Tippy from '@tippyjs/react' -import { getIframeSrc, ThroughputSelect, getCalendarValue, isK8sVersionValid, LatencySelect, AppInfo } from './utils' +import { getIframeSrc, ThroughputSelect, isK8sVersionValid, LatencySelect, AppInfo } from './utils' import { ChartTypes, AppMetricsTab, @@ -38,8 +40,6 @@ import { ChartType, StatusTypes, StatusType, - CalendarFocusInput, - CalendarFocusInputType, AppDetailsPathParams, } from './appDetails.type' import { GraphModal, GraphModalProps } from './GraphsModal' @@ -86,7 +86,7 @@ export const AppMetrics: React.FC<{ isHealthy: false, dataSourceName: '', }) - const [focusedInput, setFocusedInput] = useState(CalendarFocusInput.StartDate) + const [focusedInput, setFocusedInput] = useState('startDate') const [tab, setTab] = useState(AppMetricsTab.Aggregate) const { appId, envId } = useParams() const [calendarValue, setCalendarValue] = useState('') @@ -131,7 +131,7 @@ export const AppMetrics: React.FC<{ } function handleFocusChange(focusedInput): void { - setFocusedInput(focusedInput || CalendarFocusInput.StartDate) + setFocusedInput(focusedInput || 'startDate') } function handleApply(): void { diff --git a/src/components/app/details/appDetails/GraphsModal.tsx b/src/components/app/details/appDetails/GraphsModal.tsx index e4eda54530..3fc18e77e2 100644 --- a/src/components/app/details/appDetails/GraphsModal.tsx +++ b/src/components/app/details/appDetails/GraphsModal.tsx @@ -17,11 +17,11 @@ import { Component } from 'react' import { Moment } from 'moment' import { AppMetricsTabType, ChartType, StatusType, ChartTypes, StatusTypes, AppMetricsTab } from './appDetails.type' -import { getIframeSrc, isK8sVersionValid, ThroughputSelect, getCalendarValue, LatencySelect, AppInfo } from './utils' +import { getIframeSrc, isK8sVersionValid, ThroughputSelect, LatencySelect, AppInfo } from './utils' import { ReactComponent as GraphIcon } from '../../../../assets/icons/ic-graph.svg' import { DEFAULTK8SVERSION } from '../../../../config' import { APP_METRICS_CALENDAR_INPUT_DATE_FORMAT } from './constants' -import { DatePickerRangeController } from '@devtron-labs/devtron-fe-common-lib' +import { getCalendarValue, CalendarFocusInputType, DatePickerRangeController } from '@devtron-labs/devtron-fe-common-lib' export const ChartNames = { cpu: 'CPU Usage', @@ -60,7 +60,7 @@ interface GraphModalState { status5xx: string statusCode: StatusTypes mainChartUrl: string - focusedInput: 'startDate' | 'endDate' + focusedInput: CalendarFocusInputType calendarValue: string calendar: { startDate @@ -216,7 +216,7 @@ export class GraphModal extends Component { }) } - handleFocusChange = (focusedInput: 'startDate' | 'endDate') => { + handleFocusChange = (focusedInput: CalendarFocusInputType) => { this.setState({ focusedInput: focusedInput || 'startDate' }) } diff --git a/src/components/app/details/appDetails/appDetails.type.ts b/src/components/app/details/appDetails/appDetails.type.ts index 1cf29233aa..84018ddd00 100644 --- a/src/components/app/details/appDetails/appDetails.type.ts +++ b/src/components/app/details/appDetails/appDetails.type.ts @@ -50,15 +50,9 @@ export enum StatusType { Throughput = 'Throughput', } -export enum CalendarFocusInput { - StartDate = 'startDate', - EndDate = 'endDate', -} - export type AppMetricsTabType = 'aggregate' | 'pod' export type ChartTypes = 'cpu' | 'ram' | 'status' | 'latency' export type StatusTypes = '5xx' | '4xx' | '2xx' | 'Throughput' -export type CalendarFocusInputType = 'startDate' | 'endDate' export interface AppDetailsPathParams { appId: string diff --git a/src/components/app/details/appDetails/utils.tsx b/src/components/app/details/appDetails/utils.tsx index c20bb402ae..5306f9b19b 100644 --- a/src/components/app/details/appDetails/utils.tsx +++ b/src/components/app/details/appDetails/utils.tsx @@ -27,11 +27,10 @@ import { SelectPicker, SelectPickerProps, SelectPickerVariantType, - prefixZeroIfSingleDigit, AppEnvironment, SelectPickerOptionType, IconsProps, - DayPickerRangeControllerPresets, + getTimestampFromDateIfAvailable, } from '@devtron-labs/devtron-fe-common-lib' import { GetIFrameSrcParamsType } from './types' @@ -140,19 +139,6 @@ export const LatencySelect = (props) => { ) } -export function getCalendarValue(startDateStr: string, endDateStr: string): string { - let str: string = `${startDateStr} - ${endDateStr}` - if (endDateStr === 'now' && startDateStr.includes('now')) { - const range = DayPickerRangeControllerPresets.find((d) => d.endStr === startDateStr) - if (range) { - str = range.text - } else { - str = `${startDateStr} - ${endDateStr}` - } - } - return str -} - export function isK8sVersionValid(k8sVersion: string): boolean { if (!k8sVersion) { return false @@ -305,25 +291,6 @@ export function addChartNameExtensionToBaseURL( return url } -// Need to send either the relative time like: now-5m or the timestamp to grafana -// Assuming format is 'DD-MM-YYYY hh:mm:ss' -const getTimestampFromDateIfAvailable = (dateString: string): string => { - try { - const [day, month, yearAndTime] = dateString.split('-') - const [year, time] = yearAndTime.split(' ') - const updatedTime = time - .split(':') - .map((item) => (['0', '00'].includes(item) ? '00' : prefixZeroIfSingleDigit(Number(item)))) - .join(':') - const formattedDate = `${year}-${prefixZeroIfSingleDigit(Number(month))}-${prefixZeroIfSingleDigit(Number(day))}T${updatedTime}` - const parsedDate = new Date(formattedDate).getTime() - - return isNaN(parsedDate) ? dateString : parsedDate.toString() - } catch { - return dateString - } -} - export function addQueryParamToGrafanaURL( url: string, appId: string | number, diff --git a/src/components/common/navigation/Navigation.tsx b/src/components/common/navigation/Navigation.tsx index 2fc2259364..1401e24e69 100644 --- a/src/components/common/navigation/Navigation.tsx +++ b/src/components/common/navigation/Navigation.tsx @@ -96,6 +96,14 @@ const NavigationList: NavigationListItemType[] = [ markAsBeta: false, isAvailableInDesktop: true, }, + { + title: 'Observability', + dataTestId: 'click-on-observability', + type: 'link', + icon: 'ic-binoculars', + href: CommonURLS.OBSERVABILITY, + hideNav: false, // hideObservability + }, { title: 'Resource Watcher', dataTestId: 'click-on-resource-watcher', diff --git a/src/components/common/navigation/NavigationRoutes.tsx b/src/components/common/navigation/NavigationRoutes.tsx index b9d174013d..f279eb47d3 100644 --- a/src/components/common/navigation/NavigationRoutes.tsx +++ b/src/components/common/navigation/NavigationRoutes.tsx @@ -113,6 +113,7 @@ const SoftwareDistributionHubRenderProvider = importComponentFromFELibrary( null, 'function', ) +const Observability = importComponentFromFELibrary('Observability', null, 'function') const migrateUserPreferences: (userPreferences: UserPreferencesType) => Promise = importComponentFromFELibrary('migrateUserPreferences', null, 'function') const isFELibAvailable = importComponentFromFELibrary('isFELibAvailable', null, 'function') @@ -556,6 +557,12 @@ const NavigationRoutes = ({ reloadVersionConfig }: Readonly, ] : []), + !!Observability && ( + + + + ), + ...(!window._env_.HIDE_RELEASES && SoftwareDistributionHub ? [ Date: Sat, 6 Dec 2025 00:18:39 +0530 Subject: [PATCH 2/6] feat: Add observability permissions to user, API token, and permission group authorization. --- .../APITokens/CreateAPIToken.tsx | 2 ++ .../Authorization/APITokens/EditAPIToken.tsx | 2 ++ .../AddEdit/PermissionGroupForm.tsx | 2 ++ .../AppPermissions.component.tsx | 29 +++++++++++++++++++ .../Shared/components/AppPermissions/utils.ts | 6 ++++ .../PermissionConfigurationFormProvider.tsx | 13 ++++++++- .../PermissionConfigurationForm/types.ts | 4 +++ .../UserPermissions/AddEdit/UserForm.tsx | 2 ++ .../Authorization/types.ts | 10 ++++++- .../Authorization/utils.ts | 18 +++++++++++- .../Webhook/WebhookDetailsModal.tsx | 1 + 11 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/Pages/GlobalConfigurations/Authorization/APITokens/CreateAPIToken.tsx b/src/Pages/GlobalConfigurations/Authorization/APITokens/CreateAPIToken.tsx index 973fed3f23..e1e0988b0a 100644 --- a/src/Pages/GlobalConfigurations/Authorization/APITokens/CreateAPIToken.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/APITokens/CreateAPIToken.tsx @@ -102,6 +102,7 @@ const CreateAPIToken = ({ userRoleGroups, isSaveDisabled, allowManageAllAccess, + observabilityPermission, } = usePermissionConfiguration() const [customDate, setCustomDate] = useState(null) const [tokenResponse, setTokenResponse] = useState({ @@ -223,6 +224,7 @@ const CreateAPIToken = ({ k8sPermission, permissionType, userGroups: [], + observabilityPermission, canManageAllAccess: allowManageAllAccess, ...getDefaultUserStatusAndTimeout(), }) diff --git a/src/Pages/GlobalConfigurations/Authorization/APITokens/EditAPIToken.tsx b/src/Pages/GlobalConfigurations/Authorization/APITokens/EditAPIToken.tsx index 9ccaa362e5..602f1e9797 100644 --- a/src/Pages/GlobalConfigurations/Authorization/APITokens/EditAPIToken.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/APITokens/EditAPIToken.tsx @@ -80,6 +80,7 @@ const EditAPIToken = ({ userRoleGroups, isSaveDisabled, allowManageAllAccess, + observabilityPermission, } = usePermissionConfiguration() const history = useHistory() @@ -133,6 +134,7 @@ const EditAPIToken = ({ permissionType, userGroups: [], canManageAllAccess: allowManageAllAccess, + observabilityPermission, ...getDefaultUserStatusAndTimeout(), }) diff --git a/src/Pages/GlobalConfigurations/Authorization/PermissionGroups/AddEdit/PermissionGroupForm.tsx b/src/Pages/GlobalConfigurations/Authorization/PermissionGroups/AddEdit/PermissionGroupForm.tsx index c93e5bc927..92d418f9e7 100644 --- a/src/Pages/GlobalConfigurations/Authorization/PermissionGroups/AddEdit/PermissionGroupForm.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/PermissionGroups/AddEdit/PermissionGroupForm.tsx @@ -60,6 +60,7 @@ const PermissionGroupForm = ({ isAddMode }: { isAddMode: boolean }) => { data: permissionGroup, isSaveDisabled, allowManageAllAccess, + observabilityPermission, } = usePermissionConfiguration() const _permissionGroup = permissionGroup as PermissionGroup @@ -114,6 +115,7 @@ const PermissionGroupForm = ({ isAddMode }: { isAddMode: boolean }) => { directPermission, serverMode, chartPermission, + observabilityPermission, }) const payload: PermissionGroupCreateOrUpdatePayload = { diff --git a/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/AppPermissions.component.tsx b/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/AppPermissions.component.tsx index f01b2b7fa7..41e863de5b 100644 --- a/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/AppPermissions.component.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/AppPermissions.component.tsx @@ -26,6 +26,7 @@ import { GenericSectionErrorState, logExceptionToSentry, mapByKey, + ObservabilityPermissionFilter, ReactSelectInputAction, showError, stringComparatorBySortOrder, @@ -34,6 +35,7 @@ import { useMainContext, } from '@devtron-labs/devtron-fe-common-lib' +import { importComponentFromFELibrary } from '@Components/common' import { getUserAccessAllWorkflows, getUserAccessChartGroups, @@ -74,11 +76,15 @@ import { getRoleConfigForRoleFilter, } from './utils' +const ObservabilityPermissions = importComponentFromFELibrary('ObservabilityPermissions', null, 'function') + const AppPermissions = () => { const { serverMode } = useMainContext() const { directPermission, setDirectPermission, + observabilityPermission, + setObservabilityPermission, setChartPermission, setK8sPermission, currentK8sPermissionRef, @@ -657,6 +663,21 @@ const AppPermissions = () => { setK8sPermission(_k8sPermission) } + // Observability Permissions + const _observabilityPermission: ObservabilityPermissionFilter[] = (roleFilters ?? []) + .filter((roleFilter) => roleFilter.entity === EntityTypes.OBSERVABILITY) + .map(({ action, entityName, tenant, status, timeToLive }) => ({ + action: action as ActionTypes.ADMIN | ActionTypes.VIEW, + tenant: { label: tenant, value: tenant }, + entityName: entityName?.split(',')?.map((entity) => ({ value: entity, label: entity })) || [], + status, + timeToLive, + })) + + if (_observabilityPermission.length) { + setObservabilityPermission(_observabilityPermission) + } + setIsLoading(false) } @@ -1030,6 +1051,14 @@ const AppPermissions = () => { )} + {ObservabilityPermissions && ( + + + + )} diff --git a/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/PermissionConfigurationFormProvider.tsx b/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/PermissionConfigurationFormProvider.tsx index dcf6f59f9c..c7784aff8d 100644 --- a/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/PermissionConfigurationFormProvider.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/PermissionConfigurationFormProvider.tsx @@ -16,7 +16,13 @@ import React, { createContext, ReactNode, useContext, useEffect, useMemo, useRef, useState } from 'react' -import { ActionTypes, EntityTypes, useGetUserRoles, UserStatus } from '@devtron-labs/devtron-fe-common-lib' +import { + ActionTypes, + EntityTypes, + ObservabilityPermissionFilter, + useGetUserRoles, + UserStatus, +} from '@devtron-labs/devtron-fe-common-lib' import { importComponentFromFELibrary } from '../../../../../../components/common' import { PermissionType } from '../../../constants' @@ -60,6 +66,7 @@ export const PermissionConfigurationFormProvider = ({ ...getDefaultStatusAndTimeout(), }) const [k8sPermission, setK8sPermission] = useState([]) + const [observabilityPermission, setObservabilityPermission] = useState([]) const currentK8sPermissionRef = useRef([]) const [userRoleGroups, _setUserRoleGroups] = useState([]) @@ -123,6 +130,8 @@ export const PermissionConfigurationFormProvider = ({ setDirectPermission, chartPermission, setChartPermission, + observabilityPermission, + setObservabilityPermission, k8sPermission, setK8sPermission, currentK8sPermissionRef, @@ -148,6 +157,8 @@ export const PermissionConfigurationFormProvider = ({ setDirectPermission, chartPermission, setChartPermission, + observabilityPermission, + setObservabilityPermission, k8sPermission, setK8sPermission, currentK8sPermissionRef, diff --git a/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/types.ts b/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/types.ts index 9d089bdffc..82e5300287 100644 --- a/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/types.ts +++ b/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/types.ts @@ -16,6 +16,8 @@ import React from 'react' +import { ObservabilityPermissionFilter } from '@devtron-labs/devtron-fe-common-lib' + import { PermissionType } from '../../../constants' import { ChartGroupPermissionsFilter, @@ -52,6 +54,8 @@ export interface PermissionConfigurationFormContext { k8sPermission?: K8sPermissionFilter[] setK8sPermission?: React.Dispatch> currentK8sPermissionRef?: React.MutableRefObject + observabilityPermission: ObservabilityPermissionFilter[] + setObservabilityPermission: React.Dispatch> /** * Flag to control if status should be shown in the form * diff --git a/src/Pages/GlobalConfigurations/Authorization/UserPermissions/AddEdit/UserForm.tsx b/src/Pages/GlobalConfigurations/Authorization/UserPermissions/AddEdit/UserForm.tsx index 4d41cd3979..959e0865b9 100644 --- a/src/Pages/GlobalConfigurations/Authorization/UserPermissions/AddEdit/UserForm.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/UserPermissions/AddEdit/UserForm.tsx @@ -81,6 +81,7 @@ const UserForm = ({ isAddMode }: { isAddMode: boolean }) => { showStatus, isSaveDisabled, allowManageAllAccess, + observabilityPermission, } = usePermissionConfiguration() const _userData = userData as User @@ -153,6 +154,7 @@ const UserForm = ({ isAddMode }: { isAddMode: boolean }) => { permissionType, userGroups: selectedUserGroups, canManageAllAccess: allowManageAllAccess, + observabilityPermission, ...getDefaultUserStatusAndTimeout(), }) diff --git a/src/Pages/GlobalConfigurations/Authorization/types.ts b/src/Pages/GlobalConfigurations/Authorization/types.ts index 70c3c5ff39..921cd1b108 100644 --- a/src/Pages/GlobalConfigurations/Authorization/types.ts +++ b/src/Pages/GlobalConfigurations/Authorization/types.ts @@ -25,6 +25,7 @@ import { DeleteConfirmationModalProps, EntityTypes, K8sResourceListPayloadType, + ObservabilityPermissionFilter, OptionType, ResourceKindType, UserGroupDTO, @@ -51,7 +52,12 @@ export interface UserAndGroupPermissionsWrapProps { type PermissionStatusAndTimeout = Pick export interface APIRoleFilterDto { - entity: EntityTypes.DIRECT | EntityTypes.CHART_GROUP | EntityTypes.CLUSTER | EntityTypes.JOB + entity: + | EntityTypes.DIRECT + | EntityTypes.CHART_GROUP + | EntityTypes.CLUSTER + | EntityTypes.JOB + | EntityTypes.OBSERVABILITY team?: string entityName?: string environment?: string @@ -72,6 +78,7 @@ export interface APIRoleFilterDto { // eslint-disable-next-line @typescript-eslint/no-explicit-any resource?: any workflow?: string + tenant?: string status: UserStatusDto timeoutWindowExpression: string /** @@ -319,6 +326,7 @@ export interface CreateUserPermissionPayloadParams chartPermission: ChartGroupPermissionsFilter k8sPermission: K8sPermissionFilter[] permissionType: PermissionType + observabilityPermission: ObservabilityPermissionFilter[] } export interface DeleteUserPermissionProps diff --git a/src/Pages/GlobalConfigurations/Authorization/utils.ts b/src/Pages/GlobalConfigurations/Authorization/utils.ts index 067e7db88a..c037e7b2cf 100644 --- a/src/Pages/GlobalConfigurations/Authorization/utils.ts +++ b/src/Pages/GlobalConfigurations/Authorization/utils.ts @@ -388,9 +388,15 @@ export const getRolesAndAccessRoles = ({ chartPermission, serverMode, canManageAllAccess, + observabilityPermission, }: Pick< CreateUserPermissionPayloadParams, - 'chartPermission' | 'directPermission' | 'serverMode' | 'k8sPermission' | 'canManageAllAccess' + | 'chartPermission' + | 'directPermission' + | 'serverMode' + | 'k8sPermission' + | 'canManageAllAccess' + | 'observabilityPermission' >) => { const filteredDirectPermissions = directPermission.filter( (permission) => permission.team?.value && permission.environment.length && permission.entityName.length, @@ -411,6 +417,14 @@ export const getRolesAndAccessRoles = ({ namespace: getCommaSeparatedNamespaceList(permission.namespace), resource: getSelectedPermissionValues(permission.resource), })), + ...observabilityPermission.map(({ action, entityName, tenant, status, timeToLive }) => ({ + entity: EntityTypes.OBSERVABILITY as APIRoleFilter['entity'], + action, + entityName: entityName.map((entity) => entity.value).join(','), + tenant: tenant.value, + status, + timeToLive, + })), ] if (serverMode !== SERVER_MODE.EA_ONLY) { @@ -448,6 +462,7 @@ export const createUserPermissionPayload = ({ timeToLive, userGroups, canManageAllAccess, + observabilityPermission, }: CreateUserPermissionPayloadParams): UserCreateOrUpdateParamsType => { const { roleFilters, accessRoleFilters } = getRolesAndAccessRoles({ directPermission, @@ -455,6 +470,7 @@ export const createUserPermissionPayload = ({ chartPermission, serverMode, canManageAllAccess, + observabilityPermission, }) return { diff --git a/src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx b/src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx index 68b0b5667e..02f2594a72 100644 --- a/src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx +++ b/src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx @@ -243,6 +243,7 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType k8sPermission: [], permissionType: PermissionType.SPECIFIC, userGroups: [], + observabilityPermission: [], ...getDefaultUserStatusAndTimeout(), }) const { result: userPermissionResponse } = await createOrUpdateUser({ From 8986eb43e0e3abb5f27ec74ef818b0828608132d Mon Sep 17 00:00:00 2001 From: Arun Jain Date: Sun, 7 Dec 2025 01:37:57 +0530 Subject: [PATCH 3/6] chore: update `@devtron-labs/devtron-fe-common-lib` to beta version --- package.json | 2 +- yarn.lock | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 811d141486..2c6ff99c99 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "homepage": "/dashboard", "dependencies": { - "@devtron-labs/devtron-fe-common-lib": "1.21.0", + "@devtron-labs/devtron-fe-common-lib": "1.21.0-beta-1", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@rjsf/core": "^5.13.3", "@rjsf/utils": "^5.13.3", diff --git a/yarn.lock b/yarn.lock index 42070c1ced..440fbe00c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1722,9 +1722,9 @@ __metadata: languageName: node linkType: hard -"@devtron-labs/devtron-fe-common-lib@npm:1.21.0": - version: 1.21.0 - resolution: "@devtron-labs/devtron-fe-common-lib@npm:1.21.0" +"@devtron-labs/devtron-fe-common-lib@npm:1.21.0-beta-1": + version: 1.21.0-beta-1 + resolution: "@devtron-labs/devtron-fe-common-lib@npm:1.21.0-beta-1" dependencies: "@codemirror/autocomplete": "npm:6.18.6" "@codemirror/lang-json": "npm:6.0.1" @@ -1753,6 +1753,7 @@ __metadata: nanoid: "npm:^3.3.8" qrcode.react: "npm:^4.2.0" react-canvas-confetti: "npm:^2.0.7" + react-csv: "npm:^2.2.2" react-dates: "npm:^21.8.0" react-draggable: "npm:^4.4.5" react-international-phone: "npm:^4.5.0" @@ -1774,7 +1775,7 @@ __metadata: react-select: 5.8.0 rxjs: ^7.8.1 yaml: ^2.4.1 - checksum: 10c0/dc47c439d71e478bb1982d10ac4ca2fe637a6d7fd4713b296a89231abbf44ccca2bba5970789959663ebf228036168660804a32b40c37c7688e202e2e8cf31b3 + checksum: 10c0/425bc9b9c966f77d9e956defdb55f969f2bd34044acb948adafe1db3fad4a640f4fd5d78937665a591018ca7b3a9dc96d0b07d60a89daf69a25e95e2c419fd84 languageName: node linkType: hard @@ -5721,7 +5722,7 @@ __metadata: version: 0.0.0-use.local resolution: "dashboard@workspace:." dependencies: - "@devtron-labs/devtron-fe-common-lib": "npm:1.21.0" + "@devtron-labs/devtron-fe-common-lib": "npm:1.21.0-beta-1" "@esbuild-plugins/node-globals-polyfill": "npm:0.2.3" "@playwright/test": "npm:^1.32.1" "@rjsf/core": "npm:^5.13.3" From 8644558db9feb74df1a992070486f0c2900fd7d0 Mon Sep 17 00:00:00 2001 From: Arun Jain Date: Mon, 8 Dec 2025 16:34:33 +0530 Subject: [PATCH 4/6] feat: Integrate observability permissions into authorization forms and validation logic. --- .../APITokens/CreateAPIToken.tsx | 11 ++++- .../Authorization/APITokens/EditAPIToken.tsx | 11 ++++- .../AddEdit/PermissionGroupForm.tsx | 9 +++- .../AppPermissions.component.tsx | 2 + .../Shared/components/AppPermissions/utils.ts | 2 +- .../PermissionConfigurationForm/types.ts | 2 +- .../UserPermissions/AddEdit/UserForm.tsx | 9 +++- .../Authorization/constants.ts | 1 + .../Authorization/types.ts | 2 +- .../Authorization/utils.ts | 48 +++++++++++++------ 10 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/Pages/GlobalConfigurations/Authorization/APITokens/CreateAPIToken.tsx b/src/Pages/GlobalConfigurations/Authorization/APITokens/CreateAPIToken.tsx index e1e0988b0a..2b686e009b 100644 --- a/src/Pages/GlobalConfigurations/Authorization/APITokens/CreateAPIToken.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/APITokens/CreateAPIToken.tsx @@ -103,6 +103,7 @@ const CreateAPIToken = ({ isSaveDisabled, allowManageAllAccess, observabilityPermission, + setObservabilityPermission, } = usePermissionConfiguration() const [customDate, setCustomDate] = useState(null) const [tokenResponse, setTokenResponse] = useState({ @@ -178,7 +179,15 @@ const CreateAPIToken = ({ } const handleGenerateAPIToken = async () => { - if (!validateDirectPermissionForm(directPermission, setDirectPermission, allowManageAllAccess).isValid) { + if ( + !validateDirectPermissionForm( + directPermission, + setDirectPermission, + observabilityPermission, + setObservabilityPermission, + allowManageAllAccess, + ).isValid + ) { return } diff --git a/src/Pages/GlobalConfigurations/Authorization/APITokens/EditAPIToken.tsx b/src/Pages/GlobalConfigurations/Authorization/APITokens/EditAPIToken.tsx index 602f1e9797..6d420d6b58 100644 --- a/src/Pages/GlobalConfigurations/Authorization/APITokens/EditAPIToken.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/APITokens/EditAPIToken.tsx @@ -81,6 +81,7 @@ const EditAPIToken = ({ isSaveDisabled, allowManageAllAccess, observabilityPermission, + setObservabilityPermission, } = usePermissionConfiguration() const history = useHistory() @@ -105,7 +106,15 @@ const EditAPIToken = ({ } const handleUpdatedToken = async (tokenId) => { - if (!validateDirectPermissionForm(directPermission, setDirectPermission, allowManageAllAccess).isValid) { + if ( + !validateDirectPermissionForm( + directPermission, + setDirectPermission, + observabilityPermission, + setObservabilityPermission, + allowManageAllAccess, + ).isValid + ) { return } diff --git a/src/Pages/GlobalConfigurations/Authorization/PermissionGroups/AddEdit/PermissionGroupForm.tsx b/src/Pages/GlobalConfigurations/Authorization/PermissionGroups/AddEdit/PermissionGroupForm.tsx index 92d418f9e7..474c1a796c 100644 --- a/src/Pages/GlobalConfigurations/Authorization/PermissionGroups/AddEdit/PermissionGroupForm.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/PermissionGroups/AddEdit/PermissionGroupForm.tsx @@ -61,6 +61,7 @@ const PermissionGroupForm = ({ isAddMode }: { isAddMode: boolean }) => { isSaveDisabled, allowManageAllAccess, observabilityPermission, + setObservabilityPermission, } = usePermissionConfiguration() const _permissionGroup = permissionGroup as PermissionGroup @@ -104,7 +105,13 @@ const PermissionGroupForm = ({ isAddMode }: { isAddMode: boolean }) => { } if ( !isSuperAdminPermission && - !validateDirectPermissionForm(directPermission, setDirectPermission, allowManageAllAccess).isValid + !validateDirectPermissionForm( + directPermission, + setDirectPermission, + observabilityPermission, + setObservabilityPermission, + allowManageAllAccess, + ).isValid ) { return } diff --git a/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/AppPermissions.component.tsx b/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/AppPermissions.component.tsx index 41e863de5b..4f1d790d44 100644 --- a/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/AppPermissions.component.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/AppPermissions.component.tsx @@ -949,6 +949,8 @@ const AppPermissions = () => { const { accessTypeToErrorMap: _accessTypeToErrorMap } = validateDirectPermissionForm( directPermission, setDirectPermission, + observabilityPermission, + setObservabilityPermission, allowManageAllAccess, false, ) diff --git a/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/utils.ts b/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/utils.ts index 36cc4746cf..e65dd401f0 100644 --- a/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/utils.ts +++ b/src/Pages/GlobalConfigurations/Authorization/Shared/components/AppPermissions/utils.ts @@ -89,7 +89,7 @@ export const getNavLinksConfig = (serverMode: SERVER_MODE, superAdmin: boolean, isHidden: serverMode === SERVER_MODE.EA_ONLY, }, { - accessType: null, + accessType: 'observability', tabName: 'observability', label: 'Observability', isHidden: !getAccessManagerRoles, // Check for FE-LIB diff --git a/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/types.ts b/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/types.ts index 82e5300287..013878c64e 100644 --- a/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/types.ts +++ b/src/Pages/GlobalConfigurations/Authorization/Shared/components/PermissionConfigurationForm/types.ts @@ -86,6 +86,6 @@ export interface PermissionConfigurationFormContext { } export type AccessTypeToErrorMapType = Record< - PermissionConfigurationFormContext['directPermission'][number]['accessType'], + PermissionConfigurationFormContext['directPermission'][number]['accessType'] | 'observability', boolean > diff --git a/src/Pages/GlobalConfigurations/Authorization/UserPermissions/AddEdit/UserForm.tsx b/src/Pages/GlobalConfigurations/Authorization/UserPermissions/AddEdit/UserForm.tsx index 959e0865b9..8f9690bb86 100644 --- a/src/Pages/GlobalConfigurations/Authorization/UserPermissions/AddEdit/UserForm.tsx +++ b/src/Pages/GlobalConfigurations/Authorization/UserPermissions/AddEdit/UserForm.tsx @@ -82,6 +82,7 @@ const UserForm = ({ isAddMode }: { isAddMode: boolean }) => { isSaveDisabled, allowManageAllAccess, observabilityPermission, + setObservabilityPermission, } = usePermissionConfiguration() const _userData = userData as User @@ -136,7 +137,13 @@ const UserForm = ({ isAddMode }: { isAddMode: boolean }) => { const handleSubmit = async () => { if ( !validateForm() || - !validateDirectPermissionForm(directPermission, setDirectPermission, allowManageAllAccess).isValid + !validateDirectPermissionForm( + directPermission, + setDirectPermission, + observabilityPermission, + setObservabilityPermission, + allowManageAllAccess, + ).isValid ) { return } diff --git a/src/Pages/GlobalConfigurations/Authorization/constants.ts b/src/Pages/GlobalConfigurations/Authorization/constants.ts index 0858315231..39a0be3b55 100644 --- a/src/Pages/GlobalConfigurations/Authorization/constants.ts +++ b/src/Pages/GlobalConfigurations/Authorization/constants.ts @@ -60,4 +60,5 @@ export const DEFAULT_ACCESS_TYPE_TO_ERROR_MAP: AccessTypeToErrorMapType = { [ACCESS_TYPE_MAP.DEVTRON_APPS]: false, [ACCESS_TYPE_MAP.HELM_APPS]: false, [ACCESS_TYPE_MAP.JOBS]: false, + observability: false, } as const diff --git a/src/Pages/GlobalConfigurations/Authorization/types.ts b/src/Pages/GlobalConfigurations/Authorization/types.ts index 921cd1b108..23f1a0239c 100644 --- a/src/Pages/GlobalConfigurations/Authorization/types.ts +++ b/src/Pages/GlobalConfigurations/Authorization/types.ts @@ -284,7 +284,7 @@ interface RoleFilter { export interface DirectPermissionsRoleFilter extends RoleFilter, PermissionStatusAndTimeout { entity: EntityTypes.DIRECT | EntityTypes.JOB - accessType: ACCESS_TYPE_MAP.DEVTRON_APPS | ACCESS_TYPE_MAP.HELM_APPS | ACCESS_TYPE_MAP.JOBS + accessType: ACCESS_TYPE_MAP team: OptionType entityName: OptionType[] entityNameError?: string diff --git a/src/Pages/GlobalConfigurations/Authorization/utils.ts b/src/Pages/GlobalConfigurations/Authorization/utils.ts index c037e7b2cf..83b39185e5 100644 --- a/src/Pages/GlobalConfigurations/Authorization/utils.ts +++ b/src/Pages/GlobalConfigurations/Authorization/utils.ts @@ -77,6 +77,10 @@ const getPermissionActionValue: (roleConfig: UserRoleConfig) => string = importC 'function', ) +const validateObservabilityPermissions: ({ observabilityPermission, setObservabilityPermission }) => { + isValid: boolean +} = importComponentFromFELibrary('validateObservabilityPermissions', null, 'function') + const DEPLOYMENT_APPROVER_ROLE_VALUE = importComponentFromFELibrary('DEPLOYMENT_APPROVER_ROLE_VALUE', null, 'function') const getKeyForRoleFilter = ({ entity, team, environment, accessType, entityName, workflow }: APIRoleFilterDto) => @@ -417,14 +421,16 @@ export const getRolesAndAccessRoles = ({ namespace: getCommaSeparatedNamespaceList(permission.namespace), resource: getSelectedPermissionValues(permission.resource), })), - ...observabilityPermission.map(({ action, entityName, tenant, status, timeToLive }) => ({ - entity: EntityTypes.OBSERVABILITY as APIRoleFilter['entity'], - action, - entityName: entityName.map((entity) => entity.value).join(','), - tenant: tenant.value, - status, - timeToLive, - })), + ...observabilityPermission + .filter((permission) => !!permission.tenant?.value) + .map(({ action, entityName, tenant, status, timeToLive }) => ({ + entity: EntityTypes.OBSERVABILITY as APIRoleFilter['entity'], + action, + entityName: entityName.map((entity) => entity.value).join(','), + tenant: tenant.value, + status, + timeToLive, + })), ] if (serverMode !== SERVER_MODE.EA_ONLY) { @@ -491,6 +497,8 @@ export const createUserPermissionPayload = ({ export const validateDirectPermissionForm = ( directPermission: PermissionConfigurationFormContext['directPermission'], setDirectPermission: PermissionConfigurationFormContext['setDirectPermission'], + observabilityPermission: PermissionConfigurationFormContext['observabilityPermission'], + setObservabilityPermission: PermissionConfigurationFormContext['setObservabilityPermission'], allowManageAllAccess: boolean, showErrorToast: boolean = true, ): { @@ -550,17 +558,27 @@ export const validateDirectPermissionForm = ( [], ) - const isFormValid = Object.values(accessTypeToErrorMap).every((val) => !val) + const { isValid: areObservabilityPermissionsValid } = validateObservabilityPermissions?.({ + observabilityPermission, + setObservabilityPermission, + }) ?? { isValid: true } - if (!isFormValid && showErrorToast) { - ToastManager.showToast({ - variant: ToastVariantType.error, - description: REQUIRED_FIELDS_MISSING, - }) + const isFormValid = Object.values(accessTypeToErrorMap).every((val) => !val) && areObservabilityPermissionsValid + + if (!isFormValid) { setDirectPermission(tempPermissions) + if (showErrorToast) { + ToastManager.showToast({ + variant: ToastVariantType.error, + description: REQUIRED_FIELDS_MISSING, + }) + } } - return { isValid: isFormValid, accessTypeToErrorMap } + return { + isValid: isFormValid, + accessTypeToErrorMap: { ...accessTypeToErrorMap, observability: !areObservabilityPermissionsValid }, + } } export const getWorkflowOptions = ( From 905c25a7f61a14f5cc5a40a23540c5f2c3bd6809 Mon Sep 17 00:00:00 2001 From: Arun Jain Date: Tue, 16 Dec 2025 17:15:52 +0530 Subject: [PATCH 5/6] chore: version bump --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2c6ff99c99..193318d641 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "homepage": "/dashboard", "dependencies": { - "@devtron-labs/devtron-fe-common-lib": "1.21.0-beta-1", + "@devtron-labs/devtron-fe-common-lib": "1.21.0-beta-14", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@rjsf/core": "^5.13.3", "@rjsf/utils": "^5.13.3", diff --git a/yarn.lock b/yarn.lock index 440fbe00c1..e32de37bbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1722,9 +1722,9 @@ __metadata: languageName: node linkType: hard -"@devtron-labs/devtron-fe-common-lib@npm:1.21.0-beta-1": - version: 1.21.0-beta-1 - resolution: "@devtron-labs/devtron-fe-common-lib@npm:1.21.0-beta-1" +"@devtron-labs/devtron-fe-common-lib@npm:1.21.0-beta-14": + version: 1.21.0-beta-14 + resolution: "@devtron-labs/devtron-fe-common-lib@npm:1.21.0-beta-14" dependencies: "@codemirror/autocomplete": "npm:6.18.6" "@codemirror/lang-json": "npm:6.0.1" @@ -1775,7 +1775,7 @@ __metadata: react-select: 5.8.0 rxjs: ^7.8.1 yaml: ^2.4.1 - checksum: 10c0/425bc9b9c966f77d9e956defdb55f969f2bd34044acb948adafe1db3fad4a640f4fd5d78937665a591018ca7b3a9dc96d0b07d60a89daf69a25e95e2c419fd84 + checksum: 10c0/78d53cea9f197ee4e3e430d91bb6db03be94cbe7e32fe90c48143d5f71f383fdcb9d4668bddb6c27e469836066362a4b02a181f1d1889179f5339a387b96c0fa languageName: node linkType: hard @@ -5722,7 +5722,7 @@ __metadata: version: 0.0.0-use.local resolution: "dashboard@workspace:." dependencies: - "@devtron-labs/devtron-fe-common-lib": "npm:1.21.0-beta-1" + "@devtron-labs/devtron-fe-common-lib": "npm:1.21.0-beta-14" "@esbuild-plugins/node-globals-polyfill": "npm:0.2.3" "@playwright/test": "npm:^1.32.1" "@rjsf/core": "npm:^5.13.3" From 136d23947fbff67bcbd403bff63ef955ecf64ddd Mon Sep 17 00:00:00 2001 From: Arun Jain Date: Wed, 6 May 2026 16:34:59 +0530 Subject: [PATCH 6/6] feat: add observability navigation item and update routing --- src/components/Navigation/constants.ts | 7 +++++++ src/components/common/navigation/NavigationRoutes.tsx | 10 ++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/Navigation/constants.ts b/src/components/Navigation/constants.ts index f4e890c9be..ce428841ed 100644 --- a/src/components/Navigation/constants.ts +++ b/src/components/Navigation/constants.ts @@ -22,6 +22,12 @@ const RESOURCE_WATCHER_NAV_ITEM: NavigationItemType = importComponentFromFELibra 'function', ) +const OBSERVABILITY_NAV_ITEM: NavigationItemType = importComponentFromFELibrary( + 'OBSERVABILITY_NAV_ITEM', + null, + 'function', +) + const SECURITY_ENABLEMENT_NAV_ITEM: NavigationItemType = importComponentFromFELibrary( 'SECURITY_ENABLEMENT_NAV_ITEM', null, @@ -202,6 +208,7 @@ const NAVIGATION_LIST: NavigationGroupType[] = [ isAvailableInEA: true, }, ...(RESOURCE_WATCHER_NAV_ITEM ? [RESOURCE_WATCHER_NAV_ITEM] : []), + ...(OBSERVABILITY_NAV_ITEM ? [OBSERVABILITY_NAV_ITEM] : []), ], isAvailableInEA: true, }, diff --git a/src/components/common/navigation/NavigationRoutes.tsx b/src/components/common/navigation/NavigationRoutes.tsx index 03d0da2221..eff7ff021b 100644 --- a/src/components/common/navigation/NavigationRoutes.tsx +++ b/src/components/common/navigation/NavigationRoutes.tsx @@ -56,7 +56,6 @@ import { TempAppWindowConfig, ToastManager, ToastVariantType, - URLS as CommonURLS, useMotionTemplate, useMotionValue, UserPreferencesType, @@ -594,11 +593,10 @@ const NavigationRoutes = ({ reloadVersionConfig }: Readonly - - , + key={BASE_ROUTES.OBSERVABILITY.ROOT} + path={`${BASE_ROUTES.OBSERVABILITY.ROOT}/*`} + element={} + />, ] : []), ...(serverMode === SERVER_MODE.FULL &&