diff --git a/client/packages/lowcoder/src/constants/reduxActionConstants.ts b/client/packages/lowcoder/src/constants/reduxActionConstants.ts
index 6dd42de3d6..aea840a5c6 100644
--- a/client/packages/lowcoder/src/constants/reduxActionConstants.ts
+++ b/client/packages/lowcoder/src/constants/reduxActionConstants.ts
@@ -172,6 +172,11 @@ export const ReduxActionTypes = {
/* Enterprise Edition */
FETCH_ENTERPRISE_LICENSE : "FETCH_ENTERPRISE_LICENSE",
SET_ENTERPRISE_LICENSE : "SET_ENTERPRISE_LICENSE",
+
+ /* Environments */
+ FETCH_ENVIRONMENTS : "FETCH_ENVIRONMENTS",
+ FETCH_ENVIRONMENTS_SUCCESS: "FETCH_ENVIRONMENTS_SUCCESS",
+ FETCH_ENVIRONMENTS_FAILURE: "FETCH_ENVIRONMENTS_FAILURE",
/* Branding Setting */
FETCH_BRANDING_SETTING : "FETCH_BRANDING_SETTING",
diff --git a/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx b/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
index 41081435de..afdb01fca9 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
@@ -1,31 +1,28 @@
// client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
import React from "react";
import { Switch, Route, useRouteMatch } from "react-router-dom";
-import { EnvironmentProvider } from "./context/EnvironmentContext";
import EnvironmentRoutes from "./routes/EnvironmentRoutes";
import EnvironmentsList from "./EnvironmentsList";
/**
* Top-level Environments component
- * Provides the EnvironmentProvider at the top level
+ * No longer needs the EnvironmentProvider since we use Redux
*/
const Environments: React.FC = () => {
const { path } = useRouteMatch();
return (
-
-
- {/* Environment list route */}
-
-
-
-
- {/* All routes that need a specific environment */}
-
-
-
-
-
+
+ {/* Environment list route */}
+
+
+
+
+ {/* All routes that need a specific environment */}
+
+
+
+
);
};
diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx
index 8d8fa2c435..3f9f51a1fb 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx
@@ -1,8 +1,10 @@
-import React, { useState } from "react";
+import React, { useState, useEffect, useRef } from "react";
import { Typography, Alert, Input, Button, Space, Empty, Card, Spin, Row, Col, Tooltip, Badge } from "antd";
import { SearchOutlined, CloudServerOutlined, SyncOutlined, PlusOutlined} from "@ant-design/icons";
import { useHistory } from "react-router-dom";
-import { useEnvironmentContext } from "./context/EnvironmentContext";
+import { useSelector, useDispatch } from "react-redux";
+import { selectEnvironments, selectEnvironmentsLoading, selectEnvironmentsError } from "redux/selectors/enterpriseSelectors";
+import { fetchEnvironments } from "redux/reduxActions/enterpriseActions";
import { Environment } from "./types/environment.types";
import EnvironmentsTable from "./components/EnvironmentsTable";
import CreateEnvironmentModal from "./components/CreateEnvironmentModal";
@@ -17,23 +19,23 @@ const { Title, Text } = Typography;
* Displays a table of environments
*/
const EnvironmentsList: React.FC = () => {
- // Use the shared context instead of a local hook
- const {
- environments,
- isLoading,
- error,
- refreshEnvironments
- } = useEnvironmentContext();
+ // Use Redux state instead of context
+ const dispatch = useDispatch();
+ const environments = useSelector(selectEnvironments);
+ const isLoading = useSelector(selectEnvironmentsLoading);
+ const error = useSelector(selectEnvironmentsError);
// State for search input
const [searchText, setSearchText] = useState("");
- const [isRefreshing, setIsRefreshing] = useState(false);
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
const [isCreating, setIsCreating] = useState(false);
+
+
// Hook for navigation
const history = useHistory();
+
// Filter environments based on search text
const filteredEnvironments = environments.filter((env) => {
const searchLower = searchText.toLowerCase();
@@ -62,10 +64,8 @@ const EnvironmentsList: React.FC = () => {
};
// Handle refresh
- const handleRefresh = async () => {
- setIsRefreshing(true);
- await refreshEnvironments();
- setIsRefreshing(false);
+ const handleRefresh = () => {
+ dispatch(fetchEnvironments());
};
// Handle create environment
@@ -73,7 +73,7 @@ const EnvironmentsList: React.FC = () => {
setIsCreating(true);
try {
await createEnvironment(environmentData);
- await refreshEnvironments(); // Refresh the list after creation
+ dispatch(fetchEnvironments()); // Refresh the list after creation
} catch (error) {
console.error("Failed to create environment:", error);
throw error; // Re-throw to let the modal handle the error display
@@ -153,7 +153,7 @@ const EnvironmentsList: React.FC = () => {
Create Environment
}
+ icon={}
onClick={handleRefresh}
loading={isLoading}
type="default"
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx
index ecef655af5..b7051185c7 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx
@@ -2,9 +2,10 @@
import React, { useState, useEffect } from 'react';
import { Modal, Form, Select, Checkbox, Button, Spin, Input, Tag, Space, Alert } from 'antd';
import { messageInstance } from 'lowcoder-design/src/components/GlobalInstances';
+import { useSelector } from 'react-redux';
+import { selectLicensedEnvironments, selectEnvironmentsLoading } from 'redux/selectors/enterpriseSelectors';
import { Environment } from '../types/environment.types';
import { DeployableItemConfig } from '../types/deployable-item.types';
-import { useEnvironmentContext } from '../context/EnvironmentContext';
import { getEnvironmentTagColor, formatEnvironmentType } from '../utils/environmentUtils';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { showFirstCredentialOverwriteConfirm, showSecondCredentialOverwriteConfirm } from './credentialConfirmations';
@@ -27,7 +28,8 @@ function DeployItemModal({
onSuccess
}: DeployItemModalProps) {
const [form] = Form.useForm();
- const { environments, isLoading } = useEnvironmentContext();
+ const licensedEnvironments = useSelector(selectLicensedEnvironments);
+ const isLoading = useSelector(selectEnvironmentsLoading);
const [deploying, setDeploying] = useState(false);
const [credentialConfirmationStep, setCredentialConfirmationStep] = useState(0); // 0: not started, 1: first confirmation, 2: confirmed
@@ -39,8 +41,8 @@ function DeployItemModal({
}, [visible, form]);
// Filter out source environment from target list
- const targetEnvironments = environments.filter(
- (env: Environment) => env.environmentId !== sourceEnvironment.environmentId && env.isLicensed !== false
+ const targetEnvironments = licensedEnvironments.filter(
+ (env: Environment) => env.environmentId !== sourceEnvironment.environmentId
);
// Handle credential checkbox change with double confirmation
@@ -82,7 +84,7 @@ function DeployItemModal({
try {
const values = await form.validateFields();
- const targetEnv = environments.find(env => env.environmentId === values.targetEnvId);
+ const targetEnv = licensedEnvironments.find(env => env.environmentId === values.targetEnvId);
if (!targetEnv) {
messageInstance.error('Target environment not found');
diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/SingleEnvironmentContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/SingleEnvironmentContext.tsx
index 7ca5f0b706..4654c121c0 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/context/SingleEnvironmentContext.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/context/SingleEnvironmentContext.tsx
@@ -9,10 +9,11 @@ import React, {
} from "react";
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import { useParams } from "react-router-dom";
+ import { useDispatch } from "react-redux";
+ import { fetchEnvironments } from "redux/reduxActions/enterpriseActions";
import { getEnvironmentById, updateEnvironment } from "../services/environments.service";
import { Environment } from "../types/environment.types";
- import { useEnvironmentContext } from './EnvironmentContext';
-
+
interface SingleEnvironmentContextState {
// Environment data
environment: Environment | null;
@@ -53,8 +54,8 @@ import React, {
const { envId } = useParams<{ envId: string }>();
const environmentId = propEnvironmentId || envId;
- // Access the environments context to refresh the list
- const { refreshEnvironments } = useEnvironmentContext();
+ // Use Redux dispatch to refresh environments instead of context
+ const dispatch = useDispatch();
// State for environment data
const [environment, setEnvironment] = useState(null);
@@ -103,10 +104,8 @@ import React, {
messageInstance.success("Environment updated successfully");
// Refresh both the single environment and environments list
- await Promise.all([
- fetchEnvironment(), // Refresh the current environment
- refreshEnvironments() // Refresh the environments list
- ]);
+ await fetchEnvironment(); // Refresh the current environment
+ dispatch(fetchEnvironments()); // Refresh the environments list using Redux
return updatedEnv;
} catch (err) {
@@ -114,7 +113,7 @@ import React, {
messageInstance.error(errorMessage);
throw err;
}
- }, [environment, environmentId, fetchEnvironment, refreshEnvironments]);
+ }, [environment, environmentId, fetchEnvironment, dispatch]);
// Load environment data when the component mounts or environmentId changes
useEffect(() => {
diff --git a/client/packages/lowcoder/src/redux/reducers/uiReducers/enterpriseReducer.ts b/client/packages/lowcoder/src/redux/reducers/uiReducers/enterpriseReducer.ts
index 4f2c81459b..83ac8cd7c3 100644
--- a/client/packages/lowcoder/src/redux/reducers/uiReducers/enterpriseReducer.ts
+++ b/client/packages/lowcoder/src/redux/reducers/uiReducers/enterpriseReducer.ts
@@ -1,11 +1,14 @@
import { BrandingConfig, BrandingSettingResponse, EnterpriseLicenseResponse } from "@lowcoder-ee/api/enterpriseApi";
import { createReducer } from "@lowcoder-ee/util/reducerUtils";
import { ReduxAction, ReduxActionTypes } from "constants/reduxActionConstants";
-
+import { Environment } from "pages/setting/environments/types/environment.types";
export interface EnterpriseReduxState {
enterprise: EnterpriseLicenseResponse,
globalBranding?: BrandingConfig,
workspaceBranding?: BrandingConfig,
+ environments: Environment[],
+ environmentsLoading: boolean,
+ environmentsError: string | null,
}
const initialState: EnterpriseReduxState = {
@@ -13,7 +16,10 @@ const initialState: EnterpriseReduxState = {
eeActive: false,
remainingAPICalls: 0,
eeLicenses: [],
- }
+ },
+ environments: [],
+ environmentsLoading: false,
+ environmentsError: null,
};
const enterpriseReducer = createReducer(initialState, {
@@ -38,6 +44,29 @@ const enterpriseReducer = createReducer(initialState, {
...state,
workspaceBranding: action.payload,
}),
+
+ [ReduxActionTypes.FETCH_ENVIRONMENTS]: (
+ state: EnterpriseReduxState
+ ) => ({
+ ...state,
+ environmentsLoading: true,
+ }),
+ [ReduxActionTypes.FETCH_ENVIRONMENTS_SUCCESS]: (
+ state: EnterpriseReduxState,
+ action: ReduxAction
+ ) => ({
+ ...state,
+ environments: action.payload,
+ environmentsLoading: false,
+ }),
+ [ReduxActionTypes.FETCH_ENVIRONMENTS_FAILURE]: (
+ state: EnterpriseReduxState,
+ action: ReduxAction
+ ) => ({
+ ...state,
+ environmentsLoading: false,
+ environmentsError: action.payload,
+ }),
});
export default enterpriseReducer;
diff --git a/client/packages/lowcoder/src/redux/reduxActions/enterpriseActions.ts b/client/packages/lowcoder/src/redux/reduxActions/enterpriseActions.ts
index 85e2b9b195..2231c45c28 100644
--- a/client/packages/lowcoder/src/redux/reduxActions/enterpriseActions.ts
+++ b/client/packages/lowcoder/src/redux/reduxActions/enterpriseActions.ts
@@ -1,5 +1,6 @@
import { EnterpriseLicenseResponse, FetchBrandingSettingPayload } from "@lowcoder-ee/api/enterpriseApi";
import { ReduxActionTypes } from "constants/reduxActionConstants";
+import { Environment } from "pages/setting/environments/types/environment.types";
export const fetchEnterpriseLicense = () => ({
type: ReduxActionTypes.FETCH_ENTERPRISE_LICENSE,
@@ -16,3 +17,18 @@ export const fetchBrandingSetting = (payload: FetchBrandingSettingPayload) => {
payload,
};
};
+
+export const fetchEnvironments = () => ({
+ type: ReduxActionTypes.FETCH_ENVIRONMENTS,
+});
+
+export const fetchEnvironmentsSuccess = (environments: Environment[]) => ({
+ type: ReduxActionTypes.FETCH_ENVIRONMENTS_SUCCESS,
+ payload: environments,
+});
+
+export const fetchEnvironmentsFailure = (error: string) => ({
+ type: ReduxActionTypes.FETCH_ENVIRONMENTS_FAILURE,
+ payload: error,
+});
+
diff --git a/client/packages/lowcoder/src/redux/sagas/enterpriseSagas.ts b/client/packages/lowcoder/src/redux/sagas/enterpriseSagas.ts
index 20d19e37f0..ef44210538 100644
--- a/client/packages/lowcoder/src/redux/sagas/enterpriseSagas.ts
+++ b/client/packages/lowcoder/src/redux/sagas/enterpriseSagas.ts
@@ -1,7 +1,10 @@
import { call, put, takeLatest } from 'redux-saga/effects';
import { ReduxAction, ReduxActionTypes } from "constants/reduxActionConstants";
-import { setEnterpriseLicense } from "redux/reduxActions/enterpriseActions";
+import { setEnterpriseLicense, fetchEnvironmentsSuccess, fetchEnvironmentsFailure } from "redux/reduxActions/enterpriseActions";
import { BrandingSettingResponse, EnterpriseLicenseResponse, FetchBrandingSettingPayload, getBranding, getEnterpriseLicense } from "api/enterpriseApi";
+import { getEnvironmentsWithLicenseStatus } from "pages/setting/environments/services/environments.service";
+import { Environment } from "pages/setting/environments/types/environment.types";
+
import { AxiosResponse } from 'axios';
function* fetchEnterpriseLicenseSaga(): Generator {
@@ -14,6 +17,16 @@ function* fetchEnterpriseLicenseSaga(): Generator {
+ try {
+ const environments: Environment[] = yield call(getEnvironmentsWithLicenseStatus);
+ yield put(fetchEnvironmentsSuccess(environments));
+ } catch (error) {
+ console.error('Failed to fetch environments:', error);
+ yield put(fetchEnvironmentsFailure(error as string));
+ }
+}
+
function* fetchBrandingSettingSaga(action: ReduxAction) {
try {
const response: BrandingSettingResponse = yield getBranding(action.payload.orgId);
@@ -45,4 +58,5 @@ function* fetchBrandingSettingSaga(action: ReduxAction
state.ui.enterprise?.enterprise?.eeActive ?? false;
@@ -25,3 +26,23 @@ export const getGlobalBrandingSetting = (state: AppState) => {
export const getWorkspaceBrandingSetting = (state: AppState) => {
return state.ui.enterprise?.workspaceBranding;
}
+// Environment selectors
+export const selectEnvironments = (state: AppState) =>
+ state.ui.enterprise?.environments ?? [];
+
+export const selectEnvironmentsLoading = (state: AppState) =>
+ state.ui.enterprise?.environmentsLoading ?? false;
+
+export const selectEnvironmentsError = (state: AppState) =>
+ state.ui.enterprise?.environmentsError ?? null;
+
+export const selectUnlicensedEnvironments = (state: AppState) => {
+ const environments = state.ui.enterprise?.environments ?? [];
+ return environments.filter(env => env.isLicensed === false);
+};
+
+export const selectLicensedEnvironments = (state: AppState) => {
+ const environments = state.ui.enterprise?.environments ?? [];
+ return environments.filter(env => env.isLicensed !== false);
+};
+
diff --git a/client/packages/lowcoder/src/util/context/EnterpriseContext.tsx b/client/packages/lowcoder/src/util/context/EnterpriseContext.tsx
index 57f821cabf..d377810687 100644
--- a/client/packages/lowcoder/src/util/context/EnterpriseContext.tsx
+++ b/client/packages/lowcoder/src/util/context/EnterpriseContext.tsx
@@ -1,5 +1,5 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
-import { fetchEnterpriseLicense } from 'redux/reduxActions/enterpriseActions';
+import { fetchEnterpriseLicense, fetchEnvironments } from 'redux/reduxActions/enterpriseActions';
import { selectEnterpriseEditionStatus } from '@lowcoder-ee/redux/selectors/enterpriseSelectors';
import { useDispatch, useSelector } from 'react-redux';
import { isEEEnvironment } from "util/envUtils";
@@ -23,6 +23,7 @@ export const EnterpriseProvider: React.FC = ({ children }) => {
if (isEEEnvironment()) {
// Fetch the enterprise license only if we're in an EE environment
dispatch(fetchEnterpriseLicense());
+ dispatch(fetchEnvironments());
} else {
// Set the state to false for non-EE environments
// setEEActiveState(false);