Skip to content

Commit 536e7bb

Browse files
committed
Add Manually create env functionality
1 parent 482b802 commit 536e7bb

File tree

3 files changed

+251
-15
lines changed

3 files changed

+251
-15
lines changed

client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import React, { useState } from "react";
22
import { Typography, Alert, Input, Button, Space, Empty, Card, Spin, Row, Col, Tooltip, Badge } from "antd";
3-
import { SearchOutlined, CloudServerOutlined, SyncOutlined} from "@ant-design/icons";
3+
import { SearchOutlined, CloudServerOutlined, SyncOutlined, PlusOutlined} from "@ant-design/icons";
44
import { useHistory } from "react-router-dom";
55
import { useEnvironmentContext } from "./context/EnvironmentContext";
66
import { Environment } from "./types/environment.types";
77
import EnvironmentsTable from "./components/EnvironmentsTable";
8+
import CreateEnvironmentModal from "./components/CreateEnvironmentModal";
89
import { buildEnvironmentId } from "@lowcoder-ee/constants/routesURL";
910
import { getEnvironmentTagColor } from "./utils/environmentUtils";
11+
import { createEnvironment } from "./services/environments.service";
1012

1113
const { Title, Text } = Typography;
1214

@@ -26,6 +28,8 @@ const EnvironmentsList: React.FC = () => {
2628
// State for search input
2729
const [searchText, setSearchText] = useState("");
2830
const [isRefreshing, setIsRefreshing] = useState(false);
31+
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
32+
const [isCreating, setIsCreating] = useState(false);
2933

3034
// Hook for navigation
3135
const history = useHistory();
@@ -53,6 +57,20 @@ const EnvironmentsList: React.FC = () => {
5357
setIsRefreshing(false);
5458
};
5559

60+
// Handle create environment
61+
const handleCreateEnvironment = async (environmentData: Partial<Environment>) => {
62+
setIsCreating(true);
63+
try {
64+
await createEnvironment(environmentData);
65+
await refreshEnvironments(); // Refresh the list after creation
66+
} catch (error) {
67+
console.error("Failed to create environment:", error);
68+
throw error; // Re-throw to let the modal handle the error display
69+
} finally {
70+
setIsCreating(false);
71+
}
72+
};
73+
5674
// Count environment types
5775
const environmentCounts = environments.reduce((counts, env) => {
5876
const type = env.environmentType.toUpperCase();
@@ -110,20 +128,33 @@ const EnvironmentsList: React.FC = () => {
110128
</Col>
111129
<Col xs={24} sm={8} style={{ textAlign: 'right' }}>
112130
<Space size="middle">
113-
<Button
114-
icon={<SyncOutlined spin={isRefreshing} />}
115-
onClick={handleRefresh}
116-
loading={isLoading}
117-
type="default"
118-
style={{
119-
backgroundColor: 'rgba(255, 255, 255, 0.2)',
120-
borderColor: 'rgba(255, 255, 255, 0.4)',
121-
color: 'white',
122-
fontWeight: 500
123-
}}
124-
>
125-
Refresh
126-
</Button>
131+
<Button
132+
icon={<PlusOutlined />}
133+
onClick={() => setIsCreateModalVisible(true)}
134+
type="primary"
135+
style={{
136+
backgroundColor: 'rgba(255, 255, 255, 0.15)',
137+
borderColor: 'rgba(255, 255, 255, 0.4)',
138+
color: 'white',
139+
fontWeight: 500
140+
}}
141+
>
142+
Create Environment
143+
</Button>
144+
<Button
145+
icon={<SyncOutlined spin={isRefreshing} />}
146+
onClick={handleRefresh}
147+
loading={isLoading}
148+
type="default"
149+
style={{
150+
backgroundColor: 'rgba(255, 255, 255, 0.2)',
151+
borderColor: 'rgba(255, 255, 255, 0.4)',
152+
color: 'white',
153+
fontWeight: 500
154+
}}
155+
>
156+
Refresh
157+
</Button>
127158
</Space>
128159
</Col>
129160
</Row>
@@ -245,6 +276,14 @@ const EnvironmentsList: React.FC = () => {
245276
</div>
246277
)}
247278
</Card>
279+
280+
{/* Create Environment Modal */}
281+
<CreateEnvironmentModal
282+
visible={isCreateModalVisible}
283+
onClose={() => setIsCreateModalVisible(false)}
284+
onSave={handleCreateEnvironment}
285+
loading={isCreating}
286+
/>
248287
</div>
249288
);
250289
};
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import React, { useState } from 'react';
2+
import { Modal, Form, Input, Select, Switch, Button } from 'antd';
3+
import { Environment } from '../types/environment.types';
4+
5+
const { Option } = Select;
6+
7+
interface CreateEnvironmentModalProps {
8+
visible: boolean;
9+
onClose: () => void;
10+
onSave: (data: Partial<Environment>) => Promise<void>;
11+
loading?: boolean;
12+
}
13+
14+
const CreateEnvironmentModal: React.FC<CreateEnvironmentModalProps> = ({
15+
visible,
16+
onClose,
17+
onSave,
18+
loading = false
19+
}) => {
20+
const [form] = Form.useForm();
21+
const [submitLoading, setSubmitLoading] = useState(false);
22+
23+
const handleSubmit = async () => {
24+
try {
25+
const values = await form.validateFields();
26+
setSubmitLoading(true);
27+
28+
await onSave(values);
29+
form.resetFields(); // Reset form after successful creation
30+
onClose();
31+
} catch (error) {
32+
if (error instanceof Error) {
33+
console.error("Form validation or submission error:", error);
34+
}
35+
} finally {
36+
setSubmitLoading(false);
37+
}
38+
};
39+
40+
const handleCancel = () => {
41+
form.resetFields(); // Reset form when canceling
42+
onClose();
43+
};
44+
45+
return (
46+
<Modal
47+
title="Create New Environment"
48+
open={visible}
49+
onCancel={handleCancel}
50+
maskClosable={true}
51+
destroyOnClose={true}
52+
footer={[
53+
<Button key="back" onClick={handleCancel}>
54+
Cancel
55+
</Button>,
56+
<Button
57+
key="submit"
58+
type="primary"
59+
loading={loading || submitLoading}
60+
onClick={handleSubmit}
61+
>
62+
Create Environment
63+
</Button>
64+
]}
65+
>
66+
<Form
67+
form={form}
68+
layout="vertical"
69+
name="create_environment_form"
70+
initialValues={{
71+
environmentType: "DEV",
72+
isMaster: false
73+
}}
74+
>
75+
<Form.Item
76+
name="environmentName"
77+
label="Environment Name"
78+
rules={[
79+
{ required: true, message: 'Please enter a name' },
80+
{ min: 2, message: 'Name must be at least 2 characters' }
81+
]}
82+
>
83+
<Input placeholder="Enter environment name" />
84+
</Form.Item>
85+
86+
<Form.Item
87+
name="environmentDescription"
88+
label="Description"
89+
>
90+
<Input.TextArea
91+
placeholder="Enter description"
92+
rows={3}
93+
/>
94+
</Form.Item>
95+
96+
<Form.Item
97+
name="environmentType"
98+
label="Stage"
99+
rules={[{ required: true, message: 'Please select a stage' }]}
100+
>
101+
<Select placeholder="Select stage">
102+
<Option value="DEV">Development (DEV)</Option>
103+
<Option value="TEST">Testing (TEST)</Option>
104+
<Option value="PREPROD">Pre-Production (PREPROD)</Option>
105+
<Option value="PROD">Production (PROD)</Option>
106+
</Select>
107+
</Form.Item>
108+
109+
<Form.Item
110+
name="environmentFrontendUrl"
111+
label="Frontend URL"
112+
rules={[
113+
{ type: 'url', message: 'Please enter a valid URL' }
114+
]}
115+
>
116+
<Input placeholder="https://example.com" />
117+
</Form.Item>
118+
119+
<Form.Item
120+
name="environmentApiServiceUrl"
121+
label="API Service URL"
122+
rules={[
123+
{ type: 'url', message: 'Please enter a valid URL' }
124+
]}
125+
>
126+
<Input placeholder="https://api.example.com" />
127+
</Form.Item>
128+
129+
<Form.Item
130+
name="environmentNodeServiceUrl"
131+
label="Node Service URL"
132+
rules={[
133+
{ type: 'url', message: 'Please enter a valid URL' }
134+
]}
135+
>
136+
<Input placeholder="https://node.example.com" />
137+
</Form.Item>
138+
139+
<Form.Item
140+
name="environmentApikey"
141+
label="API Key"
142+
>
143+
<Input.TextArea
144+
placeholder="Enter API key"
145+
rows={2}
146+
/>
147+
</Form.Item>
148+
149+
<Form.Item
150+
name="isMaster"
151+
label="Master Environment"
152+
valuePropName="checked"
153+
>
154+
<Switch />
155+
</Form.Item>
156+
</Form>
157+
</Modal>
158+
);
159+
};
160+
161+
export default CreateEnvironmentModal;

client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,42 @@ export async function updateEnvironment(
4444
}
4545
}
4646

