diff --git a/client/packages/lowcoder/src/constants/routesURL.ts b/client/packages/lowcoder/src/constants/routesURL.ts
index 6bec5c190a..a675ec6495 100644
--- a/client/packages/lowcoder/src/constants/routesURL.ts
+++ b/client/packages/lowcoder/src/constants/routesURL.ts
@@ -24,6 +24,10 @@ export const AUDIT_LOG_DETAIL = "/setting/audit/:eventId/detail";
 export const APP_USAGE_DASHBOARD = "/setting/app-usage";
 export const APP_USAGE_DETAIL = "/setting/app-usage/:eventId/detail";
 
+export const ENVIRONMENT_SETTING = "/setting/environments";
+export const ENVIRONMENT_DETAIL = `${ENVIRONMENT_SETTING}/:environmentId`;
+export const ENVIRONMENT_WORKSPACE_DETAIL = `${ENVIRONMENT_DETAIL}/workspaces/:workspaceId`;
+
 export const OAUTH_PROVIDER_SETTING = "/setting/oauth-provider";
 export const OAUTH_PROVIDER_DETAIL = "/setting/oauth-provider/detail";
 
@@ -120,3 +124,7 @@ export const buildSubscriptionSettingsLink = (subscriptionId: string, productId
 export const buildSubscriptionInfoLink = (productId: string) => `${SUBSCRIPTION_SETTING}/info/${productId}`;
 
 export const buildSupportTicketLink = (ticketId: string) => `${SUPPORT_URL}/details/${ticketId}`;
+
+export const buildEnvironmentId = (environmentId: string) => `${ENVIRONMENT_SETTING}/${environmentId}`;
+export const buildEnvironmentWorkspaceId = (environmentId: string, workspaceId: string) => 
+  `${ENVIRONMENT_SETTING}/${environmentId}/workspaces/${workspaceId}`;
diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx
new file mode 100644
index 0000000000..d16f52c242
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx
@@ -0,0 +1,249 @@
+import React, {useState} from "react";
+import { useParams } from "react-router-dom";
+import {
+  Spin,
+  Typography,
+  Card,
+  Tag,
+  Tabs,
+  Alert,
+  Descriptions,
+  Dropdown,
+  Menu,
+  Button,
+  Breadcrumb,
+} from "antd";
+import {
+  ReloadOutlined,
+  LinkOutlined,
+  ClusterOutlined,
+  TeamOutlined,
+  UserOutlined,
+  SyncOutlined,
+  EditOutlined,
+  EllipsisOutlined,
+  MoreOutlined,
+  HomeOutlined
+} from "@ant-design/icons";
+
+import { useEnvironmentContext } from "./context/EnvironmentContext";
+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";
+
+const { Title, Text } = Typography;
+const { TabPane } = Tabs;
+
+
+/**
+ * Environment Detail Page Component
+ * Shows detailed information about a specific environment
+ */
+const EnvironmentDetail: React.FC = () => {
+  // Get environment ID from URL params
+  const {
+    environment,
+    isLoadingEnvironment,
+    error,
+    updateEnvironmentData
+  } = useEnvironmentContext();  
+  
+  
+  
+  const [isEditModalVisible, setIsEditModalVisible] = useState(false);
+  const [isUpdating, setIsUpdating] = useState(false);
+  
+  // Handle edit menu item click
+  const handleEditClick = () => {
+    setIsEditModalVisible(true);
+  };
+  
+  // Handle modal close
+  const handleCloseModal = () => {
+    setIsEditModalVisible(false);
+  };
+  
+  // Handle save environment
+  const handleSaveEnvironment = async (environmentId: string, data: Partial<Environment>) => {
+    setIsUpdating(true);
+    try {
+      await updateEnvironmentData(environmentId, data);
+      handleCloseModal();
+    } catch (error) {
+      console.error('Failed to update environment:', error);
+    } finally {
+      setIsUpdating(false);
+    }
+  };
+  
+  // Dropdown menu for environment actions
+  const actionsMenu = (
+    <Menu>
+      <Menu.Item key="edit" icon={<EditOutlined />} onClick={handleEditClick}>
+        Edit Environment
+      </Menu.Item>
+      {/* Add more menu items here if needed */}
+    </Menu>
+  );
+  debugger
+  
+  if (isLoadingEnvironment) {
+    return (
+      <div style={{ display: 'flex', justifyContent: 'center', padding: '50px' }}>
+        <Spin size="large" tip="Loading environment..." />
+      </div>
+    );
+  }
+
+  if (error || !environment) {
+    return (
+      <Alert
+        message="Error loading environment"
+        description={error || "Environment not found"}
+        type="error"
+        showIcon
+      />
+    );
+  }
+  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>
+
+      {/* Header with environment name and controls */}
+      {/* 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"}
+          </Title>
+          <Text type="secondary">ID: {environment.environmentId}</Text>
+        </div>
+        <div style={{ flexShrink: 0 }}>
+          <Button
+            icon={<EditOutlined />}
+            onClick={handleEditClick}
+            type="primary"
+          >
+            Edit Environment
+          </Button>
+        </div>
+      </div>
+
+      {/* Basic Environment Information Card - improved responsiveness */}
+      <Card
+        title="Environment Overview"
+        style={{ marginBottom: "24px" }}
+        extra={environment.isMaster && <Tag color="green">Master</Tag>}
+      >
+        <Descriptions
+          bordered
+          layout="vertical" // Change to vertical layout on smaller screens
+          column={{ xxl: 4, xl: 3, lg: 3, md: 2, sm: 1, xs: 1 }}
+          size="small" // Use smaller size on mobile
+        >
+          <Descriptions.Item label="Domain">
+            {environment.environmentFrontendUrl ? (
+              <a
+                href={environment.environmentFrontendUrl}
+                target="_blank"
+                rel="noopener noreferrer"
+              >
+                {environment.environmentFrontendUrl} <LinkOutlined />
+              </a>
+            ) : (
+              "No domain set"
+            )}
+          </Descriptions.Item>
+          <Descriptions.Item label="Environment Type">
+            <Tag
+              color={
+                environment.environmentType === "production"
+                  ? "red"
+                  : environment.environmentType === "testing"
+                    ? "orange"
+                    : "blue"
+              }
+            >
+              {environment.environmentType}
+            </Tag>
+          </Descriptions.Item>
+          <Descriptions.Item label="API Key Status">
+            {environment.environmentApikey ? (
+              <Tag color="green">Configured</Tag>
+            ) : (
+              <Tag color="red">Not Configured</Tag>
+            )}
+          </Descriptions.Item>
+          <Descriptions.Item label="Master Environment">
+            {environment.isMaster ? "Yes" : "No"}
+          </Descriptions.Item>
+        </Descriptions>
+      </Card>
+
+      {/* Tabs for Workspaces and User Groups */}
+      <Tabs defaultActiveKey="workspaces">
+        <TabPane tab="Workspaces" key="workspaces">
+          {/* Using our new generic component with the workspace config */}
+          <DeployableItemsTab
+            environment={environment}
+            config={workspaceConfig}
+            title="Workspaces in this Environment"
+          />
+        </TabPane>
+        <TabPane
+          tab={
+            <span>
+              <TeamOutlined /> User Groups
+            </span>
+          }
+          key="userGroups"
+        >
+          {/* Using our new generic component with the user group config */}
+          <DeployableItemsTab
+            environment={environment}
+            config={userGroupsConfig}
+            title="User Groups in this Environment"
+          />
+        </TabPane>
+      </Tabs>
+      {/* Edit Environment Modal */}
+      {environment && (
+        <EditEnvironmentModal
+          visible={isEditModalVisible}
+          environment={environment}
+          onClose={handleCloseModal}
+          onSave={handleSaveEnvironment}
+          loading={isUpdating}
+        />
+      )}
+    </div>
+  );
+};
+
+export default EnvironmentDetail;
diff --git a/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx b/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
index 61a73fe24d..a1fb13e020 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/Environments.tsx
@@ -1,3 +1,34 @@
-export function Environments() {
-  return <></>;
-}
+import React from "react";
+import { Switch, Route } from "react-router-dom";
+import { EnvironmentProvider } from "./context/EnvironmentContext";
+import EnvironmentsList from "./EnvironmentsList";
+import EnvironmentScopedRoutes from "./components/EnvironmentScopedRoutes";
+
+import {
+  ENVIRONMENT_SETTING,
+  ENVIRONMENT_DETAIL
+} from "@lowcoder-ee/constants/routesURL";
+
+/**
+ * Top-level Environments component that wraps all environment-related routes
+ * with the EnvironmentProvider for shared state management
+ */
+const Environments: React.FC = () => {
+  return (
+    <EnvironmentProvider>
+      <Switch>
+        {/* Route that shows the list of environments */}
+        <Route exact path={ENVIRONMENT_SETTING}>
+          <EnvironmentsList />
+        </Route>
+
+        {/* All other routes under /environments/:envId */}
+        <Route path={ENVIRONMENT_DETAIL}>
+          <EnvironmentScopedRoutes />
+        </Route>
+      </Switch>
+    </EnvironmentProvider>
+  );
+};
+
+export default Environments;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx
new file mode 100644
index 0000000000..09517016d8
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx
@@ -0,0 +1,103 @@
+import React, { useState } from "react";
+import { Typography, Alert, Input, Button, Space, Empty } from "antd";
+import { SearchOutlined, ReloadOutlined } 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";
+
+const { Title } = Typography;
+
+/**
+ * Environment Listing Page Component
+ * Displays a table of environments
+ */
+const EnvironmentsList: React.FC = () => {
+  // Use the shared context instead of a local hook
+  const { 
+    environments, 
+    isLoadingEnvironments, 
+    error, 
+  } = useEnvironmentContext();
+
+  console.log("Environments:", environments);
+
+  // State for search input
+  const [searchText, setSearchText] = useState("");
+
+  // Hook for navigation
+  const history = useHistory();
+
+  // Filter environments based on search text
+  const filteredEnvironments = environments.filter((env) => {
+    const searchLower = searchText.toLowerCase();
+    return (
+      (env.environmentName || "").toLowerCase().includes(searchLower) ||
+      (env.environmentFrontendUrl || "").toLowerCase().includes(searchLower) ||
+      env.environmentId.toLowerCase().includes(searchLower) ||
+      env.environmentType.toLowerCase().includes(searchLower)
+    );
+  });
+
+  // Handle row click to navigate to environment detail
+  const handleRowClick = (record: Environment) => {
+    history.push(buildEnvironmentId(record.environmentId));
+  };
+
+  return (
+    <div className="environments-container" style={{ padding: "24px", flex:1 }}>
+      {/* Header section with title and controls */}
+      <div
+        className="environments-header"
+        style={{
+          marginBottom: "24px",
+          display: "flex",
+          justifyContent: "space-between",
+          alignItems: "center",
+        }}
+      >
+        <Title level={3}>Environments</Title>
+        <Space>
+          <Input
+            placeholder="Search environments"
+            value={searchText}
+            onChange={(e) => setSearchText(e.target.value)}
+            style={{ width: 250 }}
+            prefix={<SearchOutlined />}
+            allowClear
+          />
+        </Space>
+      </div>
+
+      {/* Error handling */}
+      {error && (
+        <Alert
+          message="Error loading environments"
+          description={error}
+          type="error"
+          showIcon
+          style={{ marginBottom: "24px" }}
+        />
+      )}
+
+      {/* Empty state handling */}
+      {!isLoadingEnvironments && environments.length === 0 && !error ? (
+        <Empty
+          description="No environments found"
+          image={Empty.PRESENTED_IMAGE_SIMPLE}
+        />
+      ) : (
+        /* Table component */
+        <EnvironmentsTable
+          environments={filteredEnvironments}
+          loading={isLoadingEnvironments}
+          onRowClick={handleRowClick}
+        />
+      )}
+    </div>
+  );
+};
+
+export default EnvironmentsList;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx
new file mode 100644
index 0000000000..2867171b0a
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx
@@ -0,0 +1,261 @@
+import React, { useEffect, useState } from "react";
+import { useParams, useHistory } from "react-router-dom";
+import history from "@lowcoder-ee/util/history";
+import { 
+  Spin, 
+  Typography, 
+  Card, 
+  Row, 
+  Col, 
+  Tabs, 
+  Alert, 
+  Button,
+  Breadcrumb,
+  Space,
+  Tag,
+  Switch, 
+  message,
+  Tooltip
+} from "antd";
+import { 
+  AppstoreOutlined, 
+  DatabaseOutlined, 
+  CodeOutlined,
+  HomeOutlined,
+  TeamOutlined,
+  ArrowLeftOutlined, 
+  CloudUploadOutlined
+} from "@ant-design/icons";
+import { useEnvironmentContext } from "./context/EnvironmentContext";
+import DeployableItemsTab from "./components/DeployableItemsTab";
+import { appsConfig } from "./config/apps.config";
+import { dataSourcesConfig } from "./config/data-sources.config";
+import { queryConfig } from "./config/query.config";
+import { useDeployableItems } from "./hooks/useDeployableItems";
+import { workspaceConfig } from "./config/workspace.config";
+import { useDeployModal } from "./context/DeployModalContext";
+
+const { Title, Text } = Typography;
+const { TabPane } = Tabs;
+
+
+
+const WorkspaceDetail: React.FC = () => {
+
+    // Get parameters from URL
+    const { environmentId,workspaceId } = useParams<{ 
+      workspaceId: string; 
+      environmentId: string;
+    }>();
+    const {
+      environment,
+      isLoadingEnvironment: envLoading,
+      error: envError,
+    } = useEnvironmentContext();
+
+    const {openDeployModal} = useDeployModal();
+
+     // Use our generic hook with the workspace config
+      const {
+        items: workspaces,
+        stats: workspaceStats,
+        loading: workspaceLoading,
+        error : workspaceError,
+        toggleManagedStatus,
+        refreshItems
+      } = useDeployableItems(
+        workspaceConfig,
+        environment,
+        { workspaceId } // Additional params if needed
+      );
+      
+        // Find the current workspace in the items array
+  const workspace = workspaces.find(w => w.id === workspaceId);
+
+  const handleToggleManaged = async (checked: boolean) => {
+    if (!workspace) return;
+    
+    const success = await toggleManagedStatus(workspace, checked);
+    if (success) {
+      message.success(`Workspace is now ${checked ? 'Managed' : 'Unmanaged'}`);
+    } else {
+      message.error('Failed to change managed status');
+    }
+  };
+
+    if (envLoading || workspaceLoading ) {
+      return (
+        <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%', padding: '50px' }}>
+          <Spin size="large" tip="Loading workspace details..." />
+        </div>
+      );
+    }
+
+    if (!environment || !workspace) {
+      return (
+        <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%', padding: '50px' }}>
+          <Typography.Title level={3}>Workspace not found</Typography.Title>
+        </div>  
+      ) 
+    }
+
+  
+  return (
+    <div
+      className="workspace-detail-container"
+      style={{ padding: "24px", flex: 1 }}
+    >
+      {/* Breadcrumb navigation */}
+      <Breadcrumb style={{ marginBottom: "16px" }}>
+        <Breadcrumb.Item>
+          <span
+            style={{ cursor: "pointer" }}
+            onClick={() => history.push("/setting/environments")}
+          >
+            <HomeOutlined /> Environments
+          </span>
+        </Breadcrumb.Item>
+        <Breadcrumb.Item>
+          <span
+            style={{ cursor: "pointer" }}
+            onClick={() =>
+              history.push(`/setting/environments/${environmentId}`)
+            }
+          >
+            <TeamOutlined /> {environment.environmentName}
+          </span>
+        </Breadcrumb.Item>
+        <Breadcrumb.Item>{workspace.name}</Breadcrumb.Item>
+      </Breadcrumb>
+
+      {/* 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}
+            </Title>
+            <div
+              style={{
+                display: "flex",
+                alignItems: "center",
+                marginTop: "8px",
+              }}
+            >
+              <Text type="secondary" style={{ marginRight: "16px" }}>
+                ID: {workspace.id}
+              </Text>
+              <Tag color={workspace.managed ? "green" : "default"}>
+                {workspace.managed ? "Managed" : "Unmanaged"}
+              </Tag>
+            </div>
+          </div>
+
+          {/* Right section - Actions */}
+          <Space size="middle">
+            <div style={{ display: "flex", alignItems: "center" }}>
+              <Text style={{ marginRight: "8px" }}>Managed:</Text>
+              <Switch
+                checked={workspace.managed}
+                onChange={handleToggleManaged}
+                checkedChildren="Yes"
+                unCheckedChildren="No"
+              />
+            </div>
+            <Tooltip
+              title={
+                !workspace.managed
+                  ? "Workspace must be managed before it can be deployed"
+                  : "Deploy this workspace to another environment"
+              }
+            >
+              <Button
+                type="primary"
+                icon={<CloudUploadOutlined />}
+                onClick={() =>
+                  openDeployModal(workspace, workspaceConfig, environment)
+                }
+                disabled={!workspace.managed}
+              >
+                Deploy
+              </Button>
+            </Tooltip>
+            <Button
+              icon={<ArrowLeftOutlined />}
+              onClick={() =>
+                history.push(`/setting/environments/${environmentId}`)
+              }
+            >
+              Back
+            </Button>
+          </Space>
+        </div>
+      </Card>
+
+      {/* Tabs for Apps, Data Sources, and Queries */}
+      <Tabs defaultActiveKey="apps">
+        <TabPane
+          tab={
+            <span>
+              <AppstoreOutlined /> Apps
+            </span>
+          }
+          key="apps"
+        >
+          <DeployableItemsTab
+            environment={environment}
+            config={appsConfig}
+            additionalParams={{ workspaceId }}
+            title="Apps in this Workspace"
+          />
+        </TabPane>
+
+        {/* Update the TabPane in WorkspaceDetail.tsx */}
+        <TabPane
+          tab={
+            <span>
+              <DatabaseOutlined /> Data Sources
+            </span>
+          }
+          key="dataSources"
+        >
+          <DeployableItemsTab
+            environment={environment}
+            config={dataSourcesConfig}
+            additionalParams={{ workspaceId }}
+            title="Data Sources in this Workspace"
+          />
+        </TabPane>
+
+        <TabPane
+          tab={
+            <span>
+              <CodeOutlined /> Queries
+            </span>
+          }
+          key="queries"
+        >
+          <DeployableItemsTab
+            environment={environment}
+            config={queryConfig}
+            additionalParams={{ workspaceId }}
+            title="Queries in this Workspace"
+          />
+        </TabPane>
+      </Tabs>
+    </div>
+  );
+  }
+
+
+export default WorkspaceDetail
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx
new file mode 100644
index 0000000000..3ff4f284de
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx
@@ -0,0 +1,165 @@
+// components/DeployItemModal.tsx
+import React, { useState, useEffect } from 'react';
+import { Modal, Form, Select, Checkbox, Button, message, Spin, Input } from 'antd';
+import { Environment } from '../types/environment.types';
+import { DeployableItem, BaseStats, DeployableItemConfig } from '../types/deployable-item.types';
+import { useEnvironmentContext } from '../context/EnvironmentContext';
+
+interface DeployItemModalProps<T extends DeployableItem, S extends BaseStats> {
+  visible: boolean;
+  item: T | null;
+  sourceEnvironment: Environment;
+  config: DeployableItemConfig<T, S>;
+  onClose: () => void;
+  onSuccess?: () => void;
+}
+
+function DeployItemModal<T extends DeployableItem, S extends BaseStats>({
+  visible,
+  item,
+  sourceEnvironment,
+  config,
+  onClose,
+  onSuccess
+}: DeployItemModalProps<T, S>) {
+  const [form] = Form.useForm();
+  const { environments, isLoadingEnvironments } = useEnvironmentContext();
+  const [deploying, setDeploying] = useState(false);
+  
+  useEffect(() => {
+    if (visible) {
+      form.resetFields();
+    }
+  }, [visible, form]);
+  
+  // Filter out source environment from target list
+  const targetEnvironments = environments.filter(
+    env => env.environmentId !== sourceEnvironment.environmentId
+  );
+  
+  const handleDeploy = async () => {
+    if (!config.deploy?.enabled || !item) return;
+    
+    try {
+      const values = await form.validateFields();
+      const targetEnv = environments.find(env => env.environmentId === values.targetEnvId);
+      
+      if (!targetEnv) {
+        message.error('Target environment not found');
+        return;
+      }
+      
+      setDeploying(true);
+      
+      // Prepare parameters based on item type
+      const params = config.deploy.prepareParams(item, values, sourceEnvironment, targetEnv);
+      
+      // Execute deployment
+      await config.deploy.execute(params);
+      
+      message.success(`Successfully deployed ${item.name} to target environment`);
+      if (onSuccess) onSuccess();
+      onClose();
+    } catch (error) {
+      console.error('Deployment error:', error);
+      message.error(`Failed to deploy ${config.singularLabel.toLowerCase()}`);
+    } finally {
+      setDeploying(false);
+    }
+  };
+  
+  return (
+    <Modal
+      title={`Deploy ${config.singularLabel}: ${item?.name || ''}`}
+      open={visible}
+      onCancel={onClose}
+      footer={null}
+      destroyOnClose
+    >
+      {isLoadingEnvironments ? (
+        <div style={{ textAlign: 'center', padding: '20px' }}>
+          <Spin tip="Loading environments..." />
+        </div>
+      ) : (
+        <Form
+          form={form}
+          layout="vertical"
+        >
+          <Form.Item
+            name="targetEnvId"
+            label="Target Environment"
+            rules={[{ required: true, message: 'Please select a target environment' }]}
+          >
+            <Select placeholder="Select target environment">
+              {targetEnvironments.map((env) => (
+                <Select.Option key={env.environmentId} value={env.environmentId}>
+                  {env.environmentName}
+                </Select.Option>
+              ))}
+            </Select>
+          </Form.Item>
+          
+          {/* Render dynamic fields based on config */}
+          {config.deploy?.fields.map(field => {
+            switch (field.type) {
+              case 'checkbox':
+                return (
+                  <Form.Item
+                    key={field.name}
+                    name={field.name}
+                    valuePropName="checked"
+                    initialValue={field.defaultValue}
+                  >
+                    <Checkbox>{field.label}</Checkbox>
+                  </Form.Item>
+                );
+              case 'select':
+                return (
+                  <Form.Item
+                    key={field.name}
+                    name={field.name}
+                    label={field.label}
+                    initialValue={field.defaultValue}
+                    rules={field.required ? [{ required: true, message: `Please select ${field.label}` }] : undefined}
+                  >
+                    <Select placeholder={`Select ${field.label}`}>
+                      {field.options?.map(option => (
+                        <Select.Option key={option.value} value={option.value}>
+                          {option.label}
+                        </Select.Option>
+                      ))}
+                    </Select>
+                  </Form.Item>
+                );
+              case 'input':
+                return (
+                  <Form.Item
+                    key={field.name}
+                    name={field.name}
+                    label={field.label}
+                    initialValue={field.defaultValue}
+                    rules={field.required ? [{ required: true, message: `Please input ${field.label}` }] : undefined}
+                  >
+                    <Input placeholder={`Enter ${field.label}`} />
+                  </Form.Item>
+                );
+              default:
+                return null;
+            }
+          })}
+          
+          <Form.Item>
+            <Button type="default" onClick={onClose} style={{ marginRight: 8 }}>
+              Cancel
+            </Button>
+            <Button type="primary" onClick={handleDeploy} loading={deploying}>
+              Deploy
+            </Button>
+          </Form.Item>
+        </Form>
+      )}
+    </Modal>
+  );
+}
+
+export default DeployItemModal;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsList.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsList.tsx
new file mode 100644
index 0000000000..63f8dda721
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsList.tsx
@@ -0,0 +1,94 @@
+// 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 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';
+
+interface DeployableItemsListProps<T extends DeployableItem, S extends BaseStats> {
+  items: T[];
+  loading: boolean;
+  refreshing: boolean;
+  error?: string | null;
+  environment: Environment;
+  config: DeployableItemConfig<T, S>;
+  onToggleManaged?: (item: T, checked: boolean) => Promise<boolean>;
+  additionalParams?: Record<string, any>;
+}
+
+function DeployableItemsList<T extends DeployableItem, S extends BaseStats>({
+  items,
+  loading,
+  refreshing,
+  error,
+  environment,
+  config,
+  onToggleManaged,
+  additionalParams = {}
+}: DeployableItemsListProps<T, S>) {
+
+  const { openDeployModal } = useDeployModal();
+
+  // 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 (
+      <div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
+        <Spin tip={`Loading ${config.pluralLabel.toLowerCase()}...`} />
+      </div>
+    );
+  }
+
+  if (!items || items.length === 0 || error) {
+    return (
+      <Empty
+        description={error || `No ${config.pluralLabel.toLowerCase()} found`}
+        image={Empty.PRESENTED_IMAGE_SIMPLE}
+      />
+    );
+  }
+
+  const hasNavigation = config.buildDetailRoute({}) !== '#';
+
+  return (
+    <Table
+      columns={columns}
+      dataSource={items}
+      rowKey={config.idField}
+      pagination={{ pageSize: 10 }}
+      size="middle"
+      scroll={{ x: 'max-content' }} 
+      onRow={(record) => ({
+        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
new file mode 100644
index 0000000000..4e50a873c7
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/DeployableItemsTab.tsx
@@ -0,0 +1,126 @@
+// 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<T extends DeployableItem, S extends BaseStats> {
+  environment: Environment;
+  config: DeployableItemConfig<T, S>;
+  additionalParams?: Record<string, any>;
+  title?: string;
+}
+
+function DeployableItemsTab<T extends DeployableItem, S extends BaseStats>({
+  environment,
+  config,
+  additionalParams = {},
+  title
+}: DeployableItemsTabProps<T, S>) {
+  // Use our generic hook with the provided config
+  const {
+    items,
+    stats,
+    loading,
+    error,
+    refreshing,
+    toggleManagedStatus,
+    refreshItems
+  } = useDeployableItems<T, S>(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 (
+    <Card>
+      {/* Header with refresh button */}
+      <div
+        style={{
+          display: "flex",
+          justifyContent: "space-between",
+          alignItems: "center",
+          marginBottom: "16px",
+        }}
+      >
+        <Title level={5}>
+          {title || `${config.pluralLabel} in this Environment`}
+        </Title>
+        <Button 
+          icon={<SyncOutlined spin={refreshing} />} 
+          onClick={handleRefresh}
+          loading={loading}
+        >
+          Refresh
+        </Button>
+      </div>
+
+      {/* Render stats using the config's renderStats function */}
+      {config.renderStats(stats)}
+
+      <Divider style={{ margin: "16px 0" }} />
+
+      {/* Show error if loading failed */}
+      {error && (
+        <Alert
+          message={`Error loading ${config.pluralLabel.toLowerCase()}`}
+          description={error}
+          type="error"
+          showIcon
+          style={{ marginBottom: "16px" }}
+        />
+      )}
+
+      {/* Configuration warnings based on required props */}
+      {missingProps.length > 0 && !error && (
+        <Alert
+          message="Configuration Issue"
+          description={
+            `Missing required configuration: ${missingProps.join(', ')}`
+          }
+          type="warning"
+          showIcon
+          style={{ marginBottom: "16px" }}
+        />
+      )}
+
+      {/* Items List */}
+      <DeployableItemsList
+        items={items}
+        loading={loading && !error}
+        refreshing={refreshing}
+        error={error}
+        environment={environment}
+        config={config}
+        onToggleManaged={config.enableManaged ? handleToggleManaged : undefined}
+        additionalParams={additionalParams}
+      />
+    </Card>
+  );
+}
+
+export default DeployableItemsTab;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx
new file mode 100644
index 0000000000..5c09cc42b5
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx
@@ -0,0 +1,159 @@
+import React, { useState, useEffect } from 'react';
+import { Modal, Form, Input, Select, Switch, Button, message } from 'antd';
+import { Environment } from '../types/environment.types';
+
+const { Option } = Select;
+
+interface EditEnvironmentModalProps {
+  visible: boolean;
+  environment: Environment | null;
+  onClose: () => void;
+  onSave: (environmentId: string, data: Partial<Environment>) => Promise<void>;
+  loading?: boolean;
+}
+
+const EditEnvironmentModal: React.FC<EditEnvironmentModalProps> = ({
+  visible,
+  environment,
+  onClose,
+  onSave,
+  loading = false
+}) => {
+  const [form] = Form.useForm();
+  const [submitLoading, setSubmitLoading] = useState(false);
+
+  // Initialize form with environment data when it changes
+  useEffect(() => {
+    if (environment) {
+      form.setFieldsValue({
+        environmentName: environment.environmentName || '',
+        environmentDescription: environment.environmentDescription || '',
+        environmentType: environment.environmentType,
+        environmentApiServiceUrl: environment.environmentApiServiceUrl || '',
+        environmentFrontendUrl: environment.environmentFrontendUrl || '',
+        environmentNodeServiceUrl: environment.environmentNodeServiceUrl || '',
+        environmentApikey: environment.environmentApikey || '',
+        isMaster: environment.isMaster
+      });
+    }
+  }, [environment, form]);
+
+  const handleSubmit = async () => {
+    if (!environment) return;
+    
+    try {
+      const values = await form.validateFields();
+      setSubmitLoading(true);
+      
+      await onSave(environment.environmentId, values);
+      onClose();
+    } catch (error) {
+      if (error instanceof Error) {
+        console.error("Form validation or submission error:", error);
+      }
+    } finally {
+      setSubmitLoading(false);
+    }
+  };
+
+  return (
+    <Modal
+      title="Edit Environment"
+      open={visible}
+      onCancel={onClose}
+      maskClosable={false}
+      destroyOnClose={true}
+      footer={[
+        <Button key="back" onClick={onClose}>
+          Cancel
+        </Button>,
+        <Button 
+          key="submit" 
+          type="primary" 
+          loading={loading || submitLoading} 
+          onClick={handleSubmit}
+        >
+          Save Changes
+        </Button>
+      ]}
+    >
+      <Form
+        form={form}
+        layout="vertical"
+        name="edit_environment_form"
+      >
+        <Form.Item
+          name="environmentName"
+          label="Environment Name"
+          rules={[{ required: true, message: 'Please enter a name' }]}
+        >
+          <Input placeholder="Enter environment name" />
+        </Form.Item>
+
+        <Form.Item
+          name="environmentDescription"
+          label="Description"
+        >
+          <Input.TextArea 
+            placeholder="Enter description" 
+            rows={3}
+          />
+        </Form.Item>
+
+        <Form.Item
+          name="environmentType"
+          label="Stage"
+          rules={[{ required: true, message: 'Please select a stage' }]}
+        >
+          <Select placeholder="Select stage">
+            <Option value="DEV">Development (DEV)</Option>
+            <Option value="TEST">Testing (TEST)</Option>
+            <Option value="PREPROD">Pre-Production (PREPROD)</Option>
+            <Option value="PROD">Production (PROD)</Option>
+          </Select>
+        </Form.Item>
+
+        <Form.Item
+          name="environmentFrontendUrl"
+          label="Frontend URL"
+        >
+          <Input placeholder="https://example.com" />
+        </Form.Item>
+
+        <Form.Item
+          name="environmentApiServiceUrl"
+          label="API Service URL"
+        >
+          <Input placeholder="https://api.example.com" />
+        </Form.Item>
+
+        <Form.Item
+          name="environmentNodeServiceUrl"
+          label="Node Service URL"
+        >
+          <Input placeholder="https://node.example.com" />
+        </Form.Item>
+
+        <Form.Item
+          name="environmentApikey"
+          label="API Key"
+        >
+          <Input.TextArea 
+            placeholder="Enter API key" 
+            rows={2}
+          />
+        </Form.Item>
+
+        <Form.Item
+          name="isMaster"
+          label="Master Environment"
+          valuePropName="checked"
+        >
+          <Switch />
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+};
+
+export default EditEnvironmentModal;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentScopedRoutes.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentScopedRoutes.tsx
new file mode 100644
index 0000000000..e8a04d103b
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentScopedRoutes.tsx
@@ -0,0 +1,43 @@
+import React, { useEffect } from "react";
+import { Switch, Route, useParams } from "react-router-dom";
+import { useEnvironmentContext } from "../context/EnvironmentContext";
+import EnvironmentDetail from "../EnvironmentDetail";
+import WorkspaceDetail from "../WorkspaceDetail";
+import { DeployModalProvider } from "../context/DeployModalContext";
+
+import {
+  ENVIRONMENT_DETAIL,
+  ENVIRONMENT_WORKSPACE_DETAIL,
+} from "@lowcoder-ee/constants/routesURL";
+
+/**
+ * Component for routes scoped to a specific environment
+ * Uses the environment ID from the URL parameters to fetch the specific environment
+ */
+const EnvironmentScopedRoutes: React.FC = () => {
+  const { environmentId } = useParams<{ environmentId: string }>();
+  const { refreshEnvironment } = useEnvironmentContext();
+  
+  // When the environmentId changes, fetch the specific environment
+  useEffect(() => {
+    if (environmentId) {
+      refreshEnvironment(environmentId);
+    }
+  }, [environmentId, refreshEnvironment]);
+
+  return (
+    <DeployModalProvider>
+      <Switch>
+        <Route exact path={ENVIRONMENT_DETAIL}>
+          <EnvironmentDetail />
+        </Route>
+
+        <Route exact path={ENVIRONMENT_WORKSPACE_DETAIL}>
+          <WorkspaceDetail />
+        </Route>
+      </Switch>
+    </DeployModalProvider>
+  );
+};
+
+export default EnvironmentScopedRoutes;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx
new file mode 100644
index 0000000000..0208932d74
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx
@@ -0,0 +1,128 @@
+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';
+
+
+
+interface EnvironmentsTableProps {
+  environments: Environment[];
+  loading: boolean;
+  onRowClick: (record: Environment) => void;
+
+}
+
+/**
+ * Table component for displaying environments
+ */
+const EnvironmentsTable: React.FC<EnvironmentsTableProps> = ({
+  environments,
+  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
+    const auditUrl = `/setting/audit?environmentId=${environmentId}`;
+    window.open(auditUrl, '_blank');
+  };
+
+
+  // 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) => (
+        <Tag color={getTypeColor(type || '')}>
+          {type ? type.toUpperCase() : 'UNKNOWN'}
+        </Tag>
+      ),
+    },
+    {
+      title: 'Master',
+      dataIndex: 'isMaster',
+      key: 'isMaster',
+      render: (isMaster: boolean) => (
+        <Tag color={isMaster ? 'green' : 'default'}>
+          {isMaster ? 'Yes' : 'No'}
+        </Tag>
+      ),
+    },
+    {
+      title: 'Actions',
+      key: 'actions',
+      render: (_: any, record: Environment) => (
+        <Space size="middle" onClick={e => e.stopPropagation()}>
+          <Tooltip title="View Audit Logs">
+            <Button 
+              icon={<AuditOutlined />} 
+              size="small"
+              onClick={(e) => openAuditPage(record.environmentId, e)}
+            >
+              Audit
+            </Button>
+          </Tooltip>
+        </Space>
+      ),
+    },
+  ];
+
+  return (
+    <Table
+      dataSource={environments}
+      columns={columns}
+      rowKey="environmentId"
+      loading={loading}
+      pagination={{ 
+        pageSize: 10,
+        showSizeChanger: true,
+        pageSizeOptions: ['10', '20', '50']
+      }}
+      onRow={(record) => ({
+        onClick: () => onRowClick(record),
+        style: { 
+          cursor: 'pointer',
+          transition: 'background-color 0.3s',
+          ':hover': {
+            backgroundColor: '#f5f5f5',
+          }
+        }
+      })}
+      rowClassName={() => 'environment-row'}
+    />
+  );
+};
+
+export default EnvironmentsTable;
\ No newline at end of file
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
new file mode 100644
index 0000000000..90b673f346
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx
@@ -0,0 +1,217 @@
+// config/apps.config.tsx
+import React from 'react';
+import { Row, Col, Statistic, Tag, Space, Button, Tooltip } from 'antd';
+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 { App, AppStats } from '../types/app.types';
+
+
+import { 
+  createNameColumn, 
+  createDescriptionColumn, 
+  createPublishedColumn, 
+  createManagedColumn, 
+  createDeployColumn,
+  createAuditColumn, 
+  createIdColumn
+} from '../utils/columnFactories';
+
+// Define AppStats interface if not already defined
+
+
+export const appsConfig: DeployableItemConfig<App, AppStats> = {
+  // Basic info
+  type: 'apps',
+  singularLabel: 'App',
+  pluralLabel: 'Apps',
+  icon: <AppstoreOutlined />,
+  idField: 'id', // or applicationId if you prefer to use that directly
+  
+  // Navigation
+  buildDetailRoute: () => '#',
+
+  
+  // Configuration
+  requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'],
+  
+  // Stats rendering
+  renderStats: (stats) => (
+    <Row gutter={16}>
+      <Col span={6}>
+        <Statistic title="Total Apps" value={stats.total} prefix={<AppstoreOutlined />} />
+      </Col>
+      <Col span={6}>
+        <Statistic title="Published Apps" value={stats.published} prefix={<AppstoreOutlined />} />
+      </Col>
+      <Col span={6}>
+        <Statistic title="Managed Apps" value={stats.managed} prefix={<AppstoreOutlined />} />
+      </Col>
+      <Col span={6}>
+        <Statistic title="Unmanaged Apps" value={stats.unmanaged} prefix={<AppstoreOutlined />} />
+      </Col>
+    </Row>
+  ),
+  
+  // 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<App>(),
+      createNameColumn<App>(),
+      createPublishedColumn<App>(),
+    ];
+    
+    // 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) => <span>{role}</span>,
+    },
+   
+    {
+      title: 'Status',
+      dataIndex: 'status',
+      key: 'status',
+      render: (status: string) => (
+        <Tag color={status === 'ACTIVE' ? 'green' : 'red'} className="status-tag">
+          {status}
+        </Tag>
+      ),
+    }
+  ],
+  
+  // 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: <AuditOutlined />,
+    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) {
+        await connectManagedApp(environment.environmentId, item.name, item.applicationGid!);
+      } else {
+        await unconnectManagedApp(item.applicationGid!);
+      }
+      return true;
+    } catch (error) {
+      console.error('Error toggling managed status:', error);
+      return false;
+    }
+  },
+  // deployment options
+
+  deploy: {
+    enabled: true,
+    fields: [
+      {
+        name: 'updateDependenciesIfNeeded',
+        label: 'Update Dependencies If Needed',
+        type: 'checkbox',
+        defaultValue: false
+      },
+      {
+        name: 'publishOnTarget',
+        label: 'Publish On Target',
+        type: 'checkbox',
+        defaultValue: false
+      },
+      {
+        name: 'publicToAll',
+        label: 'Public To All',
+        type: 'checkbox',
+        defaultValue: false
+      },
+      {
+        name: 'publicToMarketplace',
+        label: 'Public To Marketplace',
+        type: 'checkbox',
+        defaultValue: false
+      }
+    ],
+    prepareParams: (item: App, values: any, sourceEnv: Environment, targetEnv: Environment) => {
+      return {
+        envId: sourceEnv.environmentId,
+        targetEnvId: targetEnv.environmentId,
+        applicationId: item.applicationId,
+        updateDependenciesIfNeeded: values.updateDependenciesIfNeeded,
+        publishOnTarget: values.publishOnTarget,
+        publicToAll: values.publicToAll,
+        publicToMarketplace: values.publicToMarketplace,
+      };
+    },
+    execute: (params: any) => deployApp(params)
+  }
+};
\ No newline at end of file
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
new file mode 100644
index 0000000000..567e460a79
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx
@@ -0,0 +1,183 @@
+// 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 { Environment } from '../types/environment.types';
+import { getMergedWorkspaceDataSources, deployDataSource } from '../services/datasources.service';
+import { connectManagedDataSource, unconnectManagedDataSource } from '../services/enterprise.service';
+import { 
+  createNameColumn, 
+  createTypeColumn,
+  createDatabaseColumn,
+  createDatasourceStatusColumn,
+  createManagedColumn, 
+  createDeployColumn,
+  createAuditColumn 
+} from '../utils/columnFactories';
+
+
+export const dataSourcesConfig: DeployableItemConfig<DataSource, DataSourceStats> = {
+  // Basic info
+  type: 'dataSources',
+  singularLabel: 'Data Source',
+  pluralLabel: 'Data Sources',
+  icon: <DatabaseOutlined />,
+  idField: 'id',
+  
+  // Navigation
+  buildDetailRoute: (params) => "#",
+  
+  // Configuration
+  requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'],
+  
+  // Stats rendering
+  renderStats: (stats) => (
+    <Row gutter={16}>
+      <Col span={8}>
+        <Statistic title="Total Data Sources" value={stats.total} prefix={<DatabaseOutlined />} />
+      </Col>
+      <Col span={8}>
+        <Statistic title="Managed Data Sources" value={stats.managed} prefix={<DatabaseOutlined />} />
+      </Col>
+      <Col span={8}>
+        <Statistic title="Unmanaged Data Sources" value={stats.unmanaged} prefix={<DatabaseOutlined />} />
+      </Col>
+    </Row>
+  ),
+  
+  // 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<string, number>);
+    
+    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) => (
+        <Tag color="blue">{type || 'Unknown'}</Tag>
+      ),
+    },
+    {
+      title: 'Database',
+      key: 'database',
+      render: (_, record: DataSource) => (
+        <span>{record.datasourceConfig?.database || 'N/A'}</span>
+      ),
+    },
+    {
+      title: 'Status',
+      dataIndex: 'datasourceStatus',
+      key: 'status',
+      render: (status: string) => (
+        <Tag color={status === 'NORMAL' ? 'green' : 'red'}>
+          {status}
+        </Tag>
+      ),
+    },
+  ],
+  
+  // 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<DataSource>(),
+      createTypeColumn<DataSource>(),
+      createDatabaseColumn<DataSource>(),
+      createDatasourceStatusColumn<DataSource>(),
+    ];
+    
+    // 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) {
+        await connectManagedDataSource(environment.environmentId, item.name, item.gid);
+      } else {
+        await unconnectManagedDataSource(item.gid);
+      }
+      return true;
+    } catch (error) {
+      console.error('Error toggling managed status:', error);
+      return false;
+    }
+  },
+  deploy: {
+    enabled: true,
+    fields: [
+      {
+        name: 'updateDependenciesIfNeeded',
+        label: 'Update Dependencies If Needed',
+        type: 'checkbox',
+        defaultValue: false
+      }
+    ],
+    prepareParams: (item: DataSource, values: any, sourceEnv: Environment, targetEnv: Environment) => {
+      return {
+        envId: sourceEnv.environmentId,
+        targetEnvId: targetEnv.environmentId,
+        datasourceId: item.id,
+        updateDependenciesIfNeeded: values.updateDependenciesIfNeeded
+      };
+    },
+    execute: (params: any) => deployDataSource(params)
+  }
+};
\ No newline at end of file
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
new file mode 100644
index 0000000000..00721f0331
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx
@@ -0,0 +1,178 @@
+// 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 { 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<Query, QueryStats> = {
+  // Basic info
+  type: 'queries',
+  singularLabel: 'Query',
+  pluralLabel: 'Queries',
+  icon: <ApiOutlined />,
+  idField: 'id',
+  
+  // Navigation - queries don't have detail pages in this implementation
+  buildDetailRoute: () => '#',
+  
+  // Configuration
+  requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'],
+  
+  // Stats rendering
+  renderStats: (stats) => (
+    <Row gutter={16}>
+      <Col span={8}>
+        <Statistic title="Total Queries" value={stats.total} prefix={<ApiOutlined />} />
+      </Col>
+      <Col span={8}>
+        <Statistic title="Managed Queries" value={stats.managed} prefix={<ApiOutlined />} />
+      </Col>
+      <Col span={8}>
+        <Statistic title="Unmanaged Queries" value={stats.unmanaged} prefix={<ApiOutlined />} />
+      </Col>
+    </Row>
+  ),
+  
+  // 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) => (
+        <Tag color="blue">{type || 'Unknown'}</Tag>
+      ),
+    },
+    {
+      title: 'Database',
+      key: 'database',
+      render: (_, record: Query) => (
+        <span>{record.datasourceConfig?.database || 'N/A'}</span>
+      ),
+    },
+    {
+      title: 'Status',
+      dataIndex: 'datasourceStatus',
+      key: 'status',
+      render: (status: string) => (
+        <Tag color={status === 'NORMAL' ? 'green' : 'red'}>
+          {status}
+        </Tag>
+      ),
+    },
+  ],
+  getColumns: ({ environment, refreshing, onToggleManaged, openDeployModal, additionalParams }) => {
+    const columns = [
+      createNameColumn<Query>(),
+      createCreatorColumn<Query>(),
+      createDateColumn<Query>('createTime', 'Creation Date'),
+      createQueryTypeColumn<Query>(),
+    ];
+    
+    // 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;
+  },
+  
+  toggleManaged: async ({ item, checked, environment }) => {
+    try {
+      if (checked) {
+        await connectManagedQuery(environment.environmentId, item.name, item.gid);
+      } else {
+        await unconnectManagedQuery(item.gid);
+      }
+      return true;
+    } catch (error) {
+      console.error('Error toggling managed status:', error);
+      return false;
+    }
+  },
+  deploy: {
+    enabled: true,
+    fields: [
+      {
+        name: 'updateDependenciesIfNeeded',
+        label: 'Update Dependencies If Needed',
+        type: 'checkbox',
+        defaultValue: false
+      }
+    ],
+    prepareParams: (item: Query, values: any, sourceEnv: Environment, targetEnv: Environment) => {
+      return {
+        envId: sourceEnv.environmentId,
+        targetEnvId: targetEnv.environmentId,
+        queryId: item.id,
+        updateDependenciesIfNeeded: values.updateDependenciesIfNeeded
+      };
+    },
+    execute: (params: any) => deployQuery(params)
+  }
+};
\ No newline at end of file
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
new file mode 100644
index 0000000000..8ae0413202
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/config/usergroups.config.tsx
@@ -0,0 +1,169 @@
+// 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<UserGroup, UserGroupStats> = {
+  // Basic info
+  type: 'userGroups',
+  singularLabel: 'User Group',
+  pluralLabel: 'User Groups',
+  icon: <TeamOutlined />,
+  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) => (
+    <Row gutter={16}>
+      <Col span={8}>
+        <Statistic title="Total User Groups" value={stats.total} prefix={<TeamOutlined />} />
+      </Col>
+      <Col span={8}>
+        <Statistic title="Total Users" value={stats.totalUsers} prefix={<UserOutlined />} />
+      </Col>
+      <Col span={8}>
+        <Statistic title="Admin Users" value={stats.adminUsers} prefix={<UserOutlined />} />
+      </Col>
+    </Row>
+  ),
+  
+  // 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) => (
+        <div>
+          <span>{record.groupName}</span>
+          {record.allUsersGroup && (
+            <Tag color="blue" style={{ marginLeft: 8 }}>All Users</Tag>
+          )}
+          {record.devGroup && (
+            <Tag color="orange" style={{ marginLeft: 8 }}>Dev</Tag>
+          )}
+        </div>
+      ),
+    },
+    {
+      title: 'ID',
+      dataIndex: 'groupId',
+      key: 'groupId',
+      ellipsis: true,
+    },
+    {
+      title: 'Users',
+      key: 'userCount',
+      render: (_, record: UserGroup) => (
+        <div>
+          <Badge count={record.stats.userCount} showZero style={{ backgroundColor: '#52c41a' }} />
+          <span style={{ marginLeft: 8 }}>
+            ({record.stats.adminUserCount} admin{record.stats.adminUserCount !== 1 ? 's' : ''})
+          </span>
+        </div>
+      ),
+    },
+    {
+      title: 'Created',
+      key: 'createTime',
+      render: (_, record: UserGroup) => formatDate(record.createTime),
+    },
+    {
+      title: 'Type',
+      key: 'type',
+      render: (_, record: UserGroup) => {
+        if (record.allUsersGroup) return <Tag color="blue">Global</Tag>;
+        if (record.devGroup) return <Tag color="orange">Dev</Tag>;
+        if (record.syncGroup) return <Tag color="purple">Sync</Tag>;
+        return <Tag color="default">Standard</Tag>;
+      },
+    }
+  ],
+  
+  // No managed status for user groups
+  enableManaged: false,
+  
+  getColumns: ({ environment, additionalParams }) => {
+    const columns = [
+      createGroupIdColumn<UserGroup>(),
+      createUserGroupNameColumn<UserGroup>(),
+    
+      createUserCountColumn<UserGroup>(),
+      createDateColumn<UserGroup>('createTime', 'Created'),
+      createGroupTypeColumn<UserGroup>(),
+    ];
+    
+    // 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
new file mode 100644
index 0000000000..87d15ae3b5
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx
@@ -0,0 +1,157 @@
+// 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 { buildEnvironmentWorkspaceId } from '@lowcoder-ee/constants/routesURL';
+import { getMergedEnvironmentWorkspaces } from '../services/workspace.service';
+import { connectManagedWorkspace, unconnectManagedWorkspace } from '../services/enterprise.service';
+import { 
+  createNameColumn, 
+  createIdColumn, 
+  createRoleColumn, 
+  createDateColumn, 
+  createStatusColumn, 
+  createManagedColumn, 
+  createAuditColumn 
+} from '../utils/columnFactories';
+
+export const workspaceConfig: DeployableItemConfig<Workspace, WorkspaceStats> = {
+  // Basic info
+  type: 'workspaces',
+  singularLabel: 'Workspace',
+  pluralLabel: 'Workspaces',
+  icon: <ClusterOutlined />,
+  idField: 'id',
+  
+  // Navigation
+  buildDetailRoute: (params) => buildEnvironmentWorkspaceId(params.environmentId, params.itemId),
+  
+  // Configuration
+  requiredEnvProps: ['environmentApikey', 'environmentApiServiceUrl'],
+  
+  // Stats rendering
+  renderStats: (stats) => (
+    <Row gutter={16}>
+      <Col span={8}>
+        <Statistic title="Total Workspaces" value={stats.total} prefix={<ClusterOutlined />} />
+      </Col>
+      <Col span={8}>
+        <Statistic title="Managed Workspaces" value={stats.managed} prefix={<ClusterOutlined />} />
+      </Col>
+      <Col span={8}>
+        <Statistic title="Unmanaged Workspaces" value={stats.unmanaged} prefix={<ClusterOutlined />} />
+      </Col>
+    </Row>
+  ),
+  
+  // 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) => <span>{role}</span>,
+    },
+    {
+      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) => (
+        <Tag color={status === 'ACTIVE' ? 'green' : 'red'} className="status-tag">
+          {status}
+        </Tag>
+      ),
+    }
+  ],
+  
+  // New getColumns method
+  getColumns: ({ environment, refreshing, onToggleManaged, additionalParams }) => {
+    const columns = [
+      createIdColumn<Workspace>(),
+      createNameColumn<Workspace>(),
+      createRoleColumn<Workspace>(),
+      createManagedColumn<Workspace>(),
+      createDateColumn<Workspace>('creationDate', 'Creation Date'),
+      createStatusColumn<Workspace>()
+
+    ];
+    
+
+    // 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) {
+        await connectManagedWorkspace(environment.environmentId, item.name, item.gid!);
+      } else {
+        await unconnectManagedWorkspace(item.gid!);
+      }
+      return true;
+    } catch (error) {
+      console.error('Error toggling managed status:', error);
+      return false;
+    }
+  },
+  
+  // Audit configuration
+  audit: {
+    enabled: true,
+    icon: <AuditOutlined />,
+    label: 'Audit',
+    tooltip: 'View audit logs for this workspace',
+    getAuditUrl: (item, environment) => 
+      `/setting/audit?environmentId=${environment.environmentId}&orgId=${item.id}&pageSize=100&pageNum=1`
+  }
+};
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/DeployModalContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/DeployModalContext.tsx
new file mode 100644
index 0000000000..7084e94058
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/context/DeployModalContext.tsx
@@ -0,0 +1,75 @@
+// context/DeployModalContext.tsx
+import React, { createContext, useContext, useState } from 'react';
+import { DeployableItem, BaseStats, DeployableItemConfig } from '../types/deployable-item.types';
+import { Environment } from '../types/environment.types';
+import DeployItemModal from '../components/DeployItemModal';
+
+interface DeployModalContextType {
+  openDeployModal: <T extends DeployableItem, S extends BaseStats>(
+    item: T,
+    config: DeployableItemConfig<T, S>,
+    sourceEnvironment: Environment,
+    onSuccess?: () => void
+  ) => void;
+}
+
+const DeployModalContext = createContext<DeployModalContextType | undefined>(undefined);
+
+export const DeployModalProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
+  const [modalState, setModalState] = useState<{
+    visible: boolean;
+    item: DeployableItem | null;
+    config: DeployableItemConfig<any, any> | null;
+    sourceEnvironment: Environment | null;
+    onSuccess?: () => void;
+  }>({
+    visible: false,
+    item: null,
+    config: null,
+    sourceEnvironment: null
+  });
+  
+  const openDeployModal = <T extends DeployableItem, S extends BaseStats>(
+    item: T,
+    config: DeployableItemConfig<T, S>,
+    sourceEnvironment: Environment,
+    onSuccess?: () => void
+  ) => {
+    setModalState({
+      visible: true,
+      item,
+      config,
+      sourceEnvironment,
+      onSuccess
+    });
+  };
+  
+  const closeDeployModal = () => {
+    setModalState(prev => ({ ...prev, visible: false }));
+  };
+  
+  return (
+    <DeployModalContext.Provider value={{ openDeployModal }}>
+      {children}
+      
+      {modalState.config && modalState.sourceEnvironment && (
+        <DeployItemModal
+          visible={modalState.visible}
+          item={modalState.item}
+          sourceEnvironment={modalState.sourceEnvironment}
+          config={modalState.config}
+          onClose={closeDeployModal}
+          onSuccess={modalState.onSuccess}
+        />
+      )}
+    </DeployModalContext.Provider>
+  );
+};
+
+export const useDeployModal = () => {
+  const context = useContext(DeployModalContext);
+  if (context === undefined) {
+    throw new Error('useDeployModal must be used within a DeployModalProvider');
+  }
+  return context;
+};
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/EnvironmentContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/EnvironmentContext.tsx
new file mode 100644
index 0000000000..f8120ff711
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/context/EnvironmentContext.tsx
@@ -0,0 +1,156 @@
+import React, {
+  createContext,
+  useContext,
+  useEffect,
+  useState,
+  useCallback,
+  ReactNode,
+} from "react";
+import { message } from "antd";
+import {
+  getEnvironmentById,
+  getEnvironments,
+  updateEnvironment,
+} from "../services/environments.service";
+import { Environment } from "../types/environment.types";
+
+interface EnvironmentContextState {
+  // Environment data
+  environment: Environment | null;
+  environments: Environment[];
+  
+  // Loading states
+  isLoadingEnvironment: boolean;
+  isLoadingEnvironments: boolean;
+  
+  // Error state
+  error: string | null;
+  
+  // Functions
+  refreshEnvironment: (envId?: string) => Promise<void>;
+  refreshEnvironments: () => Promise<void>;
+  updateEnvironmentData: (envId: string, data: Partial<Environment>) => Promise<Environment>;
+}
+
+const EnvironmentContext = createContext<EnvironmentContextState | undefined>(undefined);
+
+export const useEnvironmentContext = () => {
+  const context = useContext(EnvironmentContext);
+  if (!context) {
+    throw new Error(
+      "useEnvironmentContext must be used within an EnvironmentProvider"
+    );
+  }
+  return context;
+};
+
+interface ProviderProps {
+  children: ReactNode;
+}
+
+export const EnvironmentProvider: React.FC<ProviderProps> = ({
+  children,
+}) => {
+  // State for environment data
+  const [environment, setEnvironment] = useState<Environment | null>(null);
+  const [environments, setEnvironments] = useState<Environment[]>([]);
+
+  // Loading states
+  const [isLoadingEnvironment, setIsLoadingEnvironment] = useState<boolean>(false);
+  const [isLoadingEnvironments, setIsLoadingEnvironments] = useState<boolean>(true);
+
+  // Error state
+  const [error, setError] = useState<string | null>(null);
+
+  // Function to fetch a specific environment by ID
+  const fetchEnvironment = useCallback(async (environmentId?: string) => {
+    // Only fetch if we have an environment ID
+    if (!environmentId) {
+      setEnvironment(null);
+      return;
+    }
+    
+    setIsLoadingEnvironment(true);
+    setError(null);
+    
+    try {
+      const data = await getEnvironmentById(environmentId);
+      console.log("Environment data:", data);
+      setEnvironment(data);
+    } catch (err) {
+      const errorMessage = err instanceof Error ? err.message : "Environment not found or failed to load";
+      setError(errorMessage);
+    } finally {
+      setIsLoadingEnvironment(false);
+    }
+  }, []);
+
+  // Function to fetch all environments
+  const fetchEnvironments = useCallback(async () => {
+    setIsLoadingEnvironments(true);
+    setError(null);
+    
+    try {
+      const data = await getEnvironments();
+      console.log("Environments data:", data);
+      setEnvironments(data);
+    } catch (err) {
+      const errorMessage = err instanceof Error ? err.message : "Failed to load environments list";
+      setError(errorMessage);
+    } finally {
+      setIsLoadingEnvironments(false);
+    }
+  }, []);
+
+  // Function to update an environment
+// Function to update an environment
+const updateEnvironmentData = useCallback(async (
+  environmentId: string, 
+  data: Partial<Environment>
+): Promise<Environment> => {
+  try {
+    const updatedEnv = await updateEnvironment(environmentId, data);
+    
+    // Show success message
+    message.success("Environment updated successfully");
+    
+    // Refresh the environments list
+    fetchEnvironments();
+    
+    // If we're viewing a single environment and it's the one we updated,
+    // refresh that environment data as well
+    if (environment && environment.environmentId === environmentId) {
+      fetchEnvironment(environmentId);
+    }
+    
+    return updatedEnv;
+  } catch (err) {
+    const errorMessage = err instanceof Error ? err.message : "Failed to update environment";
+    message.error(errorMessage);
+    throw err;
+  }
+}, [environment, fetchEnvironment, fetchEnvironments]);
+
+  // Initial data loading - just fetch environments list
+  useEffect(() => {
+    fetchEnvironments();
+  }, [fetchEnvironments]);
+
+  // Create the context value
+  const value: EnvironmentContextState = {
+    environment,
+    environments,
+    isLoadingEnvironment,
+    isLoadingEnvironments,
+    error,
+    refreshEnvironment: fetchEnvironment,
+    refreshEnvironments: fetchEnvironments,
+    updateEnvironmentData,
+  };
+
+  return (
+    <EnvironmentContext.Provider value={value}>
+      {children}
+    </EnvironmentContext.Provider>
+  );
+};
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/hooks/useDeployableItems.ts b/client/packages/lowcoder/src/pages/setting/environments/hooks/useDeployableItems.ts
new file mode 100644
index 0000000000..bb04cf54fd
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/hooks/useDeployableItems.ts
@@ -0,0 +1,146 @@
+// 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<T extends DeployableItem, S extends BaseStats> {
+  items: T[];
+  stats: S;
+  loading: boolean;
+  error: string | null;
+  refreshing: boolean;
+}
+
+export interface UseDeployableItemsResult<T extends DeployableItem, S extends BaseStats> {
+  items: T[];
+  stats: S;
+  loading: boolean;
+  error: string | null;
+  refreshing: boolean;
+  toggleManagedStatus: (item: T, checked: boolean) => Promise<boolean>;
+  refreshItems: () => Promise<void>;
+}
+
+export const useDeployableItems = <T extends DeployableItem, S extends BaseStats>(
+  config: DeployableItemConfig<T, S>,
+  environment: Environment | null,
+  additionalParams: Record<string, any> = {}
+): UseDeployableItemsResult<T, S> => {
+  // 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<UseDeployableItemsState<T, S>>({
+    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<boolean> => {
+    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<void> => {
+    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
new file mode 100644
index 0000000000..8c4c8785b9
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts
@@ -0,0 +1,132 @@
+// services/appService.ts
+import { message } from "antd";
+import { getWorkspaceApps } from "./environments.service";
+import { getManagedApps } from "./enterprise.service";
+import { App } from "../types/app.types";
+import axios from "axios";
+
+export interface AppStats {
+  total: number;
+  published: number;
+  managed: number;
+  unmanaged: number;
+}
+
+export interface MergedAppsResult {
+  apps: App[];
+  stats: AppStats;
+}
+
+
+export interface DeployAppParams {
+  envId: string;
+  targetEnvId: string;
+  applicationId: string;
+  updateDependenciesIfNeeded?: boolean;
+  publishOnTarget?: boolean;
+  publicToAll?: boolean;
+  publicToMarketplace?: boolean;
+}
+
+
+// Use your existing merge function with slight modification
+export const getMergedApps = (standardApps: App[], managedApps: any[]): App[] => {
+  return standardApps.map((app) => ({
+    ...app,
+    managed: managedApps.some((managedApp) => managedApp.appGid === app.applicationGid),
+  }));
+};
+
+// Calculate app statistics
+export const calculateAppStats = (apps: App[]): AppStats => {
+  const publishedCount = apps.filter(app => app.published).length;
+  const managedCount = apps.filter(app => app.managed).length;
+  
+  return {
+    total: apps.length,
+    published: publishedCount,
+    managed: managedCount,
+    unmanaged: apps.length - managedCount
+  };
+};
+
+export async function getMergedWorkspaceApps(
+  workspaceId: string,
+  environmentId: string,
+  apiKey: string,
+  apiServiceUrl: string
+): Promise<MergedAppsResult> {
+  try {
+    // First, get regular apps for the workspace
+    const regularApps = await getWorkspaceApps(
+      workspaceId,
+      apiKey,
+      apiServiceUrl
+    );
+    
+    // If no apps, return early with empty result
+    if (!regularApps.length) {
+      return {
+        apps: [],
+        stats: {
+          total: 0,
+          published: 0,
+          managed: 0,
+          unmanaged: 0
+        }
+      };
+    }
+    
+    // Only fetch managed apps if we have regular apps
+    let managedApps = [];
+    try {
+      managedApps = await getManagedApps(environmentId);
+    } catch (error) {
+      console.error("Failed to fetch managed apps:", error);
+      // Continue with empty managed list
+    }
+    
+    // Use your existing merge function
+    const mergedApps = getMergedApps(regularApps, managedApps);
+    
+    // Calculate stats
+    const stats = calculateAppStats(mergedApps);
+    
+    return {
+      apps: mergedApps,
+      stats
+    };
+  } catch (error) {
+    const errorMessage = 
+      error instanceof Error ? error.message : "Failed to fetch apps";
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+
+
+export const deployApp = async (params: DeployAppParams): Promise<boolean> => {
+  try {
+    const response = await axios.post(
+      `/api/plugins/enterprise/deploy`, 
+      null, 
+      { 
+        params: {
+          envId: params.envId,
+          targetEnvId: params.targetEnvId,
+          applicationId: params.applicationId,
+          updateDependenciesIfNeeded: params.updateDependenciesIfNeeded ?? false,
+          publishOnTarget: params.publishOnTarget ?? false,
+          publicToAll: params.publicToAll ?? false,
+          publicToMarketplace: params.publicToMarketplace ?? false
+        }
+      }
+    );
+    
+    return response.status === 200;
+  } catch (error) {
+    console.error('Error deploying app:', error);
+    throw error;
+  }
+};
\ No newline at end of file
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
new file mode 100644
index 0000000000..b1fe06745f
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts
@@ -0,0 +1,161 @@
+// services/dataSources.service.ts
+import axios from 'axios';
+import { message } from "antd";
+import { DataSource, DataSourceWithMeta } from "../types/datasource.types";
+import { getManagedDataSources } from "./enterprise.service";
+
+export interface DataSourceStats {
+  total: number;
+  types: number;
+  managed: number;
+  unmanaged: number;
+}
+
+export interface MergedDataSourcesResult {
+  dataSources: DataSource[];
+  stats: DataSourceStats;
+}
+
+export interface DeployDataSourceParams {
+  envId: string;
+  targetEnvId: string;
+  datasourceId: string;
+  updateDependenciesIfNeeded?: boolean;
+}
+// Get data sources for a workspace - using your correct implementation
+export async function getWorkspaceDataSources(
+  workspaceId: string, 
+  apiKey: string,
+  apiServiceUrl: string
+): Promise<DataSourceWithMeta[]> {
+  try {
+    // Check if required parameters are provided
+    if (!workspaceId) {
+      throw new Error('Workspace ID is required');
+    }
+    
+    if (!apiKey) {
+      throw new Error('API key is required to fetch data sources');
+    }
+    
+    if (!apiServiceUrl) {
+      throw new Error('API service URL is required to fetch data sources');
+    }
+    
+    // Set up headers with the Bearer token format
+    const headers = {
+      Authorization: `Bearer ${apiKey}`
+    };
+    
+    // Make the API request to get data sources
+    const response = await axios.get<{data:DataSourceWithMeta[]}>(`${apiServiceUrl}/api/datasources/listByOrg`, { 
+      headers,
+      params: {
+        orgId: workspaceId
+      }
+    });
+    console.log("data source response",response);
+  
+    // Check if response is valid
+    if (!response.data) {
+      return [];
+    }
+    
+    return response.data.data;
+  } catch (error) {
+    // Handle and transform error
+    const errorMessage = error instanceof Error ? error.message : 'Failed to fetch data sources';
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+// Function to merge regular and managed data sources
+export const getMergedDataSources = (standardDataSources: DataSourceWithMeta[], managedDataSources: any[]): DataSource[] => {
+  return standardDataSources.map((dataSourceWithMeta) => {
+    const dataSource = dataSourceWithMeta.datasource;
+    return {
+      ...dataSource,
+      managed: managedDataSources.some((managedDs) => managedDs.datasourceGid === dataSource.gid),
+    };
+  });
+};
+
+// Calculate data source statistics
+export const calculateDataSourceStats = (dataSources: DataSource[]): DataSourceStats => {
+  const uniqueTypes = new Set(dataSources.map(ds => ds.type)).size;
+  const managedCount = dataSources.filter(ds => ds.managed).length;
+  
+  return {
+    total: dataSources.length,
+    types: uniqueTypes,
+    managed: managedCount,
+    unmanaged: dataSources.length - managedCount
+  };
+};
+
+// Get and merge data sources from a workspace
+export async function getMergedWorkspaceDataSources(
+  workspaceId: string,
+  environmentId: string,
+  apiKey: string,
+  apiServiceUrl: string
+): Promise<MergedDataSourcesResult> {
+  try {
+    // First, get regular data sources for the workspace
+    const regularDataSourcesWithMeta = await getWorkspaceDataSources(
+      workspaceId,
+      apiKey,
+      apiServiceUrl
+    );
+    
+    // If no data sources, return early with empty result
+    if (!regularDataSourcesWithMeta.length) {
+      return {
+        dataSources: [],
+        stats: {
+          total: 0,
+          types: 0,
+          managed: 0,
+          unmanaged: 0
+        }
+      };
+    }
+    
+    // Only fetch managed data sources if we have regular data sources
+    let managedDataSources = [];
+    try {
+      managedDataSources = await getManagedDataSources(environmentId);
+    } 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);
+    
+    // Calculate stats
+    const stats = calculateDataSourceStats(mergedDataSources);
+    
+    return {
+      dataSources: mergedDataSources,
+      stats
+    };
+  } catch (error) {
+    const errorMessage = 
+      error instanceof Error ? error.message : "Failed to fetch data sources";
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+// Function to deploy a data source to another environment
+export async function deployDataSource(params: DeployDataSourceParams): Promise<boolean> {
+  try {
+    const response = await axios.post('/api/plugins/enterprise/datasource/deploy', params);
+    return response.status === 200;
+  } catch (error) {
+    console.error('Error deploying data source:', error);
+    throw error;
+  }
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/enterprise.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/enterprise.service.ts
new file mode 100644
index 0000000000..fe24330349
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/enterprise.service.ts
@@ -0,0 +1,279 @@
+import axios from "axios";
+import { message } from "antd";
+import { ManagedOrg } from "../types/enterprise.types";
+import { Query } from "../types/query.types";
+
+
+/**
+ * Fetch workspaces for a specific environment
+ * @param apiServiceUrl - API service URL for the environment
+ * @param environmentId - ID of the environment
+ * 
+ * 
+ */
+
+export async function getManagedWorkspaces(
+  environmentId: string,
+
+): Promise<ManagedOrg[]> {
+  if (!environmentId) {
+    throw new Error("Missing environmentId");
+  }
+
+  try {
+    const res = await axios.get(`/api/plugins/enterprise/org/list`);
+    const all: ManagedOrg[] = res.data;
+    return all.filter(org => org.environmentId === environmentId);
+  } catch (err) {
+    const errorMsg = err instanceof Error ? err.message : "Failed to fetch managed workspaces";
+    message.error(errorMsg);
+    throw err;
+  }
+}
+
+
+/**
+ * Fetch workspaces for a specific environment
+ * @param apiServiceUrl - API service URL for the environment
+ * @param environmentId - ID of the environment
+ * @param orgName - Name of the workspace
+ * @param orgTags - Tags of the workspace
+ * 
+ */
+
+export async function connectManagedWorkspace(
+  environmentId: string,
+  orgName: string,
+  org_gid: string, // ✅ not optional
+  orgTags: string[] = [],
+) {
+  if (!environmentId || !orgName || !org_gid) {
+    throw new Error("Missing required params to connect org");
+  }
+
+  try {
+    const payload = {
+      environment_id: environmentId,
+      org_name: orgName,
+      org_tags: orgTags,
+      org_gid,
+    };
+
+    const res = await axios.post(`/api/plugins/enterprise/org`, payload);
+    return res.data;
+  } catch (err) {
+    const errorMsg = err instanceof Error ? err.message : "Failed to connect org";
+    message.error(errorMsg);
+    throw err;
+  }
+}
+
+
+
+/**
+ * Fetch workspaces for a specific environment
+ * @param apiServiceUrl - API service URL for the environment
+ * @param orgId - ID of the workspace
+ * 
+ */
+export async function unconnectManagedWorkspace(orgGid: string) {
+  if (!orgGid) {
+    throw new Error("Missing orgGid to unconnect workspace");
+  }
+
+  try {
+    await axios.delete(`/api/plugins/enterprise/org`, {
+      params: { orgGid }, // ✅ pass as query param
+    });
+  } catch (err) {
+    const errorMsg =
+      err instanceof Error ? err.message : "Failed to unconnect org";
+    message.error(errorMsg);
+    throw err;
+  }
+}
+
+
+
+
+// FOR APPS
+
+export async function getManagedApps(environmentId: string) {
+  const res = await axios.get(`/api/plugins/enterprise/app/list`);
+  const allApps = res.data;
+  return allApps.filter((app: any) => app.environmentId === environmentId);
+}
+
+// Connect an app
+export async function connectManagedApp(
+  environmentId: string,
+  app_name: string,
+  app_gid: string,
+  app_tags: string[] = []
+) {
+  try {
+    const payload = {
+      environment_id: environmentId,
+      app_name,
+      app_gid,
+      app_tags,
+    };
+
+    const res = await axios.post(`/api/plugins/enterprise/app`, payload);
+    return res.data;
+  } catch (err) {
+    const errorMsg =
+      err instanceof Error ? err.message : "Failed to connect app";
+    message.error(errorMsg);
+    throw err;
+  }
+}
+
+// Unconnect an app
+export async function unconnectManagedApp(appGid: string) {
+  try {
+    await axios.delete(`/api/plugins/enterprise/app`, {
+      params: { appGid },
+    });
+  } catch (err) {
+    const errorMsg = err instanceof Error ? err.message : "Failed to unconnect app";
+    message.error(errorMsg);
+    throw err;
+  }
+}
+
+// data sources
+
+export const getManagedDataSources = async (environmentId: string): Promise<any[]> => {
+  try {
+    const response = await axios.get(
+      `/api/plugins/enterprise/datasource/list?environmentId=${environmentId}`
+    );
+    return response.data || [];
+  } catch (error) {
+    console.error("Error fetching managed data sources:", error);
+    throw error;
+  }
+};
+
+// Connect a data source to be managed
+export const connectManagedDataSource = async (
+  environmentId: string,
+  name: string,
+  datasourceGid: string
+): Promise<void> => {
+  try {
+    const payload = {
+      environment_id: environmentId,
+      name,
+      datasource_gid: datasourceGid,
+    };
+
+
+    await axios.post(`/api/plugins/enterprise/datasource`, payload);
+  } catch (error) {
+    console.error("Error connecting managed data source:", error);
+    throw error;
+  }
+};
+
+// Disconnect a managed data source
+export const unconnectManagedDataSource = async (
+  datasourceGid: string
+): Promise<void> => {
+  try {
+    await axios.delete(`/api/plugins/enterprise/datasource?datasourceGid=${datasourceGid}`);
+  } catch (error) {
+    console.error("Error disconnecting managed data source:", error);
+    throw error;
+  }
+};
+
+
+
+
+export async function getManagedQueries(environmentId: string): Promise<Query[]> {
+  try {
+    if (!environmentId) {
+      throw new Error('Environment ID is required');
+    }
+    
+    // Get managed queries from the enterprise endpoint
+    const response = await axios.get(`/api/plugins/enterprise/qlQuery/list`, {
+      params: {
+        environmentId
+      }
+    });
+    
+    if (!response.data || !Array.isArray(response.data)) {
+      return [];
+    }
+    
+    // Map the response to match our Query interface
+    // Note: You may need to adjust this mapping based on the actual response structure
+    return response.data.map((item: any) => ({
+      id: item.id || item.qlQueryId,
+      gid: item.qlQueryGid,
+      name: item.qlQueryName,
+      organizationId: item.orgId,
+      libraryQueryDSL: item.libraryQueryDSL || {},
+      createTime: item.createTime,
+      creatorName: item.creatorName || '',
+      managed: true // These are managed queries
+    }));
+    
+  } catch (error) {
+    const errorMessage = error instanceof Error ? error.message : 'Failed to fetch managed queries';
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+
+export async function connectManagedQuery(
+  environmentId: string, 
+  queryName: string, 
+  queryGid: string
+): Promise<boolean> {
+  try {
+    if (!environmentId || !queryGid) {
+      throw new Error('Environment ID and Query GID are required');
+    }
+    
+    const response = await axios.post('/api/plugins/enterprise/qlQuery', {
+      environment_id: environmentId,
+      ql_query_name: queryName,
+      ql_query_tags: [],
+      ql_query_gid: queryGid
+    });
+    
+    return response.status === 200;
+    
+  } catch (error) {
+    const errorMessage = error instanceof Error ? error.message : 'Failed to connect query';
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+
+export async function unconnectManagedQuery(queryGid: string): Promise<boolean> {
+  try {
+    if (!queryGid) {
+      throw new Error('Query GID is required');
+    }
+    
+    const response = await axios.delete(`/api/plugins/enterprise/qlQuery`, {
+      params: {
+        qlQueryGid: queryGid
+      }
+    });
+    
+    return response.status === 200;
+    
+  } catch (error) {
+    const errorMessage = error instanceof Error ? error.message : 'Failed to disconnect query';
+    message.error(errorMessage);
+    throw error;
+  }
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts
new file mode 100644
index 0000000000..9fe5c96675
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts
@@ -0,0 +1,461 @@
+import axios from "axios";
+import { message } from "antd";
+import { Environment } from "../types/environment.types";
+import { Workspace } from "../types/workspace.types";
+import { UserGroup } from "../types/userGroup.types";
+import {App} from "../types/app.types";
+import { DataSourceWithMeta } from '../types/datasource.types';
+import { Query, QueryResponse } from "../types/query.types";
+
+
+
+
+export async function updateEnvironment(
+  environmentId: string, 
+  environmentData: Partial<Environment>
+): Promise<Environment> {
+  if (!environmentId) {
+    throw new Error("Missing environmentId");
+  }
+
+  try {
+    // Convert frontend model to API model
+    const payload = {
+      environment_description: environmentData.environmentDescription || "",
+      environment_icon: environmentData.environmentIcon || "",
+      environment_name: environmentData.environmentName || "",
+      environment_apikey: environmentData.environmentApikey || "",
+      environment_type: environmentData.environmentType || "",
+      environment_api_service_url: environmentData.environmentApiServiceUrl || "",
+      environment_frontend_url: environmentData.environmentFrontendUrl || "",
+      environment_node_service_url: environmentData.environmentNodeServiceUrl || "",
+      isMaster: environmentData.isMaster || false
+    };
+
+    const res = await axios.put(`/api/plugins/enterprise/environments`, payload, {
+      params: { environmentId }
+    });
+    
+    return res.data;
+  } catch (err) {
+    const errorMsg = err instanceof Error ? err.message : "Failed to update environment";
+    message.error(errorMsg);
+    throw err;
+  }
+}
+
+
+
+/**
+ * Fetch all environments
+ * @returns Promise with environments data
+ */
+export async function getEnvironments(): Promise<Environment[]> {
+  try {
+    // The response contains the data array directly in response.data
+    const response = await axios.get(
+      "/api/plugins/enterprise/environments/list"
+    );
+
+    // Return the data array directly from response.data
+    return response.data || [];
+  } catch (error) {
+    const errorMessage =
+      error instanceof Error ? error.message : "Failed to fetch environments";
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+/**
+ * Fetch a single environment by ID
+ * @param id Environment ID
+ * @returns Promise with environment data
+ */
+export async function getEnvironmentById(id: string): Promise<Environment> {
+  try {
+    const response = await axios.get(
+      `/api/plugins/enterprise/environments?environmentId=${id}`
+    );
+
+    if (!response.data) {
+      throw new Error("Failed to fetch environment");
+    }
+
+    return response.data;
+  } catch (error) {
+    const errorMessage =
+      error instanceof Error ? error.message : "Failed to fetch environment";
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+/* ================================================================================
+
+=============================== ENVIRONMENT WORKSPACES ============================
+*/
+
+/**
+ * Fetch workspaces for a specific environment
+ * @param environmentId - ID of the environment
+ * @param apiKey - API key for the environment
+ * @returns Promise with an array of workspaces
+ */
+export async function getEnvironmentWorkspaces(
+  environmentId: string,
+  apiKey: string, 
+  apiServiceUrl: string
+): Promise<Workspace[]> {
+  try {
+    // Check if required parameters are provided
+    if (!environmentId) {
+      throw new Error("Environment ID is required");
+    }
+
+    if (!apiKey) {
+      throw new Error("API key is required to fetch workspaces");
+    }
+    if (!apiServiceUrl) {
+      throw new Error('API service URL is required to fetch workspaces');
+    }
+
+    // Set up headers with the API key
+    const headers = {
+      Authorization: `Bearer ${apiKey}`
+    };
+
+    // Make the API request to get user data which includes workspaces
+    const response = await axios.get(`${apiServiceUrl}/api/users/me`, { headers });
+
+    // Check if response is valid
+    if (!response.data || !response.data.success) {
+      throw new Error(response.data?.message || "Failed to fetch workspaces");
+    }
+
+    // Extract workspaces from the response
+    const userData = response.data.data;
+
+    if (!userData.orgAndRoles || !Array.isArray(userData.orgAndRoles)) {
+      return [];
+    }
+
+    // Transform the data to match our Workspace interface
+    const workspaces: Workspace[] = userData.orgAndRoles.map((item:any) => ({
+      id: item.org.id,
+      name: item.org.name,
+      role: item.role,
+      creationDate: item.org.createTime,
+      status: item.org.state,
+      gid: item.org.gid,
+      createdBy: item.org.createdBy,
+      isAutoGeneratedOrganization: item.org.isAutoGeneratedOrganization,
+      logoUrl: item.org.logoUrl || "",
+    }));
+
+    return workspaces;
+  } catch (error) {
+    // Handle and transform error
+    const errorMessage =
+      error instanceof Error ? error.message : "Failed to fetch workspaces";
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+
+
+/* ================================================================================
+
+=============================== ENVIRONMENT USER GROUPS ============================ */
+
+export async function getEnvironmentUserGroups(
+  environmentId: string, 
+  apiKey: string,
+  apiServiceUrl: string
+): Promise<UserGroup[]> {
+  try {
+    // Check if required parameters are provided
+    if (!environmentId) {
+      throw new Error('Environment ID is required');
+    }
+    
+    if (!apiKey) {
+      throw new Error('API key is required to fetch user groups');
+    }
+    
+    if (!apiServiceUrl) {
+      throw new Error('API service URL is required to fetch user groups');
+    }
+    
+    // Set up headers with the Bearer token format
+    const headers = {
+      Authorization: `Bearer ${apiKey}`
+    };
+    
+    // Make the API request to get user groups
+    const response = await axios.get(`${apiServiceUrl}/api/groups/list`, { headers });
+    console.log(response);
+    
+    // Check if response is valid
+    if (!response.data) {
+      throw new Error('Failed to fetch user groups');
+    }
+    
+    // The response data is already an array of user groups
+    const userGroups: UserGroup[] = response.data.data || [];
+    
+    return userGroups;
+  } catch (error) {
+    // Handle and transform error
+    const errorMessage = error instanceof Error ? error.message : 'Failed to fetch user groups';
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+
+
+
+/* ================================================================================
+
+=============================== WorkSpace Details ============================ */
+
+
+/**
+ * Get a specific workspace by ID from the list of workspaces
+ * @param workspaces - Array of workspaces
+ * @param workspaceId - ID of the workspace to find
+ * @returns The found workspace or null if not found
+ */
+export function getWorkspaceById(workspaces: Workspace[], workspaceId: string): Workspace | null {
+  if (!workspaces || !workspaceId) {
+    return null;
+  }
+  
+  return workspaces.find(workspace => workspace.id === workspaceId) || null;
+}
+
+/**
+ * Fetch a specific workspace from an environment
+ * @param environmentId - ID of the environment
+ * @param workspaceId - ID of the workspace to fetch
+ * @param apiKey - API key for the environment
+ * @param apiServiceUrl - API service URL for the environment
+ * @returns Promise with the workspace or null if not found
+ */
+export async function fetchWorkspaceById(
+  environmentId: string,
+  workspaceId: string,
+  apiKey: string,
+  apiServiceUrl: string
+): Promise<Workspace | null> {
+  try {
+    // First fetch all workspaces for the environment
+    const workspaces = await getEnvironmentWorkspaces(environmentId, apiKey, apiServiceUrl);
+    
+    // Then find the specific workspace by ID
+    return getWorkspaceById(workspaces, workspaceId);
+  } catch (error) {
+    throw error;
+  }
+}
+
+/* ================================================================================
+
+=============================== WorkSpace Apps ============================ */
+
+
+
+export async function getWorkspaceApps(
+  workspaceId: string, 
+  apiKey: string,
+  apiServiceUrl: string
+): Promise<App[]> {
+  try {
+    // Check if required parameters are provided
+    if (!workspaceId) {
+      throw new Error('Workspace ID is required');
+    }
+    
+    if (!apiKey) {
+      throw new Error('API key is required to fetch apps');
+    }
+    
+    if (!apiServiceUrl) {
+      throw new Error('API service URL is required to fetch apps');
+    }
+    
+    // Set up headers with the Bearer token format
+    const headers = {
+      Authorization: `Bearer ${apiKey}`
+    };
+    
+    // Make the API request to get apps
+    // Include the orgId as a query parameter if needed
+    const response = await axios.get(`${apiServiceUrl}/api/applications/list`, { 
+      headers,
+      params: {
+        orgId: workspaceId
+      }
+    });
+    
+    // Check if response is valid
+    if (!response.data || !response.data.data) {
+      return [];
+    }
+    
+    const filteredApps = response.data.data.filter((app: App) => app.orgId === workspaceId);
+    
+    return filteredApps;
+  
+  } catch (error) {
+    // Handle and transform error
+    const errorMessage = error instanceof Error ? error.message : 'Failed to fetch apps';
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+
+/* ================================================================================
+
+=============================== WorkSpace Data Source ============================  */
+
+/**
+ * Fetch data sources for a specific workspace
+ * @param workspaceId - ID of the workspace (orgId)
+ * @param apiKey - API key for the environment
+ * @param apiServiceUrl - API service URL for the environment
+ * @returns Promise with an array of data sources
+ */
+export async function getWorkspaceDataSources(
+  workspaceId: string, 
+  apiKey: string,
+  apiServiceUrl: string
+): Promise<DataSourceWithMeta[]> {
+  try {
+    // Check if required parameters are provided
+    if (!workspaceId) {
+      throw new Error('Workspace ID is required');
+    }
+    
+    if (!apiKey) {
+      throw new Error('API key is required to fetch data sources');
+    }
+    
+    if (!apiServiceUrl) {
+      throw new Error('API service URL is required to fetch data sources');
+    }
+    
+    // Set up headers with the Bearer token format
+    const headers = {
+      Authorization: `Bearer ${apiKey}`
+    };
+    
+    // Make the API request to get data sources
+    const response = await axios.get<{data:DataSourceWithMeta[]}>(`${apiServiceUrl}/api/datasources/listByOrg`, { 
+      headers,
+      params: {
+        orgId: workspaceId
+      }
+    });
+    console.log("data source response",response);
+  
+    // Check if response is valid
+    if (!response.data) {
+      return [];
+    }
+    
+    return response.data.data ;
+  } catch (error) {
+    // Handle and transform error
+    const errorMessage = error instanceof Error ? error.message : 'Failed to fetch data sources';
+    message.error(errorMessage);
+    throw error;
+  }
+}
+
+
+
+/**
+ * Fetch queries for a specific workspace
+ * @param workspaceId - ID of the workspace (orgId)
+ * @param apiKey - API key for the environment
+ * @param apiServiceUrl - API service URL for the environment
+ * @param options - Additional options (name filter, pagination)
+ * @returns Promise with an array of queries and metadata
+ */
+export async function getWorkspaceQueries(
+  workspaceId: string, 
+  apiKey: string,
+  apiServiceUrl: string,
+  options: {
+    name?: string;
+    pageNum?: number;
+    pageSize?: number;
+  } = {}
+): Promise<{ queries: Query[], total: number }> {
+  try {
+    // Check if required parameters are provided
+    if (!workspaceId) {
+      throw new Error('Workspace ID is required');
+    }
+    
+    if (!apiKey) {
+      throw new Error('API key is required to fetch queries');
+    }
+    
+    if (!apiServiceUrl) {
+      throw new Error('API service URL is required to fetch queries');
+    }
+    
+    // Set up headers with the Bearer token format
+    const headers = {
+      Authorization: `Bearer ${apiKey}`
+    };
+    
+    // Prepare query parameters
+    const params: any = {
+      orgId: workspaceId
+    };
+    
+    // Add optional parameters if provided
+    if (options.name) params.name = options.name;
+    if (options.pageNum !== undefined) params.pageNum = options.pageNum;
+    if (options.pageSize !== undefined) params.pageSize = options.pageSize;
+    
+    // Make the API request to get queries
+    const response = await axios.get<QueryResponse>(`${apiServiceUrl}/api/library-queries/listByOrg`, { 
+      headers,
+      params
+    });
+    debugger
+    // Check if response is valid
+    if (!response.data) {
+      return { queries: [], total: 0 };
+    }
+    console.log("RESPONSE DATA QUERIES",response.data.data);
+    // Map the response to include id field required by DeployableItem
+    const queries = response.data.data.map(query => ({
+      ...query,
+      // Map to DeployableItem fields if not already present
+      id: query.id,
+      name: query.name,
+      managed: false // Default to unmanaged
+    }));
+
+    console.log("queries",queries);
+    
+    return { 
+      queries, 
+      total: response.data.total 
+    };
+  
+  } catch (error) {
+    // Handle and transform error
+    const errorMessage = error instanceof Error ? error.message : 'Failed to fetch queries';
+    message.error(errorMessage);
+    throw error;
+  }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..39eda02355
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts
@@ -0,0 +1,87 @@
+/**
+ * Get merged queries (both regular and managed) for a workspace
+ */
+import axios from 'axios';
+import { getManagedQueries } from './enterprise.service';
+import { getWorkspaceQueries } from './environments.service';
+import { Query } from '../types/query.types';
+export interface MergedQueriesResult {
+    queries: Query[];
+    stats: {
+      total: number;
+      managed: number;
+      unmanaged: number;
+    };
+  }
+
+  export interface DeployQueryParams {
+    envId: string;
+    targetEnvId: string;
+    queryId: string;
+    updateDependenciesIfNeeded?: boolean;
+  }
+  
+  
+  export async function getMergedWorkspaceQueries(
+    workspaceId: string,
+    environmentId: string,
+    apiKey: string,
+    apiServiceUrl: string
+  ): Promise<MergedQueriesResult> {
+    try {
+      // Fetch regular queries
+      
+      const regularQueries = await getWorkspaceQueries(workspaceId, apiKey, apiServiceUrl);
+      console.log("Regular queries response:", regularQueries);
+      
+      const managedQueries = await getManagedQueries(environmentId);
+      console.log("Managed queries response:", managedQueries);
+      
+      // Create a map of managed queries by GID for quick lookup
+      const managedQueryGids = new Set(managedQueries.map(query => query.gid));
+      console.log("Managed query GIDs:", Array.from(managedQueryGids));
+      
+      // Mark regular queries as managed if they exist in managed queries
+      const mergedQueries = regularQueries.queries.map((query: Query) => {
+        const isManaged = managedQueryGids.has(query.gid);
+        console.log(`Query ${query.name} (gid: ${query.gid}) is ${isManaged ? "managed" : "not managed"}`);
+        
+        return {
+          ...query,
+          managed: isManaged
+        };
+      });
+      
+      // Calculate stats
+      const total = mergedQueries.length;
+      const managed = mergedQueries.filter(query => query.managed).length;
+      console.log("Generated stats:", {
+        total,
+        managed,
+        unmanaged: total - managed
+      });
+      
+      return {
+        queries: mergedQueries,
+        stats: {
+          total,
+          managed,
+          unmanaged: total - managed
+        }
+      };
+      
+    } catch (error) {
+      console.error("Error in getMergedWorkspaceQueries:", error);
+      throw error;
+    }
+  }
+
+  export async function deployQuery(params: DeployQueryParams): Promise<boolean> {
+    try {
+      const response = await axios.post('/api/plugins/enterprise/qlQuery/deploy', params);
+      return response.status === 200;
+    } catch (error) {
+      console.error('Error deploying query:', error);
+      throw error;
+    }
+  }
\ No newline at end of file
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
new file mode 100644
index 0000000000..c56b978b55
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts
@@ -0,0 +1,76 @@
+// 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 { Workspace } from "../types/workspace.types";
+import { ManagedOrg } from "../types/enterprise.types";
+
+export interface WorkspaceStats {
+  total: number;
+  managed: number;
+  unmanaged: number;
+}
+
+export interface MergedWorkspacesResult {
+  workspaces: Workspace[];
+  stats: WorkspaceStats;
+}
+
+export async function getMergedEnvironmentWorkspaces(
+  environmentId: string,
+  apiKey: string, 
+  apiServiceUrl: string
+): Promise<MergedWorkspacesResult> {
+  try {
+    // First, get regular workspaces
+    const regularWorkspaces = await getEnvironmentWorkspaces(
+      environmentId,
+      apiKey,
+      apiServiceUrl
+    );
+    
+    // If no workspaces, return early with empty result
+    if (!regularWorkspaces.length) {
+      return {
+        workspaces: [],
+        stats: {
+          total: 0,
+          managed: 0,
+          unmanaged: 0
+        }
+      };
+    }
+    
+    // Only fetch managed workspaces if we have regular workspaces
+    let managedOrgs: ManagedOrg[] = [];
+    try {
+      managedOrgs = await getManagedWorkspaces(environmentId);
+    } catch (error) {
+      console.error("Failed to fetch managed workspaces:", error);
+      // Continue with empty managed list
+    }
+    
+    // Merge the workspaces
+    const mergedWorkspaces = regularWorkspaces.map(ws => ({
+      ...ws,
+      managed: managedOrgs.some(org => org.orgGid === ws.gid)
+    }));
+    
+    // Calculate stats
+    const managedCount = mergedWorkspaces.filter(ws => ws.managed).length;
+    
+    return {
+      workspaces: mergedWorkspaces,
+      stats: {
+        total: mergedWorkspaces.length,
+        managed: managedCount,
+        unmanaged: mergedWorkspaces.length - managedCount
+      }
+    };
+  } catch (error) {
+    const errorMessage = 
+      error instanceof Error ? error.message : "Failed to fetch workspaces";
+    message.error(errorMessage);
+    throw error;
+  }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..b3af252b58
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/app.types.ts
@@ -0,0 +1,33 @@
+import { DeployableItem, BaseStats } from "./deployable-item.types";
+
+export interface App extends DeployableItem {
+    orgId: string;
+    applicationId: string;
+    applicationGid: string;
+    name: string;
+    createAt: number;
+    createBy: string;
+    role: string;
+    applicationType: number;
+    applicationStatus: string;
+    folderId: string | null;
+    lastViewTime: number;
+    lastModifyTime: number;
+    lastEditedAt: number;
+    publicToAll: boolean;
+    publicToMarketplace: boolean;
+    agencyProfile: boolean;
+    editingUserId: string | null;
+    title: string;
+    description: string;
+    category: string;
+    icon: string;
+    published: boolean;
+    folder: boolean;
+    managed?: boolean;
+    id: string
+  }
+
+  export interface AppStats extends BaseStats {
+    published: 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
new file mode 100644
index 0000000000..f4f03072db
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/datasource.types.ts
@@ -0,0 +1,47 @@
+/**
+ * Represents a DataSource configuration
+ */
+
+import { DeployableItem, BaseStats } from "./deployable-item.types";
+export interface DataSourceConfig {
+    usingUri: boolean;
+    srvMode: boolean;
+    ssl: boolean;
+    endpoints: any[];
+    host: string | null;
+    port: number;
+    database: string | null;
+    username: string;
+    authMechanism: string | null;
+  }
+  
+  /**
+   * Represents a DataSource entity
+   */
+  export interface DataSource extends DeployableItem {
+    id: string;
+    createdBy: string;
+    gid: string;
+    name: string;
+    type: string;
+    organizationId: string;
+    creationSource: number;
+    datasourceStatus: string;
+    pluginDefinition: any | null;
+    createTime: number;
+    datasourceConfig: DataSourceConfig;
+    managed?: boolean;
+  }
+  
+  /**
+   * Represents a DataSource with additional metadata
+   */
+  export interface DataSourceWithMeta {
+    datasource: DataSource;
+    edit: boolean;
+    creatorName: string;
+  }
+
+  export interface DataSourceStats extends BaseStats {
+    byType: Record<string, number>; // 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
new file mode 100644
index 0000000000..ac223c63d9
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/deployable-item.types.ts
@@ -0,0 +1,105 @@
+// 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, any>) => 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;
+  label: string;
+  type: 'checkbox' | 'select' | 'input';
+  defaultValue?: any;
+  required?: boolean;
+  options?: Array<{label: string, value: any}>; // For select fields
+}
+// Configuration for each deployable item type
+export interface DeployableItemConfig<T extends DeployableItem, S extends BaseStats> {
+  // 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, string>) => 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<T>[];
+  
+  // New method to generate columns
+  getColumns: (params: {
+    environment: Environment;
+    refreshing: boolean;
+    onToggleManaged?: (item: T, checked: boolean) => Promise<boolean>;
+    openDeployModal?: (item: T, config: DeployableItemConfig<T, S>, environment: Environment) => void;
+    additionalParams?: Record<string, any>;
+  }) => ColumnType<T>[];
+
+  // Add audit configuration
+  audit?: AuditConfig;
+  
+  
+  
+  // Deployable configuration
+  enableManaged: boolean;
+  
+  // Service functions
+  fetchItems: (params: { environment: Environment, [key: string]: any }) => Promise<T[]>;
+  toggleManaged: (params: { item: T; checked: boolean; environment: Environment }) => Promise<boolean>;
+
+  deploy?: {
+    enabled: boolean;
+    fields: DeployField[];
+    prepareParams: (item: T, values: any, sourceEnv: Environment, targetEnv: Environment) => any;
+    execute: (params: any) => Promise<any>;
+  };
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/types/enterprise.types.ts b/client/packages/lowcoder/src/pages/setting/environments/types/enterprise.types.ts
new file mode 100644
index 0000000000..e51a787403
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/enterprise.types.ts
@@ -0,0 +1,12 @@
+import { Workspace } from "../types/workspace.types";
+export interface ManagedOrg {
+    orgGid: string;
+    environmentId: string;
+    orgName: string;
+    orgTags: string[];
+    createdAt: string;
+    updatedAt: string;
+  }
+  
+
+  export type MergedWorkspace = Workspace & { managed: boolean };
diff --git a/client/packages/lowcoder/src/pages/setting/environments/types/environment.types.ts b/client/packages/lowcoder/src/pages/setting/environments/types/environment.types.ts
new file mode 100644
index 0000000000..39766c1eae
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/environment.types.ts
@@ -0,0 +1,17 @@
+/**
+ * Interface representing an Environment entity
+ */
+export interface Environment {
+  environmentId: string;
+  environmentName?: string;
+  environmentDescription?: string;
+  environmentIcon?: string;
+  environmentType: string;
+  environmentApiServiceUrl?: string;
+  environmentNodeServiceUrl?: string;
+  environmentFrontendUrl?: string;
+  environmentApikey: string;
+  isMaster: boolean;
+  createdAt: string;
+  updatedAt: string;
+}
\ 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
new file mode 100644
index 0000000000..5d38385b0e
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/query.types.ts
@@ -0,0 +1,63 @@
+// types/query.types.ts
+import { DeployableItem, BaseStats } from './deployable-item.types';
+
+export interface LibraryQueryDSL {
+  query: {
+    compType: string;
+    comp: {
+      bodyType: string;
+      body: string;
+      httpMethod: string;
+      path: string;
+      headers: Array<{ key: string; value: string }>;
+      params: Array<{ key: string; value: string }>;
+      bodyFormData: Array<{ key: string; value: string; type: string }>;
+    };
+    id: string;
+    name: string;
+    order: number;
+    datasourceId: string;
+    triggerType: string;
+    onEvent: any[];
+    notification: {
+      showSuccess: boolean;
+      showFail: boolean;
+      fail: any[];
+    };
+    timeout: string;
+    confirmationModal: any;
+    variables: any[];
+    periodic: boolean;
+    periodicTime: string;
+    cancelPrevious: boolean;
+    depQueryName: string;
+    delayTime: string;
+    managed?: boolean;
+  };
+}
+
+export interface Query extends DeployableItem {
+  id: string;
+  gid: string;
+  organizationId: string;
+  name: string;
+  libraryQueryDSL: LibraryQueryDSL;
+  createTime: number;
+  creatorName: string;
+}
+
+export interface QueryStats extends BaseStats {
+  total: number;
+  managed: number;
+  unmanaged: number;
+}
+
+export interface QueryResponse {
+  code: number;
+  message: string;
+  data: Query[];
+  pageNum: number;
+  pageSize: number;
+  total: number;
+  success: boolean;
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/types/userGroup.types.ts b/client/packages/lowcoder/src/pages/setting/environments/types/userGroup.types.ts
new file mode 100644
index 0000000000..6a1938bcc3
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/userGroup.types.ts
@@ -0,0 +1,34 @@
+/**
+ * Represents a User Group entity in an environment
+*/
+
+import { DeployableItem, BaseStats } from "./deployable-item.types";
+
+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;
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/types/workspace.types.ts b/client/packages/lowcoder/src/pages/setting/environments/types/workspace.types.ts
new file mode 100644
index 0000000000..15f1e7dfbc
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/workspace.types.ts
@@ -0,0 +1,16 @@
+/**
+ * Represents a Workspace entity in an environment
+ */
+export interface Workspace {
+    id: string;
+    name: string;
+    role: string; // 'admin', 'member', etc.
+    creationDate?: number; // timestamp
+    status: string; // 'ACTIVE', 'INACTIVE', etc.
+    // Optional fields
+    gid?: string;
+    createdBy?: string;
+    isAutoGeneratedOrganization?: boolean | null;
+    logoUrl?: string;
+    managed?: boolean;
+  }
\ 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
new file mode 100644
index 0000000000..b33685ab70
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/utils/columnFactories.tsx
@@ -0,0 +1,309 @@
+// 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<T extends { name: string }>(): ColumnType<T> {
+  return {
+    title: 'Name',
+    dataIndex: 'name',
+    key: 'name',
+  };
+}
+
+export function createIdColumn<T extends { id: string }>(): ColumnType<T> {
+  return {
+    title: 'ID',
+    dataIndex: 'id',
+    key: 'id',
+    ellipsis: true,
+  };
+}
+
+export function createRoleColumn<T extends { role?: string }>(): ColumnType<T> {
+  return {
+    title: 'Role',
+    dataIndex: 'role',
+    key: 'role',
+    render: (role: string) => <span>{role}</span>,
+  };
+}
+
+export function createDateColumn<T>(
+  dateField: string,
+  title: string
+): ColumnType<T> {
+  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<T extends { status?: string }>(): ColumnType<T> {
+  return {
+    title: 'Status',
+    dataIndex: 'status',
+    key: 'status',
+    render: (status: string) => (
+      <Tag color={status === 'ACTIVE' ? 'green' : 'red'} className="status-tag">
+        {status}
+      </Tag>
+    ),
+  };
+}
+
+// Feature columns
+export function createManagedColumn<T extends DeployableItem>(
+  onToggleManaged?: (item: T, checked: boolean) => Promise<boolean>,
+  refreshing: boolean = false
+): ColumnType<T> {
+  return {
+    title: 'Managed',
+    key: 'managed',
+    render: (_, record: T) => (
+      <Space>
+        <Tag color={record.managed ? 'green' : 'default'}>
+          {record.managed ? 'Managed' : 'Unmanaged'}
+        </Tag>
+        {onToggleManaged && (
+          <Switch
+            size="small"
+            checked={!!record.managed}
+            loading={refreshing}
+            onClick={(checked, e) => {
+              e.stopPropagation(); // Stop row click event
+              onToggleManaged(record, checked);
+            }}
+            onChange={() => {}}
+          />
+        )}
+      </Space>
+    ),
+  };
+}
+
+export function createAuditColumn<T extends DeployableItem>(
+  config: DeployableItemConfig<T, any>,
+  environment: Environment,
+  additionalParams: Record<string, any> = {}
+): ColumnType<T> {
+  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 (
+        <Tooltip title={config.audit?.tooltip || `View audit logs`}>
+          <Button
+            icon={config.audit?.icon || <AuditOutlined />}
+            onClick={openAuditPage}
+          >
+            {config.audit?.label || 'Audit'}
+          </Button>
+        </Tooltip>
+      );
+    },
+  };
+}
+
+
+export function createDescriptionColumn<T extends { description?: string }>(): ColumnType<T> {
+  return {
+    title: 'Description',
+    dataIndex: 'description',
+    key: 'description',
+    ellipsis: true,
+  };
+}
+
+
+export function createDeployColumn<T extends DeployableItem, S extends BaseStats>(
+  config: DeployableItemConfig<T, S>,
+  environment: Environment,
+  openDeployModal: (item: T, config: DeployableItemConfig<T, S>, environment: Environment) => void
+): ColumnType<T> {
+  return {
+    title: 'Actions',
+    key: 'actions',
+    render: (_, record: T) => {
+      // Check if the item is managed
+      const isManaged = record.managed === true;
+      
+      return (
+        <Space>
+          <Tooltip title={isManaged 
+            ? `Deploy this ${config.singularLabel.toLowerCase()} to another environment` 
+            : `Item must be managed before it can be deployed`}
+          >
+            <Button
+              icon={<CloudUploadOutlined />}
+              onClick={(e) => {
+                e.stopPropagation(); // Prevent row click navigation
+                if (isManaged) {
+                  openDeployModal(record, config, environment);
+                }
+              }}
+              type="primary"
+              ghost
+              disabled={!isManaged}
+            >
+              Deploy
+            </Button>
+          </Tooltip>
+        </Space>
+      );
+    },
+  };
+}
+
+// App-specific columns
+export function createPublishedColumn<T extends { published?: boolean }>(): ColumnType<T> {
+  return {
+    title: 'Status',
+    dataIndex: 'published',
+    key: 'published',
+    render: (published: boolean) => (
+      <Tag color={published ? 'green' : 'orange'}>
+        {published ? 'Published' : 'Unpublished'}
+      </Tag>
+    ),
+  };
+}
+
+// Data Source specific columns
+export function createTypeColumn<T extends { type?: string }>(): ColumnType<T> {
+  return {
+    title: 'Type',
+    dataIndex: 'type',
+    key: 'type',
+    render: (type: string) => (
+      <Tag color="blue">{type || 'Unknown'}</Tag>
+    ),
+  };
+}
+
+export function createDatabaseColumn<T extends { datasourceConfig?: { database?: string | null } }>(): ColumnType<T> {
+  return {
+    title: 'Database',
+    key: 'database',
+    render: (_, record: T) => (
+      <span>{record.datasourceConfig?.database || 'N/A'}</span>
+    ),
+  };
+}
+
+export function createDatasourceStatusColumn<T extends { datasourceStatus?: string }>(): ColumnType<T> {
+  return {
+    title: 'Status',
+    dataIndex: 'datasourceStatus',
+    key: 'status',
+    render: (status: string) => (
+      <Tag color={status === 'NORMAL' ? 'green' : 'red'}>
+        {status}
+      </Tag>
+    ),
+  };
+}
+
+
+// Query-specific column factories to add to columnFactories.tsx
+export function createCreatorColumn<T extends { creatorName?: string }>(): ColumnType<T> {
+  return {
+    title: 'Creator',
+    dataIndex: 'creatorName',
+    key: 'creatorName',
+  };
+}
+
+export function createQueryTypeColumn<T extends { libraryQueryDSL?: { query?: { compType?: string } } }>(): ColumnType<T> {
+  return {
+    title: 'Query Type',
+    key: 'queryType',
+    render: (_, record: T) => {
+      const queryType = record.libraryQueryDSL?.query?.compType || 'Unknown';
+      return <Tag color="blue">{queryType}</Tag>;
+    },
+  };
+}
+
+export function createUserGroupNameColumn<T extends { 
+  groupName?: string; 
+  allUsersGroup?: boolean; 
+  devGroup?: boolean;
+}>(): ColumnType<T> {
+  return {
+    title: 'Name',
+    dataIndex: 'groupName',
+    key: 'groupName',
+    render: (name: string, record: T) => (
+      <div>
+        <span>{record.groupName}</span>
+        {record.allUsersGroup && (
+          <Tag color="blue" style={{ marginLeft: 8 }}>All Users</Tag>
+        )}
+        {record.devGroup && (
+          <Tag color="orange" style={{ marginLeft: 8 }}>Dev</Tag>
+        )}
+      </div>
+    ),
+  };
+}
+
+export function createGroupIdColumn<T extends { groupId?: string }>(): ColumnType<T> {
+  return {
+    title: 'ID',
+    dataIndex: 'groupId',
+    key: 'groupId',
+    ellipsis: true,
+  };
+}
+
+export function createUserCountColumn<T extends { 
+  stats?: { userCount: number; adminUserCount: number; }
+}>(): ColumnType<T> {
+  return {
+    title: 'Users',
+    key: 'userCount',
+    render: (_, record: T) => (
+      <div>
+        <Badge count={record.stats?.userCount || 0} showZero style={{ backgroundColor: '#52c41a' }} />
+        <span style={{ marginLeft: 8 }}>
+          ({record.stats?.adminUserCount || 0} admin{(record.stats?.adminUserCount || 0) !== 1 ? 's' : ''})
+        </span>
+      </div>
+    ),
+  };
+}
+
+export function createGroupTypeColumn<T extends { 
+  allUsersGroup?: boolean; 
+  devGroup?: boolean;
+  syncGroup?: boolean;
+}>(): ColumnType<T> {
+  return {
+    title: 'Type',
+    key: 'type',
+    render: (_, record: T) => {
+      if (record.allUsersGroup) return <Tag color="blue">Global</Tag>;
+      if (record.devGroup) return <Tag color="orange">Dev</Tag>;
+      if (record.syncGroup) return <Tag color="purple">Sync</Tag>;
+      return <Tag color="default">Standard</Tag>;
+    },
+  };
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/settingHome.tsx b/client/packages/lowcoder/src/pages/setting/settingHome.tsx
index c98d6540df..8eabd126aa 100644
--- a/client/packages/lowcoder/src/pages/setting/settingHome.tsx
+++ b/client/packages/lowcoder/src/pages/setting/settingHome.tsx
@@ -25,7 +25,7 @@ import { getUser } from "redux/selectors/usersSelectors";
 import history from "util/history";
 import { useParams } from "react-router-dom";
 import { BrandingSetting } from "@lowcoder-ee/pages/setting/branding/BrandingSetting";
-import { Environments } from "@lowcoder-ee/pages/setting/environments/Environments";
+import  Environments  from "@lowcoder-ee/pages/setting/environments/Environments";
 import { AppUsage } from "@lowcoder-ee/pages/setting/appUsage";
 import { AuditLog } from "@lowcoder-ee/pages/setting/audit";
 import { IdSourceHome } from "@lowcoder-ee/pages/setting/idSource";