Skip to content

Commit 7410765

Browse files
committed
Add seperate workspace and usergroups tab
1 parent dffc683 commit 7410765

File tree

3 files changed

+447
-14
lines changed

3 files changed

+447
-14
lines changed

client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import DeployableItemsTab from "./components/DeployableItemsTab";
2525
import EditEnvironmentModal from "./components/EditEnvironmentModal";
2626
import { Environment } from "./types/environment.types";
2727
import history from "@lowcoder-ee/util/history";
28-
28+
import WorkspacesTab from "./components/WorkspacesTab";
29+
import UserGroupsTab from "./components/UserGroupsTab";
2930
const { Title, Text } = Typography;
3031
const { TabPane } = Tabs;
3132

@@ -227,13 +228,10 @@ const EnvironmentDetail: React.FC = () => {
227228
{/* Tabs for Workspaces and User Groups */}
228229
<Tabs defaultActiveKey="workspaces">
229230
<TabPane tab="Workspaces" key="workspaces">
230-
{/* Using our new generic component with the workspace config */}
231-
<DeployableItemsTab
232-
environment={environment}
233-
config={workspaceConfig}
234-
title="Workspaces in this Environment"
235-
/>
231+
{/* Using our new standalone WorkspacesTab component */}
232+
<WorkspacesTab environment={environment} />
236233
</TabPane>
234+
237235
<TabPane
238236
tab={
239237
<span>
@@ -242,15 +240,11 @@ const EnvironmentDetail: React.FC = () => {
242240
}
243241
key="userGroups"
244242
>
245-
{/* Using our new generic component with the user group config */}
246-
<DeployableItemsTab
247-
environment={environment}
248-
config={userGroupsConfig}
249-
title="User Groups in this Environment"
250-
/>
243+
{/* Now using our standalone UserGroupsTab component */}
244+
<UserGroupsTab environment={environment} />
251245
</TabPane>
252246
</Tabs>
253-
247+
254248
{/* Edit Environment Modal */}
255249
{environment && (
256250
<EditEnvironmentModal
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space } from 'antd';
3+
import { SyncOutlined, TeamOutlined } from '@ant-design/icons';
4+
import Title from 'antd/lib/typography/Title';
5+
import { Environment } from '../types/environment.types';
6+
import { UserGroup } from '../types/userGroup.types';
7+
import { getEnvironmentUserGroups } from '../services/environments.service';
8+
import { Spin, Empty } from 'antd';
9+
10+
const { Search } = Input;
11+
12+
interface UserGroupsTabProps {
13+
environment: Environment;
14+
}
15+
16+
const UserGroupsTab: React.FC<UserGroupsTabProps> = ({ environment }) => {
17+
const [userGroups, setUserGroups] = useState<UserGroup[]>([]);
18+
const [stats, setStats] = useState({
19+
total: 0,
20+
active: 0,
21+
inactive: 0
22+
});
23+
const [loading, setLoading] = useState(false);
24+
const [refreshing, setRefreshing] = useState(false);
25+
const [error, setError] = useState<string | null>(null);
26+
const [searchText, setSearchText] = useState('');
27+
28+
// Fetch user groups
29+
const fetchUserGroups = async () => {
30+
if (!environment) return;
31+
32+
setLoading(true);
33+
setError(null);
34+
35+
try {
36+
// Check for required environment properties
37+
if (!environment.environmentApikey || !environment.environmentApiServiceUrl) {
38+
setError('Missing required configuration: API key or API service URL');
39+
setLoading(false);
40+
return;
41+
}
42+
43+
const groups = await getEnvironmentUserGroups(
44+
environment.environmentId,
45+
environment.environmentApikey,
46+
environment.environmentApiServiceUrl
47+
);
48+
49+
setUserGroups(groups);
50+
51+
// Calculate stats
52+
const total = groups.length;
53+
const active = groups.filter(group => group.state === 'ACTIVE').length;
54+
55+
setStats({
56+
total,
57+
active,
58+
inactive: total - active
59+
});
60+
} catch (err) {
61+
setError(err instanceof Error ? err.message : "Failed to fetch user groups");
62+
} finally {
63+
setLoading(false);
64+
setRefreshing(false);
65+
}
66+
};
67+
68+
useEffect(() => {
69+
fetchUserGroups();
70+
}, [environment]);
71+
72+
// Handle refresh
73+
const handleRefresh = () => {
74+
setRefreshing(true);
75+
fetchUserGroups();
76+
};
77+
78+
// Filter user groups based on search
79+
const filteredUserGroups = searchText
80+
? userGroups.filter(group =>
81+
group.name.toLowerCase().includes(searchText.toLowerCase()) ||
82+
group.id.toLowerCase().includes(searchText.toLowerCase()))
83+
: userGroups;
84+
85+
// Table columns
86+
const columns = [
87+
{
88+
title: 'Name',
89+
dataIndex: 'name',
90+
key: 'name',
91+
render: (text: string) => <span className="group-name">{text}</span>
92+
},
93+
{
94+
title: 'ID',
95+
dataIndex: 'id',
96+
key: 'id',
97+
ellipsis: true,
98+
},
99+
{
100+
title: 'Type',
101+
dataIndex: 'type',
102+
key: 'type',
103+
render: (type: string) => (
104+
<Tag color={type === 'USER' ? 'blue' : 'purple'}>
105+
{type}
106+
</Tag>
107+
),
108+
},
109+
{
110+
title: 'Status',
111+
dataIndex: 'state',
112+
key: 'state',
113+
render: (state: string) => (
114+
<Tag color={state === 'ACTIVE' ? 'green' : 'red'}>
115+
{state}
116+
</Tag>
117+
),
118+
},
119+
{
120+
title: 'Member Count',
121+
dataIndex: 'memberCount',
122+
key: 'memberCount',
123+
}
124+
];
125+
126+
return (
127+
<Card>
128+
{/* Header with refresh button */}
129+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" }}>
130+
<Title level={5}>User Groups in this Environment</Title>
131+
<Button
132+
icon={<SyncOutlined spin={refreshing} />}
133+
onClick={handleRefresh}
134+
loading={loading}
135+
>
136+
Refresh
137+
</Button>
138+
</div>
139+
140+
{/* Stats display */}
141+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '24px', marginBottom: '16px' }}>
142+
<div>
143+
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Total Groups</div>
144+
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.total}</div>
145+
</div>
146+
<div>
147+
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Active</div>
148+
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.active}</div>
149+
</div>
150+
<div>
151+
<div style={{ fontSize: '14px', color: '#8c8c8c' }}>Inactive</div>
152+
<div style={{ fontSize: '24px', fontWeight: 600 }}>{stats.inactive}</div>
153+
</div>
154+
</div>
155+
156+
<Divider style={{ margin: "16px 0" }} />
157+
158+
{/* Error display */}
159+
{error && (
160+
<Alert
161+
message="Error loading user groups"
162+
description={error}
163+
type="error"
164+
showIcon
165+
style={{ marginBottom: "16px" }}
166+
/>
167+
)}
168+
169+
{/* Configuration warnings */}
170+
{(!environment.environmentApikey || !environment.environmentApiServiceUrl) && !error && (
171+
<Alert
172+
message="Configuration Issue"
173+
description="Missing required configuration: API key or API service URL"
174+
type="warning"
175+
showIcon
176+
style={{ marginBottom: "16px" }}
177+
/>
178+
)}
179+
180+
{/* Content */}
181+
{loading ? (
182+
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
183+
<Spin tip="Loading user groups..." />
184+
</div>
185+
) : userGroups.length === 0 ? (
186+
<Empty
187+
description={error || "No user groups found in this environment"}
188+
image={Empty.PRESENTED_IMAGE_SIMPLE}
189+
/>
190+
) : (
191+
<>
192+
{/* Search Bar */}
193+
<div style={{ marginBottom: 16 }}>
194+
<Search
195+
placeholder="Search user groups by name or ID"
196+
allowClear
197+
onSearch={value => setSearchText(value)}
198+
onChange={e => setSearchText(e.target.value)}
199+
style={{ width: 300 }}
200+
/>
201+
{searchText && filteredUserGroups.length !== userGroups.length && (
202+
<div style={{ marginTop: 8 }}>
203+
Showing {filteredUserGroups.length} of {userGroups.length} user groups
204+
</div>
205+
)}
206+
</div>
207+
208+
<Table
209+
columns={columns}
210+
dataSource={filteredUserGroups}
211+
rowKey="id"
212+
pagination={{ pageSize: 10 }}
213+
size="middle"
214+
scroll={{ x: 'max-content' }}
215+
/>
216+
</>
217+
)}
218+
</Card>
219+
);
220+
};
221+
222+
export default UserGroupsTab;

0 commit comments

Comments
 (0)