47+
/**
48+
* Create a new environment manually
49+
* @param environmentData - Environment data to create
50+
* @returns Promise with created environment data
51+
*/
52+
export async function createEnvironment(
53+
environmentData: Partial<Environment>
54+
): Promise<Environment> {
55+
try {
56+
// Convert frontend model to API model
57+
const payload = {
58+
environment_description: environmentData.environmentDescription || "",
59+
environment_icon: environmentData.environmentIcon || "",
60+
environment_name: environmentData.environmentName || "",
61+
environment_apikey: environmentData.environmentApikey || "",
62+
environment_type: environmentData.environmentType || "",
63+
environment_api_service_url: environmentData.environmentApiServiceUrl || "",
64+
environment_frontend_url: environmentData.environmentFrontendUrl || "",
65+
environment_node_service_url: environmentData.environmentNodeServiceUrl || "",
66+
isMaster: environmentData.isMaster || false
67+
};
4768

69+
const res = await axios.post(`/api/plugins/enterprise/environments`, payload);
70+
71+
if (res.data) {
72+
messageInstance.success("Environment created successfully");
73+
return res.data;
74+
} else {
75+
throw new Error("Failed to create environment");
76+
}
77+
} catch (err) {
78+
const errorMsg = err instanceof Error ? err.message : "Failed to create environment";
79+
messageInstance.error(errorMsg);
80+
throw err;
81+
}
82+
}
4883

4984
/**
5085
* Fetch all environments
@@ -100,6 +135,7 @@ export async function getEnvironmentById(id: string): Promise<Environment> {
100135
* Fetch workspaces for a specific environment
101136
* @param environmentId - ID of the environment
102137
* @param apiKey - API key for the environment
138+
* @param apiServiceUrl - API service URL for the environment
103139
* @returns Promise with an array of workspaces
104140
*/
105141
export async function getEnvironmentWorkspaces(

0 commit comments

Comments
 (0)