1
1
import React , { useState , useEffect } from 'react' ;
2
- import { Card , Button , Divider , Alert , message , Table , Tag , Input , Space , Tooltip } from 'antd' ;
3
- import { SyncOutlined , CloudUploadOutlined , AuditOutlined } from '@ant-design/icons' ;
2
+ import { Card , Button , Divider , Alert , message , Table , Tag , Input , Space , Tooltip , Row , Col } from 'antd' ;
3
+ import { SyncOutlined , CloudUploadOutlined , AuditOutlined , AppstoreOutlined , CheckCircleFilled , CloudServerOutlined , DisconnectOutlined } from '@ant-design/icons' ;
4
4
import Title from 'antd/lib/typography/Title' ;
5
5
import { Environment } from '../types/environment.types' ;
6
6
import { Workspace } from '../types/workspace.types' ;
7
7
import { App , AppStats } from '../types/app.types' ;
8
8
import { getMergedWorkspaceApps } from '../services/apps.service' ;
9
- import { Switch , Spin , Empty } from 'antd' ;
9
+ import { Switch , Spin , Empty , Avatar } from 'antd' ;
10
10
import { ManagedObjectType , setManagedObject , unsetManagedObject } from '../services/managed-objects.service' ;
11
11
import { useDeployModal } from '../context/DeployModalContext' ;
12
12
import { appsConfig } from '../config/apps.config' ;
@@ -136,25 +136,43 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
136
136
// Table columns
137
137
const columns = [
138
138
{
139
- title : 'Name' ,
140
- dataIndex : 'name' ,
141
- key : 'name' ,
142
- render : ( text : string ) => < span className = "app-name" > { text } </ span >
143
- } ,
144
- {
145
- title : 'ID' ,
146
- dataIndex : 'applicationId' ,
147
- key : 'applicationId' ,
148
- ellipsis : true ,
139
+ title : 'App' ,
140
+ key : 'app' ,
141
+ render : ( app : App ) => (
142
+ < div style = { { display : 'flex' , alignItems : 'center' } } >
143
+ < Avatar
144
+ style = { {
145
+ backgroundColor : stringToColor ( app . name ) ,
146
+ marginRight : 12
147
+ } }
148
+ shape = "square"
149
+ >
150
+ { app . name . charAt ( 0 ) . toUpperCase ( ) }
151
+ </ Avatar >
152
+ < div >
153
+ < div style = { { fontWeight : 500 } } > { app . name } </ div >
154
+ < div style = { { fontSize : 12 , color : '#8c8c8c' , marginTop : 4 } } >
155
+ { app . applicationId }
156
+ </ div >
157
+ </ div >
158
+ </ div >
159
+ ) ,
149
160
} ,
150
161
{
151
- title : 'Published' ,
152
- dataIndex : 'published' ,
153
- key : 'published' ,
154
- render : ( published : boolean ) => (
155
- < Tag color = { published ? 'green' : 'default' } >
156
- { published ? 'Published' : 'Draft' }
157
- </ Tag >
162
+ title : 'Status' ,
163
+ key : 'status' ,
164
+ render : ( app : App ) => (
165
+ < Space direction = "vertical" size = { 0 } >
166
+ < Tag color = { app . published ? 'success' : 'default' } style = { { borderRadius : '12px' } } >
167
+ { app . published ? < CheckCircleFilled /> : null } { app . published ? 'Published' : 'Draft' }
168
+ </ Tag >
169
+ < Tag
170
+ color = { app . managed ? 'processing' : 'default' }
171
+ style = { { marginTop : 8 , borderRadius : '12px' } }
172
+ >
173
+ { app . managed ? < CloudServerOutlined /> : < DisconnectOutlined /> } { app . managed ? 'Managed' : 'Unmanaged' }
174
+ </ Tag >
175
+ </ Space >
158
176
) ,
159
177
} ,
160
178
{
@@ -165,7 +183,6 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
165
183
checked = { ! ! app . managed }
166
184
onChange = { ( checked : boolean ) => handleToggleManaged ( app , checked ) }
167
185
loading = { refreshing }
168
- size = "small"
169
186
/>
170
187
) ,
171
188
} ,
@@ -177,7 +194,6 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
177
194
< Tooltip title = "View Audit Logs" >
178
195
< Button
179
196
icon = { < AuditOutlined /> }
180
- size = "small"
181
197
onClick = { ( e ) => {
182
198
e . stopPropagation ( ) ;
183
199
const auditUrl = `/setting/audit?environmentId=${ environment . environmentId } &orgId=${ workspace . id } &appId=${ app . applicationId } &pageSize=100&pageNum=1` ;
@@ -190,7 +206,6 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
190
206
< Tooltip title = { ! app . managed ? "App must be managed before it can be deployed" : "Deploy this app to another environment" } >
191
207
< Button
192
208
type = "primary"
193
- size = "small"
194
209
icon = { < CloudUploadOutlined /> }
195
210
onClick = { ( ) => openDeployModal ( app , appsConfig , environment ) }
196
211
disabled = { ! app . managed }
@@ -203,50 +218,86 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
203
218
}
204
219
] ;
205
220
221
+ // Helper function to generate colors from strings
222
+ const stringToColor = ( str : string ) => {
223
+ let hash = 0 ;
224
+ for ( let i = 0 ; i < str . length ; i ++ ) {
225
+ hash = str . charCodeAt ( i ) + ( ( hash << 5 ) - hash ) ;
226
+ }
227
+
228
+ const hue = Math . abs ( hash % 360 ) ;
229
+ return `hsl(${ hue } , 70%, 50%)` ;
230
+ } ;
231
+
232
+ // Stat card component
233
+ const StatCard = ( { title, value, icon } : { title : string ; value : number ; icon : React . ReactNode } ) => (
234
+ < Card
235
+ style = { {
236
+ height : '100%' ,
237
+ borderRadius : '8px' ,
238
+ boxShadow : '0 2px 8px rgba(0,0,0,0.05)'
239
+ } }
240
+ >
241
+ < div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' } } >
242
+ < div >
243
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' , marginBottom : '8px' } } > { title } </ div >
244
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { value } </ div >
245
+ </ div >
246
+ < div style = { {
247
+ fontSize : '28px' ,
248
+ opacity : 0.8 ,
249
+ color : '#1890ff' ,
250
+ padding : '12px' ,
251
+ backgroundColor : 'rgba(24, 144, 255, 0.1)' ,
252
+ borderRadius : '50%' ,
253
+ display : 'flex' ,
254
+ alignItems : 'center' ,
255
+ justifyContent : 'center'
256
+ } } >
257
+ { icon }
258
+ </ div >
259
+ </ div >
260
+ </ Card >
261
+ ) ;
262
+
206
263
return (
207
- < Card >
208
- { /* Header with refresh button */ }
209
- < div style = { { display : "flex" , justifyContent : "space-between" , alignItems : "center" , marginBottom : "16px" } } >
210
- < Title level = { 5 } > Apps in this Workspace</ Title >
264
+ < div style = { { padding : '16px 0' } } >
265
+ { /* Header */ }
266
+ < div style = { {
267
+ display : "flex" ,
268
+ justifyContent : "space-between" ,
269
+ alignItems : "center" ,
270
+ marginBottom : "24px" ,
271
+ background : 'linear-gradient(135deg, #1890ff 0%, #722ed1 100%)' ,
272
+ padding : '20px 24px' ,
273
+ borderRadius : '8px' ,
274
+ color : 'white'
275
+ } } >
276
+ < div >
277
+ < Title level = { 4 } style = { { color : 'white' , margin : 0 } } >
278
+ < AppstoreOutlined style = { { marginRight : 10 } } /> Apps
279
+ </ Title >
280
+ < p style = { { marginBottom : 0 } } > Manage your workspace applications</ p >
281
+ </ div >
211
282
< Button
212
283
icon = { < SyncOutlined spin = { refreshing } /> }
213
284
onClick = { handleRefresh }
214
285
loading = { loading }
286
+ type = "primary"
287
+ ghost
215
288
>
216
289
Refresh
217
290
</ Button >
218
291
</ div >
219
292
220
- { /* Stats display */ }
221
- < div style = { { display : 'flex' , flexWrap : 'wrap' , gap : '24px' , marginBottom : '16px' } } >
222
- < div >
223
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Total Apps</ div >
224
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . total } </ div >
225
- </ div >
226
- < div >
227
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Published Apps</ div >
228
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . published } </ div >
229
- </ div >
230
- < div >
231
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Managed Apps</ div >
232
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . managed } </ div >
233
- </ div >
234
- < div >
235
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Unmanaged Apps</ div >
236
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . unmanaged } </ div >
237
- </ div >
238
- </ div >
239
-
240
- < Divider style = { { margin : "16px 0" } } />
241
-
242
293
{ /* Error display */ }
243
294
{ error && (
244
295
< Alert
245
296
message = "Error loading apps"
246
297
description = { error }
247
298
type = "error"
248
299
showIcon
249
- style = { { marginBottom : "16px " } }
300
+ style = { { marginBottom : "20px " } }
250
301
/>
251
302
) }
252
303
@@ -257,49 +308,95 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => {
257
308
description = "Missing required configuration: API key or API service URL"
258
309
type = "warning"
259
310
showIcon
260
- style = { { marginBottom : "16px " } }
311
+ style = { { marginBottom : "20px " } }
261
312
/>
262
313
) }
263
314
315
+ { /* Stats display */ }
316
+ < Row gutter = { [ 16 , 16 ] } style = { { marginBottom : '24px' } } >
317
+ < Col xs = { 12 } sm = { 12 } md = { 6 } >
318
+ < StatCard
319
+ title = "Total Apps"
320
+ value = { stats . total }
321
+ icon = { < AppstoreOutlined /> }
322
+ />
323
+ </ Col >
324
+ < Col xs = { 12 } sm = { 12 } md = { 6 } >
325
+ < StatCard
326
+ title = "Published Apps"
327
+ value = { stats . published }
328
+ icon = { < CheckCircleFilled /> }
329
+ />
330
+ </ Col >
331
+ < Col xs = { 12 } sm = { 12 } md = { 6 } >
332
+ < StatCard
333
+ title = "Managed Apps"
334
+ value = { stats . managed }
335
+ icon = { < CloudServerOutlined /> }
336
+ />
337
+ </ Col >
338
+ < Col xs = { 12 } sm = { 12 } md = { 6 } >
339
+ < StatCard
340
+ title = "Unmanaged Apps"
341
+ value = { stats . unmanaged }
342
+ icon = { < DisconnectOutlined /> }
343
+ />
344
+ </ Col >
345
+ </ Row >
346
+
264
347
{ /* Content */ }
265
- { loading ? (
266
- < div style = { { display : 'flex' , justifyContent : 'center' , padding : '20px' } } >
267
- < Spin tip = "Loading apps..." />
268
- </ div >
269
- ) : apps . length === 0 ? (
270
- < Empty
271
- description = { error || "No apps found in this workspace" }
272
- image = { Empty . PRESENTED_IMAGE_SIMPLE }
273
- />
274
- ) : (
275
- < >
276
- { /* Search Bar */ }
277
- < div style = { { marginBottom : 16 } } >
278
- < Search
279
- placeholder = "Search apps by name or ID"
280
- allowClear
281
- onSearch = { value => setSearchText ( value ) }
282
- onChange = { e => setSearchText ( e . target . value ) }
283
- style = { { width : 300 } }
284
- />
285
- { searchText && filteredApps . length !== apps . length && (
286
- < div style = { { marginTop : 8 } } >
287
- Showing { filteredApps . length } of { apps . length } apps
288
- </ div >
289
- ) }
348
+ < Card
349
+ style = { {
350
+ borderRadius : '8px' ,
351
+ boxShadow : '0 2px 8px rgba(0,0,0,0.05)'
352
+ } }
353
+ >
354
+ { loading ? (
355
+ < div style = { { display : 'flex' , justifyContent : 'center' , padding : '40px' } } >
356
+ < Spin size = "large" tip = "Loading apps..." />
290
357
</ div >
291
-
292
- < Table
293
- columns = { columns }
294
- dataSource = { filteredApps }
295
- rowKey = "applicationId"
296
- pagination = { { pageSize : 10 } }
297
- size = "middle"
298
- scroll = { { x : 'max-content' } }
358
+ ) : apps . length === 0 ? (
359
+ < Empty
360
+ description = { error || "No apps found in this workspace" }
361
+ image = { Empty . PRESENTED_IMAGE_SIMPLE }
299
362
/>
300
- </ >
301
- ) }
302
- </ Card >
363
+ ) : (
364
+ < >
365
+ { /* Search Bar */ }
366
+ < div style = { { marginBottom : 20 } } >
367
+ < Search
368
+ placeholder = "Search apps by name or ID"
369
+ allowClear
370
+ onSearch = { value => setSearchText ( value ) }
371
+ onChange = { e => setSearchText ( e . target . value ) }
372
+ style = { { width : 300 } }
373
+ size = "large"
374
+ />
375
+ { searchText && filteredApps . length !== apps . length && (
376
+ < div style = { { marginTop : 8 , color : '#8c8c8c' } } >
377
+ Showing { filteredApps . length } of { apps . length } apps
378
+ </ div >
379
+ ) }
380
+ </ div >
381
+
382
+ < Table
383
+ columns = { columns }
384
+ dataSource = { filteredApps }
385
+ rowKey = "applicationId"
386
+ pagination = { {
387
+ pageSize : 10 ,
388
+ showTotal : ( total , range ) => `${ range [ 0 ] } -${ range [ 1 ] } of ${ total } apps`
389
+ } }
390
+ rowClassName = { ( ) => 'app-row' }
391
+ style = { {
392
+ borderRadius : '8px' ,
393
+ overflow : 'hidden'
394
+ } }
395
+ />
396
+ </ >
397
+ ) }
398
+ </ Card >
399
+ </ div >
303
400
) ;
304
401
} ;
305
402
0 commit comments