-
{app.name}
+
+
+ {app.name}
+ {app.applicationStatus === 'RECYCLED' && (
+
+
+
+ )}
+
{app.applicationId}
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/ContactLowcoderModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/ContactLowcoderModal.tsx
new file mode 100644
index 000000000..22c9ddd64
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/ContactLowcoderModal.tsx
@@ -0,0 +1,132 @@
+import React, { useEffect } from 'react';
+import { Modal, Card, Row, Col, Typography, Divider } from 'antd';
+import { CustomerServiceOutlined, CloudServerOutlined } from '@ant-design/icons';
+import { useSelector, useDispatch } from 'react-redux';
+import { getDeploymentId } from 'redux/selectors/configSelectors';
+import { fetchDeploymentIdAction } from 'redux/reduxActions/configActions';
+import { Environment } from '../types/environment.types';
+
+const { Title, Text } = Typography;
+
+interface ContactLowcoderModalProps {
+ visible: boolean;
+ onClose: () => void;
+ environment: Environment;
+}
+
+/**
+ * Professional modal for contacting Lowcoder team with HubSpot form integration
+ */
+const ContactLowcoderModal: React.FC
= ({
+ visible,
+ onClose,
+ environment
+}) => {
+ // Get deploymentId from Redux config provider
+ const deploymentId = useSelector(getDeploymentId);
+ const dispatch = useDispatch();
+
+ // Fetch deployment ID when modal opens if not already available
+ useEffect(() => {
+ if (visible && !deploymentId) {
+ dispatch(fetchDeploymentIdAction());
+ }
+ }, [visible, deploymentId, dispatch]);
+
+ return (
+
+
+ Contact Lowcoder Team
+
+ }
+ open={visible}
+ onCancel={onClose}
+ footer={null}
+ width={800}
+ centered
+ style={{ top: 20 }}
+ bodyStyle={{ padding: '24px' }}
+ >
+ {/* Environment Context Section */}
+
+
+
+
+
+
+
+
+ Environment: {environment.environmentName || 'Unnamed Environment'}
+
+
+
+ Environment ID: {environment.environmentId}
+
+
+
+ Deployment ID: {deploymentId || 'Loading...'}
+
+
+
+
+
+
+
+
+ {/* HubSpot Form Integration Section */}
+
+
+ Get in Touch
+
+
+
+ Our team is here to help you resolve licensing issues and get your environment up and running.
+
+
+ {/* HubSpot Form Container */}
+
+ {/* HubSpot form will be integrated here */}
+
+
+
Contact form will be integrated here
+
+
+
+ {/* Environment data is available for form pre-filling */}
+ {/* Data available: environment.environmentName, environment.environmentId, deploymentId,
+ environment.licenseStatus, environment.environmentType, environment.licenseError */}
+
+
+ );
+};
+
+export default ContactLowcoderModal;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/CreateEnvironmentModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/CreateEnvironmentModal.tsx
new file mode 100644
index 000000000..dac35d7be
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/CreateEnvironmentModal.tsx
@@ -0,0 +1,169 @@
+import React, { useState } from 'react';
+import { Modal, Form, Input, Select, Switch, Button, Alert } from 'antd';
+import { Environment } from '../types/environment.types';
+
+const { Option } = Select;
+
+interface CreateEnvironmentModalProps {
+ visible: boolean;
+ onClose: () => void;
+ onSave: (data: Partial
) => Promise;
+ loading?: boolean;
+}
+
+const CreateEnvironmentModal: React.FC = ({
+ visible,
+ onClose,
+ onSave,
+ loading = false
+}) => {
+ const [form] = Form.useForm();
+ const [submitLoading, setSubmitLoading] = useState(false);
+
+ const handleSubmit = async () => {
+ try {
+ const values = await form.validateFields();
+ setSubmitLoading(true);
+
+ await onSave(values);
+ form.resetFields(); // Reset form after successful creation
+ onClose();
+ } catch (error) {
+ if (error instanceof Error) {
+ console.error("Form validation or submission error:", error);
+ }
+ } finally {
+ setSubmitLoading(false);
+ }
+ };
+
+ const handleCancel = () => {
+ form.resetFields(); // Reset form when canceling
+ onClose();
+ };
+
+ return (
+
+ Cancel
+ ,
+
+ Create Environment
+
+ ]}
+ >
+
+
+
+
+
+
+
+
+
+
+ Development (DEV)
+ Testing (TEST)
+ Pre-Production (PREPROD)
+ Production (PROD)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CreateEnvironmentModal;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx
index ba13fd575..95535d590 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/DataSourcesTab.tsx
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
-import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col, Avatar } from 'antd';
+import { Card, Button, Divider, Alert, Table, Tag, Input, Space, Tooltip, Row, Col, Avatar } from 'antd';
+import { messageInstance } from 'lowcoder-design/src/components/GlobalInstances';
import {
SyncOutlined,
CloudUploadOutlined,
@@ -114,10 +115,10 @@ const DataSourcesTab: React.FC = ({ environment, workspaceI
unmanaged: prev.total - managed
}));
- message.success(`${dataSource.name} is now ${checked ? 'Managed' : 'Unmanaged'}`);
+ messageInstance.success(`${dataSource.name} is now ${checked ? 'Managed' : 'Unmanaged'}`);
return true;
} catch (error) {
- message.error(`Failed to change managed status for ${dataSource.name}`);
+ messageInstance.error(`Failed to change managed status for ${dataSource.name}`);
return false;
} finally {
setRefreshing(false);
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx
index 31a0ce725..256589ac0 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx
@@ -1,6 +1,7 @@
// components/DeployItemModal.tsx
import React, { useState, useEffect } from 'react';
-import { Modal, Form, Select, Checkbox, Button, message, Spin, Input, Tag, Space } from 'antd';
+import { Modal, Form, Select, Checkbox, Button, Spin, Input, Tag, Space } from 'antd';
+import { messageInstance } from 'lowcoder-design/src/components/GlobalInstances';
import { Environment } from '../types/environment.types';
import { DeployableItemConfig } from '../types/deployable-item.types';
import { useEnvironmentContext } from '../context/EnvironmentContext';
@@ -35,7 +36,7 @@ function DeployItemModal({
// Filter out source environment from target list
const targetEnvironments = environments.filter(
- (env: Environment) => env.environmentId !== sourceEnvironment.environmentId
+ (env: Environment) => env.environmentId !== sourceEnvironment.environmentId && env.isLicensed !== false
);
const handleDeploy = async () => {
@@ -46,7 +47,7 @@ function DeployItemModal({
const targetEnv = environments.find(env => env.environmentId === values.targetEnvId);
if (!targetEnv) {
- message.error('Target environment not found');
+ messageInstance.error('Target environment not found');
return;
}
@@ -58,12 +59,12 @@ function DeployItemModal({
// Execute deployment
await config.deploy.execute(params);
- message.success(`Successfully deployed ${item.name} to target environment`);
+ messageInstance.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.deploy.singularLabel.toLowerCase()}`);
+ messageInstance.error(`Failed to deploy ${config.deploy.singularLabel.toLowerCase()}`);
} finally {
setDeploying(false);
}
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx
index 82682075f..89ec73e39 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/EditEnvironmentModal.tsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
-import { Modal, Form, Input, Select, Switch, Button, message } from 'antd';
+import { Modal, Form, Input, Select, Switch, Button } from 'antd';
import { Environment } from '../types/environment.types';
const { Option } = Select;
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx
index 16f9fc6fd..287c9ff00 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentsTable.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { Table, Tag, Button, Tooltip, Space, Card, Row, Col, Typography, Avatar } from 'antd';
-import { EditOutlined, AuditOutlined, LinkOutlined, EnvironmentOutlined, StarFilled, CloudServerOutlined } from '@ant-design/icons';
+import { Table, Tag, Button, Tooltip, Space, Card, Row, Col, Typography, Avatar, Spin, Alert } from 'antd';
+import { EditOutlined, AuditOutlined, LinkOutlined, EnvironmentOutlined, StarFilled, CloudServerOutlined, CheckCircleOutlined, CloseCircleOutlined, ExclamationCircleOutlined, SyncOutlined } from '@ant-design/icons';
import { Environment } from '../types/environment.types';
import { getEnvironmentTagColor, formatEnvironmentType } from '../utils/environmentUtils';
@@ -27,6 +27,11 @@ const EnvironmentsTable: React.FC = ({
window.open(auditUrl, '_blank');
};
+ // Handle row click - allow navigation to all environments including unlicensed
+ const handleRowClick = (env: Environment) => {
+ onRowClick(env);
+ };
+
// Generate background color for environment avatar
const getAvatarColor = (name: string) => {
let hash = 0;
@@ -44,6 +49,47 @@ const EnvironmentsTable: React.FC = ({
return `hsl(${hue}, 70%, 50%)`;
};
+ // Get license status icon and color
+ const getLicenseStatusDisplay = (env: Environment) => {
+ switch (env.licenseStatus) {
+ case 'checking':
+ return {
+ icon: ,
+ color: '#1890ff',
+ text: 'Checking...',
+ status: 'processing' as const
+ };
+ case 'licensed':
+ return {
+ icon: ,
+ color: '#52c41a',
+ text: 'Licensed',
+ status: 'success' as const
+ };
+ case 'unlicensed':
+ return {
+ icon: ,
+ color: '#ff4d4f',
+ text: 'Not Licensed',
+ status: 'error' as const
+ };
+ case 'error':
+ return {
+ icon: ,
+ color: '#faad14',
+ text: 'License Error',
+ status: 'warning' as const
+ };
+ default:
+ return {
+ icon: ,
+ color: '#d9d9d9',
+ text: 'Unknown',
+ status: 'default' as const
+ };
+ }
+ };
+
// For card display, we'll use a custom layout instead of Table
if (environments.length === 0) {
return null;
@@ -52,103 +98,185 @@ const EnvironmentsTable: React.FC = ({
return (
- {environments.map(env => (
-
- onRowClick(env)}
- >
-
-
-
{
+ const licenseDisplay = getLicenseStatusDisplay(env);
+ const isAccessible = env.isLicensed !== false;
+
+ return (
+
+ handleRowClick(env)}
+ >
+ {/* Subtle overlay for unlicensed environments */}
+ {!isAccessible && (
+
+ {/* Not Licensed Badge */}
+
}
- />
-
-
- {env.environmentName || 'Unnamed Environment'}
- {env.isMaster && (
-
-
-
- )}
-
-
- {formatEnvironmentType(env.environmentType)}
-
+ gap: '6px',
+ boxShadow: '0 2px 6px rgba(0,0,0,0.15)'
+ }}>
+ {licenseDisplay.icon}
+ {licenseDisplay.text}
+
-
-
-
- }
- onClick={(e) => openAuditPage(env.environmentId, e)}
- size="small"
- style={{ borderRadius: '50%', width: '32px', height: '32px' }}
+ )}
+
+
-
-
-
-
-
ID:
-
- {env.environmentId}
-
+
+
+ {env.environmentName || 'Unnamed Environment'}
+ {env.isMaster && (
+
+
+
+ )}
+
+
+
+ {formatEnvironmentType(env.environmentType)}
+
+
+ {licenseDisplay.text}
+
+
+
-
-
-
-
-
Master:
-
- {env.isMaster ? 'Yes' : 'No'}
-
+ {/* Only show audit button for licensed environments */}
+ {isAccessible && (
+
+
+ }
+ onClick={(e) => openAuditPage(env.environmentId, e)}
+ size="small"
+ style={{
+ borderRadius: '50%',
+ width: '32px',
+ height: '32px'
+ }}
+ />
+
+
+ )}
+
+
+
+
+
+ ID:
+ {isAccessible ? (
+
+ {env.environmentId}
+
+ ) : (
+
+ {env.environmentId}
+
+ )}
+
+
+
+
+
+ Master:
+
+ {env.isMaster ? 'Yes' : 'No'}
+
+
+
+
+
License:
+
+
+ {licenseDisplay.icon}
+
+
+ {licenseDisplay.text}
+
+
+
-
-
-
- ))}
+
+
+ );
+ })}
{environments.length > 10 && (
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx
index a42f604c1..66d8d80c8 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/QueriesTab.tsx
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
-import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col } from 'antd';
+import { Card, Button, Divider, Alert, Table, Tag, Input, Space, Tooltip, Row, Col } from 'antd';
+import { messageInstance } from 'lowcoder-design/src/components/GlobalInstances';
import {
SyncOutlined,
CloudUploadOutlined,
@@ -115,10 +116,10 @@ const QueriesTab: React.FC
= ({ environment, workspaceId }) =>
unmanaged: prev.total - managed
}));
- message.success(`${query.name} is now ${checked ? 'Managed' : 'Unmanaged'}`);
+ messageInstance.success(`${query.name} is now ${checked ? 'Managed' : 'Unmanaged'}`);
return true;
} catch (error) {
- message.error(`Failed to change managed status for ${query.name}`);
+ messageInstance.error(`Failed to change managed status for ${query.name}`);
return false;
} finally {
setRefreshing(false);
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/UnlicensedEnvironmentView.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/UnlicensedEnvironmentView.tsx
new file mode 100644
index 000000000..6b61379dd
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/UnlicensedEnvironmentView.tsx
@@ -0,0 +1,225 @@
+import React, { useState } from 'react';
+import { Button, Card, Space, Typography, Row, Col } from 'antd';
+import {
+ CustomerServiceOutlined,
+ EditOutlined,
+ ArrowLeftOutlined,
+ CloseCircleOutlined,
+ ExclamationCircleOutlined,
+ WarningOutlined
+} from '@ant-design/icons';
+import { Environment } from '../types/environment.types';
+import ContactLowcoderModal from './ContactLowcoderModal';
+import history from "@lowcoder-ee/util/history";
+
+const { Title, Text } = Typography;
+
+interface UnlicensedEnvironmentViewProps {
+ environment: Environment;
+ onEditClick: () => void;
+}
+
+/**
+ * Modern UI for unlicensed environments
+ */
+const UnlicensedEnvironmentView: React.FC = ({
+ environment,
+ onEditClick
+}) => {
+ const [isContactModalVisible, setIsContactModalVisible] = useState(false);
+
+ const getLicenseIcon = () => {
+ switch (environment.licenseStatus) {
+ case 'unlicensed':
+ return ;
+ case 'error':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getLicenseTitle = () => {
+ switch (environment.licenseStatus) {
+ case 'unlicensed':
+ return 'Environment Not Licensed';
+ case 'error':
+ return 'License Configuration Error';
+ default:
+ return 'License Issue';
+ }
+ };
+
+ const getLicenseDescription = () => {
+ if (environment.licenseError) {
+ return environment.licenseError;
+ }
+
+ switch (environment.licenseStatus) {
+ case 'unlicensed':
+ return 'This environment requires a valid license to access its features and functionality.';
+ case 'error':
+ return 'There was an error validating the license for this environment. Please check the configuration.';
+ default:
+ return 'This environment has license-related issues that need to be resolved.';
+ }
+ };
+
+ return (
+
+
+
+
+ {/* Main Status Card */}
+
+ {/* Status Icon */}
+
+ {getLicenseIcon()}
+
+
+ {/* Environment Info */}
+
+
+ {getLicenseTitle()}
+
+
+ {getLicenseDescription()}
+
+
+ {/* Environment Details */}
+
+ Environment:
+
+ {environment.environmentName || 'Unnamed Environment'}
+
+
+ ID: {environment.environmentId}
+
+
+
+
+ {/* Action Buttons */}
+
+ }
+ onClick={() => setIsContactModalVisible(true)}
+ style={{
+ width: '100%',
+ height: '48px',
+ borderRadius: '8px',
+ fontSize: '16px',
+ fontWeight: 500,
+ background: 'linear-gradient(135deg, #1890ff 0%, #0050b3 100%)',
+ border: 'none',
+ boxShadow: '0 4px 12px rgba(24, 144, 255, 0.3)'
+ }}
+ >
+ Contact Lowcoder Team
+
+
+ }
+ onClick={onEditClick}
+ style={{
+ width: '100%',
+ height: '48px',
+ borderRadius: '8px',
+ fontSize: '16px',
+ fontWeight: 500,
+ borderColor: '#d9d9d9',
+ color: '#595959'
+ }}
+ >
+ Edit Environment
+
+
+ }
+ onClick={() => history.push("/setting/environments")}
+ style={{
+ width: '100%',
+ height: '48px',
+ borderRadius: '8px',
+ fontSize: '16px',
+ fontWeight: 500,
+ borderColor: '#d9d9d9',
+ color: '#8c8c8c'
+ }}
+ >
+ Back to Environments
+
+
+
+
+ {/* Footer Help Text */}
+
+ Need assistance? Contact our team for licensing support or edit the environment configuration to resolve this issue.
+
+
+
+
+
+ {/* Contact Lowcoder Modal */}
+
setIsContactModalVisible(false)}
+ environment={environment}
+ />
+
+ );
+};
+
+export default UnlicensedEnvironmentView;
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx
index ff079a9df..0f11dac61 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/UserGroupsTab.tsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
-import { Card, Button, Alert, message, Table, Tag, Input, Space, Row, Col, Avatar, Tooltip } from 'antd';
+import { Card, Button, Alert, Table, Tag, Input, Space, Row, Col, Avatar, Tooltip } from 'antd';
import { SyncOutlined, TeamOutlined, UserOutlined, UsergroupAddOutlined, SettingOutlined, CodeOutlined } from '@ant-design/icons';
import Title from 'antd/lib/typography/Title';
import { Environment } from '../types/environment.types';
diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx
index eaf8b2a17..006ffb1f6 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspacesTab.tsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
-import { Card, Button, Divider, Alert, message, Table, Tag, Input, Space, Tooltip, Row, Col, Avatar } from 'antd';
+import { Card, Button, Divider, Alert, Table, Tag, Input, Space, Tooltip, Row, Col, Avatar } from 'antd';
import { SyncOutlined, AuditOutlined, TeamOutlined, CheckCircleFilled, CloudServerOutlined, DisconnectOutlined, FilterOutlined } from '@ant-design/icons';
import Title from 'antd/lib/typography/Title';
import { Environment } from '../types/environment.types';
diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/EnvironmentContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/EnvironmentContext.tsx
index 9f9c7ec3c..7594aa404 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/context/EnvironmentContext.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/context/EnvironmentContext.tsx
@@ -7,8 +7,8 @@ import React, {
useCallback,
ReactNode,
} from "react";
-import { message } from "antd";
-import { getEnvironments } from "../services/environments.service";
+import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
+import { getEnvironmentsWithLicenseStatus } from "../services/environments.service";
import { Environment } from "../types/environment.types";
interface EnvironmentContextState {
@@ -59,12 +59,12 @@ export const EnvironmentProvider: React.FC = ({
setError(null);
try {
- const data = await getEnvironments();
+ const data = await getEnvironmentsWithLicenseStatus();
setEnvironments(data);
- } catch (err) {
- const errorMessage = err instanceof Error ? err.message : "Failed to load environments list";
- setError(errorMessage);
- message.error(errorMessage);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : 'Failed to fetch environments';
+ messageInstance.error(errorMessage);
+ console.error('Error fetching environments:', error);
} finally {
setIsLoading(false);
}
diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/SingleEnvironmentContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/SingleEnvironmentContext.tsx
index bd93bf9f2..7ca5f0b70 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/context/SingleEnvironmentContext.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/context/SingleEnvironmentContext.tsx
@@ -7,7 +7,7 @@ import React, {
useCallback,
ReactNode,
} from "react";
- import { message } from "antd";
+ import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import { useParams } from "react-router-dom";
import { getEnvironmentById, updateEnvironment } from "../services/environments.service";
import { Environment } from "../types/environment.types";
@@ -100,7 +100,7 @@ import React, {
const updatedEnv = await updateEnvironment(environmentId, data);
// Show success message
- message.success("Environment updated successfully");
+ messageInstance.success("Environment updated successfully");
// Refresh both the single environment and environments list
await Promise.all([
@@ -111,7 +111,7 @@ import React, {
return updatedEnv;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Failed to update environment";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw err;
}
}, [environment, environmentId, fetchEnvironment, refreshEnvironments]);
diff --git a/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx b/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx
index f2e16d442..988c11e5a 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx
+++ b/client/packages/lowcoder/src/pages/setting/environments/context/WorkspaceContext.tsx
@@ -7,7 +7,7 @@ import React, {
useCallback,
ReactNode,
} from "react";
- import { message } from "antd";
+ import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import { useParams } from "react-router-dom";
import { useSingleEnvironmentContext } from "./SingleEnvironmentContext";
import { fetchWorkspaceById } from "../services/environments.service";
@@ -96,8 +96,9 @@ import React, {
...workspaceData,
managed: isManaged
});
- } catch (err) {
- const errorMessage = err instanceof Error ? err.message : "Workspace not found or failed to load";
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : 'Failed to fetch workspace';
+ messageInstance.error(errorMessage);
setError(errorMessage);
} finally {
setIsLoading(false);
@@ -135,7 +136,7 @@ import React, {
return true;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Failed to update managed status";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
return false;
}
}, [workspace, environment]);
diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts
index 8accb9db1..6d9d40eaf 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts
@@ -1,5 +1,5 @@
// services/appService.ts
-import { message } from "antd";
+import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import { getWorkspaceApps } from "./environments.service";
import { getManagedApps } from "./enterprise.service";
import { App, AppStats } from "../types/app.types";
@@ -99,7 +99,7 @@ export async function getMergedWorkspaceApps(
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Failed to fetch apps";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts
index cca28176d..0e181a6da 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts
@@ -1,6 +1,6 @@
// services/dataSources.service.ts
import axios from 'axios';
-import { message } from "antd";
+import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import { DataSource, DataSourceWithMeta } from "../types/datasource.types";
import { getManagedObjects, ManagedObject, ManagedObjectType , transferManagedObject } from "./managed-objects.service";
@@ -66,7 +66,7 @@ export async function getWorkspaceDataSources(
} catch (error) {
// Handle and transform error
const errorMessage = error instanceof Error ? error.message : 'Failed to fetch data sources';
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -145,7 +145,7 @@ export async function getMergedWorkspaceDataSources(
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Failed to fetch data sources";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -171,7 +171,8 @@ export async function deployDataSource(params: DeployDataSourceParams): Promise<
}
return response.status === 200;
} catch (error) {
- console.error('Error deploying data source:', error);
+ const errorMessage = error instanceof Error ? error.message : 'Failed to deploy data source';
+ messageInstance.error(errorMessage);
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
index f0f9f1939..f676f1734 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/services/enterprise.service.ts
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/enterprise.service.ts
@@ -1,5 +1,5 @@
import axios from "axios";
-import { message } from "antd";
+import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import { ManagedOrg } from "../types/enterprise.types";
import { Query } from "../types/query.types";
@@ -26,7 +26,7 @@ export async function getManagedWorkspaces(
return all.filter(org => org.environmentId === environmentId);
} catch (err) {
const errorMsg = err instanceof Error ? err.message : "Failed to fetch managed workspaces";
- message.error(errorMsg);
+ messageInstance.error(errorMsg);
throw err;
}
}
@@ -63,7 +63,7 @@ export async function connectManagedWorkspace(
return res.data;
} catch (err) {
const errorMsg = err instanceof Error ? err.message : "Failed to connect org";
- message.error(errorMsg);
+ messageInstance.error(errorMsg);
throw err;
}
}
@@ -88,7 +88,7 @@ export async function unconnectManagedWorkspace(orgGid: string) {
} catch (err) {
const errorMsg =
err instanceof Error ? err.message : "Failed to unconnect org";
- message.error(errorMsg);
+ messageInstance.error(errorMsg);
throw err;
}
}
@@ -124,7 +124,7 @@ export async function connectManagedApp(
} catch (err) {
const errorMsg =
err instanceof Error ? err.message : "Failed to connect app";
- message.error(errorMsg);
+ messageInstance.error(errorMsg);
throw err;
}
}
@@ -137,7 +137,7 @@ export async function unconnectManagedApp(appGid: string) {
});
} catch (err) {
const errorMsg = err instanceof Error ? err.message : "Failed to unconnect app";
- message.error(errorMsg);
+ messageInstance.error(errorMsg);
throw err;
}
}
@@ -151,7 +151,8 @@ export const getManagedDataSources = async (environmentId: string): Promise
}));
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : 'Failed to fetch managed queries';
- message.error(errorMessage);
+ const errorMsg = error instanceof Error ? error.message : 'Failed to fetch queries';
+ messageInstance.error(errorMsg);
throw error;
}
}
@@ -250,8 +253,8 @@ export async function connectManagedQuery(
return response.status === 200;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : 'Failed to connect query';
- message.error(errorMessage);
+ const errorMsg = error instanceof Error ? error.message : 'Failed to deploy query';
+ messageInstance.error(errorMsg);
throw error;
}
}
@@ -272,8 +275,8 @@ export async function unconnectManagedQuery(queryGid: string): Promise
return response.status === 200;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : 'Failed to disconnect query';
- message.error(errorMessage);
+ const errorMsg = error instanceof Error ? error.message : 'Failed to disconnect query';
+ messageInstance.error(errorMsg);
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
index e34c168b3..b0c489b7a 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/environments.service.ts
@@ -1,11 +1,12 @@
import axios from "axios";
-import { message } from "antd";
+import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
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";
+import { checkEnvironmentLicense } from './license.service';
@@ -39,12 +40,47 @@ export async function updateEnvironment(
return res.data;
} catch (err) {
const errorMsg = err instanceof Error ? err.message : "Failed to update environment";
- message.error(errorMsg);
+ messageInstance.error(errorMsg);
throw err;
}
}
+/**
+ * Create a new environment manually
+ * @param environmentData - Environment data to create
+ * @returns Promise with created environment data
+ */
+export async function createEnvironment(
+ environmentData: Partial
+): Promise {
+ 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.post(`/api/plugins/enterprise/environments`, payload);
+
+ if (res.data) {
+ messageInstance.success("Environment created successfully");
+ return res.data;
+ } else {
+ throw new Error("Failed to create environment");
+ }
+ } catch (err) {
+ const errorMsg = err instanceof Error ? err.message : "Failed to create environment";
+ messageInstance.error(errorMsg);
+ throw err;
+ }
+}
/**
* Fetch all environments
@@ -62,7 +98,7 @@ export async function getEnvironments(): Promise {
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Failed to fetch environments";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -82,11 +118,40 @@ export async function getEnvironmentById(id: string): Promise {
throw new Error("Failed to fetch environment");
}
- return response.data.data;
+ const environment = response.data.data;
+
+ // Check license status for the environment
+ const envWithLicense: Environment = {
+ ...environment,
+ licenseStatus: 'checking'
+ };
+
+ try {
+ if (environment.environmentApiServiceUrl) {
+ const licenseInfo = await checkEnvironmentLicense(
+ environment.environmentApiServiceUrl,
+ environment.environmentApikey
+ );
+
+ envWithLicense.isLicensed = licenseInfo.isValid;
+ envWithLicense.licenseStatus = licenseInfo.isValid ? 'licensed' : 'unlicensed';
+ envWithLicense.licenseError = licenseInfo.error;
+ } else {
+ envWithLicense.isLicensed = false;
+ envWithLicense.licenseStatus = 'error';
+ envWithLicense.licenseError = 'API service URL not configured';
+ }
+ } catch (error) {
+ envWithLicense.isLicensed = false;
+ envWithLicense.licenseStatus = 'error';
+ envWithLicense.licenseError = error instanceof Error ? error.message : 'License check failed';
+ }
+
+ return envWithLicense;
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Failed to fetch environment";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -100,6 +165,7 @@ export async function getEnvironmentById(id: string): Promise {
* Fetch workspaces for a specific environment
* @param environmentId - ID of the environment
* @param apiKey - API key for the environment
+ * @param apiServiceUrl - API service URL for the environment
* @returns Promise with an array of workspaces
*/
export async function getEnvironmentWorkspaces(
@@ -158,7 +224,7 @@ export async function getEnvironmentWorkspaces(
// Handle and transform error
const errorMessage =
error instanceof Error ? error.message : "Failed to fetch workspaces";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -208,7 +274,7 @@ export async function getEnvironmentUserGroups(
} catch (error) {
// Handle and transform error
const errorMessage = error instanceof Error ? error.message : 'Failed to fetch user groups';
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -297,7 +363,10 @@ export async function getWorkspaceApps(
// Then fetch applications without the orgId parameter
const response = await axios.get(`${apiServiceUrl}/api/applications/list`, {
- headers
+ headers,
+ params: {
+ withContainerSize: false
+ }
});
// Check if response is valid
@@ -305,12 +374,17 @@ export async function getWorkspaceApps(
return [];
}
- return response.data.data;
+ // Filter out DELETED apps
+ const apps = response.data.data.filter((app: any) =>
+ app.applicationStatus !== 'DELETED'
+ );
+
+ return apps;
} catch (error) {
// Handle and transform error
- const errorMessage = error instanceof Error ? error.message : 'Failed to fetch apps';
- message.error(errorMessage);
+ const errorMessage = error instanceof Error ? error.message : 'Failed to fetch workspace apps';
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -367,8 +441,8 @@ export async function getWorkspaceDataSources(
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);
+ const errorMessage = error instanceof Error ? error.message : 'Failed to fetch workspace data sources';
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -449,8 +523,59 @@ export async function getWorkspaceQueries(
} catch (error) {
// Handle and transform error
- const errorMessage = error instanceof Error ? error.message : 'Failed to fetch queries';
- message.error(errorMessage);
+ const errorMessage = error instanceof Error ? error.message : 'Failed to fetch workspace queries';
+ messageInstance.error(errorMessage);
+ throw error;
+ }
+}
+
+/**
+ * Fetch all environments and check their license status
+ * @returns Promise with environments data including license status
+ */
+export async function getEnvironmentsWithLicenseStatus(): Promise {
+ try {
+ // First fetch all environments
+ const environments = await getEnvironments();
+
+ // Check license status for each environment in parallel
+ const environmentsWithLicense = await Promise.all(
+ environments.map(async (env) => {
+ const envWithLicense: Environment = {
+ ...env,
+ licenseStatus: 'checking'
+ };
+
+ try {
+ if (env.environmentApiServiceUrl) {
+ const licenseInfo = await checkEnvironmentLicense(
+ env.environmentApiServiceUrl,
+ env.environmentApikey
+ );
+
+ envWithLicense.isLicensed = licenseInfo.isValid;
+ envWithLicense.licenseStatus = licenseInfo.isValid ? 'licensed' : 'unlicensed';
+ envWithLicense.licenseError = licenseInfo.error;
+ } else {
+ envWithLicense.isLicensed = false;
+ envWithLicense.licenseStatus = 'error';
+ envWithLicense.licenseError = 'API service URL not configured';
+ }
+ } catch (error) {
+ envWithLicense.isLicensed = false;
+ envWithLicense.licenseStatus = 'error';
+ envWithLicense.licenseError = error instanceof Error ? error.message : 'License check failed';
+ }
+
+ return envWithLicense;
+ })
+ );
+
+ return environmentsWithLicense;
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error ? error.message : "Failed to fetch environments";
+ messageInstance.error(errorMessage);
throw error;
}
}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/license.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/license.service.ts
new file mode 100644
index 000000000..ac4a1fd96
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/license.service.ts
@@ -0,0 +1,70 @@
+import axios from 'axios';
+import { EnvironmentLicense } from '../types/environment.types';
+
+/**
+ * Check license status for an environment
+ * @param apiServiceUrl - API service URL for the environment
+ * @param apiKey - API key for the environment
+ * @returns Promise with license information
+ */
+export async function checkEnvironmentLicense(
+ apiServiceUrl: string,
+ apiKey?: string
+): Promise {
+ try {
+ if (!apiServiceUrl) {
+ return {
+ isValid: false,
+ error: 'API service URL is required'
+ };
+ }
+
+ // Prepare headers with API key if available
+ const headers: Record = {};
+ if (apiKey) {
+ headers.Authorization = `Bearer ${apiKey}`;
+ }
+
+ // Make request to the license endpoint
+ const response = await axios.get(
+ `${apiServiceUrl}/api/plugins/enterprise/license`,
+ {
+ headers,
+ timeout: 5000 // 5 second timeout
+ }
+ );
+
+ // If we get a successful response, the license is valid
+ return {
+ isValid: true
+ };
+
+ } catch (error) {
+ // If the endpoint doesn't exist or returns an error, license is invalid
+ if (axios.isAxiosError(error)) {
+ if (error.response?.status === 404) {
+ return {
+ isValid: false,
+ error: 'License endpoint not found'
+ };
+ }
+ if (error.response?.status === 401 || error.response?.status === 403) {
+ return {
+ isValid: false,
+ error: 'License authentication failed'
+ };
+ }
+ if (error.code === 'ECONNABORTED') {
+ return {
+ isValid: false,
+ error: 'License check timeout'
+ };
+ }
+ }
+
+ return {
+ isValid: false,
+ error: error instanceof Error ? error.message : 'License check failed'
+ };
+ }
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts
index 589ea6a5a..0a4e0b14a 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/managed-objects.service.ts
@@ -1,5 +1,5 @@
import axios from "axios";
-import { message } from "antd";
+import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
// Object types that can be managed
export enum ManagedObjectType {
@@ -51,7 +51,7 @@ export async function isManagedObject(
}
const errorMessage = error instanceof Error ? error.message : "Failed to check managed status";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -88,7 +88,7 @@ export async function setManagedObject(
return response.status === 200;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : `Failed to set ${objType} as managed`;
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -122,7 +122,7 @@ export async function unsetManagedObject(
return response.status === 200;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : `Failed to remove ${objType} from managed`;
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -147,7 +147,7 @@ export async function getManagedObjects(
return response.data.data;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Failed to fetch managed objects";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
@@ -185,7 +185,7 @@ export async function getSingleManagedObject(
}
const errorMessage = error instanceof Error ? error.message : "Failed to fetch managed object";
- message.error(errorMessage);
+ messageInstance.error(errorMessage);
throw error;
}
}
diff --git a/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts b/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts
index 871c0e036..3683b97d7 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts
+++ b/client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts
@@ -1,5 +1,5 @@
// services/workspacesService.ts (or wherever makes sense in your structure)
-import { message } from "antd";
+import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import { getEnvironmentWorkspaces } from "./environments.service";
import { getManagedObjects, ManagedObject, ManagedObjectType, transferManagedObject } from "./managed-objects.service";
import { Workspace } from "../types/workspace.types";
@@ -69,9 +69,8 @@ export async function getMergedEnvironmentWorkspaces(
}
};
} catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : "Failed to fetch workspaces";
- message.error(errorMessage);
+ const errorMessage = error instanceof Error ? error.message : "Failed to fetch workspaces";
+ messageInstance.error(errorMessage);
throw error;
}
}
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
index 39766c1ea..388fbab02 100644
--- a/client/packages/lowcoder/src/pages/setting/environments/types/environment.types.ts
+++ b/client/packages/lowcoder/src/pages/setting/environments/types/environment.types.ts
@@ -14,4 +14,16 @@ export interface Environment {
isMaster: boolean;
createdAt: string;
updatedAt: string;
+ // License-related properties
+ isLicensed?: boolean;
+ licenseStatus?: 'checking' | 'licensed' | 'unlicensed' | 'error';
+ licenseError?: string;
+}
+
+/**
+ * Interface representing license information for an environment
+ */
+export interface EnvironmentLicense {
+ isValid: boolean;
+ error?: string;
}
\ No newline at end of file