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 } from '@ant-design/icons' ;
4
+ import Title from 'antd/lib/typography/Title' ;
5
+ import { Environment } from '../types/environment.types' ;
6
+ import { Workspace } from '../types/workspace.types' ;
7
+ import { DataSource } from '../types/datasource.types' ;
8
+ import { getMergedWorkspaceDataSources } from '../services/datasources.service' ;
9
+ import { Switch , Spin , Empty } from 'antd' ;
10
+ import { ManagedObjectType , setManagedObject , unsetManagedObject } from '../services/managed-objects.service' ;
11
+ import { useDeployModal } from '../context/DeployModalContext' ;
12
+ import { dataSourcesConfig } from '../config/data-sources.config' ;
13
+
14
+ const { Search } = Input ;
15
+
16
+ interface DataSourcesTabProps {
17
+ environment : Environment ;
18
+ workspace : Workspace ;
19
+ }
20
+
21
+ const DataSourcesTab : React . FC < DataSourcesTabProps > = ( { environment, workspace } ) => {
22
+ const [ dataSources , setDataSources ] = useState < DataSource [ ] > ( [ ] ) ;
23
+ const [ stats , setStats ] = useState ( {
24
+ total : 0 ,
25
+ types : 0 ,
26
+ managed : 0 ,
27
+ unmanaged : 0
28
+ } ) ;
29
+ const [ loading , setLoading ] = useState ( false ) ;
30
+ const [ refreshing , setRefreshing ] = useState ( false ) ;
31
+ const [ error , setError ] = useState < string | null > ( null ) ;
32
+ const [ searchText , setSearchText ] = useState ( '' ) ;
33
+ const { openDeployModal } = useDeployModal ( ) ;
34
+
35
+ // Fetch data sources
36
+ const fetchDataSources = async ( ) => {
37
+ if ( ! workspace . id || ! environment ) return ;
38
+
39
+ setLoading ( true ) ;
40
+ setError ( null ) ;
41
+
42
+ try {
43
+ const result = await getMergedWorkspaceDataSources (
44
+ workspace . id ,
45
+ environment . environmentId ,
46
+ environment . environmentApikey ,
47
+ environment . environmentApiServiceUrl !
48
+ ) ;
49
+
50
+ setDataSources ( result . dataSources ) ;
51
+ setStats ( result . stats ) ;
52
+ } catch ( err ) {
53
+ setError ( err instanceof Error ? err . message : "Failed to fetch data sources" ) ;
54
+ } finally {
55
+ setLoading ( false ) ;
56
+ setRefreshing ( false ) ;
57
+ }
58
+ } ;
59
+
60
+ useEffect ( ( ) => {
61
+ fetchDataSources ( ) ;
62
+ } , [ environment , workspace ] ) ;
63
+
64
+ // Handle refresh
65
+ const handleRefresh = ( ) => {
66
+ setRefreshing ( true ) ;
67
+ fetchDataSources ( ) ;
68
+ } ;
69
+
70
+ // Toggle managed status
71
+ const handleToggleManaged = async ( dataSource : DataSource , checked : boolean ) => {
72
+ setRefreshing ( true ) ;
73
+ try {
74
+ if ( checked ) {
75
+ await setManagedObject (
76
+ dataSource . gid ,
77
+ environment . environmentId ,
78
+ ManagedObjectType . DATASOURCE ,
79
+ dataSource . name
80
+ ) ;
81
+ } else {
82
+ await unsetManagedObject (
83
+ dataSource . gid ,
84
+ environment . environmentId ,
85
+ ManagedObjectType . DATASOURCE
86
+ ) ;
87
+ }
88
+
89
+ // Update the data source in state
90
+ const updatedDataSources = dataSources . map ( item => {
91
+ if ( item . id === dataSource . id ) {
92
+ return { ...item , managed : checked } ;
93
+ }
94
+ return item ;
95
+ } ) ;
96
+
97
+ setDataSources ( updatedDataSources ) ;
98
+
99
+ // Update stats
100
+ const managed = updatedDataSources . filter ( ds => ds . managed ) . length ;
101
+ setStats ( prev => ( {
102
+ ...prev ,
103
+ managed,
104
+ unmanaged : prev . total - managed
105
+ } ) ) ;
106
+
107
+ message . success ( `${ dataSource . name } is now ${ checked ? 'Managed' : 'Unmanaged' } ` ) ;
108
+ return true ;
109
+ } catch ( error ) {
110
+ message . error ( `Failed to change managed status for ${ dataSource . name } ` ) ;
111
+ return false ;
112
+ } finally {
113
+ setRefreshing ( false ) ;
114
+ }
115
+ } ;
116
+
117
+ // Filter data sources based on search
118
+ const filteredDataSources = searchText
119
+ ? dataSources . filter ( ds =>
120
+ ds . name . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) ||
121
+ ds . id . toString ( ) . toLowerCase ( ) . includes ( searchText . toLowerCase ( ) ) )
122
+ : dataSources ;
123
+
124
+ // Table columns
125
+ const columns = [
126
+ {
127
+ title : 'Name' ,
128
+ dataIndex : 'name' ,
129
+ key : 'name' ,
130
+ render : ( text : string ) => < span className = "datasource-name" > { text } </ span >
131
+ } ,
132
+ {
133
+ title : 'ID' ,
134
+ dataIndex : 'id' ,
135
+ key : 'id' ,
136
+ ellipsis : true ,
137
+ } ,
138
+ {
139
+ title : 'Type' ,
140
+ dataIndex : 'type' ,
141
+ key : 'type' ,
142
+ render : ( type : string ) => (
143
+ < Tag color = "blue" > { type } </ Tag >
144
+ ) ,
145
+ } ,
146
+ {
147
+ title : 'Managed' ,
148
+ key : 'managed' ,
149
+ render : ( _ : any , dataSource : DataSource ) => (
150
+ < Switch
151
+ checked = { ! ! dataSource . managed }
152
+ onChange = { ( checked : boolean ) => handleToggleManaged ( dataSource , checked ) }
153
+ loading = { refreshing }
154
+ size = "small"
155
+ />
156
+ ) ,
157
+ } ,
158
+ {
159
+ title : 'Actions' ,
160
+ key : 'actions' ,
161
+ render : ( _ : any , dataSource : DataSource ) => (
162
+ < Space onClick = { ( e ) => e . stopPropagation ( ) } >
163
+ < Tooltip title = { ! dataSource . managed ? "Data source must be managed before it can be deployed" : "Deploy this data source to another environment" } >
164
+ < Button
165
+ type = "primary"
166
+ size = "small"
167
+ icon = { < CloudUploadOutlined /> }
168
+ onClick = { ( ) => openDeployModal ( dataSource , dataSourcesConfig , environment ) }
169
+ disabled = { ! dataSource . managed }
170
+ >
171
+ Deploy
172
+ </ Button >
173
+ </ Tooltip >
174
+ </ Space >
175
+ ) ,
176
+ }
177
+ ] ;
178
+
179
+ return (
180
+ < Card >
181
+ { /* Header with refresh button */ }
182
+ < div style = { { display : "flex" , justifyContent : "space-between" , alignItems : "center" , marginBottom : "16px" } } >
183
+ < Title level = { 5 } > Data Sources in this Workspace</ Title >
184
+ < Button
185
+ icon = { < SyncOutlined spin = { refreshing } /> }
186
+ onClick = { handleRefresh }
187
+ loading = { loading }
188
+ >
189
+ Refresh
190
+ </ Button >
191
+ </ div >
192
+
193
+ { /* Stats display */ }
194
+ < div style = { { display : 'flex' , flexWrap : 'wrap' , gap : '24px' , marginBottom : '16px' } } >
195
+ < div >
196
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Total Data Sources</ div >
197
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . total } </ div >
198
+ </ div >
199
+ < div >
200
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Types</ div >
201
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . types } </ div >
202
+ </ div >
203
+ < div >
204
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Managed</ div >
205
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . managed } </ div >
206
+ </ div >
207
+ < div >
208
+ < div style = { { fontSize : '14px' , color : '#8c8c8c' } } > Unmanaged</ div >
209
+ < div style = { { fontSize : '24px' , fontWeight : 600 } } > { stats . unmanaged } </ div >
210
+ </ div >
211
+ </ div >
212
+
213
+ < Divider style = { { margin : "16px 0" } } />
214
+
215
+ { /* Error display */ }
216
+ { error && (
217
+ < Alert
218
+ message = "Error loading data sources"
219
+ description = { error }
220
+ type = "error"
221
+ showIcon
222
+ style = { { marginBottom : "16px" } }
223
+ />
224
+ ) }
225
+
226
+ { /* Configuration warnings */ }
227
+ { ( ! environment . environmentApikey || ! environment . environmentApiServiceUrl ) && ! error && (
228
+ < Alert
229
+ message = "Configuration Issue"
230
+ description = "Missing required configuration: API key or API service URL"
231
+ type = "warning"
232
+ showIcon
233
+ style = { { marginBottom : "16px" } }
234
+ />
235
+ ) }
236
+
237
+ { /* Content */ }
238
+ { loading ? (
239
+ < div style = { { display : 'flex' , justifyContent : 'center' , padding : '20px' } } >
240
+ < Spin tip = "Loading data sources..." />
241
+ </ div >
242
+ ) : dataSources . length === 0 ? (
243
+ < Empty
244
+ description = { error || "No data sources found in this workspace" }
245
+ image = { Empty . PRESENTED_IMAGE_SIMPLE }
246
+ />
247
+ ) : (
248
+ < >
249
+ { /* Search Bar */ }
250
+ < div style = { { marginBottom : 16 } } >
251
+ < Search
252
+ placeholder = "Search data sources by name or ID"
253
+ allowClear
254
+ onSearch = { value => setSearchText ( value ) }
255
+ onChange = { e => setSearchText ( e . target . value ) }
256
+ style = { { width : 300 } }
257
+ />
258
+ { searchText && filteredDataSources . length !== dataSources . length && (
259
+ < div style = { { marginTop : 8 } } >
260
+ Showing { filteredDataSources . length } of { dataSources . length } data sources
261
+ </ div >
262
+ ) }
263
+ </ div >
264
+
265
+ < Table
266
+ columns = { columns }
267
+ dataSource = { filteredDataSources }
268
+ rowKey = "id"
269
+ pagination = { { pageSize : 10 } }
270
+ size = "middle"
271
+ scroll = { { x : 'max-content' } }
272
+ />
273
+ </ >
274
+ ) }
275
+ </ Card >
276
+ ) ;
277
+ } ;
278
+
279
+ export default DataSourcesTab ;
0 commit comments