diff --git a/src/Assets/IconV2/ic-container-registry.svg b/src/Assets/IconV2/ic-container-registry.svg
new file mode 100644
index 000000000..95c9543a8
--- /dev/null
+++ b/src/Assets/IconV2/ic-container-registry.svg
@@ -0,0 +1,19 @@
+
+
+
diff --git a/src/Assets/IconV2/ic-container.svg b/src/Assets/IconV2/ic-container.svg
index 95c9543a8..1dc10d7df 100644
--- a/src/Assets/IconV2/ic-container.svg
+++ b/src/Assets/IconV2/ic-container.svg
@@ -1,19 +1,3 @@
-
-
-
diff --git a/src/Assets/IconV2/ic-speedometer.svg b/src/Assets/IconV2/ic-speedometer.svg
new file mode 100644
index 000000000..b93c8c1f5
--- /dev/null
+++ b/src/Assets/IconV2/ic-speedometer.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Assets/IconV2/ic-two-cubes.svg b/src/Assets/IconV2/ic-two-cubes.svg
new file mode 100644
index 000000000..15ca41392
--- /dev/null
+++ b/src/Assets/IconV2/ic-two-cubes.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts
index a5a013b75..c74a92f82 100644
--- a/src/Common/Constants.ts
+++ b/src/Common/Constants.ts
@@ -86,6 +86,7 @@ export const URLS = {
// NOTE: using appId since we are re-using AppConfig component
GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP_DETAIL: `${GLOBAL_CONFIG_TEMPLATES_DEVTRON_APP}/detail/:appId`,
LICENSE_AUTH: '/license-auth',
+ GLOBAL_CONFIG_EDIT_CLUSTER: '/global-config/cluster-env/edit/:clusterId',
} as const
export const ROUTES = {
@@ -381,6 +382,7 @@ export const API_STATUS_CODES = {
NOT_FOUND: 404,
REQUEST_TIMEOUT: 408,
CONFLICT: 409,
+ PRE_CONDITION_FAILED: 412,
EXPECTATION_FAILED: 417,
UNPROCESSABLE_ENTITY: 422,
LOCKED: 423,
diff --git a/src/Common/RJSF/rjsfForm.scss b/src/Common/RJSF/rjsfForm.scss
index 2ada15fe7..3a158ed33 100644
--- a/src/Common/RJSF/rjsfForm.scss
+++ b/src/Common/RJSF/rjsfForm.scss
@@ -26,6 +26,10 @@
input.form__input {
padding: 6px 8px;
+
+ &[readonly] {
+ opacity: 0.5;
+ }
}
}
diff --git a/src/Pages/ResourceBrowser/ResourceBrowser.Types.ts b/src/Pages/ResourceBrowser/ResourceBrowser.Types.ts
index 237bb62a8..baad07dc7 100644
--- a/src/Pages/ResourceBrowser/ResourceBrowser.Types.ts
+++ b/src/Pages/ResourceBrowser/ResourceBrowser.Types.ts
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-import { RefObject } from 'react'
+import { Dispatch, RefObject, SetStateAction } from 'react'
+import { GroupBase } from 'react-select'
+import { ServerErrors } from '@Common/ServerError'
+import { SelectPickerOptionType } from '@Shared/Components'
import { Nodes, NodeType } from '@Shared/types'
export interface GVKType {
@@ -59,8 +62,54 @@ export interface K8sResourceListPayloadType {
k8sRequest: ResourceListPayloadK8sRequestType
}
+export enum ResourceRecommenderHeaderType {
+ NAME = 'name',
+ NAMESPACE = 'namespace',
+ KIND = 'kind',
+ API_VERSION = 'apiVersion',
+ CONTAINER_NAME = 'containerName',
+ CPU_REQUEST = 'cpuRequest',
+ CPU_LIMIT = 'cpuLimit',
+ MEMORY_REQUEST = 'memoryRequest',
+ MEMORY_LIMIT = 'memoryLimit',
+}
+
+export type ResourceRecommenderHeaderWithStringValue = Extract<
+ ResourceRecommenderHeaderType,
+ | ResourceRecommenderHeaderType.NAME
+ | ResourceRecommenderHeaderType.NAMESPACE
+ | ResourceRecommenderHeaderType.KIND
+ | ResourceRecommenderHeaderType.API_VERSION
+ | ResourceRecommenderHeaderType.CONTAINER_NAME
+>
+
+export type ResourceRecommenderHeaderWithRecommendation = Extract<
+ ResourceRecommenderHeaderType,
+ | ResourceRecommenderHeaderType.CPU_REQUEST
+ | ResourceRecommenderHeaderType.CPU_LIMIT
+ | ResourceRecommenderHeaderType.MEMORY_REQUEST
+ | ResourceRecommenderHeaderType.MEMORY_LIMIT
+>
+
export type K8sResourceDetailDataType = {
[key: string]: string | number | object | boolean
+ additionalMetadata?: Partial<
+ Record<
+ ResourceRecommenderHeaderWithRecommendation,
+ {
+ // In case there is not limit or request set, it will be null
+ current: {
+ value: string | 'none'
+ } | null
+ // In case cron is yet to run
+ recommended: {
+ value: string | 'none'
+ } | null
+ // In case any of current or recommended is null, delta will be null
+ delta: number | null
+ }
+ >
+ >
}
export interface K8sResourceDetailType {
@@ -69,6 +118,7 @@ export interface K8sResourceDetailType {
}
export interface BulkSelectionActionWidgetProps {
+ isResourceRecommendationView: boolean
count: number
handleOpenBulkDeleteModal: () => void
handleClearBulkSelection: () => void
@@ -76,12 +126,13 @@ export interface BulkSelectionActionWidgetProps {
handleOpenUncordonNodeModal: () => void
handleOpenDrainNodeModal: () => void
handleOpenRestartWorkloadModal: () => void
+ handleOpenApplyResourceRecommendationModal: () => void
parentRef: RefObject
showBulkRestartOption: boolean
showNodeListingOptions: boolean
}
-export type RBBulkOperationType = 'restart' | 'delete' | 'cordon' | 'uncordon' | 'drain'
+export type RBBulkOperationType = 'restart' | 'delete' | 'cordon' | 'uncordon' | 'drain' | 'applyResourceRecommendation'
export interface CreateResourceRequestBodyType {
appId: string
@@ -95,6 +146,7 @@ export interface CreateResourceRequestBodyType {
export interface ResourceManifestDTO {
manifestResponse: {
manifest: Record
+ recommendedManifest?: Record
}
secretViewAccess: boolean
}
@@ -146,3 +198,21 @@ export interface NodeActionRequest {
version: string
kind: string
}
+
+export interface GVKOptionValueType {
+ kind: string
+ apiVersion: string
+}
+
+export interface GetResourceRecommenderResourceListPropsType {
+ resourceList: K8sResourceDetailType
+ reloadResourceListData: () => void
+ setShowAbsoluteValuesInResourceRecommender: Dispatch>
+ showAbsoluteValuesInResourceRecommender: boolean
+ gvkOptions: GroupBase>[]
+ areGVKOptionsLoading: boolean
+ reloadGVKOptions: () => void
+ gvkOptionsError: ServerErrors
+ isResourceListLoading: boolean
+ resourceListError: ServerErrors
+}
diff --git a/src/Pages/ResourceBrowser/constants.tsx b/src/Pages/ResourceBrowser/constants.tsx
index 263605a5d..598939977 100644
--- a/src/Pages/ResourceBrowser/constants.tsx
+++ b/src/Pages/ResourceBrowser/constants.tsx
@@ -111,3 +111,6 @@ export const NODE_DRAIN_OPTIONS_CHECKBOX_CONFIG: {
label: DRAIN_NODE_MODAL_MESSAGING.IgnoreDaemonSets.heading,
},
] as const
+
+export const GVK_FILTER_KIND_QUERY_PARAM_KEY = 'gvkFilterKind'
+export const GVK_FILTER_API_VERSION_QUERY_PARAM_KEY = 'gvkFilterApiVersion'
diff --git a/src/Pages/ResourceBrowser/service.ts b/src/Pages/ResourceBrowser/service.ts
index 9c2318238..588f5d8a7 100644
--- a/src/Pages/ResourceBrowser/service.ts
+++ b/src/Pages/ResourceBrowser/service.ts
@@ -39,7 +39,9 @@ export const getK8sResourceList = (
export const createNewResource = (
resourceListPayload: CreateResourcePayload,
-): Promise> => post(ROUTES.K8S_RESOURCE_CREATE, resourceListPayload)
+ abortControllerRef?: APIOptions['abortControllerRef'],
+): Promise> =>
+ post(ROUTES.K8S_RESOURCE_CREATE, resourceListPayload, { abortControllerRef })
export const deleteResource = (
resourceListPayload: ResourceListPayloadType,
diff --git a/src/Shared/Components/Icon/Icon.tsx b/src/Shared/Components/Icon/Icon.tsx
index 56cd6901e..1095c977d 100644
--- a/src/Shared/Components/Icon/Icon.tsx
+++ b/src/Shared/Components/Icon/Icon.tsx
@@ -45,6 +45,7 @@ import { ReactComponent as ICCluster } from '@IconsV2/ic-cluster.svg'
import { ReactComponent as ICClusterIsolated } from '@IconsV2/ic-cluster-isolated.svg'
import { ReactComponent as ICCode } from '@IconsV2/ic-code.svg'
import { ReactComponent as ICContainer } from '@IconsV2/ic-container.svg'
+import { ReactComponent as ICContainerRegistry } from '@IconsV2/ic-container-registry.svg'
import { ReactComponent as ICCookr } from '@IconsV2/ic-cookr.svg'
import { ReactComponent as ICCopy } from '@IconsV2/ic-copy.svg'
import { ReactComponent as ICCpu } from '@IconsV2/ic-cpu.svg'
@@ -84,6 +85,7 @@ import { ReactComponent as ICFilterApplied } from '@IconsV2/ic-filter-applied.sv
import { ReactComponent as ICFlask } from '@IconsV2/ic-flask.svg'
import { ReactComponent as ICFolderColor } from '@IconsV2/ic-folder-color.svg'
import { ReactComponent as ICFolderUser } from '@IconsV2/ic-folder-user.svg'
+import { ReactComponent as ICGavel } from '@IconsV2/ic-gavel.svg'
import { ReactComponent as ICGear } from '@IconsV2/ic-gear.svg'
import { ReactComponent as ICGift } from '@IconsV2/ic-gift.svg'
import { ReactComponent as ICGiftGradient } from '@IconsV2/ic-gift-gradient.svg'
@@ -160,6 +162,7 @@ import { ReactComponent as ICSortDescending } from '@IconsV2/ic-sort-descending.
import { ReactComponent as ICSortable } from '@IconsV2/ic-sortable.svg'
import { ReactComponent as ICSparkleAiColor } from '@IconsV2/ic-sparkle-ai-color.svg'
import { ReactComponent as ICSparkleColor } from '@IconsV2/ic-sparkle-color.svg'
+import { ReactComponent as ICSpeedometer } from '@IconsV2/ic-speedometer.svg'
import { ReactComponent as ICSpinny } from '@IconsV2/ic-spinny.svg'
import { ReactComponent as ICSprayCan } from '@IconsV2/ic-spray-can.svg'
import { ReactComponent as ICStack } from '@IconsV2/ic-stack.svg'
@@ -186,6 +189,7 @@ import { ReactComponent as ICTimeoutDash } from '@IconsV2/ic-timeout-dash.svg'
import { ReactComponent as ICTimer } from '@IconsV2/ic-timer.svg'
import { ReactComponent as ICTrafficSignal } from '@IconsV2/ic-traffic-signal.svg'
import { ReactComponent as ICTravclan } from '@IconsV2/ic-travclan.svg'
+import { ReactComponent as ICTwoCubes } from '@IconsV2/ic-two-cubes.svg'
import { ReactComponent as ICUbuntu } from '@IconsV2/ic-ubuntu.svg'
import { ReactComponent as ICUnknown } from '@IconsV2/ic-unknown.svg'
import { ReactComponent as ICUserCircle } from '@IconsV2/ic-user-circle.svg'
@@ -245,6 +249,7 @@ export const iconMap = {
'ic-cluster-isolated': ICClusterIsolated,
'ic-cluster': ICCluster,
'ic-code': ICCode,
+ 'ic-container-registry': ICContainerRegistry,
'ic-container': ICContainer,
'ic-cookr': ICCookr,
'ic-copy': ICCopy,
@@ -285,6 +290,7 @@ export const iconMap = {
'ic-flask': ICFlask,
'ic-folder-color': ICFolderColor,
'ic-folder-user': ICFolderUser,
+ 'ic-gavel': ICGavel,
'ic-gear': ICGear,
'ic-gift-gradient': ICGiftGradient,
'ic-gift': ICGift,
@@ -361,6 +367,7 @@ export const iconMap = {
'ic-sortable': ICSortable,
'ic-sparkle-ai-color': ICSparkleAiColor,
'ic-sparkle-color': ICSparkleColor,
+ 'ic-speedometer': ICSpeedometer,
'ic-spinny': ICSpinny,
'ic-spray-can': ICSprayCan,
'ic-stack': ICStack,
@@ -387,6 +394,7 @@ export const iconMap = {
'ic-timer': ICTimer,
'ic-traffic-signal': ICTrafficSignal,
'ic-travclan': ICTravclan,
+ 'ic-two-cubes': ICTwoCubes,
'ic-ubuntu': ICUbuntu,
'ic-unknown': ICUnknown,
'ic-user-circle': ICUserCircle,
diff --git a/src/Shared/Components/InfoBlock/InfoBlock.component.tsx b/src/Shared/Components/InfoBlock/InfoBlock.component.tsx
index 0f5309242..194cd67a1 100644
--- a/src/Shared/Components/InfoBlock/InfoBlock.component.tsx
+++ b/src/Shared/Components/InfoBlock/InfoBlock.component.tsx
@@ -19,8 +19,9 @@ import { deriveBorderRadiusAndBorderClassFromConfig } from '@Shared/Helpers'
import { Button } from '../Button'
import {
- CONTAINER_SIZE_TO_BUTTON_SIZE,
CONTAINER_SIZE_TO_CLASS_MAP,
+ CONTAINER_SIZE_TO_ICON_BUTTON_SIZE,
+ CONTAINER_SIZE_TO_TEXT_BUTTON_SIZE,
SIZE_TO_ICON_CLASS_MAP,
VARIANT_TO_ICON_MAP,
} from './constants'
@@ -40,6 +41,9 @@ const InfoBlock = ({
const baseContainerClass = `${CONTAINER_SIZE_TO_CLASS_MAP[size]} ${VARIANT_TO_BG_MAP[variant]} ${VARIANT_TO_BORDER_MAP[variant]} ${deriveBorderRadiusAndBorderClassFromConfig({ borderConfig, borderRadiusConfig })} w-100 py-8 br-4 bw-1`
const iconClass = `dc__no-shrink flex dc__fill-available-space ${SIZE_TO_ICON_CLASS_MAP[size]}`
const icon = customIcon ?? VARIANT_TO_ICON_MAP[variant]
+ const buttonSize = buttonProps?.icon
+ ? CONTAINER_SIZE_TO_ICON_BUTTON_SIZE[size]
+ : CONTAINER_SIZE_TO_TEXT_BUTTON_SIZE[size]
const renderIcon = () => {icon}
@@ -99,7 +103,7 @@ const InfoBlock = ({
{renderContent()}
- {buttonProps && }
+ {buttonProps && }
)
}
@@ -112,7 +116,7 @@ const InfoBlock = ({
{renderIcon()}
- {buttonProps && }
+ {buttonProps && }
)
}
diff --git a/src/Shared/Components/InfoBlock/constants.tsx b/src/Shared/Components/InfoBlock/constants.tsx
index 037ad7b23..26dace5b8 100644
--- a/src/Shared/Components/InfoBlock/constants.tsx
+++ b/src/Shared/Components/InfoBlock/constants.tsx
@@ -39,7 +39,12 @@ export const SIZE_TO_ICON_CLASS_MAP: Record = {
[ComponentSizeType.medium]: 'icon-dim-18',
}
-export const CONTAINER_SIZE_TO_BUTTON_SIZE: Record = {
+export const CONTAINER_SIZE_TO_TEXT_BUTTON_SIZE: Record = {
[ComponentSizeType.large]: ComponentSizeType.medium,
- [ComponentSizeType.medium]: ComponentSizeType.small,
+ [ComponentSizeType.medium]: ComponentSizeType.xs,
+}
+
+export const CONTAINER_SIZE_TO_ICON_BUTTON_SIZE: Record = {
+ [ComponentSizeType.large]: ComponentSizeType.xxs,
+ [ComponentSizeType.medium]: ComponentSizeType.xxs,
}
diff --git a/src/Shared/Components/RegistryIcon/RegistryIcon.tsx b/src/Shared/Components/RegistryIcon/RegistryIcon.tsx
index bbc97b195..215ee1e79 100644
--- a/src/Shared/Components/RegistryIcon/RegistryIcon.tsx
+++ b/src/Shared/Components/RegistryIcon/RegistryIcon.tsx
@@ -31,9 +31,9 @@ const registryIconMap: Record = {
[RegistryType.ECR]: 'ic-ecr',
[RegistryType.ARTIFACT_REGISTRY]: 'ic-google-artifact-registry',
[RegistryType.GCR]: 'ic-google-container-registry',
- [RegistryType.OTHER]: 'ic-container',
+ [RegistryType.OTHER]: 'ic-container-registry',
}
export const RegistryIcon = ({ registryType, size = 20 }: RegistryIconProps) => (
-
+
)
diff --git a/src/Shared/Helpers.tsx b/src/Shared/Helpers.tsx
index b11bccb57..bd157d487 100644
--- a/src/Shared/Helpers.tsx
+++ b/src/Shared/Helpers.tsx
@@ -61,6 +61,7 @@ import {
GitTriggers,
IntersectionChangeHandler,
IntersectionOptions,
+ Node,
Nodes,
PreventOutsideFocusProps,
TargetPlatformItemDTO,
@@ -714,3 +715,22 @@ export const smoothScrollToTop = (scrollContainer: HTMLElement, targetPosition:
return controls
}
+
+export const getGroupVersionFromApiVersion = (apiVersion: string): Pick => {
+ if (!apiVersion || apiVersion === '/') {
+ return { group: '', version: '' }
+ }
+
+ const parts = apiVersion.split('/')
+
+ if (parts.length === 1) {
+ return { group: '', version: parts[0] }
+ }
+
+ if (parts.length === 2) {
+ return { group: parts[0], version: parts[1] }
+ }
+
+ // If the apiVersion has more than two parts, we consider the first part as group and the rest as version
+ return { group: parts[0], version: parts.slice(1).join('/') }
+}
diff --git a/src/Shared/Providers/types.ts b/src/Shared/Providers/types.ts
index c1d1cd9c9..0145ede6b 100644
--- a/src/Shared/Providers/types.ts
+++ b/src/Shared/Providers/types.ts
@@ -18,7 +18,13 @@ import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react'
import { SERVER_MODE } from '../../Common'
import { ServerInfo } from '../Components/Header/types'
-import { DevtronLicenseInfo, IntelligenceConfig, LicenseInfoDialogType, ToastManager } from '..'
+import {
+ DevtronLicenseInfo,
+ EnvironmentDataValuesDTO,
+ IntelligenceConfig,
+ LicenseInfoDialogType,
+ ToastManager,
+} from '..'
export interface ReloadVersionConfigTypes {
bgUpdated: boolean
@@ -48,6 +54,7 @@ type AIAgentContextType = {
context: Record
}
+// Please make sure to make it optional if not required in gatekeeper
type CommonMainContextProps = {
setServerMode: (serverMode: SERVER_MODE) => void
isHelpGettingStartedClicked: boolean
@@ -90,7 +97,7 @@ type CommonMainContextProps = {
setIntelligenceConfig: Dispatch>
setAIAgentContext: (aiAgentContext: AIAgentContextType) => void
setSidePanelConfig: Dispatch>
-}
+} & Pick
export type MainContext = CommonMainContextProps &
(
diff --git a/src/Shared/Services/types.ts b/src/Shared/Services/types.ts
index 2d98e6c76..54ca9fc7c 100644
--- a/src/Shared/Services/types.ts
+++ b/src/Shared/Services/types.ts
@@ -60,4 +60,5 @@ export interface EnvironmentDataValuesDTO extends Pick &