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