From a1b32163c5ed79fa6650696090116ba87c2da304 Mon Sep 17 00:00:00 2001 From: vivek-devtron Date: Fri, 16 May 2025 16:00:14 +0530 Subject: [PATCH 1/7] initial commit --- .../ClusterEnvironmentDrawer.tsx | 38 +++-- .../ClusterEnvironmentDrawer/types.ts | 3 +- .../ManageCategories.component.tsx | 144 ++++++++++++++++++ .../ManageCategories/constants.tsx | 32 ++++ .../ManageCategories/types.tsx | 20 +++ .../ManageCategories/utils.ts | 26 ++++ .../ResourceList/BaseResourceList.tsx | 1 + src/components/cluster/Cluster.tsx | 64 +++++--- src/components/cluster/cluster.scss | 2 +- src/config/constants.ts | 1 + src/config/routes.ts | 1 + 11 files changed, 300 insertions(+), 32 deletions(-) create mode 100644 src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx create mode 100644 src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/constants.tsx create mode 100644 src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/types.tsx create mode 100644 src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx index ce64ac87e2..9c0449c977 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx @@ -27,6 +27,7 @@ import { Drawer, GenericEmptyState, noop, + SelectPicker, ServerErrors, showError, stopPropagation, @@ -70,6 +71,7 @@ export const ClusterEnvironmentDrawer = ({ hideClusterDrawer, isVirtual, clusterName, + category, }: ClusterEnvironmentDrawerProps) => { // STATES // Manages the loading state for create and update actions @@ -152,6 +154,7 @@ export const ClusterEnvironmentDrawer = ({ environmentName: environmentName ?? '', namespace: !id ? getNamespaceFromLocalStorage(parsedNamespace) : parsedNamespace, isProduction: !!isProduction, + category, description: description ?? '', }, validations: clusterEnvironmentDrawerFormValidationSchema({ isNamespaceMandatory: !isVirtual }), @@ -317,6 +320,17 @@ export const ClusterEnvironmentDrawer = ({ required={!isVirtual} /> +
+ +
{!isVirtual && (
)} -
- +
+ {EnvironmentLabels && !isVirtual && (
} export interface ClusterEnvironmentDrawerProps extends ClusterEnvironmentDrawerFormProps { diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx new file mode 100644 index 0000000000..b1f12e0f54 --- /dev/null +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx @@ -0,0 +1,144 @@ +import { useState } from 'react' +import { useHistory } from 'react-router-dom' + +import { + Button, + ButtonComponentType, + ButtonStyleType, + ButtonVariantType, + ComponentSizeType, + DEFAULT_ROUTE_PROMPT_MESSAGE, + Drawer, + DynamicDataTable, + stopPropagation, +} from '@devtron-labs/devtron-fe-common-lib' + +import { ReactComponent as Add } from '@Icons/ic-add.svg' +import { ReactComponent as ICClose } from '@Icons/ic-close.svg' +import { URLS } from '@Config/routes' + +import { CATEGORIES_TABLE_HEADERS } from './constants' +import { CategoriesDataRowType, CategoriesTableColumnsType } from './types' +import { getEmptyCategoriesDataRow } from './utils' + +const ManageCategories = () => { + const [apiCallInProgress] = useState(false) + + const [rows, setRows] = useState([getEmptyCategoriesDataRow()]) + const { push } = useHistory() + + const handleModalClose = () => { + push(URLS.GLOBAL_CONFIG_CLUSTER) + } + + const dataTableHandleChange = ( + updatedRow: CategoriesDataRowType, + headerKey: CategoriesTableColumnsType, + value: string, + ) => { + const updatedRows: CategoriesDataRowType[] = rows.map((row) => + row.id === updatedRow.id + ? { + ...row, + data: { + ...row.data, + [headerKey]: { + ...row.data[headerKey], + value, + }, + }, + } + : row, + ) + + setRows(updatedRows) + } + + const onClickAddRow = () => { + const newRow = getEmptyCategoriesDataRow() + setRows([newRow, ...rows]) + } + + const onDeleteRow = (row: CategoriesDataRowType) => { + const remainingRows = rows.filter(({ id }) => id !== row?.id) + if (remainingRows.length === 0) { + const emptyRowData = getEmptyCategoriesDataRow() + setRows([emptyRowData]) + return + } + setRows(remainingRows) + } + return ( + +
+
+

+ Cluster & Environment Categories +

+ +
+ +
+
+
+ +
+
+
+
+
+ ) +} + +export default ManageCategories diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/constants.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/constants.tsx new file mode 100644 index 0000000000..0a1867ab54 --- /dev/null +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/constants.tsx @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DynamicDataTableHeaderType } from '@devtron-labs/devtron-fe-common-lib' + +import { CategoriesTableColumnsType } from './types' + +export const CATEGORIES_TABLE_HEADERS: DynamicDataTableHeaderType[] = [ + { + label: 'CATEGORIES', + key: 'categories', + width: '240px', + }, + { + label: 'DESCRIPTION', + key: 'description', + width: '1fr', + }, +] diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/types.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/types.tsx new file mode 100644 index 0000000000..7cce18f7d7 --- /dev/null +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/types.tsx @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DynamicDataTableRowType } from '@devtron-labs/devtron-fe-common-lib' + +export type CategoriesTableColumnsType = 'categories' | 'description' +export type CategoriesDataRowType = DynamicDataTableRowType diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts new file mode 100644 index 0000000000..afc7092307 --- /dev/null +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts @@ -0,0 +1,26 @@ +import { DynamicDataTableRowDataType, getUniqueId } from '@devtron-labs/devtron-fe-common-lib' + +import { CategoriesDataRowType } from './types' + +export const getEmptyCategoriesDataRow = (): CategoriesDataRowType => { + const id = getUniqueId() + return { + data: { + categories: { + value: '', + type: DynamicDataTableRowDataType.TEXT, + props: { + placeholder: 'Eg. staging', + }, + }, + description: { + value: '', + type: DynamicDataTableRowDataType.TEXT, + props: { + placeholder: 'Enter description', + }, + }, + }, + id, + } +} diff --git a/src/components/ResourceBrowser/ResourceList/BaseResourceList.tsx b/src/components/ResourceBrowser/ResourceList/BaseResourceList.tsx index c6f2c47047..1eb2573e21 100644 --- a/src/components/ResourceBrowser/ResourceList/BaseResourceList.tsx +++ b/src/components/ResourceBrowser/ResourceList/BaseResourceList.tsx @@ -705,6 +705,7 @@ const BaseResourceListContent = ({ description={null} hideClusterDrawer={handleCloseCreateEnvironmentDrawer} isVirtual={false} // NOTE: if a cluster is visible in RB, it is not a virtual cluster + category={null} /> ) diff --git a/src/components/cluster/Cluster.tsx b/src/components/cluster/Cluster.tsx index 44a425a102..f154267b59 100644 --- a/src/components/cluster/Cluster.tsx +++ b/src/components/cluster/Cluster.tsx @@ -36,12 +36,7 @@ import { generatePath, Route, withRouter } from 'react-router-dom' import { ReactComponent as ClusterIcon } from '@Icons/ic-cluster.svg' import { importComponentFromFELibrary } from '../common' import { List } from '../globalConfigurations/GlobalConfiguration' -import { - getClusterList, - getEnvironmentList, - getCluster, - deleteEnvironment, -} from './cluster.service' +import { getClusterList, getEnvironmentList, getCluster, deleteEnvironment } from './cluster.service' import { ReactComponent as Add } from '@Icons/ic-add.svg' import { ReactComponent as Database } from '@Icons/ic-env.svg' import { ReactComponent as PencilEdit } from '@Icons/ic-pencil.svg' @@ -55,6 +50,7 @@ import { ClusterEnvironmentDrawer } from '@Pages/GlobalConfigurations/ClustersAn import { EnvironmentDeleteComponent } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/EnvironmentDeleteComponent' import CreateCluster from '@Pages/GlobalConfigurations/ClustersAndEnvironments/CreateCluster/CreateCluster.component' import { CreateClusterTypeEnum } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/CreateCluster/types' +import ManageCategories from '@Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component' const getRemoteConnectionConfig = importComponentFromFELibrary('getRemoteConnectionConfig', noop, 'function') const getSSHConfig: ( @@ -224,18 +220,30 @@ class ClusterList extends Component { showInfoIconTippy additionalContainerClasses="mb-20" /> -
{this.state.clusters.map( (cluster) => @@ -252,10 +260,12 @@ class ClusterList extends Component { ), )} + + + + - + { description={null} hideClusterDrawer={this.handleRedirectToClusterList} isVirtual={isVirtualCluster} + category={null} /> ) }} @@ -467,6 +478,7 @@ const Cluster = ({
{CONFIGURATION_TYPES.ENVIRONMENT}
{CONFIGURATION_TYPES.NAMESPACE}
+
{CONFIGURATION_TYPES.CATEGORY}
{CONFIGURATION_TYPES.DESCRIPTION}
@@ -478,6 +490,8 @@ const Cluster = ({ environment_name, prometheus_url, namespace, + categoryId, + category, default: isProduction, description, }) => @@ -492,6 +506,7 @@ const Cluster = ({ environmentName: environment_name, clusterId, namespace, + category:{label: category, value: categoryId}, prometheusEndpoint: prometheus_url, isProduction, description, @@ -514,6 +529,13 @@ const Cluster = ({ )}
{namespace}
+
+ {category && ( +
+ {category} +
+ )} +
{description}
diff --git a/src/components/cluster/cluster.scss b/src/components/cluster/cluster.scss index 8fb54f9502..54a927857e 100644 --- a/src/components/cluster/cluster.scss +++ b/src/components/cluster/cluster.scss @@ -52,7 +52,7 @@ .cluster-env-list_table { display: grid; column-gap: 12px; - grid-template-columns: 20px 170px 1fr 1fr 52px; + grid-template-columns: 20px 170px 160px 160px 1fr 52px; } .override-button.cta.delete { diff --git a/src/config/constants.ts b/src/config/constants.ts index 019a8bbf48..7abf360190 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -766,6 +766,7 @@ export enum MANIFEST_KEY_FIELDS { export enum CONFIGURATION_TYPES { ENVIRONMENT = 'ENVIRONMENT', NAMESPACE = 'NAMESPACE', + CATEGORY = 'CATEGORY', DESCRIPTION = 'DESCRIPTION', } diff --git a/src/config/routes.ts b/src/config/routes.ts index 6c978bbfa5..025efec76e 100644 --- a/src/config/routes.ts +++ b/src/config/routes.ts @@ -76,6 +76,7 @@ export const URLS = { GLOBAL_CONFIG_DOCKER: '/global-config/docker', GLOBAL_CONFIG_CLUSTER: '/global-config/cluster-env', GLOBAL_CONFIG_CREATE_CLUSTER: `/global-config/cluster-env/${CREATE_CLUSTER_PATH}`, + GLOBAL_CONFIG_MANAGE_CATEGORIES: `/global-config/cluster-env/manage-categories`, GLOBAL_CONFIG_CHART: '/global-config/chart-repo', GLOBAL_CONFIG_AUTH: '/global-config/auth', GLOBAL_CONFIG_AUTH_USER_PERMISSION: '/global-config/auth/users', From eea72b4180276293265a573a8e8e102c9c7b57c5 Mon Sep 17 00:00:00 2001 From: vivek-devtron Date: Fri, 16 May 2025 17:52:17 +0530 Subject: [PATCH 2/7] added dropdown on cluster --- .../ClusterEnvironmentDrawer.tsx | 1 - .../ManageCategories.component.tsx | 20 +++++++++------- src/components/cluster/Cluster.tsx | 5 ++-- src/components/cluster/ClusterForm.tsx | 24 +++++++++++++++++++ src/components/cluster/cluster.type.ts | 1 + 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx index 9c0449c977..74ec067a00 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx @@ -360,7 +360,6 @@ export const ClusterEnvironmentDrawer = ({
{ /> -
-
+
+
- +
+ +
diff --git a/src/components/cluster/ClusterForm.tsx b/src/components/cluster/ClusterForm.tsx index 117493a6e4..18229acf82 100644 --- a/src/components/cluster/ClusterForm.tsx +++ b/src/components/cluster/ClusterForm.tsx @@ -34,9 +34,11 @@ import { InfoBlock, NewClusterFormProps, noop, + OptionType, PasswordField, RadioGroup, RadioGroupItem, + SelectPicker, showError, Textarea, ToastManager, @@ -133,6 +135,7 @@ const ClusterForm = ({ handleModalClose = noop, isTlsConnection: initialIsTlsConnection = false, installationId, + category, }: ClusterFormProps & Partial) => { const [prometheusToggleEnabled, setPrometheusToggleEnabled] = useState(!!prometheusUrl) const [prometheusAuthenticationType, setPrometheusAuthenticationType] = useState({ @@ -171,6 +174,7 @@ const ClusterForm = ({ const areSomeEntriesSelected = Object.values(isClusterSelected).some((_selected) => _selected) const [isConnectedViaProxyTemp, setIsConnectedViaProxyTemp] = useState(isConnectedViaProxy) const [isConnectedViaSSHTunnelTemp, setIsConnectedViaSSHTunnelTemp] = useState(isConnectedViaSSHTunnel) + const [selectedCategory, setSelectedCategory] = useState>(category) useEffect( () => () => { @@ -390,6 +394,7 @@ const ClusterForm = ({ isAnonymous: state.authType.value === AuthenticationType.ANONYMOUS, }, server_url: '', + category: selectedCategory.value, }) const onValidation = async (state) => { @@ -679,6 +684,10 @@ const ClusterForm = ({ setSSHConnectionType(authType) } + const handleCategoryChange = (selected: OptionType) => { + setSelectedCategory(selected) + } + const clusterTitle = () => { if (!id) { return 'Add Cluster' @@ -795,6 +804,21 @@ const ClusterForm = ({ Production Non - Production +
+ +
{id !== DEFAULT_CLUSTER_ID && RemoteConnectionRadio && ( <>
diff --git a/src/components/cluster/cluster.type.ts b/src/components/cluster/cluster.type.ts index eb13dfed45..146d92157e 100644 --- a/src/components/cluster/cluster.type.ts +++ b/src/components/cluster/cluster.type.ts @@ -193,6 +193,7 @@ export type EditClusterFormProps = { sshServerAddress: string isConnectedViaSSHTunnel: boolean isTlsConnection: boolean + category: OptionType } export type ClusterFormProps = { reload: () => void } & ( From 6427d311753514ce8e083b857dc99e4b0e4e0e0a Mon Sep 17 00:00:00 2001 From: vivek-devtron Date: Fri, 16 May 2025 18:01:00 +0530 Subject: [PATCH 3/7] env payload fix --- .../ClusterEnvironmentDrawer/utils.ts | 1 + src/components/cluster/ClusterForm.tsx | 30 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/utils.ts b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/utils.ts index 9ba4588a34..6f035f108d 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/utils.ts +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/utils.ts @@ -42,6 +42,7 @@ export const getClusterEnvironmentUpdatePayload = ({ default: data.isProduction, description: data.description || '', updateLabels: !!namespaceLabels, + category: data.category?.value, ...(namespaceLabels ? { namespaceResourceVersion: resourceVersion, diff --git a/src/components/cluster/ClusterForm.tsx b/src/components/cluster/ClusterForm.tsx index 18229acf82..07b892bdcc 100644 --- a/src/components/cluster/ClusterForm.tsx +++ b/src/components/cluster/ClusterForm.tsx @@ -797,28 +797,26 @@ const ClusterForm = ({
Production Non - Production -
- -
+ {id !== DEFAULT_CLUSTER_ID && RemoteConnectionRadio && ( <>
From 09c6672143d822a9ce1547b201ffe590d1f1d217 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Tue, 20 May 2025 15:36:41 +0530 Subject: [PATCH 4/7] chore: css fixes --- .../ManageCategories/ManageCategories.component.tsx | 2 +- src/components/cluster/Cluster.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx index 30fcbfae33..a2bcf306b5 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx @@ -105,7 +105,7 @@ const ManageCategories = () => { component={ButtonComponentType.button} startIcon={} size={ComponentSizeType.medium} - text="Manage Categories" + text="Add Categories" onClick={onClickAddRow} />
diff --git a/src/components/cluster/Cluster.tsx b/src/components/cluster/Cluster.tsx index bbb0242611..e5be5802e7 100644 --- a/src/components/cluster/Cluster.tsx +++ b/src/components/cluster/Cluster.tsx @@ -230,6 +230,7 @@ class ClusterList extends Component { startIcon={} size={ComponentSizeType.medium} text="Manage Categories" + variant={ButtonVariantType.secondary} />
)} -
- +
diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx index a2bcf306b5..49ea2cb390 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useHistory } from 'react-router-dom' import { @@ -10,23 +10,37 @@ import { DEFAULT_ROUTE_PROMPT_MESSAGE, Drawer, DynamicDataTable, + ErrorScreenManager, + GenericEmptyState, + GenericFilterEmptyState, + Icon, + Progressing, + SearchBar, stopPropagation, + useAsync, + useStateFilters, } from '@devtron-labs/devtron-fe-common-lib' -import { ReactComponent as Add } from '@Icons/ic-add.svg' -import { ReactComponent as ICClose } from '@Icons/ic-close.svg' import { URLS } from '@Config/routes' import { CATEGORIES_TABLE_HEADERS } from './constants' +import { getCategoryList } from './service' import { CategoriesDataRowType, CategoriesTableColumnsType } from './types' -import { getEmptyCategoriesDataRow } from './utils' +import { getEmptyCategoriesDataRow, getInitialCategoryListData } from './utils' const ManageCategories = () => { - const [apiCallInProgress] = useState(false) + const { searchKey, handleSearch, clearFilters } = useStateFilters() + const [categoryLoader, categoryList, categoryListError, reloadCategoryList] = useAsync(getCategoryList) const [rows, setRows] = useState([getEmptyCategoriesDataRow()]) const { push } = useHistory() + useEffect(() => { + if (categoryList) { + setRows(getInitialCategoryListData(categoryList.result)) + } + }, [categoryList, searchKey]) + const handleModalClose = () => { push(URLS.GLOBAL_CONFIG_CLUSTER) } @@ -68,76 +82,122 @@ const ManageCategories = () => { } setRows(remainingRows) } + + const renderHeader = () => ( +
+

Cluster & Environment Categories

+ +
+ ) + + if (categoryListError) { + return + } + + if (categoryLoader) { + return + } + + if (categoryList.result.length === 0) { + return ( + + ) + } + + const renderSearchBar = () => ( +
+ +
+ ) + + const renderFooter = () => ( +
+
+ ) + + const renderList = () => { + if (!categoryList.result.some((res) => res.category.includes(searchKey))) { + return + } + return ( + <> +
+ +
+ {renderFooter()} + + ) + } + return (
-
-

- Cluster & Environment Categories -

- -
- -
-
-
-
- -
-
-
-
+ {renderHeader()} + {renderSearchBar()} + {renderList()}
) diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/service.ts b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/service.ts new file mode 100644 index 0000000000..3533541d9b --- /dev/null +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/service.ts @@ -0,0 +1,31 @@ +import { ResponseType, showError } from '@devtron-labs/devtron-fe-common-lib' + +import { ManageCategoryDTO } from './types' + +const getMockData: () => ManageCategoryDTO[] = () => [ + { + id: 1, + category: 'staging', + description: 'staging environment', + }, + { + id: 2, + category: 'production', + description: 'production environment', + }, +] + +export const getCategoryList = async (): Promise> => { + try { + // const response = await get(`${ROUTES.CATEGORIES}`) + + return { + code: 200, + status: 'OK', + result: getMockData(), + } + } catch (err) { + showError(err) + throw err + } +} diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/types.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/types.tsx index 7cce18f7d7..d3935fe30f 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/types.tsx +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/types.tsx @@ -16,5 +16,11 @@ import { DynamicDataTableRowType } from '@devtron-labs/devtron-fe-common-lib' +export interface ManageCategoryDTO { + id: number + category: string + description: string +} + export type CategoriesTableColumnsType = 'categories' | 'description' export type CategoriesDataRowType = DynamicDataTableRowType diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts index afc7092307..5abb60653c 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts @@ -1,9 +1,10 @@ import { DynamicDataTableRowDataType, getUniqueId } from '@devtron-labs/devtron-fe-common-lib' -import { CategoriesDataRowType } from './types' +import { CategoriesDataRowType, ManageCategoryDTO } from './types' export const getEmptyCategoriesDataRow = (): CategoriesDataRowType => { const id = getUniqueId() + return { data: { categories: { @@ -24,3 +25,24 @@ export const getEmptyCategoriesDataRow = (): CategoriesDataRowType => { id, } } + +export const getInitialCategoryListData = (categoryList: ManageCategoryDTO[]) => + categoryList.map((category) => ({ + data: { + categories: { + value: category.category, + type: DynamicDataTableRowDataType.TEXT, + props: { + placeholder: 'Eg. staging', + }, + }, + description: { + value: category.description, + type: DynamicDataTableRowDataType.TEXT, + props: { + placeholder: 'Enter description', + }, + }, + }, + id: category.id, + })) diff --git a/src/components/cluster/AssignCategorySelect.tsx b/src/components/cluster/AssignCategorySelect.tsx new file mode 100644 index 0000000000..0023fecb11 --- /dev/null +++ b/src/components/cluster/AssignCategorySelect.tsx @@ -0,0 +1,32 @@ +import { useState } from 'react' + +import { ComponentSizeType, SelectPicker } from '@devtron-labs/devtron-fe-common-lib' + +export const categoryList = ['staging', 'production'] + +export const getCategoryList = () => + categoryList.map((category) => ({ + label: category, + value: category, + })) + +export const AssignCategorySelect = () => { + const [selectedCategory, setSelectedCategory] = useState(null) + + const handleCategoryChange = (selected) => { + setSelectedCategory(selected) + } + + return ( + + ) +} diff --git a/src/components/cluster/ClusterComponents.tsx b/src/components/cluster/ClusterComponents.tsx new file mode 100644 index 0000000000..33b9e13568 --- /dev/null +++ b/src/components/cluster/ClusterComponents.tsx @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useCallback, useEffect, useRef, useState } from 'react' +import { generatePath, Route, useHistory } from 'react-router-dom' + +import { + Button, + ButtonComponentType, + ButtonVariantType, + ComponentSizeType, + ErrorScreenNotAuthorized, + FeatureTitleWithInfo, + Icon, + noop, + Progressing, + Reload, + showError, + sortCallback, +} from '@devtron-labs/devtron-fe-common-lib' + +import { importComponentFromFELibrary } from '@Components/common' +import { ViewType } from '@Config/constants' +import { URLS } from '@Config/routes' +import { ClusterEnvironmentDrawer } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer' +import CreateCluster from '@Pages/GlobalConfigurations/ClustersAndEnvironments/CreateCluster/CreateCluster.component' +import { CreateClusterTypeEnum } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/CreateCluster/types' +import ManageCategories from '@Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component' + +import { getClusterList, getEnvironmentList } from './cluster.service' +import { ClusterProps, POLLING_INTERVAL } from './cluster.type' +import { ClusterList } from './ClusterList' + +const getRemoteConnectionConfig = importComponentFromFELibrary('getRemoteConnectionConfig', noop, 'function') + +const ClusterComponents = ({ isSuperAdmin }: ClusterProps) => { + const [view, setView] = useState(ViewType.LOADING) + const [clusters, setClusters] = useState([]) + const [clusterEnvMap, setClusterEnvMap] = useState({}) + // const [browseFile, setBrowseFile] = useState(false) + // const [showEditCluster, setShowEditCluster] = useState(false) + + const timerRef = useRef(null) + + const history = useHistory() + + const pollClusterList = useCallback(async () => { + try { + const { result } = await getClusterList() + let updatedClusters = result + ? result.map((c) => ({ + ...c, + environments: clusterEnvMap[c.id], + })) + : [] + + updatedClusters = updatedClusters.concat({ + id: null, + cluster_name: '', + server_url: '', + proxyUrl: '', + sshTunnelConfig: { + user: '', + password: '', + authKey: '', + sshServerAddress: '', + }, + active: true, + config: {}, + environments: [], + insecureSkipTlsVerify: true, + isVirtualCluster: false, + remoteConnectionConfig: getRemoteConnectionConfig(), + installationId: 0, + }) + + updatedClusters = updatedClusters.sort((a, b) => sortCallback('cluster_name', a, b)) + setClusters(updatedClusters) + + const stillPolling = updatedClusters.find( + (c) => c.agentInstallationStage === 1 || c.agentInstallationStage === 3, + ) + if (!stillPolling && timerRef.current) { + clearInterval(timerRef.current) + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (err) { + // silent fail or optional error toast + } + }, [clusterEnvMap]) + + const initialize = useCallback(() => { + if (timerRef.current) { + clearInterval(timerRef.current) + } + + Promise.all([getClusterList(), window._env_.K8S_CLIENT ? { result: undefined } : getEnvironmentList()]) + .then(([clusterRes, envResponse]) => { + const environments = envResponse.result || [] + const clusterEnvironmentMap = environments.reduce((agg, curr) => { + const newAgg = { ...agg } + newAgg[curr.cluster_id] = newAgg[curr.cluster_id] || [] + newAgg[curr.cluster_id].push(curr) + return newAgg + }, {}) + + let clustersList = clusterRes.result || [] + clustersList = clustersList.map((cluster) => ({ + ...cluster, + environments: clusterEnvironmentMap[cluster.id], + })) + + clustersList = clustersList.sort((a, b) => sortCallback('cluster_name', a, b)) + + setClusters(clustersList) + setClusterEnvMap(clusterEnvironmentMap) + setView(ViewType.FORM) + + const pollingCluster = clustersList.find( + (c) => c.agentInstallationStage === 1 || c.agentInstallationStage === 3, + ) + if (pollingCluster) { + timerRef.current = setInterval(pollClusterList, POLLING_INTERVAL) + } + }) + .catch((error) => { + showError(error) + setView(ViewType.ERROR) + }) + }, []) + + // const toggleBrowseFile = () => setBrowseFile((prev) => !prev) + // const toggleShowEditCluster = () => setShowEditCluster((prev) => !prev) + + const handleRedirectToClusterList = () => { + history.push(URLS.GLOBAL_CONFIG_CLUSTER) + } + + useEffect(() => { + if (isSuperAdmin) { + initialize() + } + + return () => { + if (timerRef.current) { + clearInterval(timerRef.current) + } + } + }, [initialize, isSuperAdmin]) + + if (!isSuperAdmin) { + return ( +
+ +
+ ) + } + + if (view === ViewType.LOADING) return + if (view === ViewType.ERROR) return + + const moduleBasedTitle = `Clusters${window._env_.K8S_CLIENT ? '' : ' and Environments'}` + + return ( +
+
+ `Manage your organization’s ${moduleBasedTitle.toLowerCase()}.`} + docLink="GLOBAL_CONFIG_CLUSTER" + showInfoIconTippy + additionalContainerClasses="mb-20" + /> +
+
+
+ + {clusters.map( + (cluster) => + cluster.id && ( + + ), + )} + + + + + + + + + + { + const { clusterName } = props.match.params + const foundCluster = clusters.find((c) => c.cluster_name === clusterName) || {} + const { isVirtualCluster, id: clusterId } = foundCluster + + return ( + + ) + }} + /> +
+ ) +} + +export default ClusterComponents diff --git a/src/components/cluster/ClusterEnvironmentList.tsx b/src/components/cluster/ClusterEnvironmentList.tsx new file mode 100644 index 0000000000..4c75416ccd --- /dev/null +++ b/src/components/cluster/ClusterEnvironmentList.tsx @@ -0,0 +1,180 @@ +import { useState } from 'react' + +import { + Button, + ButtonStyleType, + ButtonVariantType, + ComponentSizeType, + Icon, + sortCallback, +} from '@devtron-labs/devtron-fe-common-lib' + +import { ReactComponent as Trash } from '@Icons/ic-delete-interactive.svg' +import { ReactComponent as Database } from '@Icons/ic-env.svg' +import { ReactComponent as VirtualEnvIcon } from '@Icons/ic-environment-temp.svg' +import { CONFIGURATION_TYPES } from '@Config/constants' +import { ClusterEnvironmentDrawer } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer' +import { EnvironmentDeleteComponent } from '@Pages/GlobalConfigurations/ClustersAndEnvironments/EnvironmentDeleteComponent' + +import { deleteEnvironment } from './cluster.service' +import { ClusterEnvironmentListProps } from './cluster.type' + +export const ClusterEnvironmentList = ({ + clusterId, + reload, + isVirtualCluster, + newEnvs, + clusterName, +}: ClusterEnvironmentListProps) => { + const [environment, setEnvironment] = useState(null) + const [confirmation, setConfirmation] = useState(false) + const [showWindow, setShowWindow] = useState(false) + + const envIcon = () => { + if (isVirtualCluster) { + return + } + return + } + + const showWindowModal = () => setShowWindow(true) + + const hideClusterDrawer = () => setShowWindow(false) + + const showToggleConfirmation = () => setConfirmation(true) + + const hideConfirmationModal = () => setConfirmation(false) + + const onDelete = async () => { + const deletePayload = { + id: environment.id, + environment_name: environment.environmentName, + cluster_id: environment.clusterId, + prometheus_endpoint: environment.prometheusEndpoint, + namespace: environment.namespace || '', + active: true, + default: environment.isProduction, + description: environment.description || '', + } + await deleteEnvironment(deletePayload) + reload() + } + + const renderActionButton = (environmentName) => ( +
+
+
+
+ ) + + const renderEnvironmentList = () => + newEnvs + .sort((a, b) => sortCallback('environment_name', a, b)) + .map( + ({ + id, + environment_name: environmentName, + prometheusEndpoint, + namespace, + categoryId, + category, + default: isProduction, + description, + }) => + id && ( +
+ setEnvironment({ + id, + environmentName, + prometheusEndpoint, + clusterId, + namespace, + category: { label: category, value: categoryId }, + default: isProduction, + description, + }) + } + > + {envIcon()} + +
+ {environmentName} + + {isProduction && ( +
+ Prod +
+ )} +
+
{namespace}
+
+ {category && ( + + {category} + + )} +
+
{description}
+ {renderActionButton(environmentName)} +
+ ), + ) + + return ( +
+
+
+
{CONFIGURATION_TYPES.ENVIRONMENT}
+
{CONFIGURATION_TYPES.NAMESPACE}
+
{CONFIGURATION_TYPES.CATEGORY}
+
{CONFIGURATION_TYPES.DESCRIPTION}
+
+
+ {renderEnvironmentList()} + + {confirmation && ( + + )} + + {showWindow && ( + + )} +
+ ) +} diff --git a/src/components/cluster/ClusterForm.tsx b/src/components/cluster/ClusterForm.tsx index 07b892bdcc..84db27bd6b 100644 --- a/src/components/cluster/ClusterForm.tsx +++ b/src/components/cluster/ClusterForm.tsx @@ -38,7 +38,6 @@ import { PasswordField, RadioGroup, RadioGroupItem, - SelectPicker, showError, Textarea, ToastManager, @@ -47,10 +46,8 @@ import { useAsync, } from '@devtron-labs/devtron-fe-common-lib' -import { ReactComponent as Warning } from '@Icons/ic-alert-triangle.svg' import { ReactComponent as ForwardArrow } from '@Icons/ic-arrow-right.svg' import { ReactComponent as Trash } from '@Icons/ic-delete-interactive.svg' -import { ReactComponent as Error } from '@Icons/ic-error-exclamation.svg' import { ReactComponent as ICHelpOutline } from '@Icons/ic-help-outline.svg' import { ReactComponent as Edit } from '@Icons/ic-pencil.svg' import { ReactComponent as ErrorIcon } from '@Icons/ic-warning-y6.svg' @@ -61,6 +58,7 @@ import { importComponentFromFELibrary, useForm } from '../common' import { RemoteConnectionType } from '../dockerRegistry/dockerType' import { getModuleInfo } from '../v2/devtronStackManager/DevtronStackManager.service' import { ModuleStatus } from '../v2/devtronStackManager/DevtronStackManager.type' +import { AssignCategorySelect } from './AssignCategorySelect' import { saveCluster, saveClusters, updateCluster, validateCluster } from './cluster.service' import { AuthenticationType, @@ -72,7 +70,12 @@ import { SSHAuthenticationType, UserDetails, } from './cluster.type' -import { getServerURLFromLocalStorage } from './cluster.util' +import { + getServerURLFromLocalStorage, + PrometheusRequiredFieldInfo, + PrometheusWarningInfo, + renderKubeConfigClusterCountInfo, +} from './cluster.util' import ClusterInfoStepsModal from './ClusterInfoStepsModal' import { ADD_CLUSTER_FORM_LOCAL_STORAGE_KEY } from './constants' import DeleteClusterConfirmationModal from './DeleteClusterConfirmationModal' @@ -83,38 +86,6 @@ import './cluster.scss' const RemoteConnectionRadio = importComponentFromFELibrary('RemoteConnectionRadio') const getRemoteConnectionConfig = importComponentFromFELibrary('getRemoteConnectionConfig', noop, 'function') -const PrometheusWarningInfo = () => ( -
-
- -
- Warning: Prometheus configuration will be removed and you - won’t be able to see metrics for applications deployed in this cluster. -
-
-
-) - -const PrometheusRequiredFieldInfo = () => ( -
-
- -
- Fill all the required fields OR turn off the above switch to skip configuring prometheus. -
-
-
-) - -const renderKubeConfigClusterCountInfo = (clusterCount: number) => ( -
-
- {clusterCount} valid cluster(s). - Select the cluster you want to add/update -
-
-) - const ClusterForm = ({ id = null, clusterName, @@ -684,10 +655,6 @@ const ClusterForm = ({ setSSHConnectionType(authType) } - const handleCategoryChange = (selected: OptionType) => { - setSelectedCategory(selected) - } - const clusterTitle = () => { if (!id) { return 'Add Cluster' @@ -804,19 +771,9 @@ const ClusterForm = ({ Production Non - Production - + + + {id !== DEFAULT_CLUSTER_ID && RemoteConnectionRadio && ( <>
diff --git a/src/components/cluster/ClusterList.tsx b/src/components/cluster/ClusterList.tsx new file mode 100644 index 0000000000..c5f3312085 --- /dev/null +++ b/src/components/cluster/ClusterList.tsx @@ -0,0 +1,197 @@ +import { useRef, useState } from 'react' + +import { + Button, + ButtonComponentType, + ButtonStyleType, + ButtonVariantType, + ComponentSizeType, + Drawer, + getClassNameForStickyHeaderWithShadow, + Icon, + noop, + showError, + useStickyEvent, +} from '@devtron-labs/devtron-fe-common-lib' + +import { clusterId } from '@Components/ClusterNodes/__mocks__/clusterAbout.mock' +import { importComponentFromFELibrary } from '@Components/common' +import { URLS } from '@Config/routes' + +import { List } from '../globalConfigurations/GlobalConfiguration' +import { getCluster } from './cluster.service' +import { ClusterListProps, EditClusterFormProps } from './cluster.type' +import { renderNoEnvironmentTab } from './cluster.util' +import { ClusterEnvironmentList } from './ClusterEnvironmentList' +import ClusterForm from './ClusterForm' + +const VirtualClusterForm = importComponentFromFELibrary('VirtualClusterForm', null, 'function') + +const getSSHConfig: ( + ...props +) => Pick = + importComponentFromFELibrary('getSSHConfig', noop, 'function') + +export const ClusterList = ({ + isVirtualCluster, + environments, + reload, + clusterName, + sshTunnelConfig, + isProd, + serverURL, + prometheusURL, + proxyUrl, + insecureSkipTlsVerify, + installationId, + category, + toConnectWithSSHTunnel, +}: ClusterListProps) => { + const [editMode, toggleEditMode] = useState(false) + const [prometheusAuth, setPrometheusAuth] = useState(undefined) + + const drawerRef = useRef(null) + + const { stickyElementRef, isStuck: isHeaderStuck } = useStickyEvent({ + containerSelector: '.global-configuration__component-wrapper', + identifier: `cluster-list__${clusterName}`, + }) + + const handleModalClose = () => { + toggleEditMode(false) + } + + const newEnvs = clusterId ? [{ id: null }].concat(environments || []) : environments || [] + + const handleEdit = async () => { + try { + const { result } = await getCluster(+clusterId) + setPrometheusAuth(result.prometheusAuth) + toggleEditMode((t) => !t) + } catch (err) { + showError(err) + } + } + + const editModeToggle = (): void => { + if (!clusterId) { + toggleEditMode((t) => !t) + } + } + + const clusterIcon = () => { + if (isVirtualCluster) { + return + } + return + } + + const handleToggleEditMode = (): void => { + toggleEditMode((t) => !t) + } + + const subTitle: string = isVirtualCluster ? 'Isolated cluster' : serverURL + + return ( +
+ + {!clusterId && ( + + + + )} +
+ {clusterId && clusterIcon()} + + {clusterName && ( +
+
+ {clusterId && ( +
+ ) +} diff --git a/src/components/cluster/cluster.type.ts b/src/components/cluster/cluster.type.ts index 146d92157e..7bdde371a3 100644 --- a/src/components/cluster/cluster.type.ts +++ b/src/components/cluster/cluster.type.ts @@ -19,8 +19,6 @@ import { RouteComponentProps } from 'react-router-dom' import { OptionType, SelectPickerOptionType } from '@devtron-labs/devtron-fe-common-lib' -import { SERVER_MODE_TYPE } from '../../config' - export const POLLING_INTERVAL = 30000 export const DEFAULT_CLUSTER_ID = 1 @@ -139,8 +137,7 @@ export interface UserInfos { config: ConfigCluster } -export interface ClusterListProps extends RouteComponentProps<{}> { - serverMode: SERVER_MODE_TYPE +export interface ClusterProps extends RouteComponentProps<{}> { isSuperAdmin: boolean } @@ -228,3 +225,20 @@ export interface DeleteClusterConfirmationModalProps { export interface DeleteClusterPayload { id: number } + +export interface ClusterListProps { + clusterName: string + isVirtualCluster: boolean + environments: any[] + reload: () => void + sshTunnelConfig: any + isProd: boolean + serverURL: string + prometheusURL: string + prometheusAuth: any + proxyUrl: string + insecureSkipTlsVerify: boolean + installationId: number + category: OptionType + toConnectWithSSHTunnel: boolean +} diff --git a/src/components/cluster/cluster.util.ts b/src/components/cluster/cluster.util.tsx similarity index 68% rename from src/components/cluster/cluster.util.ts rename to src/components/cluster/cluster.util.tsx index 4fd50747bd..cb480e8ae3 100644 --- a/src/components/cluster/cluster.util.ts +++ b/src/components/cluster/cluster.util.tsx @@ -14,15 +14,18 @@ * limitations under the License. */ -import { NodeTaintType, SelectPickerOptionType, OptionType } from '@devtron-labs/devtron-fe-common-lib' +import { Icon, NodeTaintType, OptionType, SelectPickerOptionType } from '@devtron-labs/devtron-fe-common-lib' + +import { ReactComponent as Warning } from '@Icons/ic-alert-triangle.svg' + import { - ClusterComponentType, - ClusterComponentStatusType, + AddClusterFormPrefilledInfoType, + AddEnvironmentFormPrefilledInfoType, ClusterComponentStatus, + ClusterComponentStatusType, + ClusterComponentType, ClusterTerminalParamsType, emptyClusterTerminalParamsData, - AddClusterFormPrefilledInfoType, - AddEnvironmentFormPrefilledInfoType, } from './cluster.type' import { ADD_CLUSTER_FORM_LOCAL_STORAGE_KEY, ADD_ENVIRONMENT_FORM_LOCAL_STORAGE_KEY } from './constants' @@ -84,15 +87,14 @@ export function getClusterTerminalParamsData( } } -export const createTaintsList = (list: any[], nodeLabel: string): Map => { - return list?.reduce((taints, node) => { +export const createTaintsList = (list: any[], nodeLabel: string): Map => + list?.reduce((taints, node) => { const label = node[nodeLabel] if (!taints.has(label)) { taints.set(label, node.taints) } return taints }, new Map()) -} export const getServerURLFromLocalStorage = (fallbackServerUrl: string): string => { const stringifiedClusterData = localStorage.getItem(ADD_CLUSTER_FORM_LOCAL_STORAGE_KEY) @@ -102,6 +104,7 @@ export const getServerURLFromLocalStorage = (fallbackServerUrl: string): string const clusterData: AddClusterFormPrefilledInfoType = JSON.parse(stringifiedClusterData) const serverURL = clusterData?.serverURL || fallbackServerUrl return serverURL + // eslint-disable-next-line no-empty } catch {} } @@ -116,7 +119,49 @@ export const getNamespaceFromLocalStorage = (fallbackNamespace: string): string const envData: AddEnvironmentFormPrefilledInfoType = JSON.parse(stringifiedEnvData) const namespace = envData?.namespace || fallbackNamespace return namespace + // eslint-disable-next-line no-empty } catch {} } - return fallbackNamespace + return fallbackNamespace } + +export const PrometheusWarningInfo = () => ( +
+
+ +
+ Warning: Prometheus configuration will be removed and you + won’t be able to see metrics for applications deployed in this cluster. +
+
+
+) + +export const PrometheusRequiredFieldInfo = () => ( +
+
+ +
+ Fill all the required fields OR turn off the above switch to skip configuring prometheus. +
+
+
+) + +export const renderKubeConfigClusterCountInfo = (clusterCount: number) => ( +
+
+ {clusterCount} valid cluster(s). + Select the cluster you want to add/update +
+
+) + +export const renderNoEnvironmentTab = () => ( +
+
+
No Environments Added
+
This cluster doesn't have any environments yet
+
+
+) diff --git a/src/components/globalConfigurations/GlobalConfiguration.tsx b/src/components/globalConfigurations/GlobalConfiguration.tsx index 44935c59d4..84954a30ba 100644 --- a/src/components/globalConfigurations/GlobalConfiguration.tsx +++ b/src/components/globalConfigurations/GlobalConfiguration.tsx @@ -54,7 +54,7 @@ const HostURLConfiguration = lazy(() => import('../hostURL/HostURL')) const GitOpsConfiguration = lazy(() => import('../gitOps/GitOpsConfiguration')) const GitProvider = lazy(() => import('../gitProvider/GitProvider')) const Docker = lazy(() => import('../dockerRegistry/Docker')) -const ClusterList = lazy(() => import('../cluster/Cluster')) +const Clusters = lazy(() => import('../cluster/ClusterComponents')) const ChartRepo = lazy(() => import('../chartRepo/ChartRepo')) const Notifier = lazy(() => import('../notifications/Notifications')) const Project = lazy(() => import('../project/ProjectList')) @@ -222,7 +222,7 @@ const NavItem = ({ serverMode }) => { { name: `Clusters${serverMode === SERVER_MODE.EA_ONLY ? '' : ' & Environments'}`, href: URLS.GLOBAL_CONFIG_CLUSTER, - component: ClusterList, + component: Clusters, isAvailableInEA: true, isAvailableInDesktop: true, }, @@ -622,7 +622,7 @@ const Body = ({ getHostURLConfig, checkList, serverMode, handleChecklistUpdate, { - return + return }} /> {!window._env_.K8S_CLIENT && [ From cf5080019a9f48810e648d2bda0a200574acf5e9 Mon Sep 17 00:00:00 2001 From: shivani170 Date: Thu, 22 May 2025 16:12:37 +0530 Subject: [PATCH 6/7] fix: Payload update for category in environment drawer --- .../ClusterEnvironmentDrawer.tsx | 85 +++++---- .../ManageCategories.component.tsx | 5 +- .../ManageCategories/utils.ts | 8 +- .../cluster/AssignCategorySelect.tsx | 15 +- src/components/cluster/Cluster.tsx | 11 +- .../cluster/ClusterEnvironmentList.tsx | 17 +- src/components/cluster/ClusterForm.tsx | 174 +++++++++--------- src/components/cluster/ClusterList.tsx | 2 +- src/components/cluster/cluster.type.ts | 34 ++-- 9 files changed, 175 insertions(+), 176 deletions(-) diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx index 54f1cd2c4e..72a76b1a1e 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ClusterEnvironmentDrawer/ClusterEnvironmentDrawer.tsx @@ -27,7 +27,6 @@ import { Drawer, GenericEmptyState, noop, - OptionType, ServerErrors, showError, stopPropagation, @@ -95,8 +94,6 @@ export const ClusterEnvironmentDrawer = ({ error: null, }) - const [selectedCategory, setSelectedCategory] = useState>(category) - const addEnvironmentHeaderText = `Add Environment in '${clusterName}'` /** @@ -279,6 +276,10 @@ export const ClusterEnvironmentDrawer = ({ /> ) + const handleSelectedCategory = (_selectedCategory) => { + register('category').onChange({ target: { name: 'category', value: _selectedCategory } }) + } + const renderContent = () => { if (!clusterId) { return ( @@ -297,45 +298,41 @@ export const ClusterEnvironmentDrawer = ({ onSubmit={handleSubmit(namespaceLabels.labels ? withLabelEditValidation : onValidation())} noValidate > -
-
- -
-
- -
-
- -
+
+ + + + + {!isVirtual && ( -
+
)} -
+
diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx index 49ea2cb390..7b42a940cf 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/ManageCategories.component.tsx @@ -37,7 +37,10 @@ const ManageCategories = () => { useEffect(() => { if (categoryList) { - setRows(getInitialCategoryListData(categoryList.result)) + const filteredCategories = categoryList.result.filter((category) => + category.category.toLowerCase().includes(searchKey.toLowerCase()), + ) + setRows(getInitialCategoryListData(filteredCategories)) } }, [categoryList, searchKey]) diff --git a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts index 5abb60653c..5ab6923dad 100644 --- a/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts +++ b/src/Pages/GlobalConfigurations/ClustersAndEnvironments/ManageCategories/utils.ts @@ -26,8 +26,11 @@ export const getEmptyCategoriesDataRow = (): CategoriesDataRowType => { } } -export const getInitialCategoryListData = (categoryList: ManageCategoryDTO[]) => - categoryList.map((category) => ({ +export const getInitialCategoryListData = (categoryList: ManageCategoryDTO[]): CategoriesDataRowType[] => { + if (categoryList.length === 0) { + return [getEmptyCategoriesDataRow()] + } + return categoryList.map((category) => ({ data: { categories: { value: category.category, @@ -46,3 +49,4 @@ export const getInitialCategoryListData = (categoryList: ManageCategoryDTO[]) => }, id: category.id, })) +} diff --git a/src/components/cluster/AssignCategorySelect.tsx b/src/components/cluster/AssignCategorySelect.tsx index 0023fecb11..ddee11902a 100644 --- a/src/components/cluster/AssignCategorySelect.tsx +++ b/src/components/cluster/AssignCategorySelect.tsx @@ -1,19 +1,17 @@ -import { useState } from 'react' +import { ComponentSizeType, OptionType, SelectPicker } from '@devtron-labs/devtron-fe-common-lib' -import { ComponentSizeType, SelectPicker } from '@devtron-labs/devtron-fe-common-lib' +import { AssignCategorySelectTypes } from './cluster.type' export const categoryList = ['staging', 'production'] export const getCategoryList = () => categoryList.map((category) => ({ label: category, - value: category, + value: +category, })) -export const AssignCategorySelect = () => { - const [selectedCategory, setSelectedCategory] = useState(null) - - const handleCategoryChange = (selected) => { +export const AssignCategorySelect = ({ selectedCategory, setSelectedCategory }: AssignCategorySelectTypes) => { + const handleCategoryChange = (selected: OptionType) => { setSelectedCategory(selected) } @@ -26,7 +24,8 @@ export const AssignCategorySelect = () => { options={getCategoryList()} onChange={handleCategoryChange} value={selectedCategory} - size={ComponentSizeType.large} + size={ComponentSizeType.medium} + fullWidth /> ) } diff --git a/src/components/cluster/Cluster.tsx b/src/components/cluster/Cluster.tsx index 0a24abf332..7693b46e8c 100644 --- a/src/components/cluster/Cluster.tsx +++ b/src/components/cluster/Cluster.tsx @@ -31,6 +31,7 @@ import { ComponentSizeType, useStickyEvent, getClassNameForStickyHeaderWithShadow, + Icon, } from '@devtron-labs/devtron-fe-common-lib' import { generatePath, Route, withRouter } from 'react-router-dom' import { ReactComponent as ClusterIcon } from '@Icons/ic-cluster.svg' @@ -227,7 +228,7 @@ class ClusterList extends Component { to: URLS.GLOBAL_CONFIG_MANAGE_CATEGORIES, }} component={ButtonComponentType.link} - startIcon={} + startIcon={} size={ComponentSizeType.medium} text="Manage Categories" variant={ButtonVariantType.secondary} @@ -271,10 +272,8 @@ class ClusterList extends Component { path={`${URLS.GLOBAL_CONFIG_CLUSTER}/:clusterName${URLS.CREATE_ENVIRONMENT}`} render={(props) => { const clusterName = props.match.params.clusterName - const { - isVirtualCluster, - id: clusterId, - } = this.state.clusters.find((cluster) => cluster.cluster_name === clusterName) || {} + const { isVirtualCluster, id: clusterId } = + this.state.clusters.find((cluster) => cluster.cluster_name === clusterName) || {} return ( { - if (isVirtualCluster) { - return - } - return - } - const showWindowModal = () => setShowWindow(true) const hideClusterDrawer = () => setShowWindow(false) @@ -117,7 +108,13 @@ export const ClusterEnvironmentList = ({ }) } > - {envIcon()} +
+ +
-
- -
-
- + + {id !== DEFAULT_CLUSTER_ID && ( +