@@ -10,6 +10,10 @@ import {
1010 Button ,
1111 Tag ,
1212 Result ,
13+ Row ,
14+ Col ,
15+ Statistic ,
16+ Progress ,
1317} from "antd" ;
1418import {
1519 LinkOutlined ,
@@ -21,6 +25,11 @@ import {
2125 CloseCircleOutlined ,
2226 ExclamationCircleOutlined ,
2327 SyncOutlined ,
28+ CloudServerOutlined ,
29+ UserOutlined ,
30+ SafetyOutlined ,
31+ CrownOutlined ,
32+ ApiOutlined ,
2433} from "@ant-design/icons" ;
2534
2635import { useSingleEnvironmentContext } from "./context/SingleEnvironmentContext" ;
@@ -31,10 +40,12 @@ import history from "@lowcoder-ee/util/history";
3140import WorkspacesTab from "./components/WorkspacesTab" ;
3241import UserGroupsTab from "./components/UserGroupsTab" ;
3342import EnvironmentHeader from "./components/EnvironmentHeader" ;
43+ import StatsCard from "./components/StatsCard" ;
3444import ModernBreadcrumbs from "./components/ModernBreadcrumbs" ;
3545import { getEnvironmentTagColor } from "./utils/environmentUtils" ;
46+ import { formatAPICalls , getAPICallsStatusColor } from "./services/license.service" ;
3647import ErrorComponent from './components/ErrorComponent' ;
37- const { TabPane } = Tabs ;
48+ import { Level1SettingPageContent } from "../styled" ;
3849
3950/**
4051 * Environment Detail Page Component
@@ -124,33 +135,80 @@ const EnvironmentDetail: React.FC = () => {
124135 ) ;
125136 }
126137
127- const breadcrumbItems = [
138+ // Stats data for the cards
139+ const statsData = [
128140 {
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 : (
131170 < span >
132- < HomeOutlined /> Environments
171+ < AppstoreOutlined /> Workspaces
133172 </ span >
134173 ) ,
135- onClick : ( ) => history . push ( "/setting/environments" )
174+ children : < WorkspacesTab environment = { environment } />
136175 } ,
137176 {
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 } />
140184 }
141185 ] ;
142186
143187 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+
148192 { /* Environment Header Component */ }
149193 < EnvironmentHeader
150194 environment = { environment }
151195 onEditClick = { handleEditClick }
152196 />
153197
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+
154212 { /* Basic Environment Information Card */ }
155213 < Card
156214 title = "Environment Overview"
@@ -180,13 +238,10 @@ const EnvironmentDetail: React.FC = () => {
180238 "No domain set"
181239 ) }
182240 </ 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 >
190245 </ Descriptions . Item >
191246 < Descriptions . Item label = "License Status" >
192247 { ( ( ) => {
@@ -196,29 +251,178 @@ const EnvironmentDetail: React.FC = () => {
196251 case 'licensed' :
197252 return < Tag icon = { < CheckCircleOutlined /> } color = "green" style = { { borderRadius : '4px' } } > Licensed</ Tag > ;
198253 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 > ;
200255 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 > ;
202257 default :
203258 return < Tag color = "default" style = { { borderRadius : '4px' } } > Unknown</ Tag > ;
204259 }
205260 } ) ( ) }
206261 </ 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" }
216264 </ Descriptions . Item >
217265 </ Descriptions >
218266 </ Card >
219267
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+
222426
223427 { /* Tabs for Workspaces and User Groups */ }
224428 < Tabs
@@ -227,29 +431,8 @@ const EnvironmentDetail: React.FC = () => {
227431 onChange = { setActiveTab }
228432 className = "modern-tabs"
229433 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+ />
253436
254437 { /* Edit Environment Modal */ }
255438 { environment && (
@@ -261,7 +444,7 @@ const EnvironmentDetail: React.FC = () => {
261444 loading = { isUpdating }
262445 />
263446 ) }
264- </ div >
447+ </ Level1SettingPageContent >
265448 ) ;
266449} ;
267450
0 commit comments