@@ -10,6 +10,10 @@ import {
10
10
Button ,
11
11
Tag ,
12
12
Result ,
13
+ Row ,
14
+ Col ,
15
+ Statistic ,
16
+ Progress ,
13
17
} from "antd" ;
14
18
import {
15
19
LinkOutlined ,
@@ -21,6 +25,11 @@ import {
21
25
CloseCircleOutlined ,
22
26
ExclamationCircleOutlined ,
23
27
SyncOutlined ,
28
+ CloudServerOutlined ,
29
+ UserOutlined ,
30
+ SafetyOutlined ,
31
+ CrownOutlined ,
32
+ ApiOutlined ,
24
33
} from "@ant-design/icons" ;
25
34
26
35
import { useSingleEnvironmentContext } from "./context/SingleEnvironmentContext" ;
@@ -31,10 +40,12 @@ import history from "@lowcoder-ee/util/history";
31
40
import WorkspacesTab from "./components/WorkspacesTab" ;
32
41
import UserGroupsTab from "./components/UserGroupsTab" ;
33
42
import EnvironmentHeader from "./components/EnvironmentHeader" ;
43
+ import StatsCard from "./components/StatsCard" ;
34
44
import ModernBreadcrumbs from "./components/ModernBreadcrumbs" ;
35
45
import { getEnvironmentTagColor } from "./utils/environmentUtils" ;
46
+ import { formatAPICalls , getAPICallsStatusColor } from "./services/license.service" ;
36
47
import ErrorComponent from './components/ErrorComponent' ;
37
- const { TabPane } = Tabs ;
48
+ import { Level1SettingPageContent } from "../styled" ;
38
49
39
50
/**
40
51
* Environment Detail Page Component
@@ -124,33 +135,80 @@ const EnvironmentDetail: React.FC = () => {
124
135
) ;
125
136
}
126
137
127
- const breadcrumbItems = [
138
+ // Stats data for the cards
139
+ const statsData = [
128
140
{
129
- key : 'environments' ,
130
- title : (
141
+ title : "Type" ,
142
+ value : environment . environmentType || "Unknown" ,
143
+ icon : < CloudServerOutlined /> ,
144
+ color : getEnvironmentTagColor ( environment . environmentType )
145
+ } ,
146
+ {
147
+ title : "Status" ,
148
+ value : environment . isLicensed ? "Licensed" : "Unlicensed" ,
149
+ icon : environment . isLicensed ? < CheckCircleOutlined /> : < CloseCircleOutlined /> ,
150
+ color : environment . isLicensed ? "#52c41a" : "#ff4d4f"
151
+ } ,
152
+ {
153
+ title : "API Key" ,
154
+ value : environment . environmentApikey ? "Configured" : "Not Set" ,
155
+ icon : < SafetyOutlined /> ,
156
+ color : environment . environmentApikey ? "#1890ff" : "#faad14"
157
+ } ,
158
+ {
159
+ title : "Master Env" ,
160
+ value : environment . isMaster ? "Yes" : "No" ,
161
+ icon : < UserOutlined /> ,
162
+ color : environment . isMaster ? "#722ed1" : "#8c8c8c"
163
+ }
164
+ ] ;
165
+
166
+ const tabItems = [
167
+ {
168
+ key : 'workspaces' ,
169
+ label : (
131
170
< span >
132
- < HomeOutlined /> Environments
171
+ < AppstoreOutlined /> Workspaces
133
172
</ span >
134
173
) ,
135
- onClick : ( ) => history . push ( "/setting/environments" )
174
+ children : < WorkspacesTab environment = { environment } />
136
175
} ,
137
176
{
138
- key : 'currentEnvironment' ,
139
- title : environment . environmentName
177
+ key : 'userGroups' ,
178
+ label : (
179
+ < span >
180
+ < UsergroupAddOutlined /> User Groups
181
+ </ span >
182
+ ) ,
183
+ children : < UserGroupsTab environment = { environment } />
140
184
}
141
185
] ;
142
186
143
187
return (
144
- < div
145
- className = "environment-detail-container"
146
- style = { { padding : "24px" , flex : 1 , minWidth : "1000px" } }
147
- >
188
+ < Level1SettingPageContent style = { { minWidth : "1000px" } } >
189
+ { /* Breadcrumbs */ }
190
+
191
+
148
192
{ /* Environment Header Component */ }
149
193
< EnvironmentHeader
150
194
environment = { environment }
151
195
onEditClick = { handleEditClick }
152
196
/>
153
197
198
+ { /* Stats Cards Row */ }
199
+ < Row gutter = { [ 16 , 16 ] } style = { { marginBottom : "24px" } } >
200
+ { statsData . map ( ( stat , index ) => (
201
+ < Col xs = { 24 } sm = { 12 } lg = { 6 } key = { index } >
202
+ < StatsCard
203
+ title = { stat . title }
204
+ value = { stat . value }
205
+ icon = { stat . icon }
206
+ color = { stat . color }
207
+ />
208
+ </ Col >
209
+ ) ) }
210
+ </ Row >
211
+
154
212
{ /* Basic Environment Information Card */ }
155
213
< Card
156
214
title = "Environment Overview"
@@ -180,13 +238,10 @@ const EnvironmentDetail: React.FC = () => {
180
238
"No domain set"
181
239
) }
182
240
</ Descriptions . Item >
183
- < Descriptions . Item label = "Environment Type" >
184
- < Tag
185
- color = { getEnvironmentTagColor ( environment . environmentType ) }
186
- style = { { borderRadius : '4px' } }
187
- >
188
- { environment . environmentType }
189
- </ Tag >
241
+ < Descriptions . Item label = "Environment ID" >
242
+ < code style = { { padding : '2px 6px' , background : '#f5f5f5' , borderRadius : '3px' } } >
243
+ { environment . environmentId }
244
+ </ code >
190
245
</ Descriptions . Item >
191
246
< Descriptions . Item label = "License Status" >
192
247
{ ( ( ) => {
@@ -196,29 +251,178 @@ const EnvironmentDetail: React.FC = () => {
196
251
case 'licensed' :
197
252
return < Tag icon = { < CheckCircleOutlined /> } color = "green" style = { { borderRadius : '4px' } } > Licensed</ Tag > ;
198
253
case 'unlicensed' :
199
- return < Tag icon = { < CloseCircleOutlined /> } color = "red " style = { { borderRadius : '4px' } } > Not Licensed </ Tag > ;
254
+ return < Tag icon = { < CloseCircleOutlined /> } color = "orange " style = { { borderRadius : '4px' } } > License Needed </ Tag > ;
200
255
case 'error' :
201
- return < Tag icon = { < ExclamationCircleOutlined /> } color = "orange" style = { { borderRadius : '4px' } } > License Error </ Tag > ;
256
+ return < Tag icon = { < ExclamationCircleOutlined /> } color = "orange" style = { { borderRadius : '4px' } } > Setup Required </ Tag > ;
202
257
default :
203
258
return < Tag color = "default" style = { { borderRadius : '4px' } } > Unknown</ Tag > ;
204
259
}
205
260
} ) ( ) }
206
261
</ Descriptions . Item >
207
- < Descriptions . Item label = "API Key Status" >
208
- { environment . environmentApikey ? (
209
- < Tag color = "green" style = { { borderRadius : '4px' } } > Configured</ Tag >
210
- ) : (
211
- < Tag color = "red" style = { { borderRadius : '4px' } } > Not Configured</ Tag >
212
- ) }
213
- </ Descriptions . Item >
214
- < Descriptions . Item label = "Master Environment" >
215
- { environment . isMaster ? "Yes" : "No" }
262
+ < Descriptions . Item label = "Created" >
263
+ { environment . createdAt ? new Date ( environment . createdAt ) . toLocaleDateString ( ) : "Unknown" }
216
264
</ Descriptions . Item >
217
265
</ Descriptions >
218
266
</ Card >
219
267
220
- { /* Modern Breadcrumbs navigation */ }
221
- < ModernBreadcrumbs items = { breadcrumbItems } />
268
+ < ModernBreadcrumbs
269
+ items = { [
270
+ {
271
+ key : 'environments' ,
272
+ title : 'Environments' ,
273
+ onClick : ( ) => history . push ( '/setting/environments' )
274
+ } ,
275
+ {
276
+ key : 'current' ,
277
+ title : environment . environmentName || "Environment Detail"
278
+ }
279
+ ] }
280
+ />
281
+ { /* Detailed License Information Card - only show for licensed environments with details */ }
282
+ { environment . isLicensed && environment . licenseDetails && (
283
+ < Card
284
+ title = {
285
+ < span >
286
+ < CrownOutlined style = { { color : '#52c41a' , marginRight : '8px' } } />
287
+ License Details
288
+ </ span >
289
+ }
290
+ style = { {
291
+ marginBottom : "24px" ,
292
+ borderRadius : '4px' ,
293
+ border : '1px solid #f0f0f0'
294
+ } }
295
+ className = "license-details-card"
296
+ >
297
+ < Row gutter = { [ 24 , 16 ] } >
298
+ { /* API Calls Status */ }
299
+ < Col xs = { 24 } sm = { 12 } md = { 8 } >
300
+ < Card
301
+ size = "small"
302
+ style = { { height : '100%' , textAlign : 'center' } }
303
+ styles = { { body : { padding : '16px' } } }
304
+ >
305
+ < Statistic
306
+ title = "API Calls Remaining"
307
+ value = { environment . licenseDetails . remainingAPICalls }
308
+ formatter = { ( value ) => (
309
+ < span style = { {
310
+ color : getAPICallsStatusColor (
311
+ environment . licenseDetails ?. remainingAPICalls || 0 ,
312
+ environment . licenseDetails ?. totalAPICallsLimit || 0
313
+ )
314
+ } } >
315
+ { value ?. toLocaleString ( ) }
316
+ </ span >
317
+ ) }
318
+ prefix = { < ApiOutlined /> }
319
+ />
320
+ < div style = { { marginTop : '12px' } } >
321
+ < Progress
322
+ percent = { environment . licenseDetails . apiCallsUsage || 0 }
323
+ strokeColor = { getAPICallsStatusColor (
324
+ environment . licenseDetails . remainingAPICalls ,
325
+ environment . licenseDetails . totalAPICallsLimit || 0
326
+ ) }
327
+ size = "small"
328
+ showInfo = { false }
329
+ />
330
+ < div style = { {
331
+ fontSize : '12px' ,
332
+ color : '#8c8c8c' ,
333
+ marginTop : '4px'
334
+ } } >
335
+ { environment . licenseDetails . apiCallsUsage || 0 } % used
336
+ </ div >
337
+ </ div >
338
+ </ Card >
339
+ </ Col >
340
+
341
+ { /* Total License Limit */ }
342
+ < Col xs = { 24 } sm = { 12 } md = { 8 } >
343
+ < Card
344
+ size = "small"
345
+ style = { { height : '100%' , textAlign : 'center' } }
346
+ styles = { { body : { padding : '16px' } } }
347
+ >
348
+ < Statistic
349
+ title = "Total API Calls Limit"
350
+ value = { environment . licenseDetails . totalAPICallsLimit }
351
+ formatter = { ( value ) => value ?. toLocaleString ( ) }
352
+ prefix = { < ApiOutlined /> }
353
+ />
354
+ < Tag
355
+ color = "blue"
356
+ style = { { marginTop : '12px' } }
357
+ >
358
+ { environment . licenseDetails . eeLicenses . length } License{ environment . licenseDetails . eeLicenses . length !== 1 ? 's' : '' }
359
+ </ Tag >
360
+ </ Card >
361
+ </ Col >
362
+
363
+ { /* Enterprise Edition Status */ }
364
+ < Col xs = { 24 } sm = { 12 } md = { 8 } >
365
+ < Card
366
+ size = "small"
367
+ style = { { height : '100%' , textAlign : 'center' } }
368
+ styles = { { body : { padding : '16px' } } }
369
+ >
370
+ < Statistic
371
+ title = "Enterprise Edition"
372
+ value = { environment . licenseDetails . eeActive ? "Active" : "Inactive" }
373
+ formatter = { ( value ) => (
374
+ < Tag
375
+ color = { environment . licenseDetails ?. eeActive ? "green" : "red" }
376
+ icon = { environment . licenseDetails ?. eeActive ? < CheckCircleOutlined /> : < CloseCircleOutlined /> }
377
+ >
378
+ { value }
379
+ </ Tag >
380
+ ) }
381
+ />
382
+ </ Card >
383
+ </ Col >
384
+ </ Row >
385
+
386
+ { /* License Details */ }
387
+ < div style = { { marginTop : '24px' } } >
388
+ < Typography . Title level = { 5 } style = { { marginBottom : '16px' } } >
389
+ < UserOutlined style = { { marginRight : '8px' } } />
390
+ License Information
391
+ </ Typography . Title >
392
+
393
+ < Row gutter = { [ 16 , 16 ] } >
394
+ { environment . licenseDetails . eeLicenses . map ( ( license , index ) => (
395
+ < Col xs = { 24 } sm = { 12 } md = { 8 } key = { license . uuid } >
396
+ < Card
397
+ size = "small"
398
+ style = { {
399
+ border : '1px solid #f0f0f0' ,
400
+ borderRadius : '6px'
401
+ } }
402
+ styles = { { body : { padding : '12px' } } }
403
+ >
404
+ < div style = { { marginBottom : '8px' } } >
405
+ < strong style = { { color : '#262626' } } >
406
+ { license . customerName }
407
+ </ strong >
408
+ </ div >
409
+ < div style = { { fontSize : '12px' , color : '#8c8c8c' , marginBottom : '8px' } } >
410
+ ID: { license . customerId }
411
+ </ div >
412
+ < div style = { { fontSize : '12px' , color : '#8c8c8c' , marginBottom : '8px' } } >
413
+ UUID: < span style = { { fontFamily : 'monospace' } } > { license . uuid . substring ( 0 , 8 ) } ...</ span >
414
+ </ div >
415
+ < Tag color = "blue" >
416
+ { license . apiCallsLimit . toLocaleString ( ) } calls
417
+ </ Tag >
418
+ </ Card >
419
+ </ Col >
420
+ ) ) }
421
+ </ Row >
422
+ </ div >
423
+ </ Card >
424
+ ) }
425
+
222
426
223
427
{ /* Tabs for Workspaces and User Groups */ }
224
428
< Tabs
@@ -227,29 +431,8 @@ const EnvironmentDetail: React.FC = () => {
227
431
onChange = { setActiveTab }
228
432
className = "modern-tabs"
229
433
type = "line"
230
- >
231
- < TabPane
232
- tab = {
233
- < span >
234
- < AppstoreOutlined /> Workspaces
235
- </ span >
236
- }
237
- key = "workspaces"
238
- >
239
- < WorkspacesTab environment = { environment } />
240
- </ TabPane >
241
-
242
- < TabPane
243
- tab = {
244
- < span >
245
- < UsergroupAddOutlined /> User Groups
246
- </ span >
247
- }
248
- key = "userGroups"
249
- >
250
- < UserGroupsTab environment = { environment } />
251
- </ TabPane >
252
- </ Tabs >
434
+ items = { tabItems }
435
+ />
253
436
254
437
{ /* Edit Environment Modal */ }
255
438
{ environment && (
@@ -261,7 +444,7 @@ const EnvironmentDetail: React.FC = () => {
261
444
loading = { isUpdating }
262
445
/>
263
446
) }
264
- </ div >
447
+ </ Level1SettingPageContent >
265
448
) ;
266
449
} ;
267
450
0 commit comments