From 0f5440d0e026a72f1a6760a3b93cd716650ba987 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 9 May 2025 18:54:55 +0500 Subject: [PATCH 01/37] Add managed/unmanged filtering on column --- .../setting/environments/utils/columnFactories.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/packages/lowcoder/src/pages/setting/environments/utils/columnFactories.tsx b/client/packages/lowcoder/src/pages/setting/environments/utils/columnFactories.tsx index b33685ab7..45c69c580 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/utils/columnFactories.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/utils/columnFactories.tsx @@ -69,6 +69,13 @@ export function createManagedColumn( return { title: 'Managed', key: 'managed', + filterMode: 'menu', + filters: [ + { text: 'Managed', value: true }, + { text: 'Unmanaged', value: false }, + ], + onFilter: (value, record) => record.managed === value, + filterMultiple: false, render: (_, record: T) => ( @@ -178,6 +185,13 @@ export function createPublishedColumn(): Colu title: 'Status', dataIndex: 'published', key: 'published', + filterMode: 'menu', + filters: [ + { text: 'Published', value: true }, + { text: 'Unpublished', value: false }, + ], + onFilter: (value, record) => record.published === value, + filterMultiple: false, render: (published: boolean) => ( {published ? 'Published' : 'Unpublished'} From 8e0f94b9bb7a3f2fbb4d906612eb9dcbdea737fd Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 9 May 2025 19:29:27 +0500 Subject: [PATCH 02/37] Add search filter for the objects --- .../components/DeployableItemsList.tsx | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsList.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsList.tsx index 63f8dda72..bed49855d 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsList.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsList.tsx @@ -1,12 +1,14 @@ // components/DeployableItemsList.tsx -import React from 'react'; -import { Table, Tag, Empty, Spin, Switch, Space, Button, Tooltip } from 'antd'; -import { CloudUploadOutlined } from '@ant-design/icons'; +import React, { useState } from 'react'; +import { Table, Tag, Empty, Spin, Switch, Space, Button, Tooltip, Input } from 'antd'; +import { CloudUploadOutlined, SearchOutlined } from '@ant-design/icons'; import history from '@lowcoder-ee/util/history'; import { DeployableItem, BaseStats, DeployableItemConfig } from '../types/deployable-item.types'; import { Environment } from '../types/environment.types'; import { useDeployModal } from '../context/DeployModalContext'; +const { Search } = Input; + interface DeployableItemsListProps { items: T[]; loading: boolean; @@ -30,6 +32,14 @@ function DeployableItemsList({ }: DeployableItemsListProps) { const { openDeployModal } = useDeployModal(); + const [searchText, setSearchText] = useState(''); + + // Filter items based on search + const filteredItems = searchText + ? items.filter(item => + item.name.toLowerCase().includes(searchText.toLowerCase()) || + item.id.toLowerCase().includes(searchText.toLowerCase())) + : items; // Handle row click for navigation const handleRowClick = (item: T) => { @@ -53,8 +63,7 @@ function DeployableItemsList({ onToggleManaged, openDeployModal, additionalParams - }) - + }); if (loading) { return ( @@ -76,18 +85,36 @@ function DeployableItemsList({ const hasNavigation = config.buildDetailRoute({}) !== '#'; return ( - ({ - onClick: hasNavigation ? () => handleRowClick(record) : undefined, - style: hasNavigation ? { cursor: 'pointer' } : undefined, - })} - /> + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + /> + {searchText && filteredItems.length !== items.length && ( +
+ Showing {filteredItems.length} of {items.length} {config.pluralLabel.toLowerCase()} +
+ )} +
+ +
({ + onClick: hasNavigation ? () => handleRowClick(record) : undefined, + style: hasNavigation ? { cursor: 'pointer' } : undefined, + })} + /> + ); } From 9883336f591df05288ff594ac0c4baa0d61be66c Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 9 May 2025 23:44:55 +0500 Subject: [PATCH 03/37] Improve the Environment Not Found UI --- .../environments/EnvironmentDetail.tsx | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx index b772bf373..a3a15ec03 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx @@ -93,12 +93,37 @@ const EnvironmentDetail: React.FC = () => { if (error || !environment) { return ( - +
+ + + history.push("/setting/environments")} + > + Environments + + + Not Found + + + +
+ + Environment Not Found + + + {error || "The environment you're looking for doesn't exist or you don't have permission to view it."} + + +
+
+
); } From b6d74d26d05e22d46a1085496e034b731a605b73 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 12 May 2025 17:10:38 +0500 Subject: [PATCH 04/37] create a util function and add tags in Deployment Modal --- .../components/DeployItemModal.tsx | 25 +++++++++- .../components/EnvironmentsTable.tsx | 18 ++------ .../environments/utils/environmentUtils.ts | 46 +++++++++++++++++++ 3 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/setting/environments/utils/environmentUtils.ts diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx index a58ac7d78..b7ae9bb06 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx @@ -1,9 +1,11 @@ // components/DeployItemModal.tsx import React, { useState, useEffect } from 'react'; -import { Modal, Form, Select, Checkbox, Button, message, Spin, Input } from 'antd'; +import { Modal, Form, Select, Checkbox, Button, message, Spin, Input, Tag, Space } from 'antd'; import { Environment } from '../types/environment.types'; import { DeployableItem, BaseStats, DeployableItemConfig } from '../types/deployable-item.types'; import { useEnvironmentContext } from '../context/EnvironmentContext'; +import { getEnvironmentTagColor, formatEnvironmentType } from '../utils/environmentUtils'; + interface DeployItemModalProps { visible: boolean; item: T | null; @@ -84,6 +86,18 @@ function DeployItemModal({ form={form} layout="vertical" > + {/* Source environment display */} + + + {sourceEnvironment.environmentName} + {sourceEnvironment.environmentType && ( + + {formatEnvironmentType(sourceEnvironment.environmentType)} + + )} + + + ({ diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx index 0208932d7..4f9150a9b 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Table, Tag, Button, Tooltip, Space } from 'antd'; import { EditOutlined, AuditOutlined} from '@ant-design/icons'; import { Environment } from '../types/environment.types'; +import { getEnvironmentTagColor, formatEnvironmentType } from '../utils/environmentUtils'; @@ -20,19 +21,6 @@ const EnvironmentsTable: React.FC = ({ loading, onRowClick, }) => { - // Get color for environment type/stage - const getTypeColor = (type: string): string => { - if (!type) return 'default'; - - switch (type.toUpperCase()) { - case 'DEV': return 'blue'; - case 'TEST': return 'orange'; - case 'PREPROD': return 'purple'; - case 'PROD': return 'green'; - default: return 'default'; - } - }; - // Open audit page in new tab const openAuditPage = (environmentId: string, e: React.MouseEvent) => { e.stopPropagation(); // Prevent row click from triggering @@ -65,8 +53,8 @@ const EnvironmentsTable: React.FC = ({ dataIndex: 'environmentType', key: 'environmentType', render: (type: string) => ( - - {type ? type.toUpperCase() : 'UNKNOWN'} + + {formatEnvironmentType(type)} ), }, diff --git a/client/packages/lowcoder/src/pages/setting/environments/utils/environmentUtils.ts b/client/packages/lowcoder/src/pages/setting/environments/utils/environmentUtils.ts new file mode 100644 index 000000000..f3f5d5c1b --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/environments/utils/environmentUtils.ts @@ -0,0 +1,46 @@ +/** + * Utility functions for environment-related features + */ + +/** + * Get the appropriate color for an environment tag based on its type + * @param envType The environment type/stage (e.g. 'PROD', 'DEV', 'STAGING') + * @returns A color string to use with Ant Design's Tag component + */ +export const getEnvironmentTagColor = (envType: string | undefined): string => { + if (!envType) return 'default'; + + // Normalize to uppercase for consistent comparison + const type = envType.toUpperCase(); + + switch (type) { + // Production environment + case 'PROD': + return 'red'; // Red for production - indicates caution + + // Pre-production environment + case 'PREPROD': + return 'orange'; // Orange for pre-production + + // Test environment + case 'TEST': + return 'purple'; // Purple for test environment + + // Development environment + case 'DEV': + return 'green'; // Green for development - safe to use + + default: + return 'default'; // Default gray for unknown types + } +}; + +/** + * Format an environment type for display + * @param envType The environment type string + * @returns Formatted environment type string + */ +export const formatEnvironmentType = (envType: string | undefined): string => { + if (!envType) return 'UNKNOWN'; + return envType.toUpperCase(); +}; \ No newline at end of file From bb81f1fcef61c536570bf906423065f4b2b1e287 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Thu, 15 May 2025 00:41:20 +0500 Subject: [PATCH 05/37] fix endpoint for DS and ql --- .../setting/environments/services/datasources.service.ts | 9 ++++++++- .../pages/setting/environments/services/query.service.ts | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts index b1fe06745..71d6929ac 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts @@ -152,7 +152,14 @@ export async function getMergedWorkspaceDataSources( // Function to deploy a data source to another environment export async function deployDataSource(params: DeployDataSourceParams): Promise { try { - const response = await axios.post('/api/plugins/enterprise/datasource/deploy', params); + const response = await axios.post('/api/plugins/enterprise/datasource/deploy', null, { + params: { + envId: params.envId, + targetEnvId: params.targetEnvId, + datasourceId: params.datasourceId, + updateDependenciesIfNeeded: params.updateDependenciesIfNeeded ?? false + } + }); return response.status === 200; } catch (error) { console.error('Error deploying data source:', error); diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts index 39eda0235..8f5ad6892 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts @@ -78,7 +78,14 @@ export interface MergedQueriesResult { export async function deployQuery(params: DeployQueryParams): Promise { try { - const response = await axios.post('/api/plugins/enterprise/qlQuery/deploy', params); + const response = await axios.post('/api/plugins/enterprise/qlQuery/deploy', null, { + params: { + envId: params.envId, + targetEnvId: params.targetEnvId, + queryId: params.queryId, + updateDependenciesIfNeeded: params.updateDependenciesIfNeeded ?? false + } + }); return response.status === 200; } catch (error) { console.error('Error deploying query:', error); From b5647a7e68c83528e44253ddc62164742541fb52 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Thu, 15 May 2025 18:55:47 +0500 Subject: [PATCH 06/37] updated managed endpoints --- .../environments/config/apps.config.tsx | 17 +- .../config/data-sources.config.tsx | 15 +- .../environments/config/query.config.tsx | 15 +- .../environments/config/workspace.config.tsx | 15 +- .../environments/context/WorkspaceContext.tsx | 17 +- .../services/managed-objects.service.ts | 162 ++++++++++++++++++ 6 files changed, 223 insertions(+), 18 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx index 90b673f34..293baf4cc 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx @@ -5,7 +5,8 @@ import { AppstoreOutlined, AuditOutlined } from '@ant-design/icons'; import {DeployableItemConfig } from '../types/deployable-item.types'; import { Environment } from '../types/environment.types'; import { getMergedWorkspaceApps, deployApp } from '../services/apps.service'; -import { connectManagedApp, unconnectManagedApp } from '../services/enterprise.service'; +import { connectManagedApp, unconnectManagedApp } from '../services/enterprise.service'; +import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; import { App, AppStats } from '../types/app.types'; @@ -161,11 +162,19 @@ export const appsConfig: DeployableItemConfig = { toggleManaged: async ({ item, checked, environment }) => { try { if (checked) { - await connectManagedApp(environment.environmentId, item.name, item.applicationGid!); + return await setManagedObject( + item.applicationGid!, + environment.environmentId, + ManagedObjectType.APP, + item.name + ); } else { - await unconnectManagedApp(item.applicationGid!); + return await unsetManagedObject( + item.applicationGid!, + environment.environmentId, + ManagedObjectType.APP + ); } - return true; } catch (error) { console.error('Error toggling managed status:', error); return false; diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx index 567e460a7..069b820c5 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx @@ -7,6 +7,7 @@ import { DataSource, DataSourceStats } from '../types/datasource.types'; import { Environment } from '../types/environment.types'; import { getMergedWorkspaceDataSources, deployDataSource } from '../services/datasources.service'; import { connectManagedDataSource, unconnectManagedDataSource } from '../services/enterprise.service'; +import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; import { createNameColumn, createTypeColumn, @@ -150,11 +151,19 @@ export const dataSourcesConfig: DeployableItemConfig { try { if (checked) { - await connectManagedDataSource(environment.environmentId, item.name, item.gid); + return await setManagedObject( + item.gid, + environment.environmentId, + ManagedObjectType.DATASOURCE, + item.name + ); } else { - await unconnectManagedDataSource(item.gid); + return await unsetManagedObject( + item.gid, + environment.environmentId, + ManagedObjectType.DATASOURCE + ); } - return true; } catch (error) { console.error('Error toggling managed status:', error); return false; diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx index 00721f033..11f1fb1a5 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx @@ -6,6 +6,7 @@ import { DeployableItemConfig } from '../types/deployable-item.types'; import { Query } from '../types/query.types'; import { connectManagedQuery, unconnectManagedQuery } from '../services/enterprise.service'; import { getMergedWorkspaceQueries, deployQuery } from '../services/query.service'; +import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; import { Environment } from '../types/environment.types'; import { @@ -145,11 +146,19 @@ export const queryConfig: DeployableItemConfig = { toggleManaged: async ({ item, checked, environment }) => { try { if (checked) { - await connectManagedQuery(environment.environmentId, item.name, item.gid); + return await setManagedObject( + item.gid, + environment.environmentId, + ManagedObjectType.QUERY, + item.name + ); } else { - await unconnectManagedQuery(item.gid); + return await unsetManagedObject( + item.gid, + environment.environmentId, + ManagedObjectType.QUERY + ); } - return true; } catch (error) { console.error('Error toggling managed status:', error); return false; diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx index c6d3a7dc2..e68260f1b 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx @@ -7,6 +7,7 @@ import { Environment } from '../types/environment.types'; import { buildEnvironmentWorkspaceId } from '@lowcoder-ee/constants/routesURL'; import { getMergedEnvironmentWorkspaces, deployWorkspace } from '../services/workspace.service'; import { connectManagedWorkspace, unconnectManagedWorkspace } from '../services/enterprise.service'; +import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; import { createNameColumn, createIdColumn, @@ -135,11 +136,19 @@ export const workspaceConfig: DeployableItemConfig = toggleManaged: async ({ item, checked, environment }) => { try { if (checked) { - await connectManagedWorkspace(environment.environmentId, item.name, item.gid!); + return await setManagedObject( + item.gid!, + environment.environmentId, + ManagedObjectType.ORG, + item.name + ); } else { - await unconnectManagedWorkspace(item.gid!); + return await unsetManagedObject( + item.gid!, + environment.environmentId, + ManagedObjectType.ORG + ); } - return true; } catch (error) { console.error('Error toggling managed status:', error); return false; diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx index 72c7ef356..7ad259aeb 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx @@ -12,7 +12,8 @@ import React, { import { useSingleEnvironmentContext } from "./SingleEnvironmentContext"; import { fetchWorkspaceById } from "../services/environments.service"; import { Workspace } from "../types/workspace.types"; - import { getManagedWorkspaces, connectManagedWorkspace, unconnectManagedWorkspace } from "../services/enterprise.service"; + import { getManagedWorkspaces } from "../services/enterprise.service"; + import { ManagedObjectType, setManagedObject, unsetManagedObject } from "../services/managed-objects.service"; interface WorkspaceContextState { // Workspace data @@ -113,14 +114,20 @@ import React, { try { if (checked) { // Connect the workspace as managed - await connectManagedWorkspace( + await setManagedObject( + workspace.gid!, environment.environmentId, - workspace.name, - workspace.gid! + ManagedObjectType.ORG, + workspace.name + ); } else { // Disconnect the managed workspace - await unconnectManagedWorkspace(workspace.gid!); + await unsetManagedObject( + workspace.gid!, + environment.environmentId, + ManagedObjectType.ORG + ); } // Update local state diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts new file mode 100644 index 000000000..173f1006c --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts @@ -0,0 +1,162 @@ +import axios from "axios"; +import { message } from "antd"; + +// Object types that can be managed +export enum ManagedObjectType { + ORG = "ORG", + APP = "APP", + QUERY = "QUERY", + DATASOURCE = "DATASOURCE" +} + +/** + * Check if an object is managed + * @param objGid - Object's global ID + * @param environmentId - Environment ID + * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) + * @returns Promise with boolean indicating if object is managed + */ +export async function isManagedObject( + objGid: string, + environmentId: string, + objType: ManagedObjectType +): Promise { + try { + if (!objGid || !environmentId || !objType) { + throw new Error("Missing required parameters"); + } + + const response = await axios.get(`/api/plugins/enterprise/managed-obj`, { + params: { + objGid, + environmentId, + objType + } + }); + + return response.data.managed === true; + } catch (error) { + // If the object doesn't exist as managed, it's not an error + if (axios.isAxiosError(error) && error.response?.status === 404) { + return false; + } + + const errorMessage = error instanceof Error ? error.message : "Failed to check managed status"; + message.error(errorMessage); + throw error; + } +} + +/** + * Set an object as managed + * @param objGid - Object's global ID + * @param environmentId - Environment ID + * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) + * @param objName - Object name (optional) + * @param objTags - Object tags (optional) + * @returns Promise with operation result + */ +export async function setManagedObject( + objGid: string, + environmentId: string, + objType: ManagedObjectType, + objName?: string, + objTags: string[] = [] +): Promise { + try { + if (!objGid || !environmentId || !objType) { + throw new Error("Missing required parameters"); + } + + const response = await axios.post(`/api/plugins/enterprise/managed-obj`, + // Include optional parameters in the request body instead of query params + { + objName, + objTags: objTags.length > 0 ? objTags : undefined + }, + { + params: { + objGid, + environmentId, + objType + } + } + ); + + return response.status === 200; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : `Failed to set ${objType} as managed`; + message.error(errorMessage); + throw error; + } +} + +/** + * Set an object as unmanaged + * @param objGid - Object's global ID + * @param environmentId - Environment ID + * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) + * @returns Promise with operation result + */ +export async function unsetManagedObject( + objGid: string, + environmentId: string, + objType: ManagedObjectType +): Promise { + try { + if (!objGid || !environmentId || !objType) { + throw new Error("Missing required parameters"); + } + + const response = await axios.delete(`/api/plugins/enterprise/managed-obj`, { + params: { + objGid, + environmentId, + objType + } + }); + + return response.status === 200; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : `Failed to remove ${objType} from managed`; + message.error(errorMessage); + throw error; + } +} + +/** + * Get all managed objects of a specific type for an environment + * NOTE: This function is commented out as the endpoint is not yet implemented + * TODO: Uncomment when the /managed-obj/list endpoint is available + * + * @param environmentId - Environment ID + * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) + * @returns Promise with an array of managed objects + */ +/* +export async function getManagedObjects( + environmentId: string, + objType: ManagedObjectType +): Promise { + try { + if (!environmentId || !objType) { + throw new Error("Missing required parameters"); + } + + const response = await axios.get(`/api/plugins/enterprise/managed-obj/list`, { + params: { + environmentId, + objType + } + }); + + return response.data.data || []; + } catch (error) { + const errorMessage = error instanceof Error + ? error.message + : `Failed to fetch managed ${objType.toLowerCase()}s`; + message.error(errorMessage); + throw error; + } +} +*/ \ No newline at end of file From d08218f178a62e1a62175bc0fc9e1a7dca2aac52 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Thu, 15 May 2025 19:06:45 +0500 Subject: [PATCH 07/37] fix switch for objects --- .../src/pages/setting/environments/config/apps.config.tsx | 7 +++++-- .../setting/environments/config/data-sources.config.tsx | 7 +++++-- .../src/pages/setting/environments/config/query.config.tsx | 7 +++++-- .../pages/setting/environments/config/workspace.config.tsx | 7 +++++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx index 293baf4cc..6d787010c 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx @@ -162,19 +162,22 @@ export const appsConfig: DeployableItemConfig = { toggleManaged: async ({ item, checked, environment }) => { try { if (checked) { - return await setManagedObject( + // Connect the app as managed + await setManagedObject( item.applicationGid!, environment.environmentId, ManagedObjectType.APP, item.name ); } else { - return await unsetManagedObject( + // Disconnect the managed app + await unsetManagedObject( item.applicationGid!, environment.environmentId, ManagedObjectType.APP ); } + return true; } catch (error) { console.error('Error toggling managed status:', error); return false; diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx index 069b820c5..6a0ed6022 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx @@ -151,19 +151,22 @@ export const dataSourcesConfig: DeployableItemConfig { try { if (checked) { - return await setManagedObject( + // Connect the data source as managed + await setManagedObject( item.gid, environment.environmentId, ManagedObjectType.DATASOURCE, item.name ); } else { - return await unsetManagedObject( + // Disconnect the managed data source + await unsetManagedObject( item.gid, environment.environmentId, ManagedObjectType.DATASOURCE ); } + return true; } catch (error) { console.error('Error toggling managed status:', error); return false; diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx index 11f1fb1a5..641d0f41f 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx @@ -146,19 +146,22 @@ export const queryConfig: DeployableItemConfig = { toggleManaged: async ({ item, checked, environment }) => { try { if (checked) { - return await setManagedObject( + // Connect the query as managed + await setManagedObject( item.gid, environment.environmentId, ManagedObjectType.QUERY, item.name ); } else { - return await unsetManagedObject( + // Disconnect the managed query + await unsetManagedObject( item.gid, environment.environmentId, ManagedObjectType.QUERY ); } + return true; } catch (error) { console.error('Error toggling managed status:', error); return false; diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx index e68260f1b..9174ac662 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx @@ -136,19 +136,22 @@ export const workspaceConfig: DeployableItemConfig = toggleManaged: async ({ item, checked, environment }) => { try { if (checked) { - return await setManagedObject( + // Connect the workspace as managed + await setManagedObject( item.gid!, environment.environmentId, ManagedObjectType.ORG, item.name ); } else { - return await unsetManagedObject( + // Disconnect the managed workspace + await unsetManagedObject( item.gid!, environment.environmentId, ManagedObjectType.ORG ); } + return true; } catch (error) { console.error('Error toggling managed status:', error); return false; From c1a74fe0ca29e7036f3a6c57566fc4089f28049e Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Thu, 15 May 2025 20:42:41 +0500 Subject: [PATCH 08/37] refactor AppsTab component --- .../setting/environments/WorkspaceDetail.tsx | 16 +- .../environments/components/AppsTab.tsx | 292 ++++++++++++++++++ 2 files changed, 300 insertions(+), 8 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx index 79b861882..eeeed6815 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx @@ -34,6 +34,7 @@ import { appsConfig } from "./config/apps.config"; import { dataSourcesConfig } from "./config/data-sources.config"; import { queryConfig } from "./config/query.config"; +import AppsTab from "./components/AppsTab"; const { Title, Text } = Typography; const { TabPane } = Tabs; @@ -160,14 +161,13 @@ const WorkspaceDetail: React.FC = () => { {/* Tabs for Apps, Data Sources, and Queries */} - Apps} key="apps"> - - + // Replace the Apps TabPane in WorkspaceDetail.tsx with this: + Apps} key="apps"> + + Data Sources} key="dataSources"> = ({ environment, workspace }) => { + const [apps, setApps] = useState([]); + const [stats, setStats] = useState({ + total: 0, + published: 0, + managed: 0, + unmanaged: 0 + }); + const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + const [error, setError] = useState(null); + const [searchText, setSearchText] = useState(''); + const { openDeployModal } = useDeployModal(); + + // Fetch apps + const fetchApps = async () => { + if (!workspace.id || !environment) return; + + setLoading(true); + setError(null); + + try { + const result = await getMergedWorkspaceApps( + workspace.id, + environment.environmentId, + environment.environmentApikey, + environment.environmentApiServiceUrl! + ); + + setApps(result.apps); + + // Calculate stats + const total = result.apps.length; + const published = result.apps.filter(app => app.published).length; + const managed = result.apps.filter(app => app.managed).length; + + setStats({ + total, + published, + managed, + unmanaged: total - managed + }); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to fetch apps"); + } finally { + setLoading(false); + setRefreshing(false); + } + }; + + useEffect(() => { + fetchApps(); + }, [environment, workspace]); + + // Handle refresh + const handleRefresh = () => { + setRefreshing(true); + fetchApps(); + }; + + // Toggle managed status + const handleToggleManaged = async (app: App, checked: boolean) => { + setRefreshing(true); + try { + if (checked) { + await setManagedObject( + app.applicationGid, + environment.environmentId, + ManagedObjectType.APP, + app.name + ); + } else { + await unsetManagedObject( + app.applicationGid, + environment.environmentId, + ManagedObjectType.APP + ); + } + + // Update the app in state + const updatedApps = apps.map(item => { + if (item.applicationId === app.applicationId) { + return { ...item, managed: checked }; + } + return item; + }); + + setApps(updatedApps); + + // Update stats + const managed = updatedApps.filter(app => app.managed).length; + setStats(prev => ({ + ...prev, + managed, + unmanaged: prev.total - managed + })); + + message.success(`${app.name} is now ${checked ? 'Managed' : 'Unmanaged'}`); + return true; + } catch (error) { + message.error(`Failed to change managed status for ${app.name}`); + return false; + } finally { + setRefreshing(false); + } + }; + + // Filter apps based on search + const filteredApps = searchText + ? apps.filter(app => + app.name.toLowerCase().includes(searchText.toLowerCase()) || + app.applicationId.toLowerCase().includes(searchText.toLowerCase())) + : apps; + + // Table columns + const columns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + render: (text: string) => {text} + }, + { + title: 'ID', + dataIndex: 'applicationId', + key: 'applicationId', + ellipsis: true, + }, + { + title: 'Published', + dataIndex: 'published', + key: 'published', + render: (published: boolean) => ( + + {published ? 'Published' : 'Draft'} + + ), + }, + { + title: 'Managed', + key: 'managed', + render: (_: any, app: App) => ( + handleToggleManaged(app, checked)} + loading={refreshing} + size="small" + /> + ), + }, + { + title: 'Actions', + key: 'actions', + render: (_: any, app: App) => ( + e.stopPropagation()}> + + + + + ), + } + ]; + + return ( + + {/* Header with refresh button */} +
+ Apps in this Workspace + +
+ + {/* Stats display */} +
+
+
Total Apps
+
{stats.total}
+
+
+
Published Apps
+
{stats.published}
+
+
+
Managed Apps
+
{stats.managed}
+
+
+
Unmanaged Apps
+
{stats.unmanaged}
+
+
+ + + + {/* Error display */} + {error && ( + + )} + + {/* Configuration warnings */} + {(!environment.environmentApikey || !environment.environmentApiServiceUrl) && !error && ( + + )} + + {/* Content */} + {loading ? ( +
+ +
+ ) : apps.length === 0 ? ( + + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + /> + {searchText && filteredApps.length !== apps.length && ( +
+ Showing {filteredApps.length} of {apps.length} apps +
+ )} +
+ +
+ + )} + + ); +}; + +export default AppsTab; \ No newline at end of file From 0a1879be9a70e203d5decdc34c042af9c0421022 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Thu, 15 May 2025 20:49:18 +0500 Subject: [PATCH 09/37] Seperate DS tab --- .../setting/environments/WorkspaceDetail.tsx | 7 +- .../components/DataSourcesTab.tsx | 279 ++++++++++++++++++ 2 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx index eeeed6815..25bd2f3a2 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx @@ -35,6 +35,7 @@ import { dataSourcesConfig } from "./config/data-sources.config"; import { queryConfig } from "./config/query.config"; import AppsTab from "./components/AppsTab"; +import DataSourcesTab from "./components/DataSourcesTab"; const { Title, Text } = Typography; const { TabPane } = Tabs; @@ -170,11 +171,9 @@ const WorkspaceDetail: React.FC = () => { Data Sources} key="dataSources"> - diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx new file mode 100644 index 000000000..2eba3050e --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx @@ -0,0 +1,279 @@ +import React, { useState, useEffect } from 'react'; +import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd'; +import { SyncOutlined, CloudUploadOutlined, DatabaseOutlined } from '@ant-design/icons'; +import Title from 'antd/lib/typography/Title'; +import { Environment } from '../types/environment.types'; +import { Workspace } from '../types/workspace.types'; +import { DataSource } from '../types/datasource.types'; +import { getMergedWorkspaceDataSources } from '../services/datasources.service'; +import { Switch, Spin, Empty } from 'antd'; +import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; +import { useDeployModal } from '../context/DeployModalContext'; +import { dataSourcesConfig } from '../config/data-sources.config'; + +const { Search } = Input; + +interface DataSourcesTabProps { + environment: Environment; + workspace: Workspace; +} + +const DataSourcesTab: React.FC = ({ environment, workspace }) => { + const [dataSources, setDataSources] = useState([]); + const [stats, setStats] = useState({ + total: 0, + types: 0, + managed: 0, + unmanaged: 0 + }); + const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + const [error, setError] = useState(null); + const [searchText, setSearchText] = useState(''); + const { openDeployModal } = useDeployModal(); + + // Fetch data sources + const fetchDataSources = async () => { + if (!workspace.id || !environment) return; + + setLoading(true); + setError(null); + + try { + const result = await getMergedWorkspaceDataSources( + workspace.id, + environment.environmentId, + environment.environmentApikey, + environment.environmentApiServiceUrl! + ); + + setDataSources(result.dataSources); + setStats(result.stats); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to fetch data sources"); + } finally { + setLoading(false); + setRefreshing(false); + } + }; + + useEffect(() => { + fetchDataSources(); + }, [environment, workspace]); + + // Handle refresh + const handleRefresh = () => { + setRefreshing(true); + fetchDataSources(); + }; + + // Toggle managed status + const handleToggleManaged = async (dataSource: DataSource, checked: boolean) => { + setRefreshing(true); + try { + if (checked) { + await setManagedObject( + dataSource.gid, + environment.environmentId, + ManagedObjectType.DATASOURCE, + dataSource.name + ); + } else { + await unsetManagedObject( + dataSource.gid, + environment.environmentId, + ManagedObjectType.DATASOURCE + ); + } + + // Update the data source in state + const updatedDataSources = dataSources.map(item => { + if (item.id === dataSource.id) { + return { ...item, managed: checked }; + } + return item; + }); + + setDataSources(updatedDataSources); + + // Update stats + const managed = updatedDataSources.filter(ds => ds.managed).length; + setStats(prev => ({ + ...prev, + managed, + unmanaged: prev.total - managed + })); + + message.success(`${dataSource.name} is now ${checked ? 'Managed' : 'Unmanaged'}`); + return true; + } catch (error) { + message.error(`Failed to change managed status for ${dataSource.name}`); + return false; + } finally { + setRefreshing(false); + } + }; + + // Filter data sources based on search + const filteredDataSources = searchText + ? dataSources.filter(ds => + ds.name.toLowerCase().includes(searchText.toLowerCase()) || + ds.id.toString().toLowerCase().includes(searchText.toLowerCase())) + : dataSources; + + // Table columns + const columns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + render: (text: string) => {text} + }, + { + title: 'ID', + dataIndex: 'id', + key: 'id', + ellipsis: true, + }, + { + title: 'Type', + dataIndex: 'type', + key: 'type', + render: (type: string) => ( + {type} + ), + }, + { + title: 'Managed', + key: 'managed', + render: (_: any, dataSource: DataSource) => ( + handleToggleManaged(dataSource, checked)} + loading={refreshing} + size="small" + /> + ), + }, + { + title: 'Actions', + key: 'actions', + render: (_: any, dataSource: DataSource) => ( + e.stopPropagation()}> + + + + + ), + } + ]; + + return ( + + {/* Header with refresh button */} +
+ Data Sources in this Workspace + +
+ + {/* Stats display */} +
+
+
Total Data Sources
+
{stats.total}
+
+
+
Types
+
{stats.types}
+
+
+
Managed
+
{stats.managed}
+
+
+
Unmanaged
+
{stats.unmanaged}
+
+
+ + + + {/* Error display */} + {error && ( + + )} + + {/* Configuration warnings */} + {(!environment.environmentApikey || !environment.environmentApiServiceUrl) && !error && ( + + )} + + {/* Content */} + {loading ? ( +
+ +
+ ) : dataSources.length === 0 ? ( + + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + /> + {searchText && filteredDataSources.length !== dataSources.length && ( +
+ Showing {filteredDataSources.length} of {dataSources.length} data sources +
+ )} +
+ +
+ + )} + + ); +}; + +export default DataSourcesTab; \ No newline at end of file From dffc68353b4b89346fc965e8d090de6c9723cafe Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Thu, 15 May 2025 21:20:45 +0500 Subject: [PATCH 10/37] Seperate Queries Tab --- .../setting/environments/WorkspaceDetail.tsx | 10 +- .../environments/components/QueriesTab.tsx | 271 ++++++++++++++++++ 2 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx index 25bd2f3a2..4b12fe652 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx @@ -36,6 +36,8 @@ import { queryConfig } from "./config/query.config"; import AppsTab from "./components/AppsTab"; import DataSourcesTab from "./components/DataSourcesTab"; +import QueriesTab from "./components/QueriesTab"; + const { Title, Text } = Typography; const { TabPane } = Tabs; @@ -176,15 +178,13 @@ const WorkspaceDetail: React.FC = () => { workspace={workspace} /> - Queries} key="queries"> - + ); diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx new file mode 100644 index 000000000..ad01dddd5 --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx @@ -0,0 +1,271 @@ +import React, { useState, useEffect } from 'react'; +import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd'; +import { SyncOutlined, CloudUploadOutlined, CodeOutlined } from '@ant-design/icons'; +import Title from 'antd/lib/typography/Title'; +import { Environment } from '../types/environment.types'; +import { Workspace } from '../types/workspace.types'; +import { Query } from '../types/query.types'; +import { getMergedWorkspaceQueries } from '../services/query.service'; +import { Switch, Spin, Empty } from 'antd'; +import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; +import { useDeployModal } from '../context/DeployModalContext'; +import { queryConfig } from '../config/query.config'; + +const { Search } = Input; + +interface QueriesTabProps { + environment: Environment; + workspace: Workspace; +} + +const QueriesTab: React.FC = ({ environment, workspace }) => { + const [queries, setQueries] = useState([]); + const [stats, setStats] = useState({ + total: 0, + managed: 0, + unmanaged: 0 + }); + const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + const [error, setError] = useState(null); + const [searchText, setSearchText] = useState(''); + const { openDeployModal } = useDeployModal(); + + // Fetch queries + const fetchQueries = async () => { + if (!workspace.id || !environment) return; + + setLoading(true); + setError(null); + + try { + const result = await getMergedWorkspaceQueries( + workspace.id, + environment.environmentId, + environment.environmentApikey, + environment.environmentApiServiceUrl! + ); + + setQueries(result.queries); + setStats(result.stats); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to fetch queries"); + } finally { + setLoading(false); + setRefreshing(false); + } + }; + + useEffect(() => { + fetchQueries(); + }, [environment, workspace]); + + // Handle refresh + const handleRefresh = () => { + setRefreshing(true); + fetchQueries(); + }; + + // Toggle managed status + const handleToggleManaged = async (query: Query, checked: boolean) => { + setRefreshing(true); + try { + if (checked) { + await setManagedObject( + query.gid, + environment.environmentId, + ManagedObjectType.QUERY, + query.name + ); + } else { + await unsetManagedObject( + query.gid, + environment.environmentId, + ManagedObjectType.QUERY + ); + } + + // Update the query in state + const updatedQueries = queries.map(item => { + if (item.id === query.id) { + return { ...item, managed: checked }; + } + return item; + }); + + setQueries(updatedQueries); + + // Update stats + const managed = updatedQueries.filter(q => q.managed).length; + setStats(prev => ({ + ...prev, + managed, + unmanaged: prev.total - managed + })); + + message.success(`${query.name} is now ${checked ? 'Managed' : 'Unmanaged'}`); + return true; + } catch (error) { + message.error(`Failed to change managed status for ${query.name}`); + return false; + } finally { + setRefreshing(false); + } + }; + + // Filter queries based on search + const filteredQueries = searchText + ? queries.filter(query => + query.name.toLowerCase().includes(searchText.toLowerCase()) || + query.id.toLowerCase().includes(searchText.toLowerCase())) + : queries; + + // Table columns + const columns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + render: (text: string) => {text} + }, + { + title: 'ID', + dataIndex: 'id', + key: 'id', + ellipsis: true, + }, + { + title: 'Creator', + dataIndex: 'creatorName', + key: 'creatorName', + }, + { + title: 'Managed', + key: 'managed', + render: (_: any, query: Query) => ( + handleToggleManaged(query, checked)} + loading={refreshing} + size="small" + /> + ), + }, + { + title: 'Actions', + key: 'actions', + render: (_: any, query: Query) => ( + e.stopPropagation()}> + + + + + ), + } + ]; + + return ( + + {/* Header with refresh button */} +
+ Queries in this Workspace + +
+ + {/* Stats display */} +
+
+
Total Queries
+
{stats.total}
+
+
+
Managed
+
{stats.managed}
+
+
+
Unmanaged
+
{stats.unmanaged}
+
+
+ + + + {/* Error display */} + {error && ( + + )} + + {/* Configuration warnings */} + {(!environment.environmentApikey || !environment.environmentApiServiceUrl) && !error && ( + + )} + + {/* Content */} + {loading ? ( +
+ +
+ ) : queries.length === 0 ? ( + + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + /> + {searchText && filteredQueries.length !== queries.length && ( +
+ Showing {filteredQueries.length} of {queries.length} queries +
+ )} +
+ +
+ + )} + + ); +}; + +export default QueriesTab; \ No newline at end of file From 741076551ddfc08fb4eec6b61698c5b8f41750ce Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Thu, 15 May 2025 21:45:00 +0500 Subject: [PATCH 11/37] Add seperate workspace and usergroups tab --- .../environments/EnvironmentDetail.tsx | 22 +- .../environments/components/UserGroupsTab.tsx | 222 ++++++++++++++++++ .../environments/components/WorkspacesTab.tsx | 217 +++++++++++++++++ 3 files changed, 447 insertions(+), 14 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx create mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx index a3a15ec03..e42c3db64 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx @@ -25,7 +25,8 @@ import DeployableItemsTab from "./components/DeployableItemsTab"; import EditEnvironmentModal from "./components/EditEnvironmentModal"; import { Environment } from "./types/environment.types"; import history from "@lowcoder-ee/util/history"; - +import WorkspacesTab from "./components/WorkspacesTab"; +import UserGroupsTab from "./components/UserGroupsTab"; const { Title, Text } = Typography; const { TabPane } = Tabs; @@ -227,13 +228,10 @@ const EnvironmentDetail: React.FC = () => { {/* Tabs for Workspaces and User Groups */} - {/* Using our new generic component with the workspace config */} - + {/* Using our new standalone WorkspacesTab component */} + + @@ -242,15 +240,11 @@ const EnvironmentDetail: React.FC = () => { } key="userGroups" > - {/* Using our new generic component with the user group config */} - + {/* Now using our standalone UserGroupsTab component */} + - + {/* Edit Environment Modal */} {environment && ( = ({ environment }) => { + const [userGroups, setUserGroups] = useState([]); + const [stats, setStats] = useState({ + total: 0, + active: 0, + inactive: 0 + }); + const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + const [error, setError] = useState(null); + const [searchText, setSearchText] = useState(''); + + // Fetch user groups + const fetchUserGroups = async () => { + if (!environment) return; + + setLoading(true); + setError(null); + + try { + // Check for required environment properties + if (!environment.environmentApikey || !environment.environmentApiServiceUrl) { + setError('Missing required configuration: API key or API service URL'); + setLoading(false); + return; + } + + const groups = await getEnvironmentUserGroups( + environment.environmentId, + environment.environmentApikey, + environment.environmentApiServiceUrl + ); + + setUserGroups(groups); + + // Calculate stats + const total = groups.length; + const active = groups.filter(group => group.state === 'ACTIVE').length; + + setStats({ + total, + active, + inactive: total - active + }); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to fetch user groups"); + } finally { + setLoading(false); + setRefreshing(false); + } + }; + + useEffect(() => { + fetchUserGroups(); + }, [environment]); + + // Handle refresh + const handleRefresh = () => { + setRefreshing(true); + fetchUserGroups(); + }; + + // Filter user groups based on search + const filteredUserGroups = searchText + ? userGroups.filter(group => + group.name.toLowerCase().includes(searchText.toLowerCase()) || + group.id.toLowerCase().includes(searchText.toLowerCase())) + : userGroups; + + // Table columns + const columns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + render: (text: string) => {text} + }, + { + title: 'ID', + dataIndex: 'id', + key: 'id', + ellipsis: true, + }, + { + title: 'Type', + dataIndex: 'type', + key: 'type', + render: (type: string) => ( + + {type} + + ), + }, + { + title: 'Status', + dataIndex: 'state', + key: 'state', + render: (state: string) => ( + + {state} + + ), + }, + { + title: 'Member Count', + dataIndex: 'memberCount', + key: 'memberCount', + } + ]; + + return ( + + {/* Header with refresh button */} +
+ User Groups in this Environment + +
+ + {/* Stats display */} +
+
+
Total Groups
+
{stats.total}
+
+
+
Active
+
{stats.active}
+
+
+
Inactive
+
{stats.inactive}
+
+
+ + + + {/* Error display */} + {error && ( + + )} + + {/* Configuration warnings */} + {(!environment.environmentApikey || !environment.environmentApiServiceUrl) && !error && ( + + )} + + {/* Content */} + {loading ? ( +
+ +
+ ) : userGroups.length === 0 ? ( + + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + /> + {searchText && filteredUserGroups.length !== userGroups.length && ( +
+ Showing {filteredUserGroups.length} of {userGroups.length} user groups +
+ )} +
+ +
+ + )} + + ); +}; + +export default UserGroupsTab; \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx new file mode 100644 index 000000000..f2f8b408c --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx @@ -0,0 +1,217 @@ +import React, { useState, useEffect } from 'react'; +import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd'; +import { SyncOutlined, CloudUploadOutlined } from '@ant-design/icons'; +import Title from 'antd/lib/typography/Title'; +import { Environment } from '../types/environment.types'; +import { Workspace } from '../types/workspace.types'; +import { getMergedEnvironmentWorkspaces } from '../services/workspace.service'; +import { Spin, Empty } from 'antd'; + +import history from '@lowcoder-ee/util/history'; + +const { Search } = Input; + +interface WorkspacesTabProps { + environment: Environment; +} + +const WorkspacesTab: React.FC = ({ environment }) => { + const [workspaces, setWorkspaces] = useState([]); + const [stats, setStats] = useState({ + total: 0, + managed: 0, + unmanaged: 0 + }); + const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + const [error, setError] = useState(null); + const [searchText, setSearchText] = useState(''); + + // Fetch workspaces + const fetchWorkspaces = async () => { + if (!environment) return; + + setLoading(true); + setError(null); + + try { + // Check for required environment properties + if (!environment.environmentApikey || !environment.environmentApiServiceUrl) { + setError('Missing required configuration: API key or API service URL'); + setLoading(false); + return; + } + + const result = await getMergedEnvironmentWorkspaces( + environment.environmentId, + environment.environmentApikey, + environment.environmentApiServiceUrl + ); + + setWorkspaces(result.workspaces); + setStats(result.stats); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to fetch workspaces"); + } finally { + setLoading(false); + setRefreshing(false); + } + }; + + useEffect(() => { + fetchWorkspaces(); + }, [environment]); + + // Handle refresh + const handleRefresh = () => { + setRefreshing(true); + fetchWorkspaces(); + }; + + // Toggle managed status + + + // Handle row click for navigation + const handleRowClick = (workspace: Workspace) => { + history.push(`/setting/environments/${environment.environmentId}/workspaces/${workspace.id}`); + }; + + // Filter workspaces based on search + const filteredWorkspaces = searchText + ? workspaces.filter(workspace => + workspace.name.toLowerCase().includes(searchText.toLowerCase()) || + workspace.id.toLowerCase().includes(searchText.toLowerCase())) + : workspaces; + + // Table columns + const columns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + render: (text: string) => {text} + }, + { + title: 'ID', + dataIndex: 'id', + key: 'id', + ellipsis: true, + }, + { + title: 'Role', + dataIndex: 'role', + key: 'role', + }, + { + title: 'Status', + dataIndex: 'status', + key: 'status', + render: (status: string) => ( + + {status} + + ), + }, + ]; + + return ( + + {/* Header with refresh button */} +
+ Workspaces in this Environment + +
+ + {/* Stats display */} +
+
+
Total Workspaces
+
{stats.total}
+
+
+
Managed
+
{stats.managed}
+
+
+
Unmanaged
+
{stats.unmanaged}
+
+
+ + + + {/* Error display */} + {error && ( + + )} + + {/* Configuration warnings */} + {(!environment.environmentApikey || !environment.environmentApiServiceUrl) && !error && ( + + )} + + {/* Content */} + {loading ? ( +
+ +
+ ) : workspaces.length === 0 ? ( + + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + /> + {searchText && filteredWorkspaces.length !== workspaces.length && ( +
+ Showing {filteredWorkspaces.length} of {workspaces.length} workspaces +
+ )} +
+ +
({ + onClick: () => handleRowClick(record), + style: { cursor: 'pointer' } + })} + /> + + )} + + ); +}; + +export default WorkspacesTab; \ No newline at end of file From 6383d7fa3d0fe43903f6a8a9c7c1e4bb1ec6e451 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Thu, 15 May 2025 23:16:43 +0500 Subject: [PATCH 12/37] remove unnecessary code --- .../environments/EnvironmentDetail.tsx | 3 - .../setting/environments/WorkspaceDetail.tsx | 5 - .../environments/components/AppsTab.tsx | 2 +- .../components/DeployItemModal.tsx | 18 +- .../components/DeployableItemsList.tsx | 121 ------- .../components/DeployableItemsTab.tsx | 126 ------- .../environments/components/UserGroupsTab.tsx | 83 +++-- .../environments/config/apps.config.tsx | 186 +--------- .../config/data-sources.config.tsx | 172 +--------- .../environments/config/query.config.tsx | 166 +-------- .../environments/config/usergroups.config.tsx | 169 --------- .../environments/config/workspace.config.tsx | 171 +--------- .../context/DeployModalContext.tsx | 18 +- .../environments/hooks/useDeployableItems.ts | 146 -------- .../environments/services/apps.service.ts | 8 +- .../environments/services/query.service.ts | 8 +- .../setting/environments/types/app.types.ts | 10 +- .../environments/types/datasource.types.ts | 8 +- .../types/deployable-item.types.ts | 91 +---- .../setting/environments/types/query.types.ts | 7 +- .../environments/types/userGroup.types.ts | 54 ++- .../environments/utils/columnFactories.tsx | 323 ------------------ 22 files changed, 127 insertions(+), 1768 deletions(-) delete mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsList.tsx delete mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsTab.tsx delete mode 100644 client/packages/lowcoder/src/pages/setting/environments/config/usergroups.config.tsx delete mode 100644 client/packages/lowcoder/src/pages/setting/environments/hooks/useDeployableItems.ts delete mode 100644 client/packages/lowcoder/src/pages/setting/environments/utils/columnFactories.tsx diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx index e42c3db64..16dc4470a 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx @@ -19,9 +19,6 @@ import { } from "@ant-design/icons"; import { useSingleEnvironmentContext } from "./context/SingleEnvironmentContext"; -import { workspaceConfig } from "./config/workspace.config"; -import { userGroupsConfig } from "./config/usergroups.config"; -import DeployableItemsTab from "./components/DeployableItemsTab"; import EditEnvironmentModal from "./components/EditEnvironmentModal"; import { Environment } from "./types/environment.types"; import history from "@lowcoder-ee/util/history"; diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx index 4b12fe652..8c87b2ef6 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx @@ -28,12 +28,7 @@ import { useSingleEnvironmentContext } from "./context/SingleEnvironmentContext" import { useWorkspaceContext } from "./context/WorkspaceContext"; import { useDeployModal } from "./context/DeployModalContext"; -import DeployableItemsTab from "./components/DeployableItemsTab"; import { workspaceConfig } from "./config/workspace.config"; -import { appsConfig } from "./config/apps.config"; -import { dataSourcesConfig } from "./config/data-sources.config"; -import { queryConfig } from "./config/query.config"; - import AppsTab from "./components/AppsTab"; import DataSourcesTab from "./components/DataSourcesTab"; import QueriesTab from "./components/QueriesTab"; diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx index f5ca4b388..70e0d68ec 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd'; -import { SyncOutlined, CloudUploadOutlined, SearchOutlined } from '@ant-design/icons'; +import { SyncOutlined, CloudUploadOutlined } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { Workspace } from '../types/workspace.types'; diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx index b7ae9bb06..5f5ebc67f 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx @@ -2,27 +2,27 @@ import React, { useState, useEffect } from 'react'; import { Modal, Form, Select, Checkbox, Button, message, Spin, Input, Tag, Space } from 'antd'; import { Environment } from '../types/environment.types'; -import { DeployableItem, BaseStats, DeployableItemConfig } from '../types/deployable-item.types'; +import { DeployableItemConfig } from '../types/deployable-item.types'; import { useEnvironmentContext } from '../context/EnvironmentContext'; import { getEnvironmentTagColor, formatEnvironmentType } from '../utils/environmentUtils'; -interface DeployItemModalProps { +interface DeployItemModalProps { visible: boolean; - item: T | null; + item: any | null; sourceEnvironment: Environment; - config: DeployableItemConfig; + config: DeployableItemConfig; onClose: () => void; onSuccess?: () => void; } -function DeployItemModal({ +function DeployItemModal({ visible, item, sourceEnvironment, config, onClose, onSuccess -}: DeployItemModalProps) { +}: DeployItemModalProps) { const [form] = Form.useForm(); const { environments, isLoading } = useEnvironmentContext(); const [deploying, setDeploying] = useState(false); @@ -39,7 +39,7 @@ function DeployItemModal({ ); const handleDeploy = async () => { - if (!config.deploy?.enabled || !item) return; + if (!config.deploy || !item) return; try { const values = await form.validateFields(); @@ -63,7 +63,7 @@ function DeployItemModal({ onClose(); } catch (error) { console.error('Deployment error:', error); - message.error(`Failed to deploy ${config.singularLabel.toLowerCase()}`); + message.error(`Failed to deploy ${config.deploy.singularLabel.toLowerCase()}`); } finally { setDeploying(false); } @@ -71,7 +71,7 @@ function DeployItemModal({ return ( { - items: T[]; - loading: boolean; - refreshing: boolean; - error?: string | null; - environment: Environment; - config: DeployableItemConfig; - onToggleManaged?: (item: T, checked: boolean) => Promise; - additionalParams?: Record; -} - -function DeployableItemsList({ - items, - loading, - refreshing, - error, - environment, - config, - onToggleManaged, - additionalParams = {} -}: DeployableItemsListProps) { - - const { openDeployModal } = useDeployModal(); - const [searchText, setSearchText] = useState(''); - - // Filter items based on search - const filteredItems = searchText - ? items.filter(item => - item.name.toLowerCase().includes(searchText.toLowerCase()) || - item.id.toLowerCase().includes(searchText.toLowerCase())) - : items; - - // Handle row click for navigation - const handleRowClick = (item: T) => { - // Skip navigation if the route is just '#' (for non-navigable items) - if (config.buildDetailRoute({}) === '#') return; - - // Build the route using the config and navigate - const route = config.buildDetailRoute({ - environmentId: environment.environmentId, - itemId: item[config.idField] as string, - ...additionalParams - }); - - history.push(route); - }; - - // Get columns from config - const columns = config.getColumns({ - environment, - refreshing, - onToggleManaged, - openDeployModal, - additionalParams - }); - - if (loading) { - return ( -
- -
- ); - } - - if (!items || items.length === 0 || error) { - return ( - - ); - } - - const hasNavigation = config.buildDetailRoute({}) !== '#'; - - return ( - <> - {/* Search Bar */} -
- setSearchText(value)} - onChange={e => setSearchText(e.target.value)} - style={{ width: 300 }} - /> - {searchText && filteredItems.length !== items.length && ( -
- Showing {filteredItems.length} of {items.length} {config.pluralLabel.toLowerCase()} -
- )} -
- -
({ - onClick: hasNavigation ? () => handleRowClick(record) : undefined, - style: hasNavigation ? { cursor: 'pointer' } : undefined, - })} - /> - - ); -} - -export default DeployableItemsList; \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsTab.tsx deleted file mode 100644 index 4e50a873c..000000000 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsTab.tsx +++ /dev/null @@ -1,126 +0,0 @@ -// components/DeployableItemsTab.tsx -import React from 'react'; -import { Card, Button, Divider, Alert, message } from 'antd'; -import { SyncOutlined } from '@ant-design/icons'; -import Title from 'antd/lib/typography/Title'; -import { Environment } from '../types/environment.types'; -import { DeployableItem, BaseStats, DeployableItemConfig } from '../types/deployable-item.types'; -import { useDeployableItems } from '../hooks/useDeployableItems'; -import DeployableItemsList from './DeployableItemsList'; - -interface DeployableItemsTabProps { - environment: Environment; - config: DeployableItemConfig; - additionalParams?: Record; - title?: string; -} - -function DeployableItemsTab({ - environment, - config, - additionalParams = {}, - title -}: DeployableItemsTabProps) { - // Use our generic hook with the provided config - const { - items, - stats, - loading, - error, - refreshing, - toggleManagedStatus, - refreshItems - } = useDeployableItems(config, environment, additionalParams); - - // Handle toggling managed status - const handleToggleManaged = async (item: T, checked: boolean) => { - const success = await toggleManagedStatus(item, checked); - - if (success) { - message.success(`${item.name} is now ${checked ? 'Managed' : 'Unmanaged'}`); - } else { - message.error(`Failed to toggle managed state for ${item.name}`); - } - - return success; - }; - - // Handle refresh button click - const handleRefresh = () => { - refreshItems(); - message.info(`Refreshing ${config.pluralLabel.toLowerCase()}...`); - }; - - // Check for missing required environment properties - const missingProps = config.requiredEnvProps.filter( - prop => !environment[prop as keyof Environment] - ); - - return ( - - {/* Header with refresh button */} -
- - {title || `${config.pluralLabel} in this Environment`} - - -
- - {/* Render stats using the config's renderStats function */} - {config.renderStats(stats)} - - - - {/* Show error if loading failed */} - {error && ( - - )} - - {/* Configuration warnings based on required props */} - {missingProps.length > 0 && !error && ( - - )} - - {/* Items List */} - -
- ); -} - -export default DeployableItemsTab; \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx index 9d0150192..701dcd43d 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx @@ -3,7 +3,7 @@ import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space } from import { SyncOutlined, TeamOutlined } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; -import { UserGroup } from '../types/userGroup.types'; +import { UserGroup, UserGroupsTabStats } from '../types/userGroup.types'; import { getEnvironmentUserGroups } from '../services/environments.service'; import { Spin, Empty } from 'antd'; @@ -15,10 +15,11 @@ interface UserGroupsTabProps { const UserGroupsTab: React.FC = ({ environment }) => { const [userGroups, setUserGroups] = useState([]); - const [stats, setStats] = useState({ + const [stats, setStats] = useState({ total: 0, - active: 0, - inactive: 0 + allUsers: 0, + developers: 0, + custom: 0 }); const [loading, setLoading] = useState(false); const [refreshing, setRefreshing] = useState(false); @@ -40,22 +41,28 @@ const UserGroupsTab: React.FC = ({ environment }) => { return; } - const groups = await getEnvironmentUserGroups( + const response = await getEnvironmentUserGroups( environment.environmentId, environment.environmentApikey, environment.environmentApiServiceUrl ); + // Extract the groups from the data array in the response + const groups = response|| []; + setUserGroups(groups); // Calculate stats const total = groups.length; - const active = groups.filter(group => group.state === 'ACTIVE').length; + const allUsers = groups.filter((group: UserGroup) => group.allUsersGroup).length; + const developers = groups.filter((group: UserGroup) => group.devGroup).length; + const custom = total - (allUsers + developers); setStats({ total, - active, - inactive: total - active + allUsers, + developers, + custom }); } catch (err) { setError(err instanceof Error ? err.message : "Failed to fetch user groups"); @@ -78,48 +85,48 @@ const UserGroupsTab: React.FC = ({ environment }) => { // Filter user groups based on search const filteredUserGroups = searchText ? userGroups.filter(group => - group.name.toLowerCase().includes(searchText.toLowerCase()) || - group.id.toLowerCase().includes(searchText.toLowerCase())) + group.groupName.toLowerCase().includes(searchText.toLowerCase()) || + group.groupId.toLowerCase().includes(searchText.toLowerCase())) : userGroups; // Table columns const columns = [ { title: 'Name', - dataIndex: 'name', - key: 'name', + dataIndex: 'groupName', + key: 'groupName', render: (text: string) => {text} }, { title: 'ID', - dataIndex: 'id', - key: 'id', + dataIndex: 'groupId', + key: 'groupId', ellipsis: true, }, { title: 'Type', - dataIndex: 'type', key: 'type', - render: (type: string) => ( - - {type} - - ), + render: (_: any, group: UserGroup) => { + if (group.allUsersGroup) return All Users; + if (group.devGroup) return Developers; + return Custom; + }, + }, + { + title: 'Members', + key: 'members', + render: (_: any, group: UserGroup) => group.stats?.userCount || 0, }, { - title: 'Status', - dataIndex: 'state', - key: 'state', - render: (state: string) => ( - - {state} - - ), + title: 'Admin Members', + key: 'adminMembers', + render: (_: any, group: UserGroup) => group.stats?.adminUserCount || 0, }, { - title: 'Member Count', - dataIndex: 'memberCount', - key: 'memberCount', + title: 'Created', + dataIndex: 'createTime', + key: 'createTime', + render: (createTime: number) => new Date(createTime).toLocaleDateString(), } ]; @@ -144,12 +151,16 @@ const UserGroupsTab: React.FC = ({ environment }) => {
{stats.total}
-
Active
-
{stats.active}
+
All Users Groups
+
{stats.allUsers}
+
+
+
Developer Groups
+
{stats.developers}
-
Inactive
-
{stats.inactive}
+
Custom Groups
+
{stats.custom}
@@ -208,7 +219,7 @@ const UserGroupsTab: React.FC = ({ environment }) => {
= { - // Basic info - type: 'apps', - singularLabel: 'App', - pluralLabel: 'Apps', - icon: , - idField: 'id', // or applicationId if you prefer to use that directly - - // Navigation - buildDetailRoute: () => '#', +// Define AppStats interface if not already defined - - // Configuration - requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'], - - // Stats rendering - renderStats: (stats) => ( - - - } /> - - - } /> - - - } /> - - - } /> - - - ), - - // Stats calculation - calculateStats: (apps) => { - const total = apps.length; - const published = apps.filter(app => app.published).length; - const managed = apps.filter(app => app.managed).length; - - return { - total, - published, - managed, - unmanaged: total - managed - }; - }, - - // Table configuration - getColumns: ({ environment, refreshing, onToggleManaged, openDeployModal, additionalParams }) => { - const columns = [ - createIdColumn(), - createNameColumn(), - createPublishedColumn(), - ]; - - // Add managed column if enabled - if (appsConfig.enableManaged && onToggleManaged) { - columns.push(createManagedColumn(onToggleManaged, refreshing)); - } - - // Add deploy column if enabled - if (appsConfig.deploy?.enabled && openDeployModal) { - columns.push(createDeployColumn(appsConfig, environment, openDeployModal)); - } - - // Add audit column if enabled - if (appsConfig.audit?.enabled) { - columns.push(createAuditColumn(appsConfig, environment, additionalParams)); - } - - return columns; - }, - columns: [ - { - title: 'Name', - dataIndex: 'name', - key: 'name', - }, - { - title: 'ID', - dataIndex: 'id', - key: 'id', - ellipsis: true, - }, - { - title: 'Role', - dataIndex: 'role', - key: 'role', - render: (role: string) => {role}, - }, - - { - title: 'Status', - dataIndex: 'status', - key: 'status', - render: (status: string) => ( - - {status} - - ), - } - ], - - // Deployment options - enableManaged: true, - - // Service functions - fetchItems: async ({ environment, workspaceId }) => { - if (!workspaceId) { - throw new Error("Workspace ID is required to fetch apps"); - } - - const result = await getMergedWorkspaceApps( - workspaceId, - environment.environmentId, - environment.environmentApikey, - environment.environmentApiServiceUrl! - ); - - // Map to ensure proper id field - return result.apps.map(app => ({ - ...app, - id: app.applicationId // Map applicationId to id for DeployableItem compatibility - })); - }, - audit: { - enabled: true, - icon: , - label: 'Audit', - tooltip: 'View audit logs for this app', - getAuditUrl: (item, environment, additionalParams) => { - console.log("Additional params:", additionalParams); - return `/setting/audit?environmentId=${environment.environmentId}&orgId=${item.id}&appId=${additionalParams?.workspaceId}&pageSize=100&pageNum=1` - } - }, - toggleManaged: async ({ item, checked, environment }) => { - try { - if (checked) { - // Connect the app as managed - await setManagedObject( - item.applicationGid!, - environment.environmentId, - ManagedObjectType.APP, - item.name - ); - } else { - // Disconnect the managed app - await unsetManagedObject( - item.applicationGid!, - environment.environmentId, - ManagedObjectType.APP - ); - } - return true; - } catch (error) { - console.error('Error toggling managed status:', error); - return false; - } - }, - // deployment options +export const appsConfig: DeployableItemConfig = { + deploy: { - enabled: true, + singularLabel: 'App', fields: [ { name: 'updateDependenciesIfNeeded', diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx index 6a0ed6022..11e9e54fc 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx @@ -1,179 +1,15 @@ // config/data-sources.config.tsx import React from 'react'; -import { Row, Col, Statistic, Tag, Space, Button, Tooltip } from 'antd'; -import { DatabaseOutlined, CloudUploadOutlined } from '@ant-design/icons'; import { DeployableItemConfig } from '../types/deployable-item.types'; -import { DataSource, DataSourceStats } from '../types/datasource.types'; +import { DataSource} from '../types/datasource.types'; import { Environment } from '../types/environment.types'; -import { getMergedWorkspaceDataSources, deployDataSource } from '../services/datasources.service'; -import { connectManagedDataSource, unconnectManagedDataSource } from '../services/enterprise.service'; -import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; -import { - createNameColumn, - createTypeColumn, - createDatabaseColumn, - createDatasourceStatusColumn, - createManagedColumn, - createDeployColumn, - createAuditColumn -} from '../utils/columnFactories'; +import { deployDataSource, DataSourceStats } from '../services/datasources.service'; -export const dataSourcesConfig: DeployableItemConfig = { - // Basic info - type: 'dataSources', - singularLabel: 'Data Source', - pluralLabel: 'Data Sources', - icon: , - idField: 'id', - - // Navigation - buildDetailRoute: (params) => "#", - - // Configuration - requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'], - - // Stats rendering - renderStats: (stats) => ( - - - } /> - - - } /> - - - } /> - - - ), - - // Stats calculation - calculateStats: (dataSources) => { - const total = dataSources.length; - const managed = dataSources.filter(ds => ds.managed).length; - - // Calculate counts by type - const byType = dataSources.reduce((acc, ds) => { - const type = ds.type || 'Unknown'; - acc[type] = (acc[type] || 0) + 1; - return acc; - }, {} as Record); - - return { - total, - managed, - unmanaged: total - managed, - byType - }; - }, - - // Table configuration - Customize based on your existing UI - columns: [ - { - title: 'Name', - dataIndex: 'name', - key: 'name', - }, - { - title: 'Type', - dataIndex: 'type', - key: 'type', - render: (type: string) => ( - {type || 'Unknown'} - ), - }, - { - title: 'Database', - key: 'database', - render: (_, record: DataSource) => ( - {record.datasourceConfig?.database || 'N/A'} - ), - }, - { - title: 'Status', - dataIndex: 'datasourceStatus', - key: 'status', - render: (status: string) => ( - - {status} - - ), - }, - ], - - // Deployment options - enableManaged: true, - - // Service functions - fetchItems: async ({ environment, workspaceId }) => { - if (!workspaceId) { - throw new Error("Workspace ID is required to fetch data sources"); - } - - const result = await getMergedWorkspaceDataSources( - workspaceId, - environment.environmentId, - environment.environmentApikey, - environment.environmentApiServiceUrl! - ); - - return result.dataSources; - }, - getColumns: ({ environment, refreshing, onToggleManaged, openDeployModal, additionalParams }) => { - const columns = [ - createNameColumn(), - createTypeColumn(), - createDatabaseColumn(), - createDatasourceStatusColumn(), - ]; - - // Add managed column if enabled - if (dataSourcesConfig.enableManaged && onToggleManaged) { - columns.push(createManagedColumn(onToggleManaged, refreshing)); - } - - // Add deploy column if enabled - if (dataSourcesConfig.deploy?.enabled && openDeployModal) { - columns.push(createDeployColumn(dataSourcesConfig, environment, openDeployModal)); - } - - // Add audit column if enabled - if (dataSourcesConfig.audit?.enabled) { - columns.push(createAuditColumn(dataSourcesConfig, environment, additionalParams)); - } - - return columns; - }, - - - toggleManaged: async ({ item, checked, environment }) => { - try { - if (checked) { - // Connect the data source as managed - await setManagedObject( - item.gid, - environment.environmentId, - ManagedObjectType.DATASOURCE, - item.name - ); - } else { - // Disconnect the managed data source - await unsetManagedObject( - item.gid, - environment.environmentId, - ManagedObjectType.DATASOURCE - ); - } - return true; - } catch (error) { - console.error('Error toggling managed status:', error); - return false; - } - }, +export const dataSourcesConfig: DeployableItemConfig = { deploy: { - enabled: true, + singularLabel: 'Data Source', fields: [ { name: 'updateDependenciesIfNeeded', diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx index 641d0f41f..5396bd877 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx @@ -1,174 +1,16 @@ // config/query.config.tsx -import React from 'react'; -import { Row, Col, Statistic, Tag } from 'antd'; -import { ApiOutlined } from '@ant-design/icons'; import { DeployableItemConfig } from '../types/deployable-item.types'; import { Query } from '../types/query.types'; -import { connectManagedQuery, unconnectManagedQuery } from '../services/enterprise.service'; -import { getMergedWorkspaceQueries, deployQuery } from '../services/query.service'; -import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; +import { deployQuery } from '../services/query.service'; import { Environment } from '../types/environment.types'; -import { - createNameColumn, - createCreatorColumn, - createDateColumn, - createQueryTypeColumn, - createManagedColumn, - createDeployColumn, - createAuditColumn -} from '../utils/columnFactories'; -// Define QueryStats interface -export interface QueryStats { - total: number; - managed: number; - unmanaged: number; -} -export const queryConfig: DeployableItemConfig = { - // Basic info - type: 'queries', - singularLabel: 'Query', - pluralLabel: 'Queries', - icon: , - idField: 'id', - - // Navigation - queries don't have detail pages in this implementation - buildDetailRoute: () => '#', - - // Configuration - requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'], - - // Stats rendering - renderStats: (stats) => ( - - - } /> - - - } /> - - - } /> - - - ), - - // Stats calculation - calculateStats: (queries) => { - const total = queries.length; - const managed = queries.filter(q => q.managed).length; - - return { - total, - managed, - unmanaged: total - managed - }; - }, - columns: [ - { - title: 'Name', - dataIndex: 'name', - key: 'name', - }, - { - title: 'Type', - dataIndex: 'type', - key: 'type', - render: (type: string) => ( - {type || 'Unknown'} - ), - }, - { - title: 'Database', - key: 'database', - render: (_, record: Query) => ( - {record.datasourceConfig?.database || 'N/A'} - ), - }, - { - title: 'Status', - dataIndex: 'datasourceStatus', - key: 'status', - render: (status: string) => ( - - {status} - - ), - }, - ], - getColumns: ({ environment, refreshing, onToggleManaged, openDeployModal, additionalParams }) => { - const columns = [ - createNameColumn(), - createCreatorColumn(), - createDateColumn('createTime', 'Creation Date'), - createQueryTypeColumn(), - ]; - - // Add managed column if enabled - if (queryConfig.enableManaged && onToggleManaged) { - columns.push(createManagedColumn(onToggleManaged, refreshing)); - } - - // Add deploy column if enabled - if (queryConfig.deploy?.enabled && openDeployModal) { - columns.push(createDeployColumn(queryConfig, environment, openDeployModal)); - } - - // Add audit column if enabled - if (queryConfig.audit?.enabled) { - columns.push(createAuditColumn(queryConfig, environment, additionalParams)); - } - - return columns; - }, - - // Deployment options - enableManaged: true, - - // Service functions - fetchItems: async ({ environment, workspaceId }) => { - if (!workspaceId) { - throw new Error("Workspace ID is required to fetch queries"); - } - - const result = await getMergedWorkspaceQueries( - workspaceId, - environment.environmentId, - environment.environmentApikey, - environment.environmentApiServiceUrl! - ); - - return result.queries; - }, + +export const queryConfig: DeployableItemConfig = { - toggleManaged: async ({ item, checked, environment }) => { - try { - if (checked) { - // Connect the query as managed - await setManagedObject( - item.gid, - environment.environmentId, - ManagedObjectType.QUERY, - item.name - ); - } else { - // Disconnect the managed query - await unsetManagedObject( - item.gid, - environment.environmentId, - ManagedObjectType.QUERY - ); - } - return true; - } catch (error) { - console.error('Error toggling managed status:', error); - return false; - } - }, deploy: { - enabled: true, + singularLabel: 'Query', fields: [ { name: 'updateDependenciesIfNeeded', diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/usergroups.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/usergroups.config.tsx deleted file mode 100644 index 8ae041320..000000000 --- a/client/packages/lowcoder/src/pages/setting/environments/config/usergroups.config.tsx +++ /dev/null @@ -1,169 +0,0 @@ -// config/usergroups.config.tsx -import React from 'react'; -import { Row, Col, Statistic, Tag, Badge } from 'antd'; -import { TeamOutlined, UserOutlined } from '@ant-design/icons'; -import { getEnvironmentUserGroups } from '../services/environments.service'; -import { UserGroup, UserGroupStats } from '../types/userGroup.types'; -import { DeployableItemConfig } from '../types/deployable-item.types'; -import { - createUserGroupNameColumn, - createGroupIdColumn, - createUserCountColumn, - createDateColumn, - createGroupTypeColumn, - createAuditColumn -} from '../utils/columnFactories'; - -const formatDate = (timestamp: number): string => { - if (!timestamp) return 'N/A'; - const date = new Date(timestamp); - return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; -}; - - -export const userGroupsConfig: DeployableItemConfig = { - // Basic info - type: 'userGroups', - singularLabel: 'User Group', - pluralLabel: 'User Groups', - icon: , - idField: 'id', - - // Navigation - No navigation for user groups, provide a dummy function - buildDetailRoute: () => '#', - - // Configuration - requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'], - - // Stats rendering - Custom for user groups - renderStats: (stats) => ( - - - } /> - - - } /> - - - } /> - - - ), - - // Stats calculation - Custom for user groups - calculateStats: (userGroups) => { - const total = userGroups.length; - const totalUsers = userGroups.reduce( - (sum, group) => sum + (group.stats?.userCount ?? 0), - 0 - ); - const adminUsers = userGroups.reduce( - (sum, group) => sum + (group.stats?.adminUserCount ?? 0), - 0 - ); - - return { - total, - managed: 0, // User groups don't have managed/unmanaged state - unmanaged: 0, // User groups don't have managed/unmanaged state - totalUsers, - adminUsers - }; - }, - - // Table configuration - columns: [ - { - title: 'Name', - dataIndex: 'groupName', - key: 'groupName', - render: (name: string, record: UserGroup) => ( -
- {record.groupName} - {record.allUsersGroup && ( - All Users - )} - {record.devGroup && ( - Dev - )} -
- ), - }, - { - title: 'ID', - dataIndex: 'groupId', - key: 'groupId', - ellipsis: true, - }, - { - title: 'Users', - key: 'userCount', - render: (_, record: UserGroup) => ( -
- - - ({record.stats.adminUserCount} admin{record.stats.adminUserCount !== 1 ? 's' : ''}) - -
- ), - }, - { - title: 'Created', - key: 'createTime', - render: (_, record: UserGroup) => formatDate(record.createTime), - }, - { - title: 'Type', - key: 'type', - render: (_, record: UserGroup) => { - if (record.allUsersGroup) return Global; - if (record.devGroup) return Dev; - if (record.syncGroup) return Sync; - return Standard; - }, - } - ], - - // No managed status for user groups - enableManaged: false, - - getColumns: ({ environment, additionalParams }) => { - const columns = [ - createGroupIdColumn(), - createUserGroupNameColumn(), - - createUserCountColumn(), - createDateColumn('createTime', 'Created'), - createGroupTypeColumn(), - ]; - - // User groups aren't managed, so we don't add the managed column - - // Add audit column if enabled - if (userGroupsConfig.audit?.enabled) { - columns.push(createAuditColumn(userGroupsConfig, environment, additionalParams)); - } - - return columns; - }, - // Service functions - fetchItems: async ({ environment }) => { - const userGroups = await getEnvironmentUserGroups( - environment.environmentId, - environment.environmentApikey, - environment.environmentApiServiceUrl! - ); - - // Map the required properties to satisfy DeployableItem interface - return userGroups.map(group => ({ - ...group, - id: group.groupId, // Map groupId to id - name: group.groupName // Map groupName to name - })); - }, - - // Dummy function for toggleManaged (will never be called since enableManaged is false) - toggleManaged: async () => { - return false; - } -}; \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx index 9174ac662..4b55b4257 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx @@ -1,176 +1,17 @@ // config/workspace.config.tsx -import React from 'react'; -import { Row, Col, Statistic, Tag } from 'antd'; -import { ClusterOutlined, AuditOutlined } from '@ant-design/icons'; -import { Workspace, WorkspaceStats, DeployableItemConfig } from '../types/deployable-item.types'; + +import { DeployableItemConfig } from '../types/deployable-item.types'; import { Environment } from '../types/environment.types'; -import { buildEnvironmentWorkspaceId } from '@lowcoder-ee/constants/routesURL'; -import { getMergedEnvironmentWorkspaces, deployWorkspace } from '../services/workspace.service'; -import { connectManagedWorkspace, unconnectManagedWorkspace } from '../services/enterprise.service'; -import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; -import { - createNameColumn, - createIdColumn, - createRoleColumn, - createDateColumn, - createStatusColumn, - createManagedColumn, - createAuditColumn -} from '../utils/columnFactories'; +import { deployWorkspace } from '../services/workspace.service'; +import { Workspace } from '../types/workspace.types'; -export const workspaceConfig: DeployableItemConfig = { - // Basic info - type: 'workspaces', - singularLabel: 'Workspace', - pluralLabel: 'Workspaces', - icon: , - idField: 'id', - - // Navigation - buildDetailRoute: (params) => buildEnvironmentWorkspaceId(params.environmentId, params.itemId), - - // Configuration - requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'], - - // Stats rendering - renderStats: (stats) => ( - -
- } /> - - - } /> - - - } /> - - - ), - - // Stats calculation - calculateStats: (workspaces) => { - const total = workspaces.length; - const managed = workspaces.filter(w => w.managed).length; - return { - total, - managed, - unmanaged: total - managed - }; - }, - - // Original columns for backward compatibility - columns: [ - { - title: 'Name', - dataIndex: 'name', - key: 'name', - }, - { - title: 'ID', - dataIndex: 'id', - key: 'id', - ellipsis: true, - }, - { - title: 'Role', - dataIndex: 'role', - key: 'role', - render: (role: string) => {role}, - }, - { - title: 'Creation Date', - key: 'creationDate', - render: (_, record: Workspace) => { - if (!record.creationDate) return 'N/A'; - const date = new Date(record.creationDate); - return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; - }, - }, - { - title: 'Status', - dataIndex: 'status', - key: 'status', - render: (status: string) => ( - - {status} - - ), - } - ], - - // New getColumns method - getColumns: ({ environment, refreshing, onToggleManaged, additionalParams }) => { - const columns = [ - createIdColumn(), - createNameColumn(), - createRoleColumn(), - createManagedColumn(), - createDateColumn('creationDate', 'Creation Date'), - createStatusColumn() - ]; - - // Add audit column if enabled - if (workspaceConfig.audit?.enabled) { - columns.push(createAuditColumn(workspaceConfig, environment, additionalParams)); - } - - return columns; - }, - - // Enable managed functionality - enableManaged: true, - - // Fetch function - fetchItems: async ({ environment }) => { - const result = await getMergedEnvironmentWorkspaces( - environment.environmentId, - environment.environmentApikey, - environment.environmentApiServiceUrl! - ); - return result.workspaces; - }, - - // Toggle managed status - toggleManaged: async ({ item, checked, environment }) => { - try { - if (checked) { - // Connect the workspace as managed - await setManagedObject( - item.gid!, - environment.environmentId, - ManagedObjectType.ORG, - item.name - ); - } else { - // Disconnect the managed workspace - await unsetManagedObject( - item.gid!, - environment.environmentId, - ManagedObjectType.ORG - ); - } - return true; - } catch (error) { - console.error('Error toggling managed status:', error); - return false; - } - }, - - // Audit configuration - audit: { - enabled: true, - icon: , - label: 'Audit', - tooltip: 'View audit logs for this workspace', - getAuditUrl: (item, environment) => - `/setting/audit?environmentId=${environment.environmentId}&orgId=${item.id}&pageSize=100&pageNum=1` - }, +export const workspaceConfig: DeployableItemConfig = { // Deploy configuration deploy: { - enabled: true, + singularLabel: 'Workspace', fields: [], prepareParams: (item: Workspace, values: any, sourceEnv: Environment, targetEnv: Environment) => { if (!item.gid) { diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/DeployModalContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/DeployModalContext.tsx index 7084e9405..904ab62c6 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/context/DeployModalContext.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/context/DeployModalContext.tsx @@ -1,13 +1,13 @@ // context/DeployModalContext.tsx import React, { createContext, useContext, useState } from 'react'; -import { DeployableItem, BaseStats, DeployableItemConfig } from '../types/deployable-item.types'; +import { DeployableItemConfig } from '../types/deployable-item.types'; import { Environment } from '../types/environment.types'; import DeployItemModal from '../components/DeployItemModal'; interface DeployModalContextType { - openDeployModal: ( - item: T, - config: DeployableItemConfig, + openDeployModal: ( + item: any, + config: DeployableItemConfig, sourceEnvironment: Environment, onSuccess?: () => void ) => void; @@ -18,8 +18,8 @@ const DeployModalContext = createContext(und export const DeployModalProvider: React.FC<{children: React.ReactNode}> = ({ children }) => { const [modalState, setModalState] = useState<{ visible: boolean; - item: DeployableItem | null; - config: DeployableItemConfig | null; + item: any | null; + config: DeployableItemConfig | null; sourceEnvironment: Environment | null; onSuccess?: () => void; }>({ @@ -29,9 +29,9 @@ export const DeployModalProvider: React.FC<{children: React.ReactNode}> = ({ chi sourceEnvironment: null }); - const openDeployModal = ( - item: T, - config: DeployableItemConfig, + const openDeployModal = ( + item: any, + config: DeployableItemConfig, sourceEnvironment: Environment, onSuccess?: () => void ) => { diff --git a/client/packages/lowcoder/src/pages/setting/environments/hooks/useDeployableItems.ts b/client/packages/lowcoder/src/pages/setting/environments/hooks/useDeployableItems.ts deleted file mode 100644 index bb04cf54f..000000000 --- a/client/packages/lowcoder/src/pages/setting/environments/hooks/useDeployableItems.ts +++ /dev/null @@ -1,146 +0,0 @@ -// hooks/useDeployableItems.ts -import { useState, useEffect, useCallback } from "react"; -import { DeployableItem, BaseStats, DeployableItemConfig } from "../types/deployable-item.types"; -import { Environment } from "../types/environment.types"; - -interface UseDeployableItemsState { - items: T[]; - stats: S; - loading: boolean; - error: string | null; - refreshing: boolean; -} - -export interface UseDeployableItemsResult { - items: T[]; - stats: S; - loading: boolean; - error: string | null; - refreshing: boolean; - toggleManagedStatus: (item: T, checked: boolean) => Promise; - refreshItems: () => Promise; -} - -export const useDeployableItems = ( - config: DeployableItemConfig, - environment: Environment | null, - additionalParams: Record = {} -): UseDeployableItemsResult => { - // Create a default empty stats object based on the config's calculateStats method - const createEmptyStats = (): S => { - return config.calculateStats([]) as S; - }; - - const [state, setState] = useState>({ - items: [], - stats: createEmptyStats(), - loading: false, - error: null, - refreshing: false - }); - - const fetchItems = useCallback(async () => { - if (!environment) return; - - // Check for required environment properties - const missingProps = config.requiredEnvProps.filter(prop => !environment[prop as keyof Environment]); - - if (missingProps.length > 0) { - setState(prev => ({ - ...prev, - loading: false, - error: `Missing required configuration: ${missingProps.join(', ')}` - })); - return; - } - - setState(prev => ({ ...prev, loading: true, error: null })); - - try { - // Call the fetchItems function from the config - const items = await config.fetchItems({ - environment, - ...additionalParams - }); - - // Calculate stats using the config's function - const stats = config.calculateStats(items); - - // Update state with items and stats - setState({ - items, - stats, - loading: false, - error: null, - refreshing: false - }); - } catch (err) { - setState(prev => ({ - ...prev, - loading: false, - refreshing: false, - error: err instanceof Error ? err.message : "Failed to fetch items" - })); - } - }, [environment, config]); - - useEffect(() => { - if (environment) { - fetchItems(); - } - }, [environment, fetchItems]); - - const toggleManagedStatus = async (item: T, checked: boolean): Promise => { - if (!config.enableManaged) return false; - if (!environment) return false; - - setState(prev => ({ ...prev, refreshing: true })); - - try { - // Call the toggleManaged function from the config - const success = await config.toggleManaged({ - item, - checked, - environment - }); - - if (success) { - // Optimistically update the state - setState(prev => { - // Update items with the new managed status - const updatedItems = prev.items.map(i => - i[config.idField] === item[config.idField] ? { ...i, managed: checked } : i - ); - - // Recalculate stats - const stats = config.calculateStats(updatedItems); - - return { - ...prev, - items: updatedItems, - stats, - refreshing: false - }; - }); - } else { - setState(prev => ({ ...prev, refreshing: false })); - } - - return success; - } catch (err) { - setState(prev => ({ ...prev, refreshing: false })); - return false; - } - }; - - const refreshItems = async (): Promise => { - setState(prev => ({ ...prev, refreshing: true })); - await fetchItems(); - }; - - return { - ...state, - toggleManagedStatus, - refreshItems - }; -}; \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts index 52528b4d0..570084f39 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts @@ -2,15 +2,9 @@ import { message } from "antd"; import { getWorkspaceApps } from "./environments.service"; import { getManagedApps } from "./enterprise.service"; -import { App } from "../types/app.types"; +import { App, AppStats } from "../types/app.types"; import axios from "axios"; -export interface AppStats { - total: number; - published: number; - managed: number; - unmanaged: number; -} export interface MergedAppsResult { apps: App[]; diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts index 8f5ad6892..f48d038d5 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts @@ -4,14 +4,10 @@ import axios from 'axios'; import { getManagedQueries } from './enterprise.service'; import { getWorkspaceQueries } from './environments.service'; -import { Query } from '../types/query.types'; +import { Query, QueryStats } from '../types/query.types'; export interface MergedQueriesResult { queries: Query[]; - stats: { - total: number; - managed: number; - unmanaged: number; - }; + stats: QueryStats; } export interface DeployQueryParams { diff --git a/client/packages/lowcoder/src/pages/setting/environments/types/app.types.ts b/client/packages/lowcoder/src/pages/setting/environments/types/app.types.ts index b3af252b5..775a589fb 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/types/app.types.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/types/app.types.ts @@ -1,6 +1,5 @@ -import { DeployableItem, BaseStats } from "./deployable-item.types"; -export interface App extends DeployableItem { +export interface App { orgId: string; applicationId: string; applicationGid: string; @@ -28,6 +27,9 @@ export interface App extends DeployableItem { id: string } - export interface AppStats extends BaseStats { - published: number + export interface AppStats { + total: number; + published: number; + managed: number; + unmanaged: number; } \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/types/datasource.types.ts b/client/packages/lowcoder/src/pages/setting/environments/types/datasource.types.ts index f4f03072d..b33e14e86 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/types/datasource.types.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/types/datasource.types.ts @@ -1,8 +1,6 @@ /** * Represents a DataSource configuration */ - -import { DeployableItem, BaseStats } from "./deployable-item.types"; export interface DataSourceConfig { usingUri: boolean; srvMode: boolean; @@ -18,7 +16,7 @@ export interface DataSourceConfig { /** * Represents a DataSource entity */ - export interface DataSource extends DeployableItem { + export interface DataSource { id: string; createdBy: string; gid: string; @@ -41,7 +39,3 @@ export interface DataSourceConfig { edit: boolean; creatorName: string; } - - export interface DataSourceStats extends BaseStats { - byType: Record; // Count by each type - } \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/types/deployable-item.types.ts b/client/packages/lowcoder/src/pages/setting/environments/types/deployable-item.types.ts index ac223c63d..55964eefe 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/types/deployable-item.types.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/types/deployable-item.types.ts @@ -1,45 +1,7 @@ // types/deployable-item.types.ts -import { ReactNode } from 'react'; import { Environment } from './environment.types'; -import { ColumnType } from 'antd/lib/table'; -// Base interface for all deployable items -export interface AuditConfig { - enabled: boolean; - icon?: React.ReactNode; - label?: string; - tooltip?: string; - getAuditUrl: (item: any, environment: Environment, additionalParams?: Record) => string; -} -export interface DeployableItem { - id: string; - name: string; - managed?: boolean; - [key: string]: any; // Allow for item-specific properties -} - -// Workspace specific implementation -export interface Workspace extends DeployableItem { - id: string; - name: string; - role?: string; - creationDate?: number; - status?: string; - managed?: boolean; - gid?: string; -} - -// Stats interface that can be extended for specific item types -// Base interface for stats -export interface BaseStats { - total: number; - managed: number; - unmanaged: number; - [key: string]: any; -} -export interface WorkspaceStats extends BaseStats {} - export interface DeployField { name: string; @@ -50,56 +12,11 @@ export interface DeployField { options?: Array<{label: string, value: any}>; // For select fields } // Configuration for each deployable item type -export interface DeployableItemConfig { - // Identifying info - type: string; // e.g., 'workspaces' - singularLabel: string; // e.g., 'Workspace' - pluralLabel: string; // e.g., 'Workspaces' - - // UI elements - icon: ReactNode; // Icon to use in stats - - // Navigation - buildDetailRoute: (params: Record) => string; - - // Configuration - requiredEnvProps: string[]; // Required environment properties - - // Customization - idField: string; // Field to use as the ID (e.g., 'id') - - // Stats - renderStats: (stats: S) => ReactNode; - calculateStats: (items: T[]) => S; - - // Original columns (will be deprecated) - columns: ColumnType[]; - - // New method to generate columns - getColumns: (params: { - environment: Environment; - refreshing: boolean; - onToggleManaged?: (item: T, checked: boolean) => Promise; - openDeployModal?: (item: T, config: DeployableItemConfig, environment: Environment) => void; - additionalParams?: Record; - }) => ColumnType[]; - - // Add audit configuration - audit?: AuditConfig; - - - - // Deployable configuration - enableManaged: boolean; - - // Service functions - fetchItems: (params: { environment: Environment, [key: string]: any }) => Promise; - toggleManaged: (params: { item: T; checked: boolean; environment: Environment }) => Promise; - - deploy?: { - enabled: boolean; +export interface DeployableItemConfig { + deploy: { + singularLabel: string; fields: DeployField[]; - prepareParams: (item: T, values: any, sourceEnv: Environment, targetEnv: Environment) => any; + prepareParams: (item: any, values: any, sourceEnv: Environment, targetEnv: Environment) => any; execute: (params: any) => Promise; }; } \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/types/query.types.ts b/client/packages/lowcoder/src/pages/setting/environments/types/query.types.ts index 5d38385b0..212efeaac 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/types/query.types.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/types/query.types.ts @@ -1,5 +1,4 @@ // types/query.types.ts -import { DeployableItem, BaseStats } from './deployable-item.types'; export interface LibraryQueryDSL { query: { @@ -32,11 +31,10 @@ export interface LibraryQueryDSL { cancelPrevious: boolean; depQueryName: string; delayTime: string; - managed?: boolean; }; } -export interface Query extends DeployableItem { +export interface Query { id: string; gid: string; organizationId: string; @@ -44,9 +42,10 @@ export interface Query extends DeployableItem { libraryQueryDSL: LibraryQueryDSL; createTime: number; creatorName: string; + managed?: boolean; } -export interface QueryStats extends BaseStats { +export interface QueryStats { total: number; managed: number; unmanaged: number; 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 index 6a1938bcc..5791204c8 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/types/userGroup.types.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/types/userGroup.types.ts @@ -1,34 +1,26 @@ -/** - * Represents a User Group entity in an environment -*/ +export interface UserGroupStats { + users: string[]; + adminUserCount: number; + userCount: number; +} -import { DeployableItem, BaseStats } from "./deployable-item.types"; +export interface UserGroup { + groupId: string; + groupGid: string; + groupName: string; + allUsersGroup: boolean; + visitorRole: string; + createTime: number; + dynamicRule: any; + stats: UserGroupStats; + syncDelete: boolean; + devGroup: boolean; + syncGroup: boolean; +} -export interface UserGroup extends DeployableItem { - 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; - id: string; - name: string; - } - - - /** - * Statistics for User Groups - */ -export interface UserGroupStats extends BaseStats { - totalUsers: number; - adminUsers: number; +export interface UserGroupsTabStats { + total: number; + allUsers: number; + developers: number; + custom: number; } \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/utils/columnFactories.tsx b/client/packages/lowcoder/src/pages/setting/environments/utils/columnFactories.tsx deleted file mode 100644 index 45c69c580..000000000 --- a/client/packages/lowcoder/src/pages/setting/environments/utils/columnFactories.tsx +++ /dev/null @@ -1,323 +0,0 @@ -// utils/columnFactories.tsx -import React from 'react'; -import { Tag, Space, Switch, Button, Tooltip, Badge} from 'antd'; -import { CloudUploadOutlined, AuditOutlined } from '@ant-design/icons'; -import { ColumnType } from 'antd/lib/table'; -import { DeployableItem, DeployableItemConfig, BaseStats } from '../types/deployable-item.types'; -import { Environment } from '../types/environment.types'; - -// Base columns for workspace -export function createNameColumn(): ColumnType { - return { - title: 'Name', - dataIndex: 'name', - key: 'name', - }; -} - -export function createIdColumn(): ColumnType { - return { - title: 'ID', - dataIndex: 'id', - key: 'id', - ellipsis: true, - }; -} - -export function createRoleColumn(): ColumnType { - return { - title: 'Role', - dataIndex: 'role', - key: 'role', - render: (role: string) => {role}, - }; -} - -export function createDateColumn( - dateField: string, - title: string -): ColumnType { - return { - title: title, - key: dateField, - render: (_, record: any) => { - if (!record[dateField]) return 'N/A'; - const date = new Date(record[dateField]); - return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; - }, - }; -} - -export function createStatusColumn(): ColumnType { - return { - title: 'Status', - dataIndex: 'status', - key: 'status', - render: (status: string) => ( - - {status} - - ), - }; -} - -// Feature columns -export function createManagedColumn( - onToggleManaged?: (item: T, checked: boolean) => Promise, - refreshing: boolean = false -): ColumnType { - return { - title: 'Managed', - key: 'managed', - filterMode: 'menu', - filters: [ - { text: 'Managed', value: true }, - { text: 'Unmanaged', value: false }, - ], - onFilter: (value, record) => record.managed === value, - filterMultiple: false, - render: (_, record: T) => ( - - - {record.managed ? 'Managed' : 'Unmanaged'} - - {onToggleManaged && ( - { - e.stopPropagation(); // Stop row click event - onToggleManaged(record, checked); - }} - onChange={() => {}} - /> - )} - - ), - }; -} - -export function createAuditColumn( - config: DeployableItemConfig, - environment: Environment, - additionalParams: Record = {} -): ColumnType { - return { - title: 'Audit', - key: 'audit', - render: (_, record: T) => { - const openAuditPage = (e: React.MouseEvent) => { - e.stopPropagation(); - if (config.audit?.getAuditUrl) { - const auditUrl = config.audit.getAuditUrl(record, environment, additionalParams); - window.open(auditUrl, '_blank'); - } - }; - - return ( - - - - ); - }, - }; -} - - -export function createDescriptionColumn(): ColumnType { - return { - title: 'Description', - dataIndex: 'description', - key: 'description', - ellipsis: true, - }; -} - - -export function createDeployColumn( - config: DeployableItemConfig, - environment: Environment, - openDeployModal: (item: T, config: DeployableItemConfig, environment: Environment) => void -): ColumnType { - return { - title: 'Actions', - key: 'actions', - render: (_, record: T) => { - // Check if the item is managed - const isManaged = record.managed === true; - - return ( - - - - - - ); - }, - }; -} - -// App-specific columns -export function createPublishedColumn(): ColumnType { - return { - title: 'Status', - dataIndex: 'published', - key: 'published', - filterMode: 'menu', - filters: [ - { text: 'Published', value: true }, - { text: 'Unpublished', value: false }, - ], - onFilter: (value, record) => record.published === value, - filterMultiple: false, - render: (published: boolean) => ( - - {published ? 'Published' : 'Unpublished'} - - ), - }; -} - -// Data Source specific columns -export function createTypeColumn(): ColumnType { - return { - title: 'Type', - dataIndex: 'type', - key: 'type', - render: (type: string) => ( - {type || 'Unknown'} - ), - }; -} - -export function createDatabaseColumn(): ColumnType { - return { - title: 'Database', - key: 'database', - render: (_, record: T) => ( - {record.datasourceConfig?.database || 'N/A'} - ), - }; -} - -export function createDatasourceStatusColumn(): ColumnType { - return { - title: 'Status', - dataIndex: 'datasourceStatus', - key: 'status', - render: (status: string) => ( - - {status} - - ), - }; -} - - -// Query-specific column factories to add to columnFactories.tsx -export function createCreatorColumn(): ColumnType { - return { - title: 'Creator', - dataIndex: 'creatorName', - key: 'creatorName', - }; -} - -export function createQueryTypeColumn(): ColumnType { - return { - title: 'Query Type', - key: 'queryType', - render: (_, record: T) => { - const queryType = record.libraryQueryDSL?.query?.compType || 'Unknown'; - return {queryType}; - }, - }; -} - -export function createUserGroupNameColumn(): ColumnType { - return { - title: 'Name', - dataIndex: 'groupName', - key: 'groupName', - render: (name: string, record: T) => ( -
- {record.groupName} - {record.allUsersGroup && ( - All Users - )} - {record.devGroup && ( - Dev - )} -
- ), - }; -} - -export function createGroupIdColumn(): ColumnType { - return { - title: 'ID', - dataIndex: 'groupId', - key: 'groupId', - ellipsis: true, - }; -} - -export function createUserCountColumn(): ColumnType { - return { - title: 'Users', - key: 'userCount', - render: (_, record: T) => ( -
- - - ({record.stats?.adminUserCount || 0} admin{(record.stats?.adminUserCount || 0) !== 1 ? 's' : ''}) - -
- ), - }; -} - -export function createGroupTypeColumn(): ColumnType { - return { - title: 'Type', - key: 'type', - render: (_, record: T) => { - if (record.allUsersGroup) return Global; - if (record.devGroup) return Dev; - if (record.syncGroup) return Sync; - return Standard; - }, - }; -} \ No newline at end of file From 199c869755f34ff91a13c4acba8318ab96beca50 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 16 May 2025 00:07:12 +0500 Subject: [PATCH 13/37] add audit buttons in tabs --- .../environments/components/AppsTab.tsx | 16 +++++++++- .../components/DataSourcesTab.tsx | 16 +++++++++- .../environments/components/QueriesTab.tsx | 16 +++++++++- .../environments/components/WorkspacesTab.tsx | 32 ++++++++++++++++++- 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx index 70e0d68ec..b943b83a2 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd'; -import { SyncOutlined, CloudUploadOutlined } from '@ant-design/icons'; +import { SyncOutlined, CloudUploadOutlined, AuditOutlined } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { Workspace } from '../types/workspace.types'; @@ -10,6 +10,7 @@ import { Switch, Spin, Empty } from 'antd'; import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; import { useDeployModal } from '../context/DeployModalContext'; import { appsConfig } from '../config/apps.config'; +import history from "@lowcoder-ee/util/history"; const { Search } = Input; @@ -173,6 +174,19 @@ const AppsTab: React.FC = ({ environment, workspace }) => { key: 'actions', render: (_: any, app: App) => ( e.stopPropagation()}> + + + + + + + + ), + }, ]; return ( From f4fba8e3cfc10c2d96aa8d40c00501ea9c6edca4 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 16 May 2025 00:31:02 +0500 Subject: [PATCH 14/37] Update Apps UI --- .../environments/components/AppsTab.tsx | 271 ++++++++++++------ 1 file changed, 184 insertions(+), 87 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx index b943b83a2..b2fde8d7a 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx @@ -1,12 +1,12 @@ import React, { useState, useEffect } from 'react'; -import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd'; -import { SyncOutlined, CloudUploadOutlined, AuditOutlined } from '@ant-design/icons'; +import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col } from 'antd'; +import { SyncOutlined, CloudUploadOutlined, AuditOutlined, AppstoreOutlined, CheckCircleFilled, CloudServerOutlined, DisconnectOutlined } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { Workspace } from '../types/workspace.types'; import { App, AppStats } from '../types/app.types'; import { getMergedWorkspaceApps } from '../services/apps.service'; -import { Switch, Spin, Empty } from 'antd'; +import { Switch, Spin, Empty, Avatar } from 'antd'; import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; import { useDeployModal } from '../context/DeployModalContext'; import { appsConfig } from '../config/apps.config'; @@ -136,25 +136,43 @@ const AppsTab: React.FC = ({ environment, workspace }) => { // Table columns const columns = [ { - title: 'Name', - dataIndex: 'name', - key: 'name', - render: (text: string) => {text} - }, - { - title: 'ID', - dataIndex: 'applicationId', - key: 'applicationId', - ellipsis: true, + title: 'App', + key: 'app', + render: (app: App) => ( +
+ + {app.name.charAt(0).toUpperCase()} + +
+
{app.name}
+
+ {app.applicationId} +
+
+
+ ), }, { - title: 'Published', - dataIndex: 'published', - key: 'published', - render: (published: boolean) => ( - - {published ? 'Published' : 'Draft'} - + title: 'Status', + key: 'status', + render: (app: App) => ( + + + {app.published ? : null} {app.published ? 'Published' : 'Draft'} + + + {app.managed ? : } {app.managed ? 'Managed' : 'Unmanaged'} + + ), }, { @@ -165,7 +183,6 @@ const AppsTab: React.FC = ({ environment, workspace }) => { checked={!!app.managed} onChange={(checked: boolean) => handleToggleManaged(app, checked)} loading={refreshing} - size="small" /> ), }, @@ -177,7 +194,6 @@ const AppsTab: React.FC = ({ environment, workspace }) => { - {/* Stats display */} -
-
-
Total Apps
-
{stats.total}
-
-
-
Published Apps
-
{stats.published}
-
-
-
Managed Apps
-
{stats.managed}
-
-
-
Unmanaged Apps
-
{stats.unmanaged}
-
-
- - - {/* Error display */} {error && ( = ({ environment, workspace }) => { description={error} type="error" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} @@ -257,49 +308,95 @@ const AppsTab: React.FC = ({ environment, workspace }) => { description="Missing required configuration: API key or API service URL" type="warning" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} + {/* Stats display */} + +
+ } + /> + + + } + /> + + + } + /> + + + } + /> + + + {/* Content */} - {loading ? ( -
- -
- ) : apps.length === 0 ? ( - - ) : ( - <> - {/* Search Bar */} -
- setSearchText(value)} - onChange={e => setSearchText(e.target.value)} - style={{ width: 300 }} - /> - {searchText && filteredApps.length !== apps.length && ( -
- Showing {filteredApps.length} of {apps.length} apps -
- )} + + {loading ? ( +
+
- -
- - )} - + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + size="large" + /> + {searchText && filteredApps.length !== apps.length && ( +
+ Showing {filteredApps.length} of {apps.length} apps +
+ )} +
+ +
`${range[0]}-${range[1]} of ${total} apps` + }} + rowClassName={() => 'app-row'} + style={{ + borderRadius: '8px', + overflow: 'hidden' + }} + /> + + )} + + ); }; From c3a770eb3cdd8bfc42139167f30b493ae2c04ef3 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 16 May 2025 01:29:37 +0500 Subject: [PATCH 15/37] update UI for DS and queries --- .../components/DataSourcesTab.tsx | 281 +++++++++++++----- .../environments/components/QueriesTab.tsx | 271 ++++++++++++----- 2 files changed, 395 insertions(+), 157 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx index 3d4c7ed6f..068552232 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx @@ -1,12 +1,21 @@ import React, { useState, useEffect } from 'react'; -import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd'; -import { SyncOutlined, CloudUploadOutlined, DatabaseOutlined, AuditOutlined } from '@ant-design/icons'; +import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col } from 'antd'; +import { + SyncOutlined, + CloudUploadOutlined, + DatabaseOutlined, + AuditOutlined, + ApiOutlined, + CheckCircleFilled, + CloudServerOutlined, + DisconnectOutlined +} from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { Workspace } from '../types/workspace.types'; import { DataSource } from '../types/datasource.types'; import { getMergedWorkspaceDataSources } from '../services/datasources.service'; -import { Switch, Spin, Empty } from 'antd'; +import { Switch, Spin, Empty, Avatar } from 'antd'; import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; import { useDeployModal } from '../context/DeployModalContext'; import { dataSourcesConfig } from '../config/data-sources.config'; @@ -125,23 +134,47 @@ const DataSourcesTab: React.FC = ({ environment, workspace // Table columns const columns = [ { - title: 'Name', - dataIndex: 'name', - key: 'name', - render: (text: string) => {text} - }, - { - title: 'ID', - dataIndex: 'id', - key: 'id', - ellipsis: true, + title: 'Data Source', + key: 'datasource', + render: (dataSource: DataSource) => ( +
+ } + /> +
+
{dataSource.name}
+
+ {dataSource.id} +
+
+
+ ), }, { title: 'Type', dataIndex: 'type', key: 'type', render: (type: string) => ( - {type} + + {type} + + ), + }, + { + title: 'Status', + key: 'status', + render: (dataSource: DataSource) => ( + + {dataSource.managed ? : } {dataSource.managed ? 'Managed' : 'Unmanaged'} + ), }, { @@ -152,7 +185,6 @@ const DataSourcesTab: React.FC = ({ environment, workspace checked={!!dataSource.managed} onChange={(checked: boolean) => handleToggleManaged(dataSource, checked)} loading={refreshing} - size="small" /> ), }, @@ -164,7 +196,6 @@ const DataSourcesTab: React.FC = ({ environment, workspace - {/* Stats display */} -
-
-
Total Data Sources
-
{stats.total}
-
-
-
Types
-
{stats.types}
-
-
-
Managed
-
{stats.managed}
-
-
-
Unmanaged
-
{stats.unmanaged}
-
-
- - - {/* Error display */} {error && ( = ({ environment, workspace description={error} type="error" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} @@ -244,49 +317,95 @@ const DataSourcesTab: React.FC = ({ environment, workspace description="Missing required configuration: API key or API service URL" type="warning" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} + {/* Stats display */} + +
+ } + /> + + + } + /> + + + } + /> + + + } + /> + + + {/* Content */} - {loading ? ( -
- -
- ) : dataSources.length === 0 ? ( - - ) : ( - <> - {/* Search Bar */} -
- setSearchText(value)} - onChange={e => setSearchText(e.target.value)} - style={{ width: 300 }} - /> - {searchText && filteredDataSources.length !== dataSources.length && ( -
- Showing {filteredDataSources.length} of {dataSources.length} data sources -
- )} + + {loading ? ( +
+
- -
- - )} - + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + size="large" + /> + {searchText && filteredDataSources.length !== dataSources.length && ( +
+ Showing {filteredDataSources.length} of {dataSources.length} data sources +
+ )} +
+ +
`${range[0]}-${range[1]} of ${total} data sources` + }} + rowClassName={() => 'datasource-row'} + style={{ + borderRadius: '8px', + overflow: 'hidden' + }} + /> + + )} + + ); }; diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx index e811f3071..ff1e01e39 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx @@ -1,12 +1,22 @@ import React, { useState, useEffect } from 'react'; -import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd'; -import { SyncOutlined, CloudUploadOutlined, CodeOutlined, AuditOutlined } from '@ant-design/icons'; +import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col } from 'antd'; +import { + SyncOutlined, + CloudUploadOutlined, + CodeOutlined, + AuditOutlined, + UserOutlined, + CloudServerOutlined, + DisconnectOutlined, + ApiOutlined, + ThunderboltOutlined +} from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { Workspace } from '../types/workspace.types'; import { Query } from '../types/query.types'; import { getMergedWorkspaceQueries } from '../services/query.service'; -import { Switch, Spin, Empty } from 'antd'; +import { Switch, Spin, Empty, Avatar } from 'antd'; import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; import { useDeployModal } from '../context/DeployModalContext'; import { queryConfig } from '../config/query.config'; @@ -121,24 +131,68 @@ const QueriesTab: React.FC = ({ environment, workspace }) => { query.id.toLowerCase().includes(searchText.toLowerCase())) : queries; + // Helper function to generate colors from strings + const stringToColor = (str: string) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + + const hue = Math.abs(hash % 360); + return `hsl(${hue}, 70%, 50%)`; + }; + // Table columns const columns = [ { - title: 'Name', - dataIndex: 'name', - key: 'name', - render: (text: string) => {text} - }, - { - title: 'ID', - dataIndex: 'id', - key: 'id', - ellipsis: true, + title: 'Query', + key: 'query', + render: (query: Query) => ( +
+ } + > + +
+
{query.name}
+
+ {query.id} +
+
+
+ ), }, { title: 'Creator', dataIndex: 'creatorName', key: 'creatorName', + render: (creatorName: string) => ( +
+ } + style={{ backgroundColor: '#1890ff' }} + /> + {creatorName} +
+ ) + }, + { + title: 'Status', + key: 'status', + render: (query: Query) => ( + + {query.managed ? : } {query.managed ? 'Managed' : 'Unmanaged'} + + ), }, { title: 'Managed', @@ -148,7 +202,6 @@ const QueriesTab: React.FC = ({ environment, workspace }) => { checked={!!query.managed} onChange={(checked: boolean) => handleToggleManaged(query, checked)} loading={refreshing} - size="small" /> ), }, @@ -160,7 +213,6 @@ const QueriesTab: React.FC = ({ environment, workspace }) => { - {/* Stats display */} -
-
-
Total Queries
-
{stats.total}
-
-
-
Managed
-
{stats.managed}
-
-
-
Unmanaged
-
{stats.unmanaged}
-
-
- - - {/* Error display */} {error && ( = ({ environment, workspace }) => { description={error} type="error" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} @@ -236,49 +316,88 @@ const QueriesTab: React.FC = ({ environment, workspace }) => { description="Missing required configuration: API key or API service URL" type="warning" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} + {/* Stats display */} + +
+ } + /> + + + } + /> + + + } + /> + + + {/* Content */} - {loading ? ( -
- -
- ) : queries.length === 0 ? ( - - ) : ( - <> - {/* Search Bar */} -
- setSearchText(value)} - onChange={e => setSearchText(e.target.value)} - style={{ width: 300 }} - /> - {searchText && filteredQueries.length !== queries.length && ( -
- Showing {filteredQueries.length} of {queries.length} queries -
- )} + + {loading ? ( +
+
- -
- - )} - + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + size="large" + /> + {searchText && filteredQueries.length !== queries.length && ( +
+ Showing {filteredQueries.length} of {queries.length} queries +
+ )} +
+ +
`${range[0]}-${range[1]} of ${total} queries` + }} + rowClassName={() => 'query-row'} + style={{ + borderRadius: '8px', + overflow: 'hidden' + }} + /> + + )} + + ); }; From 3e35b5d0fde3da88eb05ba7a8ae89bd019476d49 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 16 May 2025 13:39:18 +0500 Subject: [PATCH 16/37] update UI for workspaces tab --- .../environments/components/WorkspacesTab.tsx | 258 ++++++++++++------ 1 file changed, 176 insertions(+), 82 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx index 90a22619d..2068ccf8b 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip } from 'antd'; -import { SyncOutlined, CloudUploadOutlined, AuditOutlined } from '@ant-design/icons'; +import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col, Avatar } from 'antd'; +import { SyncOutlined, AuditOutlined, TeamOutlined, CheckCircleFilled, CloudServerOutlined, DisconnectOutlined } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { Workspace } from '../types/workspace.types'; @@ -68,9 +68,6 @@ const WorkspacesTab: React.FC = ({ environment }) => { fetchWorkspaces(); }; - // Toggle managed status - - // Handle row click for navigation const handleRowClick = (workspace: Workspace) => { history.push(`/setting/environments/${environment.environmentId}/workspaces/${workspace.id}`); @@ -83,19 +80,72 @@ const WorkspacesTab: React.FC = ({ environment }) => { workspace.id.toLowerCase().includes(searchText.toLowerCase())) : workspaces; + // Helper function to generate colors from strings + const stringToColor = (str: string) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + + const hue = Math.abs(hash % 360); + return `hsl(${hue}, 70%, 50%)`; + }; + + // Stat card component + const StatCard = ({ title, value, icon }: { title: string; value: number; icon: React.ReactNode }) => ( + +
+
+
{title}
+
{value}
+
+
+ {icon} +
+
+
+ ); + // Table columns const columns = [ { - title: 'Name', - dataIndex: 'name', - key: 'name', - render: (text: string) => {text} - }, - { - title: 'ID', - dataIndex: 'id', - key: 'id', - ellipsis: true, + title: 'Workspace', + key: 'workspace', + render: (workspace: Workspace) => ( +
+ + {workspace.name.charAt(0).toUpperCase()} + +
+
{workspace.name}
+
+ {workspace.id} +
+
+
+ ), }, { title: 'Role', @@ -107,7 +157,8 @@ const WorkspacesTab: React.FC = ({ environment }) => { dataIndex: 'status', key: 'status', render: (status: string) => ( - + + {status === 'ACTIVE' ? : null} {status} ), @@ -116,7 +167,14 @@ const WorkspacesTab: React.FC = ({ environment }) => { title: 'Managed', key: 'managed', render: (_: any, workspace: Workspace) => ( - + + {workspace.managed + ? + : + } {workspace.managed ? 'Managed' : 'Unmanaged'} ), @@ -129,7 +187,6 @@ const WorkspacesTab: React.FC = ({ environment }) => { - {/* Stats display */} -
-
-
Total Workspaces
-
{stats.total}
-
-
-
Managed
-
{stats.managed}
-
-
-
Unmanaged
-
{stats.unmanaged}
-
-
- - - {/* Error display */} {error && ( = ({ environment }) => { description={error} type="error" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} @@ -194,53 +249,92 @@ const WorkspacesTab: React.FC = ({ environment }) => { description="Missing required configuration: API key or API service URL" type="warning" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} + {/* Stats display */} + +
+ } + /> + + + } + /> + + + } + /> + + + {/* Content */} - {loading ? ( -
- -
- ) : workspaces.length === 0 ? ( - - ) : ( - <> - {/* Search Bar */} -
- setSearchText(value)} - onChange={e => setSearchText(e.target.value)} - style={{ width: 300 }} - /> - {searchText && filteredWorkspaces.length !== workspaces.length && ( -
- Showing {filteredWorkspaces.length} of {workspaces.length} workspaces -
- )} + + {loading ? ( +
+
- -
({ - onClick: () => handleRowClick(record), - style: { cursor: 'pointer' } - })} + ) : workspaces.length === 0 ? ( + - - )} - + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + size="large" + /> + {searchText && filteredWorkspaces.length !== workspaces.length && ( +
+ Showing {filteredWorkspaces.length} of {workspaces.length} workspaces +
+ )} +
+ +
`${range[0]}-${range[1]} of ${total} workspaces` + }} + style={{ + borderRadius: '8px', + overflow: 'hidden' + }} + onRow={(record) => ({ + onClick: () => handleRowClick(record), + style: { cursor: 'pointer' } + })} + rowClassName={() => 'workspace-row'} + /> + + )} + + ); }; From 548e73b3586959c15cfbf68823f7b4c989909d85 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 16 May 2025 13:46:08 +0500 Subject: [PATCH 17/37] update UI user groups tab --- .../environments/components/UserGroupsTab.tsx | 285 +++++++++++++----- 1 file changed, 203 insertions(+), 82 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx index 701dcd43d..b9c2606e8 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space } from 'antd'; -import { SyncOutlined, TeamOutlined } from '@ant-design/icons'; +import { Card, Button, Alert, message, Table, Tag, Input, Space, Row, Col, Avatar, Tooltip } from 'antd'; +import { SyncOutlined, TeamOutlined, UserOutlined, UsergroupAddOutlined, SettingOutlined, CodeOutlined } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { UserGroup, UserGroupsTabStats } from '../types/userGroup.types'; @@ -89,83 +89,158 @@ const UserGroupsTab: React.FC = ({ environment }) => { group.groupId.toLowerCase().includes(searchText.toLowerCase())) : userGroups; + // Helper function to generate colors from strings + const stringToColor = (str: string) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + + const hue = Math.abs(hash % 360); + return `hsl(${hue}, 70%, 50%)`; + }; + + // Stat card component + const StatCard = ({ title, value, icon }: { title: string; value: number; icon: React.ReactNode }) => ( + +
+
+
{title}
+
{value}
+
+
+ {icon} +
+
+
+ ); + // Table columns const columns = [ { - title: 'Name', - dataIndex: 'groupName', - key: 'groupName', - render: (text: string) => {text} - }, - { - title: 'ID', - dataIndex: 'groupId', - key: 'groupId', - ellipsis: true, + title: 'User Group', + key: 'group', + render: (group: UserGroup) => ( +
+ + {group.groupName.charAt(0).toUpperCase()} + +
+
{group.groupName}
+
+ {group.groupId} +
+
+
+ ), }, { title: 'Type', key: 'type', render: (_: any, group: UserGroup) => { - if (group.allUsersGroup) return All Users; - if (group.devGroup) return Developers; - return Custom; + if (group.allUsersGroup) return ( + + All Users + + ); + if (group.devGroup) return ( + + Developers + + ); + return ( + + Custom + + ); }, }, { title: 'Members', key: 'members', - render: (_: any, group: UserGroup) => group.stats?.userCount || 0, + render: (_: any, group: UserGroup) => ( + + + {group.stats?.userCount || 0} + + + ), }, { title: 'Admin Members', key: 'adminMembers', - render: (_: any, group: UserGroup) => group.stats?.adminUserCount || 0, + render: (_: any, group: UserGroup) => ( + + + {group.stats?.adminUserCount || 0} + + + ), }, { title: 'Created', dataIndex: 'createTime', key: 'createTime', - render: (createTime: number) => new Date(createTime).toLocaleDateString(), + render: (createTime: number) => ( + + {new Date(createTime).toLocaleDateString()} + + ), } ]; return ( - - {/* Header with refresh button */} -
- User Groups in this Environment +
+ {/* Header */} +
+
+ + <UsergroupAddOutlined style={{ marginRight: 10 }} /> User Groups + +

Manage user groups in this environment

+
- {/* Stats display */} -
-
-
Total Groups
-
{stats.total}
-
-
-
All Users Groups
-
{stats.allUsers}
-
-
-
Developer Groups
-
{stats.developers}
-
-
-
Custom Groups
-
{stats.custom}
-
-
- - - {/* Error display */} {error && ( = ({ environment }) => { description={error} type="error" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} @@ -184,49 +259,95 @@ const UserGroupsTab: React.FC = ({ environment }) => { description="Missing required configuration: API key or API service URL" type="warning" showIcon - style={{ marginBottom: "16px" }} + style={{ marginBottom: "20px" }} /> )} + {/* Stats display */} + +
+ } + /> + + + } + /> + + + } + /> + + + } + /> + + + {/* Content */} - {loading ? ( -
- -
- ) : userGroups.length === 0 ? ( - - ) : ( - <> - {/* Search Bar */} -
- setSearchText(value)} - onChange={e => setSearchText(e.target.value)} - style={{ width: 300 }} - /> - {searchText && filteredUserGroups.length !== userGroups.length && ( -
- Showing {filteredUserGroups.length} of {userGroups.length} user groups -
- )} + + {loading ? ( +
+
- -
- - )} - + ) : ( + <> + {/* Search Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + size="large" + /> + {searchText && filteredUserGroups.length !== userGroups.length && ( +
+ Showing {filteredUserGroups.length} of {userGroups.length} user groups +
+ )} +
+ +
`${range[0]}-${range[1]} of ${total} user groups` + }} + style={{ + borderRadius: '8px', + overflow: 'hidden' + }} + rowClassName={() => 'group-row'} + /> + + )} + + ); }; From 7f4d11f7f9e9cf2d175b1ba739ef01a6d046424f Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 16 May 2025 14:32:38 +0500 Subject: [PATCH 18/37] update environment detail header --- .../environments/EnvironmentDetail.tsx | 150 ++++++++++-------- .../components/EnvironmentHeader.tsx | 111 +++++++++++++ .../components/ModernBreadcrumbs.tsx | 48 ++++++ 3 files changed, 240 insertions(+), 69 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx create mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/ModernBreadcrumbs.tsx diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx index 16dc4470a..ed5b3d8ce 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx @@ -3,19 +3,19 @@ import { Spin, Typography, Card, - Tag, Tabs, Alert, Descriptions, Menu, Button, - Breadcrumb, + Tag, } from "antd"; import { LinkOutlined, - TeamOutlined, + HomeOutlined, + AppstoreOutlined, + UsergroupAddOutlined, EditOutlined, - HomeOutlined } from "@ant-design/icons"; import { useSingleEnvironmentContext } from "./context/SingleEnvironmentContext"; @@ -24,6 +24,8 @@ import { Environment } from "./types/environment.types"; import history from "@lowcoder-ee/util/history"; import WorkspacesTab from "./components/WorkspacesTab"; import UserGroupsTab from "./components/UserGroupsTab"; +import EnvironmentHeader from "./components/EnvironmentHeader"; +import ModernBreadcrumbs from "./components/ModernBreadcrumbs"; const { Title, Text } = Typography; const { TabPane } = Tabs; @@ -42,6 +44,7 @@ const EnvironmentDetail: React.FC = () => { const [isEditModalVisible, setIsEditModalVisible] = useState(false); const [isUpdating, setIsUpdating] = useState(false); + const [activeTab, setActiveTab] = useState('workspaces'); // Handle edit menu item click const handleEditClick = () => { @@ -90,21 +93,27 @@ const EnvironmentDetail: React.FC = () => { } if (error || !environment) { + const errorItems = [ + { + key: 'environments', + title: ( + + Environments + + ), + onClick: () => history.push("/setting/environments") + }, + { + key: 'notFound', + title: 'Not Found' + } + ]; + return (
- - - history.push("/setting/environments")} - > - Environments - - - Not Found - + - +
Environment Not Found @@ -124,58 +133,53 @@ const EnvironmentDetail: React.FC = () => { </div> ); } + + const breadcrumbItems = [ + { + key: 'environments', + title: ( + <span> + <HomeOutlined /> Environments + </span> + ), + onClick: () => history.push("/setting/environments") + }, + { + key: 'currentEnvironment', + title: environment.environmentName + } + ]; + // Get environment type tag color + const getEnvironmentTypeColor = () => { + switch(environment.environmentType) { + case 'production': + return 'red'; + case 'testing': + return 'orange'; + default: + return 'blue'; + } + }; + return ( <div className="environment-detail-container" style={{ padding: "24px", flex: 1 }} > - <Breadcrumb style={{ marginBottom: "16px" }}> - <Breadcrumb.Item> - <span - style={{ cursor: "pointer" }} - onClick={() => history.push("/setting/environments")} - > - <HomeOutlined /> Environments - </span> - </Breadcrumb.Item> - <Breadcrumb.Item>{environment.environmentName}</Breadcrumb.Item> - </Breadcrumb> + <ModernBreadcrumbs items={breadcrumbItems} /> - {/* Header with environment name and controls */} - <div - className="environment-header" - style={{ - marginBottom: "24px", - display: "flex", - justifyContent: "space-between", - alignItems: "flex-start", - flexWrap: "wrap", - gap: "16px", - }} - > - <div style={{ flex: "1 1 auto", minWidth: "200px" }}> - <Title level={3} style={{ margin: 0, wordBreak: "break-word" }}> - {environment.environmentName || "Unnamed Environment"} - - ID: {environment.environmentId} -
-
- -
-
+ {/* Environment Header Component */} + {/* Basic Environment Information Card - improved responsiveness */} Master} + style={{ marginBottom: "24px", borderRadius: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.05)' }} + className="environment-overview-card" > { {environment.environmentType} {environment.environmentApikey ? ( - Configured + Configured ) : ( - Not Configured + Not Configured )} @@ -223,8 +222,21 @@ const EnvironmentDetail: React.FC = () => { {/* Tabs for Workspaces and User Groups */} - - + + + Workspaces + + } + key="workspaces" + > {/* Using our new standalone WorkspacesTab component */} @@ -232,7 +244,7 @@ const EnvironmentDetail: React.FC = () => { - User Groups + User Groups } key="userGroups" diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx new file mode 100644 index 000000000..78a3e93ca --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { Button, Tag, Typography, Row, Col } from 'antd'; +import { EditOutlined, EnvironmentOutlined } from '@ant-design/icons'; +import { Environment } from '../types/environment.types'; + +const { Title, Text } = Typography; + +interface EnvironmentHeaderProps { + environment: Environment; + onEditClick: () => void; +} + +/** + * Header component for environment details + * Displays environment name, ID, type, and controls + */ +const EnvironmentHeader: React.FC = ({ + environment, + onEditClick +}) => { + // Determine header gradient based on environment type + const getHeaderGradient = () => { + switch(environment.environmentType) { + case 'production': + return 'linear-gradient(135deg, #f5222d 0%, #fa8c16 100%)'; + case 'testing': + return 'linear-gradient(135deg, #fa8c16 0%, #faad14 100%)'; + default: + return 'linear-gradient(135deg, #1890ff 0%, #096dd9 100%)'; + } + }; + + // Get environment type tag color + const getEnvironmentTypeColor = () => { + switch(environment.environmentType) { + case 'production': + return 'red'; + case 'testing': + return 'orange'; + default: + return 'blue'; + } + }; + + return ( +
+ +
+
+
+ +
+
+ + {environment.environmentName || "Unnamed Environment"} + +
+ + ID: {environment.environmentId} + + + {environment.environmentType} + + {environment.isMaster && ( + + Master + + )} +
+
+
+ + + + + + + ); +}; + +export default EnvironmentHeader; \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/ModernBreadcrumbs.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/ModernBreadcrumbs.tsx new file mode 100644 index 000000000..1e8e61879 --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/environments/components/ModernBreadcrumbs.tsx @@ -0,0 +1,48 @@ +import React, { ReactNode } from 'react'; +import { Breadcrumb } from 'antd'; +import { BreadcrumbProps } from 'antd/lib/breadcrumb'; + +interface ModernBreadcrumbsProps extends BreadcrumbProps { + /** + * Items to display in the breadcrumb + */ + items?: { + key: string; + title: ReactNode; + onClick?: () => void; + }[]; +} + +/** + * Modern styled breadcrumb component with consistent styling + */ +const ModernBreadcrumbs: React.FC = ({ items = [], ...props }) => { + return ( +
+ + {items.map(item => ( + + {item.onClick ? ( + + {item.title} + + ) : ( + item.title + )} + + ))} + +
+ ); +}; + +export default ModernBreadcrumbs; \ No newline at end of file From 9a8329e68e363f071229d86a8a2e0dda26ed6aa2 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 16 May 2025 14:50:28 +0500 Subject: [PATCH 19/37] create utils for different env colors --- .../environments/EnvironmentDetail.tsx | 25 ++----------- .../components/EnvironmentHeader.tsx | 29 ++------------- .../environments/utils/environmentUtils.ts | 37 ++++++++++++++++--- 3 files changed, 37 insertions(+), 54 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx index ed5b3d8ce..ec75728ba 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx @@ -26,6 +26,7 @@ import WorkspacesTab from "./components/WorkspacesTab"; import UserGroupsTab from "./components/UserGroupsTab"; import EnvironmentHeader from "./components/EnvironmentHeader"; import ModernBreadcrumbs from "./components/ModernBreadcrumbs"; +import { getEnvironmentTagColor } from "./utils/environmentUtils"; const { Title, Text } = Typography; const { TabPane } = Tabs; @@ -74,15 +75,7 @@ const EnvironmentDetail: React.FC = () => { } }; - // Dropdown menu for environment actions - const actionsMenu = ( - - } onClick={handleEditClick}> - Edit Environment - - {/* Add more menu items here if needed */} - - ); + if (isLoading) { return ( @@ -149,18 +142,6 @@ const EnvironmentDetail: React.FC = () => { title: environment.environmentName } ]; - - // Get environment type tag color - const getEnvironmentTypeColor = () => { - switch(environment.environmentType) { - case 'production': - return 'red'; - case 'testing': - return 'orange'; - default: - return 'blue'; - } - }; return (
{ {environment.environmentType} diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx index 78a3e93ca..9d6bb4dba 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Button, Tag, Typography, Row, Col } from 'antd'; import { EditOutlined, EnvironmentOutlined } from '@ant-design/icons'; import { Environment } from '../types/environment.types'; +import { getEnvironmentTagColor, getEnvironmentHeaderGradient } from '../utils/environmentUtils'; const { Title, Text } = Typography; @@ -18,36 +19,12 @@ const EnvironmentHeader: React.FC = ({ environment, onEditClick }) => { - // Determine header gradient based on environment type - const getHeaderGradient = () => { - switch(environment.environmentType) { - case 'production': - return 'linear-gradient(135deg, #f5222d 0%, #fa8c16 100%)'; - case 'testing': - return 'linear-gradient(135deg, #fa8c16 0%, #faad14 100%)'; - default: - return 'linear-gradient(135deg, #1890ff 0%, #096dd9 100%)'; - } - }; - - // Get environment type tag color - const getEnvironmentTypeColor = () => { - switch(environment.environmentType) { - case 'production': - return 'red'; - case 'testing': - return 'orange'; - default: - return 'blue'; - } - }; - return (
= ({ ID: {environment.environmentId} {environment.environmentType} diff --git a/client/packages/lowcoder/src/pages/setting/environments/utils/environmentUtils.ts b/client/packages/lowcoder/src/pages/setting/environments/utils/environmentUtils.ts index f3f5d5c1b..59a21f859 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/utils/environmentUtils.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/utils/environmentUtils.ts @@ -4,7 +4,7 @@ /** * Get the appropriate color for an environment tag based on its type - * @param envType The environment type/stage (e.g. 'PROD', 'DEV', 'STAGING') + * @param envType The environment type/stage (DEV, TEST, PREPROD, PROD) * @returns A color string to use with Ant Design's Tag component */ export const getEnvironmentTagColor = (envType: string | undefined): string => { @@ -14,27 +14,52 @@ export const getEnvironmentTagColor = (envType: string | undefined): string => { const type = envType.toUpperCase(); switch (type) { - // Production environment case 'PROD': return 'red'; // Red for production - indicates caution - // Pre-production environment case 'PREPROD': return 'orange'; // Orange for pre-production - // Test environment case 'TEST': return 'purple'; // Purple for test environment - // Development environment case 'DEV': - return 'green'; // Green for development - safe to use + return 'blue'; // Blue for development default: return 'default'; // Default gray for unknown types } }; +/** + * Get the appropriate background gradient for an environment based on its type + * @param envType The environment type/stage (DEV, TEST, PREPROD, PROD) + * @returns A CSS linear gradient string for the background + */ +export const getEnvironmentHeaderGradient = (envType: string | undefined): string => { + if (!envType) return 'linear-gradient(135deg, #1890ff 0%, #096dd9 100%)'; + + // Normalize to uppercase for consistent comparison + const type = envType.toUpperCase(); + + switch (type) { + case 'PROD': + return 'linear-gradient(135deg, #f5222d 0%, #fa8c16 100%)'; + + case 'PREPROD': + return 'linear-gradient(135deg, #fa8c16 0%, #faad14 100%)'; + + case 'TEST': + return 'linear-gradient(135deg, #722ed1 0%, #b37feb 100%)'; + + case 'DEV': + return 'linear-gradient(135deg, #1890ff 0%, #096dd9 100%)'; + + default: + return 'linear-gradient(135deg, #1890ff 0%, #096dd9 100%)'; + } +}; + /** * Format an environment type for display * @param envType The environment type string From b3799a019c547c86ce285e3d41d2413791ee5d47 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 16 May 2025 15:09:00 +0500 Subject: [PATCH 20/37] update environments listing page --- .../setting/environments/EnvironmentsList.tsx | 238 +++++++++++++++--- .../components/EnvironmentsTable.tsx | 221 +++++++++------- 2 files changed, 333 insertions(+), 126 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx index ead872249..1b3372a04 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx @@ -1,14 +1,14 @@ import React, { useState } from "react"; -import { Typography, Alert, Input, Button, Space, Empty } from "antd"; -import { SearchOutlined, ReloadOutlined } from "@ant-design/icons"; +import { Typography, Alert, Input, Button, Space, Empty, Card, Spin, Row, Col, Tooltip, Badge } from "antd"; +import { SearchOutlined, ReloadOutlined, PlusOutlined, EnvironmentOutlined } from "@ant-design/icons"; import { useHistory } from "react-router-dom"; import { useEnvironmentContext } from "./context/EnvironmentContext"; import { Environment } from "./types/environment.types"; import EnvironmentsTable from "./components/EnvironmentsTable"; import { buildEnvironmentId } from "@lowcoder-ee/constants/routesURL"; -import EditEnvironmentModal from "./components/EditEnvironmentModal"; +import { getEnvironmentTagColor } from "./utils/environmentUtils"; -const { Title } = Typography; +const { Title, Text } = Typography; /** * Environment Listing Page Component @@ -19,13 +19,13 @@ const EnvironmentsList: React.FC = () => { const { environments, isLoading, - error, + error, + refreshEnvironments } = useEnvironmentContext(); - console.log("Environments:", environments); - // State for search input const [searchText, setSearchText] = useState(""); + const [isRefreshing, setIsRefreshing] = useState(false); // Hook for navigation const history = useHistory(); @@ -46,20 +46,160 @@ const EnvironmentsList: React.FC = () => { history.push(buildEnvironmentId(record.environmentId)); }; + // Handle refresh + const handleRefresh = async () => { + setIsRefreshing(true); + await refreshEnvironments(); + setIsRefreshing(false); + }; + + // Count environment types + const environmentCounts = environments.reduce((counts, env) => { + const type = env.environmentType.toUpperCase(); + counts[type] = (counts[type] || 0) + 1; + return counts; + }, {} as Record); + return ( -
- {/* Header section with title and controls */} +
+ {/* Modern gradient header */}
- Environments - + +
+
+
+ +
+
+ + Environments + + + Manage your deployment environments across dev, test, preprod, and production + +
+
+ + + + + + + + + + + {/* Environment type stats */} + {environments.length > 0 && ( + + + + + + +
+
+ {environments.length} +
+
+ Total Environments +
+
+
+ + + {['PROD', 'PREPROD', 'TEST', 'DEV'].map(type => ( + + +
+
+ {environmentCounts[type] || 0} +
+
+ + {type} Environments +
+
+
+ + ))} + + + + + )} + + {/* Main content card */} + { prefix={} allowClear /> - - + } + > + {/* Error handling */} + {error && ( + + )} - {/* Error handling */} - {error && ( - - )} + {/* Loading, empty state or table */} + {isLoading ? ( +
+ +
+ ) : environments.length === 0 && !error ? ( + + ) : filteredEnvironments.length === 0 ? ( + + ) : ( + /* Table component */ + + )} - {/* Empty state handling */} - {!isLoading && environments.length === 0 && !error ? ( - - ) : ( - /* Table component */ - - )} + {/* Results counter when searching */} + {searchText && filteredEnvironments.length !== environments.length && ( +
+ Showing {filteredEnvironments.length} of {environments.length} environments +
+ )} +
); }; diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx index 4f9150a9b..b5bdf7475 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx @@ -1,115 +1,164 @@ import React from 'react'; -import { Table, Tag, Button, Tooltip, Space } from 'antd'; -import { EditOutlined, AuditOutlined} from '@ant-design/icons'; +import { Table, Tag, Button, Tooltip, Space, Card, Row, Col, Typography, Avatar } from 'antd'; +import { EditOutlined, AuditOutlined, LinkOutlined, EnvironmentOutlined, StarFilled } from '@ant-design/icons'; import { Environment } from '../types/environment.types'; import { getEnvironmentTagColor, formatEnvironmentType } from '../utils/environmentUtils'; - +const { Text, Title } = Typography; interface EnvironmentsTableProps { environments: Environment[]; loading: boolean; onRowClick: (record: Environment) => void; - } /** - * Table component for displaying environments + * Modern card-based layout for displaying environments */ const EnvironmentsTable: React.FC = ({ environments, loading, onRowClick, }) => { - // Open audit page in new tab - const openAuditPage = (environmentId: string, e: React.MouseEvent) => { + // Open audit page in new tab + const openAuditPage = (environmentId: string, e: React.MouseEvent) => { e.stopPropagation(); // Prevent row click from triggering const auditUrl = `/setting/audit?environmentId=${environmentId}`; window.open(auditUrl, '_blank'); }; + // Generate background color for environment avatar + const getAvatarColor = (name: string) => { + let hash = 0; + for (let i = 0; i < name.length; i++) { + hash = name.charCodeAt(i) + ((hash << 5) - hash); + } + + const type = name.toUpperCase(); + if (type === 'PROD') return '#f5222d'; + if (type === 'PREPROD') return '#fa8c16'; + if (type === 'TEST') return '#722ed1'; + if (type === 'DEV') return '#1890ff'; + + const hue = Math.abs(hash % 360); + return `hsl(${hue}, 70%, 50%)`; + }; - // Define table columns - const columns = [ - { - title: 'Name', - dataIndex: 'environmentName', - key: 'environmentName', - render: (name: string) => name || 'Unnamed Environment', - }, - { - title: 'Domain', - dataIndex: 'environmentFrontendUrl', - key: 'environmentFrontendUrl', - render: (url: string) => url || 'No URL', - }, - { - title: 'ID', - dataIndex: 'environmentId', - key: 'environmentId', - }, - { - title: 'Stage', - dataIndex: 'environmentType', - key: 'environmentType', - render: (type: string) => ( - - {formatEnvironmentType(type)} - - ), - }, - { - title: 'Master', - dataIndex: 'isMaster', - key: 'isMaster', - render: (isMaster: boolean) => ( - - {isMaster ? 'Yes' : 'No'} - - ), - }, - { - title: 'Actions', - key: 'actions', - render: (_: any, record: Environment) => ( - e.stopPropagation()}> - - - - - ), - }, - ]; + // For card display, we'll use a custom layout instead of Table + if (environments.length === 0) { + return null; + } return ( -
({ - onClick: () => onRowClick(record), - style: { - cursor: 'pointer', - transition: 'background-color 0.3s', - ':hover': { - backgroundColor: '#f5f5f5', - } - } - })} - rowClassName={() => 'environment-row'} - /> +
+ + {environments.map(env => ( +
+ onRowClick(env)} + > +
+
+ } + /> +
+ + {env.environmentName || 'Unnamed Environment'} + {env.isMaster && ( + <Tooltip title="Master Environment"> + <StarFilled style={{ color: '#faad14', marginLeft: '8px', fontSize: '14px' }} /> + </Tooltip> + )} + + + {formatEnvironmentType(env.environmentType)} + +
+
+
+ +
+
+ +
+
+
+ ID: + + {env.environmentId} + +
+ + + +
+ Master: + + {env.isMaster ? 'Yes' : 'No'} + +
+
+
+
+ + ))} + + + {environments.length > 10 && ( +
+ + Showing all {environments.length} environments + +
+ )} + ); }; From 933878e2733469c20f586eaee86a25754ff5f942 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Fri, 16 May 2025 18:50:10 +0500 Subject: [PATCH 21/37] add breadcrumbs component and fix tabs styling --- .../setting/environments/WorkspaceDetail.tsx | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx index 8c87b2ef6..7d9abb845 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx @@ -6,7 +6,6 @@ import { Card, Tabs, Button, - Breadcrumb, Space, Tag, Switch, @@ -32,6 +31,7 @@ import { workspaceConfig } from "./config/workspace.config"; import AppsTab from "./components/AppsTab"; import DataSourcesTab from "./components/DataSourcesTab"; import QueriesTab from "./components/QueriesTab"; +import ModernBreadcrumbs from "./components/ModernBreadcrumbs"; const { Title, Text } = Typography; const { TabPane } = Tabs; @@ -79,25 +79,35 @@ const WorkspaceDetail: React.FC = () => { ); } + const breadcrumbItems = [ + { + key: 'environments', + title: ( + + Environments + + ), + onClick: () => history.push("/setting/environments") + }, + { + key: 'environment', + title: ( + + {environment.environmentName} + + ), + onClick: () => history.push(`/setting/environments/${environment.environmentId}`) + }, + { + key: 'workspace', + title: workspace.name + } + ]; + return (
- {/* Breadcrumb navigation */} - - - history.push("/setting/environments")}> - Environments - - - - history.push(`/setting/environments/${environment.environmentId}`)} - > - {environment.environmentName} - - - {workspace.name} - + {/* Modern Breadcrumbs navigation */} + {/* Workspace header with details and actions */} @@ -158,14 +168,13 @@ const WorkspaceDetail: React.FC = () => { {/* Tabs for Apps, Data Sources, and Queries */} - - // Replace the Apps TabPane in WorkspaceDetail.tsx with this: - Apps} key="apps"> - - + + Apps} key="apps"> + + Data Sources} key="dataSources"> Date: Fri, 16 May 2025 19:03:54 +0500 Subject: [PATCH 22/37] fix environments icon on listing page --- .../pages/setting/environments/EnvironmentsList.tsx | 11 ++--------- .../environments/components/EnvironmentHeader.tsx | 4 ++-- .../environments/components/EnvironmentsTable.tsx | 4 ++-- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx index 1b3372a04..3a1f6f385 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { Typography, Alert, Input, Button, Space, Empty, Card, Spin, Row, Col, Tooltip, Badge } from "antd"; -import { SearchOutlined, ReloadOutlined, PlusOutlined, EnvironmentOutlined } from "@ant-design/icons"; +import { SearchOutlined, ReloadOutlined, CloudServerOutlined} from "@ant-design/icons"; import { useHistory } from "react-router-dom"; import { useEnvironmentContext } from "./context/EnvironmentContext"; import { Environment } from "./types/environment.types"; @@ -96,7 +96,7 @@ const EnvironmentsList: React.FC = () => { justifyContent: 'center', boxShadow: '0 4px 8px rgba(0,0,0,0.1)' }}> - +
@@ -120,13 +120,6 @@ const EnvironmentsList: React.FC = () => { > Refresh </Button> - <Button - icon={<PlusOutlined />} - type="primary" - size="large" - > - New Environment - </Button> </Space> </Col> </Row> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx index 9d6bb4dba..58fea8066 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Button, Tag, Typography, Row, Col } from 'antd'; -import { EditOutlined, EnvironmentOutlined } from '@ant-design/icons'; +import { EditOutlined, CloudServerOutlined } from '@ant-design/icons'; import { Environment } from '../types/environment.types'; import { getEnvironmentTagColor, getEnvironmentHeaderGradient } from '../utils/environmentUtils'; @@ -44,7 +44,7 @@ const EnvironmentHeader: React.FC<EnvironmentHeaderProps> = ({ alignItems: 'center', justifyContent: 'center' }}> - <EnvironmentOutlined /> + <CloudServerOutlined /> </div> <div> <Title level={3} style={{ margin: '0 0 4px 0', color: 'white' }}> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx index b5bdf7475..16f9fc6fd 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Table, Tag, Button, Tooltip, Space, Card, Row, Col, Typography, Avatar } from 'antd'; -import { EditOutlined, AuditOutlined, LinkOutlined, EnvironmentOutlined, StarFilled } from '@ant-design/icons'; +import { EditOutlined, AuditOutlined, LinkOutlined, EnvironmentOutlined, StarFilled, CloudServerOutlined } from '@ant-design/icons'; import { Environment } from '../types/environment.types'; import { getEnvironmentTagColor, formatEnvironmentType } from '../utils/environmentUtils'; @@ -79,7 +79,7 @@ const EnvironmentsTable: React.FC<EnvironmentsTableProps> = ({ fontSize: '20px' }} size={48} - icon={<EnvironmentOutlined />} + icon={<CloudServerOutlined />} /> <div> <Title level={5} style={{ margin: 0, marginBottom: '4px' }}> From b97f125f24b8e552a6aef085498bd4bfe961aff7 Mon Sep 17 00:00:00 2001 From: Faran Javed <faran1997@outlook.com> Date: Fri, 16 May 2025 19:51:43 +0500 Subject: [PATCH 23/37] fix refresh buttons --- .../pages/setting/environments/components/AppsTab.tsx | 9 +++++++-- .../setting/environments/components/DataSourcesTab.tsx | 9 +++++++-- .../environments/components/EnvironmentHeader.tsx | 10 ++++++++-- .../setting/environments/components/QueriesTab.tsx | 9 +++++++-- .../setting/environments/components/UserGroupsTab.tsx | 9 +++++++-- .../setting/environments/components/WorkspacesTab.tsx | 9 +++++++-- 6 files changed, 43 insertions(+), 12 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx index b2fde8d7a..b69822606 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx @@ -283,8 +283,13 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => { icon={<SyncOutlined spin={refreshing} />} onClick={handleRefresh} loading={loading} - type="primary" - ghost + type="default" + style={{ + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderColor: 'rgba(255, 255, 255, 0.4)', + color: 'white', + fontWeight: 500 + }} > Refresh </Button> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx index 068552232..443cdd8e5 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx @@ -292,8 +292,13 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace icon={<SyncOutlined spin={refreshing} />} onClick={handleRefresh} loading={loading} - type="primary" - ghost + type="default" + style={{ + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderColor: 'rgba(255, 255, 255, 0.4)', + color: 'white', + fontWeight: 500 + }} > Refresh </Button> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx index 58fea8066..0a999129e 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx @@ -73,9 +73,15 @@ const EnvironmentHeader: React.FC<EnvironmentHeaderProps> = ({ <Button icon={<EditOutlined />} onClick={onEditClick} - type="primary" - ghost + type="default" size="large" + style={{ + background: 'white', + color: '#1890ff', + borderColor: 'white', + fontWeight: 500, + boxShadow: '0 2px 4px rgba(0,0,0,0.1)' + }} > Edit Environment </Button> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx index ff1e01e39..3ec86dfff 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx @@ -291,8 +291,13 @@ const QueriesTab: React.FC<QueriesTabProps> = ({ environment, workspace }) => { icon={<SyncOutlined spin={refreshing} />} onClick={handleRefresh} loading={loading} - type="primary" - ghost + type="default" + style={{ + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderColor: 'rgba(255, 255, 255, 0.4)', + color: 'white', + fontWeight: 500 + }} > Refresh </Button> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx index b9c2606e8..ff079a9df 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx @@ -234,8 +234,13 @@ const UserGroupsTab: React.FC<UserGroupsTabProps> = ({ environment }) => { icon={<SyncOutlined spin={refreshing} />} onClick={handleRefresh} loading={loading} - type="primary" - ghost + type="default" + style={{ + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderColor: 'rgba(255, 255, 255, 0.4)', + color: 'white', + fontWeight: 500 + }} > Refresh </Button> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx index 2068ccf8b..1e3b6c16e 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx @@ -224,8 +224,13 @@ const WorkspacesTab: React.FC<WorkspacesTabProps> = ({ environment }) => { icon={<SyncOutlined spin={refreshing} />} onClick={handleRefresh} loading={loading} - type="primary" - ghost + type="default" + style={{ + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderColor: 'rgba(255, 255, 255, 0.4)', + color: 'white', + fontWeight: 500 + }} > Refresh </Button> From f3a36d88c59c396f5610f771be91c291435be10a Mon Sep 17 00:00:00 2001 From: Faran Javed <faran1997@outlook.com> Date: Fri, 16 May 2025 20:09:13 +0500 Subject: [PATCH 24/37] fix tabs rendering issue --- .../pages/setting/environments/WorkspaceDetail.tsx | 6 +++--- .../setting/environments/components/AppsTab.tsx | 13 ++++++------- .../environments/components/DataSourcesTab.tsx | 13 ++++++------- .../setting/environments/components/QueriesTab.tsx | 12 ++++++------ 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx index 7d9abb845..66ba819fa 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx @@ -172,20 +172,20 @@ const WorkspaceDetail: React.FC = () => { <TabPane tab={<span><AppstoreOutlined /> Apps</span>} key="apps"> <AppsTab environment={environment} - workspace={workspace} + workspaceId={workspace.id} /> </TabPane> <TabPane tab={<span><DatabaseOutlined /> Data Sources</span>} key="dataSources"> <DataSourcesTab environment={environment} - workspace={workspace} + workspaceId={workspace.id} /> </TabPane> <TabPane tab={<span><CodeOutlined /> Queries</span>} key="queries"> <QueriesTab environment={environment} - workspace={workspace} + workspaceId={workspace.id} /> </TabPane> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx index b69822606..f2b372055 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx @@ -3,7 +3,6 @@ import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Toolti import { SyncOutlined, CloudUploadOutlined, AuditOutlined, AppstoreOutlined, CheckCircleFilled, CloudServerOutlined, DisconnectOutlined } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; -import { Workspace } from '../types/workspace.types'; import { App, AppStats } from '../types/app.types'; import { getMergedWorkspaceApps } from '../services/apps.service'; import { Switch, Spin, Empty, Avatar } from 'antd'; @@ -16,10 +15,10 @@ const { Search } = Input; interface AppsTabProps { environment: Environment; - workspace: Workspace; + workspaceId: string; } -const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => { +const AppsTab: React.FC<AppsTabProps> = ({ environment, workspaceId }) => { const [apps, setApps] = useState<App[]>([]); const [stats, setStats] = useState<AppStats>({ total: 0, @@ -35,14 +34,14 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => { // Fetch apps const fetchApps = async () => { - if (!workspace.id || !environment) return; + if (!workspaceId || !environment) return; setLoading(true); setError(null); try { const result = await getMergedWorkspaceApps( - workspace.id, + workspaceId, environment.environmentId, environment.environmentApikey, environment.environmentApiServiceUrl! @@ -71,7 +70,7 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => { useEffect(() => { fetchApps(); - }, [environment, workspace]); + }, [environment, workspaceId]); // Handle refresh const handleRefresh = () => { @@ -196,7 +195,7 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspace }) => { icon={<AuditOutlined />} onClick={(e) => { e.stopPropagation(); - const auditUrl = `/setting/audit?environmentId=${environment.environmentId}&orgId=${workspace.id}&appId=${app.applicationId}&pageSize=100&pageNum=1`; + const auditUrl = `/setting/audit?environmentId=${environment.environmentId}&orgId=${workspaceId}&appId=${app.applicationId}&pageSize=100&pageNum=1`; window.open(auditUrl, '_blank'); }} > diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx index 443cdd8e5..f916131be 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx @@ -12,7 +12,6 @@ import { } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; -import { Workspace } from '../types/workspace.types'; import { DataSource } from '../types/datasource.types'; import { getMergedWorkspaceDataSources } from '../services/datasources.service'; import { Switch, Spin, Empty, Avatar } from 'antd'; @@ -25,10 +24,10 @@ const { Search } = Input; interface DataSourcesTabProps { environment: Environment; - workspace: Workspace; + workspaceId: string; } -const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace }) => { +const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspaceId }) => { const [dataSources, setDataSources] = useState<DataSource[]>([]); const [stats, setStats] = useState({ total: 0, @@ -44,14 +43,14 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace // Fetch data sources const fetchDataSources = async () => { - if (!workspace.id || !environment) return; + if (!workspaceId || !environment) return; setLoading(true); setError(null); try { const result = await getMergedWorkspaceDataSources( - workspace.id, + workspaceId, environment.environmentId, environment.environmentApikey, environment.environmentApiServiceUrl! @@ -69,7 +68,7 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace useEffect(() => { fetchDataSources(); - }, [environment, workspace]); + }, [environment, workspaceId]); // Handle refresh const handleRefresh = () => { @@ -198,7 +197,7 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspace icon={<AuditOutlined />} onClick={(e) => { e.stopPropagation(); - const auditUrl = `/setting/audit?environmentId=${environment.environmentId}&orgId=${workspace.id}&datasourceId=${dataSource.id}&pageSize=100&pageNum=1`; + const auditUrl = `/setting/audit?environmentId=${environment.environmentId}&orgId=${workspaceId}&datasourceId=${dataSource.id}&pageSize=100&pageNum=1`; window.open(auditUrl, '_blank'); }} > diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx index 3ec86dfff..d5b377579 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx @@ -26,10 +26,10 @@ const { Search } = Input; interface QueriesTabProps { environment: Environment; - workspace: Workspace; + workspaceId: string; } -const QueriesTab: React.FC<QueriesTabProps> = ({ environment, workspace }) => { +const QueriesTab: React.FC<QueriesTabProps> = ({ environment, workspaceId }) => { const [queries, setQueries] = useState<Query[]>([]); const [stats, setStats] = useState({ total: 0, @@ -44,14 +44,14 @@ const QueriesTab: React.FC<QueriesTabProps> = ({ environment, workspace }) => { // Fetch queries const fetchQueries = async () => { - if (!workspace.id || !environment) return; + if (!workspaceId || !environment) return; setLoading(true); setError(null); try { const result = await getMergedWorkspaceQueries( - workspace.id, + workspaceId, environment.environmentId, environment.environmentApikey, environment.environmentApiServiceUrl! @@ -69,7 +69,7 @@ const QueriesTab: React.FC<QueriesTabProps> = ({ environment, workspace }) => { useEffect(() => { fetchQueries(); - }, [environment, workspace]); + }, [environment, workspaceId]); // Handle refresh const handleRefresh = () => { @@ -215,7 +215,7 @@ const QueriesTab: React.FC<QueriesTabProps> = ({ environment, workspace }) => { icon={<AuditOutlined />} onClick={(e) => { e.stopPropagation(); - const auditUrl = `/setting/audit?environmentId=${environment.environmentId}&orgId=${workspace.id}&queryId=${query.id}&pageSize=100&pageNum=1`; + const auditUrl = `/setting/audit?environmentId=${environment.environmentId}&orgId=${workspaceId}&queryId=${query.id}&pageSize=100&pageNum=1`; window.open(auditUrl, '_blank'); }} > From 4b9f37d2a38180c60945ea7ebfb3a75379fe0fa9 Mon Sep 17 00:00:00 2001 From: Faran Javed <faran1997@outlook.com> Date: Fri, 16 May 2025 20:28:03 +0500 Subject: [PATCH 25/37] fix refresh button for the environment listing --- .../setting/environments/EnvironmentsList.tsx | 26 +++++++++++-------- .../components/EditEnvironmentModal.tsx | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx index 3a1f6f385..613ed47a2 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { Typography, Alert, Input, Button, Space, Empty, Card, Spin, Row, Col, Tooltip, Badge } from "antd"; -import { SearchOutlined, ReloadOutlined, CloudServerOutlined} from "@ant-design/icons"; +import { SearchOutlined, CloudServerOutlined, SyncOutlined} from "@ant-design/icons"; import { useHistory } from "react-router-dom"; import { useEnvironmentContext } from "./context/EnvironmentContext"; import { Environment } from "./types/environment.types"; @@ -110,16 +110,20 @@ const EnvironmentsList: React.FC = () => { </Col> <Col xs={24} sm={8} style={{ textAlign: 'right' }}> <Space size="middle"> - <Button - icon={<ReloadOutlined spin={isRefreshing} />} - onClick={handleRefresh} - type="primary" - ghost - loading={isLoading && !isRefreshing} - size="large" - > - Refresh - </Button> + <Button + icon={<SyncOutlined spin={isRefreshing} />} + onClick={handleRefresh} + loading={isLoading} + type="default" + style={{ + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderColor: 'rgba(255, 255, 255, 0.4)', + color: 'white', + fontWeight: 500 + }} + > + Refresh + </Button> </Space> </Col> </Row> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx index 88bdd852f..8f28fd26c 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx @@ -61,7 +61,7 @@ const EditEnvironmentModal: React.FC<EditEnvironmentModalProps> = ({ title="Edit Environment" open={visible} onCancel={onClose} - maskClosable={false} + maskClosable={true} destroyOnClose={true} footer={[ <Button key="back" onClick={onClose}> From 2b4a71896dde60d93ed358b572c4618749da1496 Mon Sep 17 00:00:00 2001 From: Faran Javed <faran1997@outlook.com> Date: Fri, 16 May 2025 21:16:53 +0500 Subject: [PATCH 26/37] setup for new managed-obj endpoint --- .../environments/components/AppsTab.tsx | 2 +- .../components/DataSourcesTab.tsx | 1 - .../environments/components/QueriesTab.tsx | 1 - .../environments/context/WorkspaceContext.tsx | 2 +- .../services/managed-objects.service.ts | 26 +++++++------------ 5 files changed, 11 insertions(+), 21 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx index f2b372055..f26fd5ea5 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx @@ -87,7 +87,7 @@ const AppsTab: React.FC<AppsTabProps> = ({ environment, workspaceId }) => { app.applicationGid, environment.environmentId, ManagedObjectType.APP, - app.name + ); } else { await unsetManagedObject( diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx index f916131be..54b9fd5f2 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx @@ -85,7 +85,6 @@ const DataSourcesTab: React.FC<DataSourcesTabProps> = ({ environment, workspaceI dataSource.gid, environment.environmentId, ManagedObjectType.DATASOURCE, - dataSource.name ); } else { await unsetManagedObject( diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx index d5b377579..85bda783e 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx @@ -86,7 +86,6 @@ const QueriesTab: React.FC<QueriesTabProps> = ({ environment, workspaceId }) => query.gid, environment.environmentId, ManagedObjectType.QUERY, - query.name ); } else { await unsetManagedObject( diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx index 7ad259aeb..154bc3f97 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx @@ -118,7 +118,7 @@ import React, { workspace.gid!, environment.environmentId, ManagedObjectType.ORG, - workspace.name + ); } else { diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts index 173f1006c..fdbecb4ac 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts @@ -59,29 +59,20 @@ export async function isManagedObject( export async function setManagedObject( objGid: string, environmentId: string, - objType: ManagedObjectType, - objName?: string, - objTags: string[] = [] + objType: ManagedObjectType ): Promise<boolean> { try { if (!objGid || !environmentId || !objType) { throw new Error("Missing required parameters"); } - const response = await axios.post(`/api/plugins/enterprise/managed-obj`, - // Include optional parameters in the request body instead of query params - { - objName, - objTags: objTags.length > 0 ? objTags : undefined - }, - { - params: { - objGid, - environmentId, - objType - } - } - ); + const requestBody = { + objGid, + environmentId, + objType + }; + + const response = await axios.post(`/api/plugins/enterprise/managed-obj`, requestBody); return response.status === 200; } catch (error) { @@ -91,6 +82,7 @@ export async function setManagedObject( } } + /** * Set an object as unmanaged * @param objGid - Object's global ID From 766043239ddddaa0fa0102596aa8a43a671f06c1 Mon Sep 17 00:00:00 2001 From: Faran Javed <faran1997@outlook.com> Date: Fri, 16 May 2025 22:23:36 +0500 Subject: [PATCH 27/37] implement new managed obj endpoints --- .../environments/context/WorkspaceContext.tsx | 7 +- .../environments/services/apps.service.ts | 20 +- .../services/datasources.service.ts | 12 +- .../services/managed-objects.service.ts | 306 +++++++++--------- .../environments/services/query.service.ts | 10 +- .../services/workspace.service.ts | 8 +- 6 files changed, 182 insertions(+), 181 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx index 154bc3f97..f2e16d442 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx @@ -12,8 +12,7 @@ import React, { import { useSingleEnvironmentContext } from "./SingleEnvironmentContext"; import { fetchWorkspaceById } from "../services/environments.service"; import { Workspace } from "../types/workspace.types"; - import { getManagedWorkspaces } from "../services/enterprise.service"; - import { ManagedObjectType, setManagedObject, unsetManagedObject } from "../services/managed-objects.service"; + import { getManagedObjects, ManagedObjectType, setManagedObject, unsetManagedObject } from "../services/managed-objects.service"; interface WorkspaceContextState { // Workspace data @@ -87,10 +86,10 @@ import React, { } // Fetch managed workspaces to check if this one is managed - const managedWorkspaces = await getManagedWorkspaces(environment.environmentId); + const managedWorkspaces = await getManagedObjects(environment.environmentId, ManagedObjectType.ORG); // Set the managed status - const isManaged = managedWorkspaces.some(org => org.orgGid === workspaceData.gid); + const isManaged = managedWorkspaces.some(org => org.objGid === workspaceData.gid); // Update the workspace with managed status setWorkspace({ diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts index 570084f39..c057b3a52 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts @@ -4,6 +4,7 @@ import { getWorkspaceApps } from "./environments.service"; import { getManagedApps } from "./enterprise.service"; import { App, AppStats } from "../types/app.types"; import axios from "axios"; +import { getManagedObjects, ManagedObject } from "./managed-objects.service"; export interface MergedAppsResult { @@ -24,10 +25,13 @@ export interface DeployAppParams { // Use your existing merge function with slight modification -export const getMergedApps = (standardApps: App[], managedApps: any[]): App[] => { +export const getMergedApps = (standardApps: App[], managedObjects: ManagedObject[]): App[] => { return standardApps.map((app) => ({ ...app, - managed: managedApps.some((managedApp) => managedApp.appGid === app.applicationGid), + managed: managedObjects.some((managedObj) => + managedObj.objGid === app.applicationGid && + managedObj.objType === "APP" + ), })); }; @@ -71,17 +75,17 @@ export async function getMergedWorkspaceApps( }; } - // Only fetch managed apps if we have regular apps - let managedApps = []; + // Fetch managed objects instead of managed apps + let managedObjects: ManagedObject[] = []; try { - managedApps = await getManagedApps(environmentId); + managedObjects = await getManagedObjects(environmentId); } catch (error) { - console.error("Failed to fetch managed apps:", error); + console.error("Failed to fetch managed objects:", error); // Continue with empty managed list } - // Use your existing merge function - const mergedApps = getMergedApps(regularApps, managedApps); + // Use the updated merge function + const mergedApps = getMergedApps(regularApps, managedObjects); // Calculate stats const stats = calculateAppStats(mergedApps); diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts index 71d6929ac..0e7b9d6bb 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import { message } from "antd"; import { DataSource, DataSourceWithMeta } from "../types/datasource.types"; -import { getManagedDataSources } from "./enterprise.service"; +import { getManagedObjects, ManagedObject, ManagedObjectType } from "./managed-objects.service"; export interface DataSourceStats { total: number; @@ -71,12 +71,12 @@ export async function getWorkspaceDataSources( } // Function to merge regular and managed data sources -export const getMergedDataSources = (standardDataSources: DataSourceWithMeta[], managedDataSources: any[]): DataSource[] => { +export const getMergedDataSources = (standardDataSources: DataSourceWithMeta[], managedObjects: ManagedObject[]): DataSource[] => { return standardDataSources.map((dataSourceWithMeta) => { const dataSource = dataSourceWithMeta.datasource; return { ...dataSource, - managed: managedDataSources.some((managedDs) => managedDs.datasourceGid === dataSource.gid), + managed: managedObjects.some((obj) => obj.objGid === dataSource.gid && obj.objType === ManagedObjectType.DATASOURCE), }; }); }; @@ -123,16 +123,16 @@ export async function getMergedWorkspaceDataSources( } // Only fetch managed data sources if we have regular data sources - let managedDataSources = []; + let managedObjects: ManagedObject[] = []; try { - managedDataSources = await getManagedDataSources(environmentId); + managedObjects = await getManagedObjects(environmentId, ManagedObjectType.DATASOURCE); } catch (error) { console.error("Failed to fetch managed data sources:", error); // Continue with empty managed list } // Use the merge function - const mergedDataSources = getMergedDataSources(regularDataSourcesWithMeta, managedDataSources); + const mergedDataSources = getMergedDataSources(regularDataSourcesWithMeta, managedObjects); // Calculate stats const stats = calculateDataSourceStats(mergedDataSources); diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts index fdbecb4ac..9f3615c36 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts @@ -1,154 +1,152 @@ -import axios from "axios"; -import { message } from "antd"; - -// Object types that can be managed -export enum ManagedObjectType { - ORG = "ORG", - APP = "APP", - QUERY = "QUERY", - DATASOURCE = "DATASOURCE" -} - -/** - * Check if an object is managed - * @param objGid - Object's global ID - * @param environmentId - Environment ID - * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) - * @returns Promise with boolean indicating if object is managed - */ -export async function isManagedObject( - objGid: string, - environmentId: string, - objType: ManagedObjectType -): Promise<boolean> { - try { - if (!objGid || !environmentId || !objType) { - throw new Error("Missing required parameters"); - } - - const response = await axios.get(`/api/plugins/enterprise/managed-obj`, { - params: { - objGid, - environmentId, - objType - } - }); - - return response.data.managed === true; - } catch (error) { - // If the object doesn't exist as managed, it's not an error - if (axios.isAxiosError(error) && error.response?.status === 404) { - return false; - } - - const errorMessage = error instanceof Error ? error.message : "Failed to check managed status"; - message.error(errorMessage); - throw error; - } -} - -/** - * Set an object as managed - * @param objGid - Object's global ID - * @param environmentId - Environment ID - * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) - * @param objName - Object name (optional) - * @param objTags - Object tags (optional) - * @returns Promise with operation result - */ -export async function setManagedObject( - objGid: string, - environmentId: string, - objType: ManagedObjectType -): Promise<boolean> { - try { - if (!objGid || !environmentId || !objType) { - throw new Error("Missing required parameters"); - } - - const requestBody = { - objGid, - environmentId, - objType - }; - - const response = await axios.post(`/api/plugins/enterprise/managed-obj`, requestBody); - - return response.status === 200; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : `Failed to set ${objType} as managed`; - message.error(errorMessage); - throw error; - } -} - - -/** - * Set an object as unmanaged - * @param objGid - Object's global ID - * @param environmentId - Environment ID - * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) - * @returns Promise with operation result - */ -export async function unsetManagedObject( - objGid: string, - environmentId: string, - objType: ManagedObjectType -): Promise<boolean> { - try { - if (!objGid || !environmentId || !objType) { - throw new Error("Missing required parameters"); - } - - const response = await axios.delete(`/api/plugins/enterprise/managed-obj`, { - params: { - objGid, - environmentId, - objType - } - }); - - return response.status === 200; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : `Failed to remove ${objType} from managed`; - message.error(errorMessage); - throw error; - } -} - -/** - * Get all managed objects of a specific type for an environment - * NOTE: This function is commented out as the endpoint is not yet implemented - * TODO: Uncomment when the /managed-obj/list endpoint is available - * - * @param environmentId - Environment ID - * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) - * @returns Promise with an array of managed objects - */ -/* -export async function getManagedObjects( - environmentId: string, - objType: ManagedObjectType -): Promise<any[]> { - try { - if (!environmentId || !objType) { - throw new Error("Missing required parameters"); - } - - const response = await axios.get(`/api/plugins/enterprise/managed-obj/list`, { - params: { - environmentId, - objType - } - }); - - return response.data.data || []; - } catch (error) { - const errorMessage = error instanceof Error - ? error.message - : `Failed to fetch managed ${objType.toLowerCase()}s`; - message.error(errorMessage); - throw error; - } -} -*/ \ No newline at end of file +import axios from "axios"; +import { message } from "antd"; + +// Object types that can be managed +export enum ManagedObjectType { + ORG = "ORG", + APP = "APP", + QUERY = "QUERY", + DATASOURCE = "DATASOURCE" +} + +// Add this interface after the ManagedObjectType enum +export interface ManagedObject { + id: string; + managedId: string; + objGid: string; + environmentId: string; + objType: ManagedObjectType; +} + +/** + * Check if an object is managed + * @param objGid - Object's global ID + * @param environmentId - Environment ID + * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) + * @returns Promise with boolean indicating if object is managed + */ +export async function isManagedObject( + objGid: string, + environmentId: string, + objType: ManagedObjectType +): Promise<boolean> { + try { + if (!objGid || !environmentId || !objType) { + throw new Error("Missing required parameters"); + } + + const response = await axios.get(`/api/plugins/enterprise/managed-obj`, { + params: { + objGid, + environmentId, + objType + } + }); + + return response.data.managed === true; + } catch (error) { + // If the object doesn't exist as managed, it's not an error + if (axios.isAxiosError(error) && error.response?.status === 404) { + return false; + } + + const errorMessage = error instanceof Error ? error.message : "Failed to check managed status"; + message.error(errorMessage); + throw error; + } +} + +/** + * Set an object as managed + * @param objGid - Object's global ID + * @param environmentId - Environment ID + * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) + * @param objName - Object name (optional) + * @param objTags - Object tags (optional) + * @returns Promise with operation result + */ +export async function setManagedObject( + objGid: string, + environmentId: string, + objType: ManagedObjectType +): Promise<boolean> { + try { + if (!objGid || !environmentId || !objType) { + throw new Error("Missing required parameters"); + } + + const requestBody = { + objGid, + environmentId, + objType + }; + + const response = await axios.post(`/api/plugins/enterprise/managed-obj`, requestBody); + + return response.status === 200; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : `Failed to set ${objType} as managed`; + message.error(errorMessage); + throw error; + } +} + + +/** + * Set an object as unmanaged + * @param objGid - Object's global ID + * @param environmentId - Environment ID + * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) + * @returns Promise with operation result + */ +export async function unsetManagedObject( + objGid: string, + environmentId: string, + objType: ManagedObjectType +): Promise<boolean> { + try { + if (!objGid || !environmentId || !objType) { + throw new Error("Missing required parameters"); + } + + const response = await axios.delete(`/api/plugins/enterprise/managed-obj`, { + params: { + objGid, + environmentId, + objType + } + }); + + return response.status === 200; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : `Failed to remove ${objType} from managed`; + message.error(errorMessage); + throw error; + } +} + +// Add this new function +export async function getManagedObjects( + environmentId: string, + objType?: ManagedObjectType +): Promise<ManagedObject[]> { + try { + if (!environmentId) { + throw new Error("Missing environment ID"); + } + + const response = await axios.get(`/api/plugins/enterprise/managed-obj/list`, { + params: { + environmentId, + ...(objType && { objType }) // Only include objType in params if it's provided + } + }); + + return response.data.data; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : "Failed to fetch managed objects"; + message.error(errorMessage); + throw error; + } +} + diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts index f48d038d5..8d69af92a 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts @@ -2,7 +2,7 @@ * Get merged queries (both regular and managed) for a workspace */ import axios from 'axios'; -import { getManagedQueries } from './enterprise.service'; +import { getManagedObjects, ManagedObject, ManagedObjectType } from './managed-objects.service'; import { getWorkspaceQueries } from './environments.service'; import { Query, QueryStats } from '../types/query.types'; export interface MergedQueriesResult { @@ -30,11 +30,11 @@ export interface MergedQueriesResult { const regularQueries = await getWorkspaceQueries(workspaceId, apiKey, apiServiceUrl); console.log("Regular queries response:", regularQueries); - const managedQueries = await getManagedQueries(environmentId); - console.log("Managed queries response:", managedQueries); + const managedObjects = await getManagedObjects(environmentId, ManagedObjectType.QUERY); + console.log("Managed queries response:", managedObjects); - // Create a map of managed queries by GID for quick lookup - const managedQueryGids = new Set(managedQueries.map(query => query.gid)); + // Create a set of managed query GIDs for quick lookup + const managedQueryGids = new Set(managedObjects.map(obj => obj.objGid)); console.log("Managed query GIDs:", Array.from(managedQueryGids)); // Mark regular queries as managed if they exist in managed queries diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts index 43bc302a2..c6f7881dc 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts @@ -1,7 +1,7 @@ // services/workspacesService.ts (or wherever makes sense in your structure) import { message } from "antd"; import { getEnvironmentWorkspaces } from "./environments.service"; -import { getManagedWorkspaces } from "./enterprise.service"; +import { getManagedObjects, ManagedObject, ManagedObjectType } from "./managed-objects.service"; import { Workspace } from "../types/workspace.types"; import { ManagedOrg } from "../types/enterprise.types"; import axios from "axios"; @@ -43,9 +43,9 @@ export async function getMergedEnvironmentWorkspaces( } // Only fetch managed workspaces if we have regular workspaces - let managedOrgs: ManagedOrg[] = []; + let managedObjects: ManagedObject[] = []; try { - managedOrgs = await getManagedWorkspaces(environmentId); + managedObjects = await getManagedObjects(environmentId, ManagedObjectType.ORG); } catch (error) { console.error("Failed to fetch managed workspaces:", error); // Continue with empty managed list @@ -54,7 +54,7 @@ export async function getMergedEnvironmentWorkspaces( // Merge the workspaces const mergedWorkspaces = regularWorkspaces.map(ws => ({ ...ws, - managed: managedOrgs.some(org => org.orgGid === ws.gid) + managed: managedObjects.some(obj => obj.objGid === ws.gid && obj.objType === ManagedObjectType.ORG) })); // Calculate stats From 6fcafe4367847e8e161ec3623e88f044e31f3abf Mon Sep 17 00:00:00 2001 From: Faran Javed <faran1997@outlook.com> Date: Sat, 17 May 2025 17:01:35 +0500 Subject: [PATCH 28/37] update workspace header UI --- .../setting/environments/WorkspaceDetail.tsx | 77 +----- .../components/WorkspaceHeader.tsx | 257 ++++++++++++++++++ 2 files changed, 268 insertions(+), 66 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx index 66ba819fa..efe528b38 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx @@ -3,14 +3,8 @@ import history from "@lowcoder-ee/util/history"; import { Spin, Typography, - Card, Tabs, - Button, - Space, - Tag, - Switch, message, - Tooltip } from "antd"; import { AppstoreOutlined, @@ -18,8 +12,6 @@ import { CodeOutlined, HomeOutlined, TeamOutlined, - ArrowLeftOutlined, - CloudUploadOutlined } from "@ant-design/icons"; // Use the context hooks @@ -32,8 +24,8 @@ import AppsTab from "./components/AppsTab"; import DataSourcesTab from "./components/DataSourcesTab"; import QueriesTab from "./components/QueriesTab"; import ModernBreadcrumbs from "./components/ModernBreadcrumbs"; +import WorkspaceHeader from "./components/WorkspaceHeader"; -const { Title, Text } = Typography; const { TabPane } = Tabs; const WorkspaceDetail: React.FC = () => { @@ -42,6 +34,8 @@ const WorkspaceDetail: React.FC = () => { const { workspace, isLoading, error, toggleManagedStatus } = useWorkspaceContext(); const { openDeployModal } = useDeployModal(); + console.log("workspace render", workspace); + const [isToggling, setIsToggling] = useState(false); // Handle toggle managed status @@ -109,63 +103,14 @@ const WorkspaceDetail: React.FC = () => { {/* Modern Breadcrumbs navigation */} <ModernBreadcrumbs items={breadcrumbItems} /> - {/* Workspace header with details and actions */} - <Card style={{ marginBottom: "24px" }} bodyStyle={{ padding: "16px 24px" }}> - <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}> - {/* Left section - Workspace info */} - <div> - <Title level={3} style={{ margin: 0 }}> - {workspace.name} - -
- - ID: {workspace.id} - - - {workspace.managed ? "Managed" : "Unmanaged"} - -
-
- - {/* Right section - Actions */} - -
- Managed: - -
- - - - -
- - + {/* New Workspace Header */} + openDeployModal(workspace, workspaceConfig, environment)} + /> {/* Tabs for Apps, Data Sources, and Queries */} diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx new file mode 100644 index 000000000..5872dd348 --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx @@ -0,0 +1,257 @@ +import React, { useState } from "react"; +import { + Typography, + Switch, + Button, + Tag, + Tooltip, + Row, + Col, + Statistic, + Avatar, + Space, + Divider, + Card, + Dropdown, + Menu +} from "antd"; +import { + CloudUploadOutlined, + SettingOutlined, + TeamOutlined, + AppstoreOutlined, + DatabaseOutlined, + CodeOutlined, + CloudServerOutlined, + ClockCircleOutlined, + MoreOutlined, + StarOutlined, + StarFilled +} from "@ant-design/icons"; +import { Environment } from "../types/environment.types"; +import { Workspace } from "../types/workspace.types"; +import styled from "styled-components"; + +const { Title, Text } = Typography; + +// Styled components for custom design +const HeaderWrapper = styled.div` + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + position: relative; + margin-bottom: 24px; +`; + +const GradientBanner = styled.div` + background: linear-gradient(135deg, #2b5876 0%, #4e4376 100%); + height: 140px; + position: relative; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: repeating-linear-gradient( + 45deg, + rgba(255,255,255,0.1), + rgba(255,255,255,0.1) 1px, + transparent 1px, + transparent 10px + ); + animation: moveBackground 30s linear infinite; + } + + @keyframes moveBackground { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(100px, 100px); + } + } +`; + +const ContentContainer = styled.div` + background-color: white; + padding: 24px; + position: relative; +`; + +const AvatarContainer = styled.div` + position: absolute; + top: -50px; + left: 24px; + background: white; + padding: 4px; + border-radius: 16px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +`; + +const StatusBadge = styled(Tag)<{ $active?: boolean }>` + position: absolute; + top: 12px; + right: 12px; + font-weight: 600; + font-size: 12px; + padding: 4px 12px; + border-radius: 20px; + border: none; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + background: ${props => props.$active ? 'linear-gradient(135deg, #52c41a, #389e0d)' : '#f0f0f0'}; + color: ${props => props.$active ? 'white' : '#666'}; +`; + +const StatCard = styled(Card)` + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + transition: all 0.3s; + + &:hover { + transform: translateY(-3px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); + } +`; + +const ActionButton = styled(Button)` + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + height: 38px; +`; + +const FavoriteButton = styled(Button)` + position: absolute; + top: 12px; + right: 80px; + border: none; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +`; + +interface WorkspaceHeaderProps { + workspace: Workspace; + environment: Environment; + isToggling: boolean; + onToggleManagedStatus: (checked: boolean) => Promise; + onDeploy: () => void; +} + +const WorkspaceHeader: React.FC = ({ + workspace, + environment, + isToggling, + onToggleManagedStatus, + onDeploy +}) => { + + // Generate a consistent color for the workspace avatar + const getAvatarColor = (name: string) => { + let hash = 0; + for (let i = 0; i < name.length; i++) { + hash = name.charCodeAt(i) + ((hash << 5) - hash); + } + const hue = Math.abs(hash % 360); + return `hsl(${hue}, 70%, 50%)`; + }; + + // Format date for last updated + const formatDate = (date: number | undefined) => { + if (!date) return "N/A"; + return new Date(date).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric" + }); + }; + + // Mock data - in a real app this would come from props + const stats = { + users: 12, + apps: 24, + datasources: 8, + queries: 15, + lastUpdated: "2023-08-15T10:30:00Z" + }; + + + + return ( + + + + {workspace.managed ? "Managed" : "Unmanaged"} + + + + + + + + {workspace.name.charAt(0).toUpperCase()} + + + + +
+ + {workspace.name} + + + ID: {workspace.id} + + + created on {formatDate(workspace.creationDate)} + + + {environment.environmentName} + + + + + +
+
+ Managed: + +
+ + } + onClick={onDeploy} + disabled={!workspace.managed} + > + Deploy + + +
+ + + + + + + + ); +}; + +export default WorkspaceHeader; \ No newline at end of file From 009d6fa180cc85752d4009f06c26ad3f8695984b Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 19 May 2025 16:57:17 +0500 Subject: [PATCH 29/37] fix width issue for Workspace detai page --- .../src/pages/setting/environments/WorkspaceDetail.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx index efe528b38..bec8ce7f8 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx @@ -99,7 +99,12 @@ const WorkspaceDetail: React.FC = () => { ]; return ( -
+
{/* Modern Breadcrumbs navigation */} From cec515c050d4ac648e83549951e7ba1c380d9b11 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 19 May 2025 18:54:02 +0500 Subject: [PATCH 30/37] add managed filter --- .../environments/components/AppsTab.tsx | 35 +++++++++--- .../components/DataSourcesTab.tsx | 55 +++++++++++++------ .../environments/components/QueriesTab.tsx | 36 +++++++++--- 3 files changed, 90 insertions(+), 36 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx index f26fd5ea5..f7e14bb71 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col } from 'antd'; -import { SyncOutlined, CloudUploadOutlined, AuditOutlined, AppstoreOutlined, CheckCircleFilled, CloudServerOutlined, DisconnectOutlined } from '@ant-design/icons'; +import { SyncOutlined, CloudUploadOutlined, AuditOutlined, AppstoreOutlined, CheckCircleFilled, CloudServerOutlined, DisconnectOutlined, FilterOutlined } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { App, AppStats } from '../types/app.types'; @@ -31,6 +31,7 @@ const AppsTab: React.FC = ({ environment, workspaceId }) => { const [error, setError] = useState(null); const [searchText, setSearchText] = useState(''); const { openDeployModal } = useDeployModal(); + const [showManagedOnly, setShowManagedOnly] = useState(false); // Fetch apps const fetchApps = async () => { @@ -132,6 +133,10 @@ const AppsTab: React.FC = ({ environment, workspaceId }) => { app.applicationId.toLowerCase().includes(searchText.toLowerCase())) : apps; + const displayedApps = showManagedOnly + ? filteredApps.filter(app => app.managed) + : filteredApps; + // Table columns const columns = [ { @@ -366,8 +371,8 @@ const AppsTab: React.FC = ({ environment, workspaceId }) => { /> ) : ( <> - {/* Search Bar */} -
+ {/* Search and Filter Bar */} +
= ({ environment, workspaceId }) => { style={{ width: 300 }} size="large" /> - {searchText && filteredApps.length !== apps.length && ( -
- Showing {filteredApps.length} of {apps.length} apps -
- )} +
+ {searchText && displayedApps.length !== apps.length && ( +
+ Showing {displayedApps.length} of {apps.length} apps +
+ )} +
= ({ environment, workspaceI const [error, setError] = useState(null); const [searchText, setSearchText] = useState(''); const { openDeployModal } = useDeployModal(); + const [showManagedOnly, setShowManagedOnly] = useState(false); // Fetch data sources const fetchDataSources = async () => { @@ -122,13 +124,17 @@ const DataSourcesTab: React.FC = ({ environment, workspaceI } }; - // Filter data sources based on search + // Filter data sources based on managed status and search const filteredDataSources = searchText ? dataSources.filter(ds => ds.name.toLowerCase().includes(searchText.toLowerCase()) || ds.id.toString().toLowerCase().includes(searchText.toLowerCase())) : dataSources; + const displayedDataSources = showManagedOnly + ? filteredDataSources.filter(ds => ds.managed) + : filteredDataSources; + // Table columns const columns = [ { @@ -356,6 +362,29 @@ const DataSourcesTab: React.FC = ({ environment, workspaceI + {/* Update the search and filter bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + size="large" + /> +
+ {/* Content */} = ({ environment, workspaceI ) : ( <> {/* Search Bar */} -
- setSearchText(value)} - onChange={e => setSearchText(e.target.value)} - style={{ width: 300 }} - size="large" - /> - {searchText && filteredDataSources.length !== dataSources.length && ( -
- Showing {filteredDataSources.length} of {dataSources.length} data sources -
- )} -
+ {searchText && displayedDataSources.length !== dataSources.length && ( +
+ Showing {displayedDataSources.length} of {dataSources.length} data sources +
+ )}
= ({ environment, workspaceId }) => const [error, setError] = useState(null); const [searchText, setSearchText] = useState(''); const { openDeployModal } = useDeployModal(); + const [showManagedOnly, setShowManagedOnly] = useState(false); // Fetch queries const fetchQueries = async () => { @@ -130,6 +132,10 @@ const QueriesTab: React.FC = ({ environment, workspaceId }) => query.id.toLowerCase().includes(searchText.toLowerCase())) : queries; + const displayedQueries = showManagedOnly + ? filteredQueries.filter(query => query.managed) + : filteredQueries; + // Helper function to generate colors from strings const stringToColor = (str: string) => { let hash = 0; @@ -367,8 +373,8 @@ const QueriesTab: React.FC = ({ environment, workspaceId }) => /> ) : ( <> - {/* Search Bar */} -
+ {/* Search and Filter Bar */} +
= ({ environment, workspaceId }) => style={{ width: 300 }} size="large" /> - {searchText && filteredQueries.length !== queries.length && ( -
- Showing {filteredQueries.length} of {queries.length} queries -
- )} +
+ {searchText && displayedQueries.length !== queries.length && ( +
+ Showing {displayedQueries.length} of {queries.length} queries +
+ )} +
Date: Mon, 19 May 2025 19:22:10 +0500 Subject: [PATCH 31/37] update UI for workspace header banner --- .../components/WorkspaceHeader.tsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx index 5872dd348..9cc2bc61f 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx @@ -43,11 +43,12 @@ const HeaderWrapper = styled.div` margin-bottom: 24px; `; -const GradientBanner = styled.div` - background: linear-gradient(135deg, #2b5876 0%, #4e4376 100%); +const GradientBanner = styled.div<{ avatarColor: string }>` + background: linear-gradient(135deg, ${props => props.avatarColor} 0%, #feb47b 100%); height: 140px; position: relative; overflow: hidden; + transition: background 1s ease-in-out; &::before { content: ''; @@ -74,12 +75,22 @@ const GradientBanner = styled.div` transform: translate(100px, 100px); } } + + &:hover { + background: linear-gradient(135deg, #feb47b 0%, ${props => props.avatarColor} 100%); + transition: background 1s ease-in-out; + } `; const ContentContainer = styled.div` background-color: white; padding: 24px; position: relative; + transition: transform 0.3s ease-in-out; + + &:hover { + transform: translateY(-5px); + } `; const AvatarContainer = styled.div` @@ -169,20 +180,13 @@ const WorkspaceHeader: React.FC = ({ }); }; - // Mock data - in a real app this would come from props - const stats = { - users: 12, - apps: 24, - datasources: 8, - queries: 15, - lastUpdated: "2023-08-15T10:30:00Z" - }; + return ( - + {workspace.managed ? "Managed" : "Unmanaged"} From 7a311e6e5801cf82423e887aa9d9d82aae43f373 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 19 May 2025 19:31:44 +0500 Subject: [PATCH 32/37] fix search UI for datasources --- .../components/DataSourcesTab.tsx | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx index fdc1186fa..213869b55 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col } from 'antd'; +import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col, Avatar } from 'antd'; import { SyncOutlined, CloudUploadOutlined, @@ -15,7 +15,7 @@ import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { DataSource } from '../types/datasource.types'; import { getMergedWorkspaceDataSources } from '../services/datasources.service'; -import { Switch, Spin, Empty, Avatar } from 'antd'; +import { Switch, Spin, Empty } from 'antd'; import { ManagedObjectType, setManagedObject, unsetManagedObject } from '../services/managed-objects.service'; import { useDeployModal } from '../context/DeployModalContext'; import { dataSourcesConfig } from '../config/data-sources.config'; @@ -362,29 +362,6 @@ const DataSourcesTab: React.FC = ({ environment, workspaceI - {/* Update the search and filter bar */} -
- setSearchText(value)} - onChange={e => setSearchText(e.target.value)} - style={{ width: 300 }} - size="large" - /> -
- {/* Content */} = ({ environment, workspaceI /> ) : ( <> - {/* Search Bar */} + {/* Search and Filter Bar */} +
+ setSearchText(value)} + onChange={e => setSearchText(e.target.value)} + style={{ width: 300 }} + size="large" + /> +
+ {searchText && displayedDataSources.length !== dataSources.length && (
Showing {displayedDataSources.length} of {dataSources.length} data sources From 66365b3f04edfa26b88b3a786d29d4c01711b1ef Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 19 May 2025 19:43:23 +0500 Subject: [PATCH 33/37] add managed filter for Workspaces Tab --- .../environments/components/WorkspacesTab.tsx | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx index 1e3b6c16e..eaf8b2a17 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col, Avatar } from 'antd'; -import { SyncOutlined, AuditOutlined, TeamOutlined, CheckCircleFilled, CloudServerOutlined, DisconnectOutlined } from '@ant-design/icons'; +import { SyncOutlined, AuditOutlined, TeamOutlined, CheckCircleFilled, CloudServerOutlined, DisconnectOutlined, FilterOutlined } from '@ant-design/icons'; import Title from 'antd/lib/typography/Title'; import { Environment } from '../types/environment.types'; import { Workspace } from '../types/workspace.types'; @@ -26,6 +26,7 @@ const WorkspacesTab: React.FC = ({ environment }) => { const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); const [searchText, setSearchText] = useState(''); + const [showManagedOnly, setShowManagedOnly] = useState(false); // Fetch workspaces const fetchWorkspaces = async () => { @@ -73,13 +74,17 @@ const WorkspacesTab: React.FC = ({ environment }) => { history.push(`/setting/environments/${environment.environmentId}/workspaces/${workspace.id}`); }; - // Filter workspaces based on search + // Filter workspaces based on search and managed status const filteredWorkspaces = searchText ? workspaces.filter(workspace => workspace.name.toLowerCase().includes(searchText.toLowerCase()) || workspace.id.toLowerCase().includes(searchText.toLowerCase())) : workspaces; + const displayedWorkspaces = showManagedOnly + ? filteredWorkspaces.filter(workspace => workspace.managed) + : filteredWorkspaces; + // Helper function to generate colors from strings const stringToColor = (str: string) => { let hash = 0; @@ -301,8 +306,8 @@ const WorkspacesTab: React.FC = ({ environment }) => { /> ) : ( <> - {/* Search Bar */} -
+ {/* Search and Filter Bar */} +
= ({ environment }) => { style={{ width: 300 }} size="large" /> - {searchText && filteredWorkspaces.length !== workspaces.length && ( -
- Showing {filteredWorkspaces.length} of {workspaces.length} workspaces -
- )} +
+ {searchText && displayedWorkspaces.length !== workspaces.length && ( +
+ Showing {displayedWorkspaces.length} of {workspaces.length} workspaces +
+ )} +
Date: Mon, 19 May 2025 19:51:40 +0500 Subject: [PATCH 34/37] change action button positions for TABS --- .../environments/components/AppsTab.tsx | 21 ++++++++++--------- .../components/DataSourcesTab.tsx | 21 ++++++++++--------- .../environments/components/QueriesTab.tsx | 21 ++++++++++--------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx index f7e14bb71..6f9aea130 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/AppsTab.tsx @@ -195,6 +195,17 @@ const AppsTab: React.FC = ({ environment, workspaceId }) => { key: 'actions', render: (_: any, app: App) => ( e.stopPropagation()}> + + + + - - - ), } diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx index 213869b55..ba13fd575 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx @@ -197,6 +197,17 @@ const DataSourcesTab: React.FC = ({ environment, workspaceI key: 'actions', render: (_: any, dataSource: DataSource) => ( e.stopPropagation()}> + + + + - - - ), } diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx index 71632cfec..a42f604c1 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx @@ -215,6 +215,17 @@ const QueriesTab: React.FC = ({ environment, workspaceId }) => key: 'actions', render: (_: any, query: Query) => ( e.stopPropagation()}> + + + + - - - ), } From cde8c0c68595cd35858e6d5db27c535937eb1c82 Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 19 May 2025 20:10:54 +0500 Subject: [PATCH 35/37] update breadcrumbs position --- .../setting/environments/EnvironmentDetail.tsx | 6 ++++-- .../setting/environments/WorkspaceDetail.tsx | 6 +++--- .../components/ModernBreadcrumbs.tsx | 18 ++++++++++++------ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx index ec75728ba..2106d8d3d 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx @@ -148,14 +148,14 @@ const EnvironmentDetail: React.FC = () => { className="environment-detail-container" style={{ padding: "24px", flex: 1 }} > - - {/* Environment Header Component */} + + {/* Basic Environment Information Card - improved responsiveness */} { + {/* Modern Breadcrumbs navigation */} + {/* Tabs for Workspaces and User Groups */} { minWidth: "1000px", overflowX: "auto" }}> - {/* Modern Breadcrumbs navigation */} - - {/* New Workspace Header */} { onDeploy={() => openDeployModal(workspace, workspaceConfig, environment)} /> + {/* Modern Breadcrumbs navigation */} + + {/* Tabs for Apps, Data Sources, and Queries */} Apps} key="apps"> diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/ModernBreadcrumbs.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/ModernBreadcrumbs.tsx index 1e8e61879..1a3d35524 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/ModernBreadcrumbs.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/ModernBreadcrumbs.tsx @@ -19,24 +19,30 @@ interface ModernBreadcrumbsProps extends BreadcrumbProps { const ModernBreadcrumbs: React.FC = ({ items = [], ...props }) => { return (
- + {items.map(item => ( {item.onClick ? ( e.currentTarget.style.textDecoration = 'underline'} + onMouseLeave={(e) => e.currentTarget.style.textDecoration = 'none'} > {item.title} ) : ( - item.title + + {item.title} + )} ))} From c1c874b3a796f742d94a0476e48756f594b032fc Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 19 May 2025 23:12:05 +0500 Subject: [PATCH 36/37] test managed linked object --- .../services/managed-objects.service.ts | 44 ++++++++++++++++++- .../services/workspace.service.ts | 23 +++++++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts index 9f3615c36..fdbcec0e5 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts @@ -68,7 +68,8 @@ export async function isManagedObject( export async function setManagedObject( objGid: string, environmentId: string, - objType: ManagedObjectType + objType: ManagedObjectType, + managedId?: string ): Promise { try { if (!objGid || !environmentId || !objType) { @@ -78,7 +79,8 @@ export async function setManagedObject( const requestBody = { objGid, environmentId, - objType + objType, + ...(managedId && { managedId }) }; const response = await axios.post(`/api/plugins/enterprise/managed-obj`, requestBody); @@ -150,3 +152,41 @@ export async function getManagedObjects( } } +/** + * Get a single managed object by its parameters + * @param objGid - Object's global ID + * @param environmentId - Environment ID + * @param objType - Object type (ORG, APP, QUERY, DATASOURCE) + * @returns Promise with ManagedObject if found + */ +export async function getSingleManagedObject( + objGid: string, + environmentId: string, + objType: ManagedObjectType +): Promise { + try { + if (!objGid || !environmentId || !objType) { + throw new Error("Missing required parameters"); + } + + const response = await axios.get(`/api/plugins/enterprise/managed-obj`, { + params: { + objGid, + environmentId, + objType + } + }); + + return response.data.data || null; + } catch (error) { + // If the object doesn't exist as managed, return null instead of throwing + if (axios.isAxiosError(error) && error.response?.status === 404) { + return null; + } + + const errorMessage = error instanceof Error ? error.message : "Failed to fetch managed object"; + message.error(errorMessage); + throw error; + } +} + diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts index c6f7881dc..3ead76f4a 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts @@ -1,7 +1,7 @@ // services/workspacesService.ts (or wherever makes sense in your structure) import { message } from "antd"; import { getEnvironmentWorkspaces } from "./environments.service"; -import { getManagedObjects, ManagedObject, ManagedObjectType } from "./managed-objects.service"; +import { getManagedObjects, getSingleManagedObject, ManagedObject, ManagedObjectType, setManagedObject } from "./managed-objects.service"; import { Workspace } from "../types/workspace.types"; import { ManagedOrg } from "../types/enterprise.types"; import axios from "axios"; @@ -95,6 +95,27 @@ export async function deployWorkspace(params: { targetEnvId: params.targetEnvId } }); + + // After successful deployment, set the managed object in target environment + if (response.status === 200) { + const res = await getSingleManagedObject( + params.workspaceId, + params.envId, + ManagedObjectType.ORG + ); + // managedID => res.managedId + //objGID = res.objGid + // targetEnvId = params.targetEnvId + // objType = ManagedObjectType.ORG + await setManagedObject( + res?.objGid!, + params.targetEnvId, + ManagedObjectType.ORG, + res?.managedId! + ); + } + + return response.status === 200; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Failed to deploy workspace'; From 23f526dcc5c35581e526da8353ea8b92cda7eb8a Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Mon, 19 May 2025 23:25:52 +0500 Subject: [PATCH 37/37] make a util function to link the managed object --- .../setting/environments/services/apps.service.ts | 10 ++++++++++ .../environments/services/datasources.service.ts | 10 +++++++++- .../services/managed-objects.service.ts | 15 +++++++++++++++ .../environments/services/query.service.ts | 10 +++++++++- .../environments/services/workspace.service.ts | 15 +++------------ 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts index c057b3a52..bfd7ae348 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts @@ -5,6 +5,7 @@ import { getManagedApps } from "./enterprise.service"; import { App, AppStats } from "../types/app.types"; import axios from "axios"; import { getManagedObjects, ManagedObject } from "./managed-objects.service"; +import { ManagedObjectType, transferManagedObject } from "./managed-objects.service"; export interface MergedAppsResult { @@ -122,6 +123,15 @@ export const deployApp = async (params: DeployAppParams): Promise => { } ); + if (response.status === 200) { + await transferManagedObject( + params.applicationId, + params.envId, + params.targetEnvId, + ManagedObjectType.APP + ); + } + return response.status === 200; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Failed to deploy app'; diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts index 0e7b9d6bb..694c28dd1 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import { message } from "antd"; import { DataSource, DataSourceWithMeta } from "../types/datasource.types"; -import { getManagedObjects, ManagedObject, ManagedObjectType } from "./managed-objects.service"; +import { getManagedObjects, ManagedObject, ManagedObjectType , transferManagedObject } from "./managed-objects.service"; export interface DataSourceStats { total: number; @@ -160,6 +160,14 @@ export async function deployDataSource(params: DeployDataSourceParams): Promise< updateDependenciesIfNeeded: params.updateDependenciesIfNeeded ?? false } }); + if (response.status === 200) { + await transferManagedObject( + params.datasourceId, + params.envId, + params.targetEnvId, + ManagedObjectType.DATASOURCE + ); + } return response.status === 200; } catch (error) { console.error('Error deploying data source:', error); diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts index fdbcec0e5..589ea6a5a 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts @@ -190,3 +190,18 @@ export async function getSingleManagedObject( } } + +export async function transferManagedObject(objGid: string, sourceEnvId: string, targetEnvId: string, objType: ManagedObjectType): Promise { + try { + const managedObject = await getSingleManagedObject(objGid, sourceEnvId, objType); + if (managedObject) { + await setManagedObject(managedObject.objGid, targetEnvId, objType, managedObject.managedId); + } else { + throw new Error(`Managed object not found for objGid: ${objGid}`); + } + } catch (error) { + console.error('Error transferring managed object:', error); + throw error; + } +} + diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts index 8d69af92a..6801097be 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts @@ -2,7 +2,7 @@ * Get merged queries (both regular and managed) for a workspace */ import axios from 'axios'; -import { getManagedObjects, ManagedObject, ManagedObjectType } from './managed-objects.service'; +import { getManagedObjects, ManagedObjectType, transferManagedObject } from './managed-objects.service'; import { getWorkspaceQueries } from './environments.service'; import { Query, QueryStats } from '../types/query.types'; export interface MergedQueriesResult { @@ -82,6 +82,14 @@ export interface MergedQueriesResult { updateDependenciesIfNeeded: params.updateDependenciesIfNeeded ?? false } }); + if (response.status === 200) { + await transferManagedObject( + params.queryId, + params.envId, + params.targetEnvId, + ManagedObjectType.QUERY + ); + } return response.status === 200; } catch (error) { console.error('Error deploying query:', error); diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts index 3ead76f4a..4f7978f3b 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts @@ -1,7 +1,7 @@ // services/workspacesService.ts (or wherever makes sense in your structure) import { message } from "antd"; import { getEnvironmentWorkspaces } from "./environments.service"; -import { getManagedObjects, getSingleManagedObject, ManagedObject, ManagedObjectType, setManagedObject } from "./managed-objects.service"; +import { getManagedObjects, ManagedObject, ManagedObjectType, transferManagedObject } from "./managed-objects.service"; import { Workspace } from "../types/workspace.types"; import { ManagedOrg } from "../types/enterprise.types"; import axios from "axios"; @@ -98,20 +98,11 @@ export async function deployWorkspace(params: { // After successful deployment, set the managed object in target environment if (response.status === 200) { - const res = await getSingleManagedObject( + await transferManagedObject( params.workspaceId, params.envId, - ManagedObjectType.ORG - ); - // managedID => res.managedId - //objGID = res.objGid - // targetEnvId = params.targetEnvId - // objType = ManagedObjectType.ORG - await setManagedObject( - res?.objGid!, params.targetEnvId, - ManagedObjectType.ORG, - res?.managedId! + ManagedObjectType.ORG ); }