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 , DatabaseOutlined , AuditOutlined } from '@ant-design/icons' ;
2
+ import { Card , Button , Divider , Alert , message , Table , Tag , Input , Space , Tooltip , Row , Col } from 'antd' ;
3
+ import {
4
+ SyncOutlined ,
5
+ CloudUploadOutlined ,
6
+ DatabaseOutlined ,
7
+ AuditOutlined ,
8
+ ApiOutlined ,
9
+ CheckCircleFilled ,
10
+ CloudServerOutlined ,
11
+ DisconnectOutlined
12
+ } from '@ant-design/icons' ;
4
13
import Title from 'antd/lib/typography/Title' ;
5
14
import { Environment } from '../types/environment.types' ;
6
15
import { Workspace } from '../types/workspace.types' ;
7
16
import { DataSource } from '../types/datasource.types' ;
8
17
import { getMergedWorkspaceDataSources } from '../services/datasources.service' ;
9
- import { Switch , Spin , Empty } from 'antd' ;
18
+ import { Switch , Spin , Empty , Avatar } from 'antd' ;
10
19
import { ManagedObjectType , setManagedObject , unsetManagedObject } from '../services/managed-objects.service' ;
11
20
import { useDeployModal } from '../context/DeployModalContext' ;
12
21
import { dataSourcesConfig } from '../config/data-sources.config' ;
@@ -125,23 +134,47 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace
125
134
// Table columns
126
135
const columns = [
127
136
{
128
- title : 'Name' ,
129
- dataIndex : 'name' ,
130
- key : 'name' ,
131
- render : ( text : string ) => < span className = "datasource-name" > { text } </ span >
132
- } ,
133
- {
134
- title : 'ID' ,
135
- dataIndex : 'id' ,
136
- key : 'id' ,
137
- ellipsis : true ,
137
+ title : 'Data Source' ,
138
+ key : 'datasource' ,
139
+ render : ( dataSource : DataSource ) => (
140
+ < div style = { { display : 'flex' , alignItems : 'center' } } >
141
+ < Avatar
142
+ style = { {
143
+ backgroundColor : getDataSourceColor ( dataSource . type ) ,
144
+ marginRight : 12
145
+ } }
146
+ shape = "square"
147
+ icon = { < DatabaseOutlined /> }
148
+ />
149
+ < div >
150
+ < div style = { { fontWeight : 500 } } > { dataSource . name } </ div >
151
+ < div style = { { fontSize : 12 , color : '#8c8c8c' , marginTop : 4 } } >
152
+ { dataSource . id }
153
+ </ div >
154
+ </ div >
155
+ </ div >
156
+ ) ,
138
157
} ,
139
158
{
140
159
title : 'Type' ,
141
160
dataIndex : 'type' ,
142
161
key : 'type' ,
143
162
render : ( type : string ) => (
144
- < Tag color = "blue" > { type } </ Tag >
163
+ < Tag color = { getDataSourceColor ( type ) } style = { { borderRadius : '12px' , padding : '2px 12px' } } >
164
+ { type }
165
+ </ Tag >
166
+ ) ,
167
+ } ,
168
+ {
169
+ title : 'Status' ,
170
+ key : 'status' ,
171
+ render : ( dataSource : DataSource ) => (
172
+ < Tag
173
+ color = { dataSource . managed ? 'processing' : 'default' }
174
+ style = { { borderRadius : '12px' } }
175
+ >
176
+ { dataSource . managed ? < CloudServerOutlined /> : < DisconnectOutlined /> } { dataSource . managed ? 'Managed' : 'Unmanaged' }
177
+ </ Tag >
145
178
) ,
146
179
} ,
147
180
{
@@ -152,7 +185,6 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace
152
185
checked = { ! ! dataSource . managed }
153
186
onChange = { ( checked : boolean ) => handleToggleManaged ( dataSource , checked ) }
154
187
loading = { refreshing }
155
- size = "small"
156
188
/>
157
189
) ,
158
190
} ,
@@ -164,7 +196,6 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace
164
196
< Tooltip title = "View Audit Logs" >
165
197
< Button
166
198
icon = { < AuditOutlined /> }
167
- size = "small"
168
199
onClick = { ( e ) => {
169
200
e . stopPropagation ( ) ;
170
201
const auditUrl = `/setting/audit?environmentId=${ environment . environmentId } &orgId=${ workspace . id } &datasourceId=${ dataSource . id } &pageSize=100&pageNum=1` ;
@@ -177,7 +208,6 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace
177
208
< Tooltip title = { ! dataSource . managed ? "Data source must be managed before it can be deployed" : "Deploy this data source to another environment" } >
178
209
< Button
179
210
type = "primary"
180
- size = "small"
181
211
icon = { < CloudUploadOutlined /> }
182
212
onClick = { ( ) => openDeployModal ( dataSource , dataSourcesConfig , environment ) }
183
213
disabled = { ! dataSource . managed }
@@ -190,50 +220,93 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace
190
220
}
191
221
] ;
192
222
223
+ // Helper function to get color based on data source type
224
+ const getDataSourceColor = ( type : string ) => {
225
+ const colorMap : { [ key : string ] : string } = {
226
+ 'mysql' : '#4479A1' ,
227
+ 'postgres' : '#336791' ,
228
+ 'mongodb' : '#4DB33D' ,
229
+ 'redis' : '#DC382D' ,
230
+ 'rest' : '#FF6C37' ,
231
+ 'graphql' : '#E10098' ,
232
+ 'elasticsearch' : '#005571' ,
233
+ 'oracle' : '#F80000' ,
234
+ 'mssql' : '#CC2927' ,
235
+ 'snowflake' : '#29B5E8'
236
+ } ;
237
+
238
+ return colorMap [ type . toLowerCase ( ) ] || '#1890ff' ;
239
+ } ;
240
+
241
+ // Stat card component
242
+ const StatCard = ( { title, value, icon } : { title : string ; value : number ; icon : React . ReactNode } ) => (
243
+ < Card
244
+ style = { {
245
+ height : '100%' ,
246
+ borderRadius : '8px' ,
247
+ boxShadow : '0 2px 8px rgba(0,0,0,0.05)'
248
+ } }
249
+ >
250
+ < div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' } } >
251
+ < div >
252
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' , marginBottom : '8px' } } > { title } </ div >
253
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { value } </ div >
254
+ </ div >
255
+ < div style = { {
256
+ fontSize : '28px' ,
257
+ opacity : 0.8 ,
258
+ color : '#1890ff' ,
259
+ padding : '12px' ,
260
+ backgroundColor : 'rgba(24, 144, 255, 0.1)' ,
261
+ borderRadius : '50%' ,
262
+ display : 'flex' ,
263
+ alignItems : 'center' ,
264
+ justifyContent : 'center'
265
+ } } >
266
+ { icon }
267
+ </ div >
268
+ </ div >
269
+ </ Card >
270
+ ) ;
271
+
193
272
return (
194
- < Card >
195
- { /* Header with refresh button */ }
196
- < div style = { { display : "flex" , justifyContent : "space-between" , alignItems : "center" , marginBottom : "16px" } } >
197
- < Title level = { 5 } > Data Sources in this Workspace</ Title >
273
+ < div style = { { padding : '16px 0' } } >
274
+ { /* Header */ }
275
+ < div style = { {
276
+ display : "flex" ,
277
+ justifyContent : "space-between" ,
278
+ alignItems : "center" ,
279
+ marginBottom : "24px" ,
280
+ background : 'linear-gradient(135deg, #1890ff 0%, #13c2c2 100%)' ,
281
+ padding : '20px 24px' ,
282
+ borderRadius : '8px' ,
283
+ color : 'white'
284
+ } } >
285
+ < div >
286
+ < Title level = { 4 } style = { { color : 'white' , margin : 0 } } >
287
+ < DatabaseOutlined style = { { marginRight : 10 } } /> Data Sources
288
+ </ Title >
289
+ < p style = { { marginBottom : 0 } } > Manage your workspace data connections</ p >
290
+ </ div >
198
291
< Button
199
292
icon = { < SyncOutlined spin = { refreshing } /> }
200
293
onClick = { handleRefresh }
201
294
loading = { loading }
295
+ type = "primary"
296
+ ghost
202
297
>
203
298
Refresh
204
299
</ Button >
205
300
</ div >
206
301
207
- { /* Stats display */ }
208
- < div style = { { display : 'flex' , flexWrap : 'wrap' , gap : '24px' , marginBottom : '16px' } } >
209
- < div >
210
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Total Data Sources</ div >
211
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . total } </ div >
212
- </ div >
213
- < div >
214
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Types</ div >
215
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . types } </ div >
216
- </ div >
217
- < div >
218
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Managed</ div >
219
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . managed } </ div >
220
- </ div >
221
- < div >
222
- < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Unmanaged</ div >
223
- < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . unmanaged } </ div >
224
- </ div >
225
- </ div >
226
-
227
- < Divider style = { { margin : "16px 0" } } />
228
-
229
302
{ /* Error display */ }
230
303
{ error && (
231
304
< Alert
232
305
message = "Error loading data sources"
233
306
description = { error }
234
307
type = "error"
235
308
showIcon
236
- style = { { marginBottom : "16px " } }
309
+ style = { { marginBottom : "20px " } }
237
310
/>
238
311
) }
239
312
@@ -244,49 +317,95 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace
244
317
description = "Missing required configuration: API key or API service URL"
245
318
type = "warning"
246
319
showIcon
247
- style = { { marginBottom : "16px " } }
320
+ style = { { marginBottom : "20px " } }
248
321
/>
249
322
) }
250
323
324
+ { /* Stats display */ }
325
+ < Row gutter = { [ 16 , 16 ] } style = { { marginBottom : '24px' } } >
326
+ < Col xs = { 12 } sm = { 12 } md = { 6 } >
327
+ < StatCard
328
+ title = "Total Data Sources"
329
+ value = { stats . total }
330
+ icon = { < DatabaseOutlined /> }
331
+ />
332
+ </ Col >
333
+ < Col xs = { 12 } sm = { 12 } md = { 6 } >
334
+ < StatCard
335
+ title = "Available Types"
336
+ value = { stats . types }
337
+ icon = { < ApiOutlined /> }
338
+ />
339
+ </ Col >
340
+ < Col xs = { 12 } sm = { 12 } md = { 6 } >
341
+ < StatCard
342
+ title = "Managed"
343
+ value = { stats . managed }
344
+ icon = { < CloudServerOutlined /> }
345
+ />
346
+ </ Col >
347
+ < Col xs = { 12 } sm = { 12 } md = { 6 } >
348
+ < StatCard
349
+ title = "Unmanaged"
350
+ value = { stats . unmanaged }
351
+ icon = { < DisconnectOutlined /> }
352
+ />
353
+ </ Col >
354
+ </ Row >
355
+
251
356
{ /* Content */ }
252
- { loading ? (
253
- < div style = { { display : 'flex' , justifyContent : 'center' , padding : '20px' } } >
254
- < Spin tip = "Loading data sources..." />
255
- </ div >
256
- ) : dataSources . length === 0 ? (
257
- < Empty
258
- description = { error || "No data sources found in this workspace" }
259
- image = { Empty . PRESENTED_IMAGE_SIMPLE }
260
- />
261
- ) : (
262
- < >
263
- { /* Search Bar */ }
264
- < div style = { { marginBottom : 16 } } >
265
- < Search
266
- placeholder = "Search data sources by name or ID"
267
- allowClear
268
- onSearch = { value => setSearchText ( value ) }
269
- onChange = { e => setSearchText ( e . target . value ) }
270
- style = { { width : 300 } }
271
- />
272
- { searchText && filteredDataSources . length !== dataSources . length && (
273
- < div style = { { marginTop : 8 } } >
274
- Showing { filteredDataSources . length } of { dataSources . length } data sources
275
- </ div >
276
- ) }
357
+ < Card
358
+ style = { {
359
+ borderRadius : '8px' ,
360
+ boxShadow : '0 2px 8px rgba(0,0,0,0.05)'
361
+ } }
362
+ >
363
+ { loading ? (
364
+ < div style = { { display : 'flex' , justifyContent : 'center' , padding : '40px' } } >
365
+ < Spin size = "large" tip = "Loading data sources..." />
277
366
</ div >
278
-
279
- < Table
280
- columns = { columns }
281
- dataSource = { filteredDataSources }
282
- rowKey = "id"
283
- pagination = { { pageSize : 10 } }
284
- size = "middle"
285
- scroll = { { x : 'max-content' } }
367
+ ) : dataSources . length === 0 ? (
368
+ < Empty
369
+ description = { error || "No data sources found in this workspace" }
370
+ image = { Empty . PRESENTED_IMAGE_SIMPLE }
286
371
/>
287
- </ >
288
- ) }
289
- </ Card >
372
+ ) : (
373
+ < >
374
+ { /* Search Bar */ }
375
+ < div style = { { marginBottom : 20 } } >
376
+ < Search
377
+ placeholder = "Search data sources by name or ID"
378
+ allowClear
379
+ onSearch = { value => setSearchText ( value ) }
380
+ onChange = { e => setSearchText ( e . target . value ) }
381
+ style = { { width : 300 } }
382
+ size = "large"
383
+ />
384
+ { searchText && filteredDataSources . length !== dataSources . length && (
385
+ < div style = { { marginTop : 8 , color : '#8c8c8c' } } >
386
+ Showing { filteredDataSources . length } of { dataSources . length } data sources
387
+ </ div >
388
+ ) }
389
+ </ div >
390
+
391
+ < Table
392
+ columns = { columns }
393
+ dataSource = { filteredDataSources }
394
+ rowKey = "id"
395
+ pagination = { {
396
+ pageSize : 10 ,
397
+ showTotal : ( total , range ) => `${ range [ 0 ] } -${ range [ 1 ] } of ${ total } data sources`
398
+ } }
399
+ rowClassName = { ( ) => 'datasource-row' }
400
+ style = { {
401
+ borderRadius : '8px' ,
402
+ overflow : 'hidden'
403
+ } }
404
+ />
405
+ </ >
406
+ ) }
407
+ </ Card >
408
+ </ div >
290
409
) ;
291
410
} ;
292
411
0 commit comments