1
- import React , { useState , useEffect , useRef } from "react" ;
2
- import { Typography , Alert , Input , Button , Space , Empty , Card , Spin , Row , Col , Tooltip , Badge } from "antd" ;
3
- import { SearchOutlined , CloudServerOutlined , SyncOutlined , PlusOutlined } from "@ant-design/icons" ;
1
+ import React , { useState , useEffect } from "react" ;
2
+ import { Alert , Empty , Spin } from "antd" ;
3
+ import { SyncOutlined } from "@ant-design/icons" ;
4
+ import { AddIcon , Search , TacoButton } from "lowcoder-design" ;
4
5
import { useHistory } from "react-router-dom" ;
5
6
import { useSelector , useDispatch } from "react-redux" ;
6
7
import { selectEnvironments , selectEnvironmentsLoading , selectEnvironmentsError } from "redux/selectors/enterpriseSelectors" ;
@@ -9,10 +10,49 @@ import { Environment } from "./types/environment.types";
9
10
import EnvironmentsTable from "./components/EnvironmentsTable" ;
10
11
import CreateEnvironmentModal from "./components/CreateEnvironmentModal" ;
11
12
import { buildEnvironmentId } from "@lowcoder-ee/constants/routesURL" ;
12
- import { getEnvironmentTagColor } from "./utils/environmentUtils" ;
13
13
import { createEnvironment } from "./services/environments.service" ;
14
-
15
- const { Title, Text } = Typography ;
14
+ import styled from "styled-components" ;
15
+
16
+ const EnvironmentsWrapper = styled . div `
17
+ display: flex;
18
+ flex-direction: column;
19
+ width: 100%;
20
+ height: 100%;
21
+ ` ;
22
+
23
+ const HeaderWrapper = styled . div `
24
+ display: flex;
25
+ align-items: center;
26
+ height: 92px;
27
+ padding: 28px 36px;
28
+ width: 100%;
29
+ ` ;
30
+
31
+ const Title = styled . div `
32
+ font-weight: 500;
33
+ font-size: 18px;
34
+ color: #222222;
35
+ line-height: 18px;
36
+ flex-grow: 1;
37
+ ` ;
38
+
39
+ const AddBtn = styled ( TacoButton ) `
40
+ min-width: 96px;
41
+ width: fit-content;
42
+ height: 32px;
43
+ ` ;
44
+
45
+ const RefreshBtn = styled ( TacoButton ) `
46
+ width: fit-content;
47
+ height: 32px;
48
+ margin-right: 12px;
49
+ ` ;
50
+
51
+ const BodyWrapper = styled . div `
52
+ width: 100%;
53
+ flex-grow: 1;
54
+ padding: 0 24px;
55
+ ` ;
16
56
17
57
/**
18
58
* Environment Listing Page Component
@@ -29,13 +69,10 @@ const EnvironmentsList: React.FC = () => {
29
69
const [ searchText , setSearchText ] = useState ( "" ) ;
30
70
const [ isCreateModalVisible , setIsCreateModalVisible ] = useState ( false ) ;
31
71
const [ isCreating , setIsCreating ] = useState ( false ) ;
32
-
33
-
34
72
35
73
// Hook for navigation
36
74
const history = useHistory ( ) ;
37
75
38
-
39
76
// Filter environments based on search text
40
77
const filteredEnvironments = environments . filter ( ( env ) => {
41
78
const searchLower = searchText . toLowerCase ( ) ;
@@ -82,182 +119,45 @@ const EnvironmentsList: React.FC = () => {
82
119
}
83
120
} ;
84
121
85
- // Count environment types
86
- const environmentCounts = environments . reduce ( ( counts , env ) => {
87
- const type = env . environmentType . toUpperCase ( ) ;
88
- counts [ type ] = ( counts [ type ] || 0 ) + 1 ;
89
- return counts ;
90
- } , { } as Record < string , number > ) ;
91
-
92
122
return (
93
- < div
94
- className = "environments-container"
95
- style = { {
96
- padding : "24px" ,
97
- flex : 1 ,
98
- minWidth : "1000px" ,
99
- // Ensure minimum width to prevent excessive shrinking
100
- } }
101
- >
102
- { /* Modern gradient header */ }
103
- < div
104
- className = "environments-header"
105
- style = { {
106
- marginBottom : "24px" ,
107
- background : 'linear-gradient(135deg, #0050b3 0%, #1890ff 100%)' ,
108
- padding : '24px 32px' ,
109
- borderRadius : '12px' ,
110
- color : 'white' ,
111
- boxShadow : '0 4px 12px rgba(0,0,0,0.1)'
112
- } }
113
- >
114
- < Row justify = "space-between" align = "middle" gutter = { [ 16 , 16 ] } >
115
- < Col xs = { 24 } sm = { 16 } >
116
- < div style = { { display : 'flex' , alignItems : 'center' , gap : '20px' } } >
117
- < div style = { {
118
- fontSize : '36px' ,
119
- backgroundColor : 'rgba(255,255,255,0.2)' ,
120
- width : '72px' ,
121
- height : '72px' ,
122
- borderRadius : '50%' ,
123
- display : 'flex' ,
124
- alignItems : 'center' ,
125
- justifyContent : 'center' ,
126
- boxShadow : '0 4px 8px rgba(0,0,0,0.1)'
127
- } } >
128
- < CloudServerOutlined />
129
- </ div >
130
- < div >
131
- < Title level = { 2 } style = { { margin : '0 0 4px 0' , color : 'white' } } >
132
- Environments
133
- </ Title >
134
- < Text style = { { color : 'rgba(255,255,255,0.85)' , fontSize : '16px' } } >
135
- Manage your deployment environments across dev, test, preprod, and production
136
- </ Text >
137
- </ div >
138
- </ div >
139
- </ Col >
140
- < Col xs = { 24 } sm = { 8 } style = { { textAlign : 'right' } } >
141
- < Space size = "middle" >
142
- < Button
143
- icon = { < PlusOutlined /> }
144
- onClick = { ( ) => setIsCreateModalVisible ( true ) }
145
- type = "primary"
146
- style = { {
147
- backgroundColor : 'rgba(255, 255, 255, 0.15)' ,
148
- borderColor : 'rgba(255, 255, 255, 0.4)' ,
149
- color : 'white' ,
150
- fontWeight : 500
151
- } }
152
- >
153
- Create Environment
154
- </ Button >
155
- < Button
156
- icon = { < SyncOutlined spin = { isLoading } /> }
157
- onClick = { handleRefresh }
158
- loading = { isLoading }
159
- type = "default"
160
- style = { {
161
- backgroundColor : 'rgba(255, 255, 255, 0.2)' ,
162
- borderColor : 'rgba(255, 255, 255, 0.4)' ,
163
- color : 'white' ,
164
- fontWeight : 500
165
- } }
166
- >
167
- Refresh
168
- </ Button >
169
- </ Space >
170
- </ Col >
171
- </ Row >
172
- </ div >
173
-
174
- { /* Environment type stats */ }
175
- { environments . length > 0 && (
176
- < Row gutter = { [ 16 , 16 ] } style = { { marginBottom : '24px' } } >
177
- < Col span = { 24 } >
178
- < Card
179
- title = "Environment Overview"
180
- style = { {
181
- borderRadius : '12px' ,
182
- boxShadow : '0 2px 8px rgba(0,0,0,0.05)'
183
- } }
184
- styles = { { header : { borderBottom : '1px solid #f0f0f0' , padding : '16px 24px' } } }
185
- bodyStyle = { { padding : '24px' } }
186
- >
187
- < Row gutter = { [ 32 , 16 ] } justify = "space-around" >
188
- < Col >
189
- < Tooltip title = "Total number of environments" >
190
- < div style = { { textAlign : 'center' } } >
191
- < div style = { { fontSize : '38px' , fontWeight : 600 , color : '#1890ff' } } >
192
- { environments . length }
193
- </ div >
194
- < div style = { { fontSize : '14px' , color : '#8c8c8c' , marginTop : '4px' } } >
195
- Total Environments
196
- </ div >
197
- </ div >
198
- </ Tooltip >
199
- </ Col >
200
-
201
- { [ 'PROD' , 'PREPROD' , 'TEST' , 'DEV' ] . map ( type => (
202
- < Col key = { type } >
203
- < Tooltip title = { `Number of ${ type } environments` } >
204
- < div style = { { textAlign : 'center' } } >
205
- < div style = { {
206
- fontSize : '38px' ,
207
- fontWeight : 600 ,
208
- color : getEnvironmentTagColor ( type ) === 'default' ? '#8c8c8c' : getEnvironmentTagColor ( type )
209
- } } >
210
- { environmentCounts [ type ] || 0 }
211
- </ div >
212
- < div style = { { fontSize : '14px' , color : '#8c8c8c' , marginTop : '4px' , display : 'flex' , justifyContent : 'center' , alignItems : 'center' , gap : '6px' } } >
213
- < Badge color = { getEnvironmentTagColor ( type ) } />
214
- { type } Environments
215
- </ div >
216
- </ div >
217
- </ Tooltip >
218
- </ Col >
219
- ) ) }
220
- </ Row >
221
- </ Card >
222
- </ Col >
223
- </ Row >
224
- ) }
225
-
226
- { /* Main content card */ }
227
- < Card
228
- title = "Environment List"
229
- style = { {
230
- borderRadius : '12px' ,
231
- boxShadow : '0 2px 8px rgba(0,0,0,0.05)' ,
232
- } }
233
- styles = { { header : { borderBottom : '1px solid #f0f0f0' , padding : '16px 24px' } } }
234
- bodyStyle = { { padding : '24px' } }
235
- extra = {
236
- < Input
237
- placeholder = "Search environments"
238
- value = { searchText }
239
- onChange = { ( e ) => setSearchText ( e . target . value ) }
240
- style = { { width : 250 } }
241
- prefix = { < SearchOutlined /> }
242
- allowClear
243
- />
244
- }
245
- >
123
+ < EnvironmentsWrapper >
124
+ < HeaderWrapper >
125
+ < Title > Environments</ Title >
126
+ < Search
127
+ placeholder = "Search"
128
+ value = { searchText }
129
+ onChange = { ( e ) => setSearchText ( e . target . value ) }
130
+ style = { { width : "192px" , height : "32px" , margin : "0 12px 0 0" } }
131
+ />
132
+ < RefreshBtn
133
+ buttonType = "normal"
134
+ icon = { < SyncOutlined spin = { isLoading } /> }
135
+ onClick = { handleRefresh }
136
+ loading = { isLoading }
137
+ >
138
+ Refresh
139
+ </ RefreshBtn >
140
+ < AddBtn buttonType = "primary" onClick = { ( ) => setIsCreateModalVisible ( true ) } >
141
+ New Environment
142
+ </ AddBtn >
143
+ </ HeaderWrapper >
144
+
145
+ < BodyWrapper >
246
146
{ /* Error handling */ }
247
147
{ error && (
248
148
< Alert
249
149
message = "Error loading environments"
250
150
description = { error }
251
151
type = "error"
252
152
showIcon
253
- style = { { marginBottom : "24px " } }
153
+ style = { { marginBottom : "16px " } }
254
154
/>
255
155
) }
256
156
257
157
{ /* Loading, empty state or table */ }
258
158
{ isLoading ? (
259
159
< div style = { { display : 'flex' , justifyContent : 'center' , padding : '60px 0' } } >
260
- < Spin size = "large" tip = "Loading environments..." />
160
+ < Spin size = "large" />
261
161
</ div >
262
162
) : environments . length === 0 && ! error ? (
263
163
< Empty
@@ -286,7 +186,7 @@ const EnvironmentsList: React.FC = () => {
286
186
Showing { filteredEnvironments . length } of { environments . length } environments
287
187
</ div >
288
188
) }
289
- </ Card >
189
+ </ BodyWrapper >
290
190
291
191
{ /* Create Environment Modal */ }
292
192
< CreateEnvironmentModal
@@ -295,7 +195,7 @@ const EnvironmentsList: React.FC = () => {
295
195
onSave = { handleCreateEnvironment }
296
196
loading = { isCreating }
297
197
/>
298
- </ div >
198
+ </ EnvironmentsWrapper >
299
199
) ;
300
200
} ;
301
201
0 commit comments