Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0a1879b

Browse files
committed
Seperate DS tab
1 parent c1a74fe commit 0a1879b

File tree

2 files changed

+282
-4
lines changed

2 files changed

+282
-4
lines changed

‎client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { dataSourcesConfig } from "./config/data-sources.config";
3535
import { queryConfig } from "./config/query.config";
3636

3737
import AppsTab from "./components/AppsTab";
38+
import DataSourcesTab from "./components/DataSourcesTab";
3839
const { Title, Text } = Typography;
3940
const { TabPane } = Tabs;
4041

@@ -170,11 +171,9 @@ const WorkspaceDetail: React.FC = () => {
170171
</TabPane>
171172

172173
<TabPane tab={<span><DatabaseOutlined /> Data Sources</span>} key="dataSources">
173-
<DeployableItemsTab
174+
<DataSourcesTab
174175
environment={environment}
175-
config={dataSourcesConfig}
176-
additionalParams={{ workspaceId: workspace.id }}
177-
title="Data Sources in this Workspace"
176+
workspace={workspace}
178177
/>
179178
</TabPane>
180179

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
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

Comments
 (0)