diff --git a/package-lock.json b/package-lock.json index e70a93913..a17850285 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.15.1", + "version": "1.15.1-beta-5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.15.1", + "version": "1.15.1-beta-5", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index a580e75b3..a0ddbd81d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.15.1", + "version": "1.15.1-beta-5", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", 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-gavel.svg b/src/Assets/IconV2/ic-gavel.svg new file mode 100644 index 000000000..e2f32f692 --- /dev/null +++ b/src/Assets/IconV2/ic-gavel.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-minus.svg b/src/Assets/IconV2/ic-minus.svg new file mode 100644 index 000000000..3f0a5f895 --- /dev/null +++ b/src/Assets/IconV2/ic-minus.svg @@ -0,0 +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/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 302c24049..48d2bc0b8 100644 --- a/src/Shared/Components/Icon/Icon.tsx +++ b/src/Shared/Components/Icon/Icon.tsx @@ -42,6 +42,7 @@ import { ReactComponent as ICCloudVms } from '@IconsV2/ic-cloud-vms.svg' import { ReactComponent as ICCluster } from '@IconsV2/ic-cluster.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' @@ -77,6 +78,7 @@ import { ReactComponent as ICFilter } from '@IconsV2/ic-filter.svg' import { ReactComponent as ICFilterApplied } from '@IconsV2/ic-filter-applied.svg' import { ReactComponent as ICFlask } from '@IconsV2/ic-flask.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' @@ -122,6 +124,7 @@ import { ReactComponent as ICMegaphoneRight } from '@IconsV2/ic-megaphone-right. import { ReactComponent as ICMemory } from '@IconsV2/ic-memory.svg' import { ReactComponent as ICMicrosoft } from '@IconsV2/ic-microsoft.svg' import { ReactComponent as ICMinikube } from '@IconsV2/ic-minikube.svg' +import { ReactComponent as ICMinus } from '@IconsV2/ic-minus.svg' import { ReactComponent as ICMissing } from '@IconsV2/ic-missing.svg' import { ReactComponent as ICMobile } from '@IconsV2/ic-mobile.svg' import { ReactComponent as ICMonitoring } from '@IconsV2/ic-monitoring.svg' @@ -148,6 +151,7 @@ import { ReactComponent as ICSortAscending } from '@IconsV2/ic-sort-ascending.sv import { ReactComponent as ICSortDescending } from '@IconsV2/ic-sort-descending.svg' import { ReactComponent as ICSortable } from '@IconsV2/ic-sortable.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' @@ -173,6 +177,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' @@ -227,6 +232,7 @@ export const iconMap = { 'ic-cloud-vms': ICCloudVms, 'ic-cluster': ICCluster, 'ic-code': ICCode, + 'ic-container-registry': ICContainerRegistry, 'ic-container': ICContainer, 'ic-cookr': ICCookr, 'ic-copy': ICCopy, @@ -263,6 +269,7 @@ export const iconMap = { 'ic-filter': ICFilter, 'ic-flask': ICFlask, 'ic-folder-user': ICFolderUser, + 'ic-gavel': ICGavel, 'ic-gear': ICGear, 'ic-gift-gradient': ICGiftGradient, 'ic-gift': ICGift, @@ -308,6 +315,7 @@ export const iconMap = { 'ic-memory': ICMemory, 'ic-microsoft': ICMicrosoft, 'ic-minikube': ICMinikube, + 'ic-minus': ICMinus, 'ic-missing': ICMissing, 'ic-mobile': ICMobile, 'ic-monitoring': ICMonitoring, @@ -334,6 +342,7 @@ export const iconMap = { 'ic-sort-descending': ICSortDescending, 'ic-sortable': ICSortable, 'ic-sparkle-color': ICSparkleColor, + 'ic-speedometer': ICSpeedometer, 'ic-spinny': ICSpinny, 'ic-spray-can': ICSprayCan, 'ic-stack': ICStack, @@ -359,6 +368,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/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/types.ts b/src/Shared/types.ts index bd265c365..4d681f611 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -138,6 +138,7 @@ export enum Nodes { Overview = 'Overview', MonitoringDashboard = 'MonitoringDashboard', UpgradeCluster = 'UpgradeCluster', + ResourceRecommender = 'ResourceRecommender', } // FIXME: This should be `typeof Nodes[keyof typeof Nodes]` instead since the key and values are not the same. Same to be removed from duplications in dashboard