+ );
+ }
+
+ // If no user groups or error, show empty state
+ if (!userGroups || userGroups.length === 0 || error) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+export default UserGroupsList;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/hooks/useEnvironmentDetail.ts b/client/packages/lowcoder/src/pages/setting/environments/hooks/useEnvironmentDetail.ts
index a52deb4888..7e4d9f37e8 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/hooks/useEnvironmentDetail.ts
+++ b/client/packages/lowcoder/src/pages/setting/environments/hooks/useEnvironmentDetail.ts
@@ -2,15 +2,19 @@ import { useState, useEffect, useCallback } from "react";
import {
getEnvironmentById,
getEnvironmentWorkspaces,
+ getEnvironmentUserGroups,
} from "../services/environments.service";
import { Environment } from "../types/environment.types";
import { Workspace } from "../types/workspace.types";
+import { UserGroup } from "../types/userGroup.types";
+
/**
* Custom hook to fetch and manage environment detail data
* @param id - Environment ID to fetch
- * @returns Object containing environment data, loading state, error state, and refresh function
+ * @returns Object containing environment data, workspaces, user groups, loading states, error states, and refresh functions
*/
export const useEnvironmentDetail = (id: string) => {
+ // Environment state
const [environment, setEnvironment] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
@@ -20,6 +24,11 @@ export const useEnvironmentDetail = (id: string) => {
const [workspacesLoading, setWorkspacesLoading] = useState(false);
const [workspacesError, setWorkspacesError] = useState(null);
+ // User Groups state
+ const [userGroups, setUserGroups] = useState([]);
+ const [userGroupsLoading, setUserGroupsLoading] = useState(false);
+ const [userGroupsError, setUserGroupsError] = useState(null);
+
// Function to fetch environment data
const fetchEnvironmentData = useCallback(async () => {
if (!id) {
@@ -45,7 +54,7 @@ export const useEnvironmentDetail = (id: string) => {
}
}, [id]);
- // Function to fetch workspaces for the environment
+ // Function to fetch workspaces
const fetchWorkspaces = useCallback(async () => {
// Don't fetch workspaces if environment is not loaded yet
if (!environment) {
@@ -56,10 +65,11 @@ export const useEnvironmentDetail = (id: string) => {
setWorkspacesError(null);
try {
- // Get the API key from the environment
+ // Get the API key and API service URL from the environment
const apiKey = environment.environmentApikey;
const apiServiceUrl = environment.environmentApiServiceUrl;
+ // Check if both API key and API service URL are configured
if (!apiKey) {
setWorkspacesError(
"No API key configured for this environment. Workspaces cannot be fetched."
@@ -67,15 +77,17 @@ export const useEnvironmentDetail = (id: string) => {
setWorkspacesLoading(false);
return;
}
+
if (!apiServiceUrl) {
- setWorkspacesError('No API service URL configured for this environment. Workspaces cannot be fetched.');
+ setWorkspacesError(
+ "No API service URL configured for this environment. Workspaces cannot be fetched."
+ );
setWorkspacesLoading(false);
return;
}
- // Call the function with environment ID and API key
+ // Call the function with environment ID, API key, and API service URL
const data = await getEnvironmentWorkspaces(id, apiKey, apiServiceUrl);
- console.log(data);
setWorkspaces(data);
} catch (err) {
setWorkspacesError(
@@ -86,17 +98,62 @@ export const useEnvironmentDetail = (id: string) => {
}
}, [environment, id]);
+ // Function to fetch user groups
+ const fetchUserGroups = useCallback(async () => {
+ // Don't fetch user groups if environment is not loaded yet
+ if (!environment) {
+ return;
+ }
+
+ setUserGroupsLoading(true);
+ setUserGroupsError(null);
+
+ try {
+ // Get the API key and API service URL from the environment
+ const apiKey = environment.environmentApikey;
+ const apiServiceUrl = environment.environmentApiServiceUrl;
+
+ // Check if both API key and API service URL are configured
+ if (!apiKey) {
+ setUserGroupsError(
+ "No API key configured for this environment. User groups cannot be fetched."
+ );
+ setUserGroupsLoading(false);
+ return;
+ }
+
+ if (!apiServiceUrl) {
+ setUserGroupsError(
+ "No API service URL configured for this environment. User groups cannot be fetched."
+ );
+ setUserGroupsLoading(false);
+ return;
+ }
+
+ // Call the function with environment ID, API key, and API service URL
+ const data = await getEnvironmentUserGroups(id, apiKey, apiServiceUrl);
+ setUserGroups(data);
+ } catch (err) {
+ setUserGroupsError(
+ err instanceof Error ? err.message : "Failed to fetch user groups"
+ );
+ } finally {
+ setUserGroupsLoading(false);
+ }
+ }, [environment, id]);
+
// Fetch environment data on mount and when ID changes
useEffect(() => {
fetchEnvironmentData();
}, [fetchEnvironmentData]);
- // Fetch workspaces when environment is loaded
+ // Fetch workspaces and user groups after environment is loaded
useEffect(() => {
if (environment) {
fetchWorkspaces();
+ fetchUserGroups();
}
- }, [environment, fetchWorkspaces]);
+ }, [environment, fetchWorkspaces, fetchUserGroups]);
// Calculate workspace statistics
const workspaceStats = {
@@ -104,7 +161,22 @@ export const useEnvironmentDetail = (id: string) => {
managed: 0, // To be implemented later
unmanaged: workspaces.length, // To be implemented later
apiKeyConfigured: !!environment?.environmentApikey,
- apiServiceUrlConfigured: !!environment?.environmentApiServiceUrl
+ apiServiceUrlConfigured: !!environment?.environmentApiServiceUrl,
+ };
+
+ // Calculate user group statistics
+ const userGroupStats = {
+ total: userGroups.length,
+ totalUsers: userGroups.reduce(
+ (acc, group) => acc + group.stats.userCount,
+ 0
+ ),
+ adminUsers: userGroups.reduce(
+ (acc, group) => acc + group.stats.adminUserCount,
+ 0
+ ),
+ apiKeyConfigured: !!environment?.environmentApikey,
+ apiServiceUrlConfigured: !!environment?.environmentApiServiceUrl,
};
// Return the state and functions to refresh data
@@ -121,5 +193,12 @@ export const useEnvironmentDetail = (id: string) => {
workspacesError,
refreshWorkspaces: fetchWorkspaces,
workspaceStats,
+
+ // User Groups data
+ userGroups,
+ userGroupsLoading,
+ userGroupsError,
+ refreshUserGroups: fetchUserGroups,
+ userGroupStats,
};
};
diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts
index 1205087d94..fc34d51a04 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts
@@ -2,6 +2,7 @@ import axios from "axios";
import { message } from "antd";
import { Environment } from "../types/environment.types";
import { Workspace } from "../types/workspace.types";
+import { UserGroup } from "../types/userGroup.types";
/**
* Fetch all environments
@@ -119,3 +120,54 @@ export async function getEnvironmentWorkspaces(
throw error;
}
}
+
+
+
+/* ================================================================================
+
+=============================== ENVIRONMENT USER GROUPS ============================ */
+
+export async function getEnvironmentUserGroups(
+ environmentId: string,
+ apiKey: string,
+ apiServiceUrl: string
+): Promise {
+ try {
+ // Check if required parameters are provided
+ if (!environmentId) {
+ throw new Error('Environment ID is required');
+ }
+
+ if (!apiKey) {
+ throw new Error('API key is required to fetch user groups');
+ }
+
+ if (!apiServiceUrl) {
+ throw new Error('API service URL is required to fetch user groups');
+ }
+
+ // Set up headers with the Bearer token format
+ const headers = {
+ Authorization: `Bearer ${apiKey}`
+ };
+
+ // Make the API request to get user groups
+ const response = await axios.get(`${apiServiceUrl}/api/groups/list`, { headers });
+ console.log(response);
+
+ // Check if response is valid
+ if (!response.data) {
+ throw new Error('Failed to fetch user groups');
+ }
+
+ // The response data is already an array of user groups
+ const userGroups: UserGroup[] = response.data.data || [];
+
+ return userGroups;
+ } catch (error) {
+ // Handle and transform error
+ const errorMessage = error instanceof Error ? error.message : 'Failed to fetch user groups';
+ message.error(errorMessage);
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/types/userGroup.types.ts b/client/packages/lowcoder/src/pages/setting/environments/types/userGroup.types.ts
new file mode 100644
index 0000000000..cd5ec1ec8b
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/userGroup.types.ts
@@ -0,0 +1,20 @@
+/**
+ * Represents a User Group entity in an environment
+ */
+export interface UserGroup {
+ groupId: string;
+ groupGid: string;
+ groupName: string;
+ allUsersGroup: boolean;
+ visitorRole: string;
+ createTime: number;
+ dynamicRule: any;
+ stats: {
+ users: string[];
+ userCount: number;
+ adminUserCount: number;
+ };
+ syncDelete: boolean;
+ devGroup: boolean;
+ syncGroup: boolean;
+ }
\ No newline at end of file
From 2d6ec6b473053ef9868cf85b901479a76ffddd40 Mon Sep 17 00:00:00 2001
From: Faran Javed
Date: Thu, 10 Apr 2025 22:06:31 +0500
Subject: [PATCH 09/68] setup workspace detail page
---
.../setting/environments/Environments.tsx | 17 +-
.../setting/environments/WorkspaceDetail.tsx | 221 ++++++++++++++++++
.../environments/components/AppsList.tsx | 125 ++++++++++
.../environments/hooks/useWorkspaceDetail.ts | 145 ++++++++++++
.../services/environments.service.ts | 98 +++++++-
.../setting/environments/types/app.types.ts | 25 ++
6 files changed, 625 insertions(+), 6 deletions(-)
create mode 100644 client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx
create mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/AppsList.tsx
create mode 100644 client/packages/lowcoder/src/pages/setting/environments/hooks/useWorkspaceDetail.ts
create mode 100644 client/packages/lowcoder/src/pages/setting/environments/types/app.types.ts
diff --git a/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx b/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
index 07d0086a49..2095dd835d 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
@@ -3,20 +3,27 @@ import React from "react";
import { Switch, Route, useRouteMatch } from "react-router-dom";
import EnvironmentsList from "./EnvironmentsList"; // Rename your current component
import EnvironmentDetail from "./EnvironmentDetail";
+import WorkspaceDetail from "./WorkspaceDetail";
+import { ENVIRONMENT_WORKSPACE_DETAIL } from "@lowcoder-ee/constants/routesURL";
-import { ENVIRONMENT_SETTING, ENVIRONMENT_DETAIL } from "@lowcoder-ee/constants/routesURL";
-
+import {
+ ENVIRONMENT_SETTING,
+ ENVIRONMENT_DETAIL,
+} from "@lowcoder-ee/constants/routesURL";
const Environments: React.FC = () => {
return (
-
-
+
+
+
-
+
+
+
);
};
diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx
new file mode 100644
index 0000000000..ab6255b49b
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx
@@ -0,0 +1,221 @@
+import React, { useEffect, useState } from "react";
+import { useParams, useHistory } from "react-router-dom";
+import history from "@lowcoder-ee/util/history";
+import { useWorkspaceDetail } from "./hooks/useWorkspaceDetail";
+
+
+import {
+ Spin,
+ Typography,
+ Card,
+ Row,
+ Col,
+ Tabs,
+ Alert,
+ Button,
+ Statistic,
+ Divider,
+ Breadcrumb
+} from "antd";
+import {
+ ReloadOutlined,
+ AppstoreOutlined,
+ DatabaseOutlined,
+ CodeOutlined,
+ HomeOutlined,
+ TeamOutlined,
+ SyncOutlined,
+ ArrowLeftOutlined
+} from "@ant-design/icons";
+import { getEnvironmentById } from './services/environments.service';
+import AppsList from './components/AppsList';
+import { Workspace } from './types/workspace.types';
+import { Environment } from './types/environment.types';
+import { App } from './types/app.types';
+
+const { Title, Text } = Typography;
+const { TabPane } = Tabs;
+
+
+const WorkspaceDetail: React.FC = () => {
+ // Get parameters from URL
+ const { environmentId, workspaceId } = useParams<{
+ environmentId: string;
+ workspaceId: string;
+ }>();
+
+ // Use the custom hook
+ const {
+ environment,
+ workspace,
+ apps,
+ appsLoading,
+ appsError,
+ refreshApps,
+ appStats,
+ isLoading,
+ hasError
+ } = useWorkspaceDetail(environmentId, workspaceId);
+
+ // Handle loading/error states
+ if (isLoading) {
+ return ;
+ }
+
+ // Handle loading/error states
+if (isLoading) {
+ return (
+