diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx index 041997779..245e12971 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentDetail.tsx @@ -10,6 +10,10 @@ import { Button, Tag, Result, + Row, + Col, + Statistic, + Progress, } from "antd"; import { LinkOutlined, @@ -21,6 +25,11 @@ import { CloseCircleOutlined, ExclamationCircleOutlined, SyncOutlined, + CloudServerOutlined, + UserOutlined, + SafetyOutlined, + CrownOutlined, + ApiOutlined, } from "@ant-design/icons"; import { useSingleEnvironmentContext } from "./context/SingleEnvironmentContext"; @@ -31,10 +40,12 @@ import history from "@lowcoder-ee/util/history"; import WorkspacesTab from "./components/WorkspacesTab"; import UserGroupsTab from "./components/UserGroupsTab"; import EnvironmentHeader from "./components/EnvironmentHeader"; +import StatsCard from "./components/StatsCard"; import ModernBreadcrumbs from "./components/ModernBreadcrumbs"; import { getEnvironmentTagColor } from "./utils/environmentUtils"; +import { formatAPICalls, getAPICallsStatusColor } from "./services/license.service"; import ErrorComponent from './components/ErrorComponent'; -const { TabPane } = Tabs; +import { Level1SettingPageContent } from "../styled"; /** * Environment Detail Page Component @@ -124,33 +135,80 @@ const EnvironmentDetail: React.FC = () => { ); } - const breadcrumbItems = [ + // Stats data for the cards + const statsData = [ { - key: 'environments', - title: ( + title: "Type", + value: environment.environmentType || "Unknown", + icon: , + color: getEnvironmentTagColor(environment.environmentType) + }, + { + title: "Status", + value: environment.isLicensed ? "Licensed" : "Unlicensed", + icon: environment.isLicensed ? : , + color: environment.isLicensed ? "#52c41a" : "#ff4d4f" + }, + { + title: "API Key", + value: environment.environmentApikey ? "Configured" : "Not Set", + icon: , + color: environment.environmentApikey ? "#1890ff" : "#faad14" + }, + { + title: "Master Env", + value: environment.isMaster ? "Yes" : "No", + icon: , + color: environment.isMaster ? "#722ed1" : "#8c8c8c" + } + ]; + + const tabItems = [ + { + key: 'workspaces', + label: ( - Environments + Workspaces ), - onClick: () => history.push("/setting/environments") + children: }, { - key: 'currentEnvironment', - title: environment.environmentName + key: 'userGroups', + label: ( + + User Groups + + ), + children: } ]; return ( -
+ + {/* Breadcrumbs */} + + {/* Environment Header Component */} + {/* Stats Cards Row */} + + {statsData.map((stat, index) => ( + + + + ))} + + {/* Basic Environment Information Card */} { "No domain set" )} - - - {environment.environmentType} - + + + {environment.environmentId} + {(() => { @@ -196,29 +251,178 @@ const EnvironmentDetail: React.FC = () => { case 'licensed': return } color="green" style={{ borderRadius: '4px' }}>Licensed; case 'unlicensed': - return } color="red" style={{ borderRadius: '4px' }}>Not Licensed; + return } color="orange" style={{ borderRadius: '4px' }}>License Needed; case 'error': - return } color="orange" style={{ borderRadius: '4px' }}>License Error; + return } color="orange" style={{ borderRadius: '4px' }}>Setup Required; default: return Unknown; } })()} - - {environment.environmentApikey ? ( - Configured - ) : ( - Not Configured - )} - - - {environment.isMaster ? "Yes" : "No"} + + {environment.createdAt ? new Date(environment.createdAt).toLocaleDateString() : "Unknown"} - {/* Modern Breadcrumbs navigation */} - + history.push('/setting/environments') + }, + { + key: 'current', + title: environment.environmentName || "Environment Detail" + } + ]} + /> + {/* Detailed License Information Card - only show for licensed environments with details */} + {environment.isLicensed && environment.licenseDetails && ( + + + License Details + + } + style={{ + marginBottom: "24px", + borderRadius: '4px', + border: '1px solid #f0f0f0' + }} + className="license-details-card" + > + + {/* API Calls Status */} + + + ( + + {value?.toLocaleString()} + + )} + prefix={} + /> +
+ +
+ {environment.licenseDetails.apiCallsUsage || 0}% used +
+
+
+ + + {/* Total License Limit */} + + + value?.toLocaleString()} + prefix={} + /> + + {environment.licenseDetails.eeLicenses.length} License{environment.licenseDetails.eeLicenses.length !== 1 ? 's' : ''} + + + + + {/* Enterprise Edition Status */} + + + ( + : } + > + {value} + + )} + /> + + +
+ + {/* License Details */} +
+ + + License Information + + + + {environment.licenseDetails.eeLicenses.map((license, index) => ( + + +
+ + {license.customerName} + +
+
+ ID: {license.customerId} +
+
+ UUID: {license.uuid.substring(0, 8)}... +
+ + {license.apiCallsLimit.toLocaleString()} calls + +
+ + ))} +
+
+
+ )} + {/* Tabs for Workspaces and User Groups */} { onChange={setActiveTab} className="modern-tabs" type="line" - > - - Workspaces - - } - key="workspaces" - > - - - - - User Groups - - } - key="userGroups" - > - - - + items={tabItems} + /> {/* Edit Environment Modal */} {environment && ( @@ -261,7 +444,7 @@ const EnvironmentDetail: React.FC = () => { loading={isUpdating} /> )} -
+ ); }; diff --git a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx index 7b041b81f..36be33166 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/EnvironmentsList.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { Alert, Empty, Spin, Card } from "antd"; +import { Alert, Empty, Spin, Card, Row, Col } from "antd"; import { SyncOutlined, CloudServerOutlined } from "@ant-design/icons"; import { AddIcon, Search, TacoButton } from "lowcoder-design"; import { useHistory } from "react-router-dom"; @@ -9,6 +9,7 @@ import { fetchEnvironments } from "redux/reduxActions/enterpriseActions"; import { Environment } from "./types/environment.types"; import EnvironmentsTable from "./components/EnvironmentsTable"; import CreateEnvironmentModal from "./components/CreateEnvironmentModal"; +import StatsCard from "./components/StatsCard"; import { buildEnvironmentId } from "@lowcoder-ee/constants/routesURL"; import { createEnvironment } from "./services/environments.service"; import { getEnvironmentTagColor } from "./utils/environmentUtils"; @@ -107,37 +108,6 @@ const EnvironmentsList: React.FC = () => { return ; }; - // Stat card component - const StatCard = ({ title, value, color }: { title: string; value: number; color: string }) => ( - -
-
-
{title}
-
{value}
-
-
- {getEnvironmentIcon(title)} -
-
-
- ); - // Filter environments based on search text const filteredEnvironments = environments.filter((env) => { const searchLower = searchText.toLowerCase(); @@ -201,75 +171,65 @@ const EnvironmentsList: React.FC = () => { > Refresh - setIsCreateModalVisible(true)}> - New Environment + } + onClick={() => setIsCreateModalVisible(true)} + > + Add Environment - {/* Environment Type Statistics */} - {!isLoading && environments.length > 0 && ( - -
- {environmentStats.map(([type, count]) => ( -
- -
- ))} -
-
- )} + {/* Environment Statistics Cards */} + + + {environmentStats.map(([type, count]) => ( + + + + ))} + + - {/* Error handling */} {error && ( )} - {/* Loading, empty state or table */} - {isLoading ? ( -
- -
- ) : environments.length === 0 && !error ? ( - - ) : filteredEnvironments.length === 0 ? ( - - ) : ( - /* Table component */ + )} + + {(filteredEnvironments.length > 0 || isLoading) && ( )} - - {/* Results counter when searching */} - {searchText && filteredEnvironments.length !== environments.length && ( -
- Showing {filteredEnvironments.length} of {environments.length} environments -
- )}
- {/* Create Environment Modal */} setIsCreateModalVisible(false)} diff --git a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx index 6ed3a1427..8ca8d3294 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx @@ -4,6 +4,8 @@ import { Spin, Typography, Tabs, + Row, + Col, } from "antd"; import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; import { @@ -12,6 +14,8 @@ import { CodeOutlined, HomeOutlined, TeamOutlined, + CloudServerOutlined, + CheckCircleOutlined, } from "@ant-design/icons"; // Use the context hooks @@ -25,9 +29,9 @@ import DataSourcesTab from "./components/DataSourcesTab"; import QueriesTab from "./components/QueriesTab"; import ModernBreadcrumbs from "./components/ModernBreadcrumbs"; import WorkspaceHeader from "./components/WorkspaceHeader"; +import StatsCard from "./components/StatsCard"; import ErrorComponent from "./components/ErrorComponent"; - -const { TabPane } = Tabs; +import { Level1SettingPageContent } from "../styled"; const WorkspaceDetail: React.FC = () => { // Use the context hooks @@ -35,7 +39,6 @@ const WorkspaceDetail: React.FC = () => { const { workspace, isLoading, error, toggleManagedStatus } = useWorkspaceContext(); const { openDeployModal } = useDeployModal(); - const [isToggling, setIsToggling] = useState(false); // Handle toggle managed status @@ -58,7 +61,17 @@ const WorkspaceDetail: React.FC = () => { if (isLoading) { return (
- + +
+ Loading workspace details... +
+
); } @@ -98,13 +111,53 @@ const WorkspaceDetail: React.FC = () => { } ]; + const tabItems = [ + { + key: 'apps', + label: ( + + Apps + + ), + children: ( + + ) + }, + { + key: 'dataSources', + label: ( + + Data Sources + + ), + children: ( + + ) + }, + { + key: 'queries', + label: ( + + Queries + + ), + children: ( + + ) + } + ]; + return ( -
+ {/* New Workspace Header */} { onDeploy={() => openDeployModal(workspace, workspaceConfig, environment)} /> + {/* Stats Cards Row */} + + + : } + color={workspace.managed ? "#52c41a" : "#faad14"} + /> + + + } + color="#1890ff" + /> + + + } + color="#722ed1" + /> + + + } + color="#52c41a" + /> + + + {/* Modern Breadcrumbs navigation */} @@ -122,29 +211,9 @@ const WorkspaceDetail: React.FC = () => { defaultActiveKey="apps" className="modern-tabs" type="line" - > - Apps} key="apps"> - - - - Data Sources} key="dataSources"> - - - Queries} key="queries"> - - - - -
+ items={tabItems} + /> + ); }; diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/ContactLowcoderModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/ContactLowcoderModal.tsx index 145b08e98..34ab0526f 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/ContactLowcoderModal.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/ContactLowcoderModal.tsx @@ -100,7 +100,7 @@ const ContactLowcoderModal: React.FC = ({ width={800} centered style={{ top: 20 }} - bodyStyle={{ padding: '24px' }} + styles={{ body: { padding: '24px' } }} > {/* Environment Context Section */} = ({ background: '#fafafa', border: '1px solid #f0f0f0' }} - bodyStyle={{ padding: '16px' }} + styles={{ body: { padding: '16px' } }} > diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/CreateEnvironmentModal.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/CreateEnvironmentModal.tsx index 421a001e4..8a6132c0a 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/CreateEnvironmentModal.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/CreateEnvironmentModal.tsx @@ -192,9 +192,9 @@ const CreateEnvironmentModal: React.FC = ({ 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 593e4be2e..3b90bc826 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, Tooltip } from 'antd'; +import { Modal, Form, Input, Select, Switch, Button, Alert, Tooltip } from 'antd'; import { useSelector } from 'react-redux'; import { selectMasterEnvironment, selectHasMasterEnvironment } from 'redux/selectors/enterpriseSelectors'; import { Environment } from '../types/environment.types'; @@ -191,7 +191,13 @@ const EditEnvironmentModal: React.FC = ({ - + ); diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx index 0a999129e..43ecb5fc0 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/EnvironmentHeader.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Button, Tag, Typography, Row, Col } from 'antd'; import { EditOutlined, CloudServerOutlined } from '@ant-design/icons'; import { Environment } from '../types/environment.types'; -import { getEnvironmentTagColor, getEnvironmentHeaderGradient } from '../utils/environmentUtils'; +import { getEnvironmentTagColor } from '../utils/environmentUtils'; const { Title, Text } = Typography; @@ -24,11 +24,11 @@ const EnvironmentHeader: React.FC = ({ className="environment-header" style={{ marginBottom: "24px", - background: getEnvironmentHeaderGradient(environment.environmentType), + background: '#fff', padding: '20px 24px', borderRadius: '8px', - color: 'white', - boxShadow: '0 2px 8px rgba(0,0,0,0.1)' + border: '1px solid #f0f0f0', + boxShadow: '0 2px 8px rgba(0,0,0,0.06)' }} > @@ -36,35 +36,50 @@ const EnvironmentHeader: React.FC = ({
- + <Title level={3} style={{ margin: '0 0 8px 0', color: '#222222', fontWeight: '500' }}> {environment.environmentName || "Unnamed Environment"}
- + ID: {environment.environmentId} {environment.environmentType} {environment.isMaster && ( - + Master )} + {environment.isLicensed === false && ( + + Unlicensed + + )}
@@ -73,14 +88,10 @@ const EnvironmentHeader: React.FC = ({ -
- - {/* Footer Help Text */} - - Need assistance? Contact our team for licensing support or edit the environment configuration to resolve this issue. - - - - + + + + + + {/* Help Text */} + + + Need assistance? Contact our team for licensing support or edit the environment configuration to resolve this issue. + + {/* Contact Lowcoder Modal */} = ({ onClose={() => setIsContactModalVisible(false)} environment={environment} /> - + ); }; diff --git a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx index f4c00c22e..462e6bf35 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/components/WorkspaceHeader.tsx @@ -7,144 +7,20 @@ import { Tooltip, Row, Col, - Statistic, Avatar, Space, - Divider, - Card, - Dropdown, - Menu } from "antd"; import { CloudUploadOutlined, - SettingOutlined, TeamOutlined, - AppstoreOutlined, - DatabaseOutlined, - CodeOutlined, CloudServerOutlined, ClockCircleOutlined, - MoreOutlined, - StarOutlined, - StarFilled } from "@ant-design/icons"; import { Environment } from "../types/environment.types"; import { Workspace } from "../types/workspace.types"; -import styled from "styled-components"; const { Title, Text } = Typography; -// Styled components for custom design -const HeaderWrapper = styled.div` - border-radius: 12px; - overflow: hidden; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - position: relative; - margin-bottom: 24px; -`; - -const GradientBanner = styled.div<{ avatarColor: string }>` - background: linear-gradient(135deg, ${props => props.avatarColor} 0%, rgba(24, 144, 255, 0.8) 100%); - height: 140px; - position: relative; - overflow: hidden; - transition: background 1s ease-in-out; - - &::before { - content: ''; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: repeating-linear-gradient( - 45deg, - rgba(255,255,255,0.1), - rgba(255,255,255,0.1) 1px, - transparent 1px, - transparent 10px - ); - animation: moveBackground 30s linear infinite; - } - - @keyframes moveBackground { - 0% { - transform: translate(0, 0); - } - 100% { - transform: translate(100px, 100px); - } - } - - &:hover { - background: linear-gradient(135deg, rgba(24, 144, 255, 0.8) 0%, ${props => props.avatarColor} 100%); - transition: background 1s ease-in-out; - } -`; - -const ContentContainer = styled.div` - background-color: white; - padding: 24px; - position: relative; - transition: transform 0.3s ease-in-out; - - &:hover { - transform: translateY(-2px); - } -`; - -const AvatarContainer = styled.div` - position: absolute; - top: -50px; - left: 24px; - background: white; - padding: 4px; - border-radius: 8px; - border: 1px solid #f0f0f0; -`; - -const StatusBadge = styled(Tag)<{ $active?: boolean }>` - position: absolute; - top: 12px; - right: 12px; - font-weight: 500; - font-size: 12px; - padding: 4px 12px; - border-radius: 4px; - border: none; - background: ${props => props.$active ? '#52c41a' : '#f5f5f5'}; - color: ${props => props.$active ? 'white' : '#8c8c8c'}; -`; - -const StatCard = styled(Card)` - border-radius: 4px; - border: 1px solid #f0f0f0; - transition: all 0.3s; - - &:hover { - transform: translateY(-2px); - border-color: #d9d9d9; - } -`; - -const ActionButton = styled(Button)` - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - height: 32px; -`; - -const FavoriteButton = styled(Button)` - position: absolute; - top: 12px; - right: 80px; - border: none; - border-radius: 4px; - background: rgba(255, 255, 255, 0.9); - color: #722ed1; -`; - interface WorkspaceHeaderProps { workspace: Workspace; environment: Environment; @@ -172,7 +48,7 @@ const WorkspaceHeader: React.FC = ({ }; // Format date for last updated - const formatDate = (date: number | undefined) => { + const formatDate = (date: number | undefined) => { if (!date) return "N/A"; return new Date(date).toLocaleDateString("en-US", { month: "short", @@ -181,81 +57,97 @@ const WorkspaceHeader: React.FC = ({ }); }; - - - - return ( - - - - {workspace.managed ? "Managed" : "Unmanaged"} - - - - - - - - {workspace.name.charAt(0).toUpperCase()} - - - - - - - {workspace.name} - - - ID: {workspace.id} - - - created on {formatDate(workspace.creationDate)} - - - {environment.environmentName} - - - - - -
-
- Managed: - -
- - } - onClick={onDeploy} - disabled={!workspace.managed} +
+ + +
+ + {workspace.name.charAt(0).toUpperCase()} + +
+ + {workspace.name} + +
+ + ID: {workspace.id} + + + + {formatDate(workspace.creationDate)} + + + + {environment.environmentName} + + - Deploy - - + {workspace.managed ? 'Managed' : 'Unmanaged'} + +
- - - - - - - +
+ + +
+
+ Managed: + +
+ + + +
+ +
+
); }; diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx index 591621862..ce802e8de 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx @@ -5,18 +5,10 @@ import { DataSource} from '../types/datasource.types'; import { Environment } from '../types/environment.types'; import { deployDataSource, DataSourceStats } from '../services/datasources.service'; - - export const dataSourcesConfig: DeployableItemConfig = { deploy: { singularLabel: 'Data Source', fields: [ - { - name: 'updateDependenciesIfNeeded', - label: 'Update Dependencies If Needed', - type: 'checkbox', - defaultValue: false - }, { name: 'deployCredential', label: 'Overwrite Credentials', @@ -29,7 +21,6 @@ export const dataSourcesConfig: DeployableItemConfig = { envId: sourceEnv.environmentId, targetEnvId: targetEnv.environmentId, datasourceId: item.id, - updateDependenciesIfNeeded: values.updateDependenciesIfNeeded, datasourceGid: item.gid, deployCredential: values.deployCredential ?? false }; diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx index 35189c8c9..7495e5371 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/query.config.tsx @@ -4,17 +4,14 @@ import { Query } from '../types/query.types'; import { deployQuery } from '../services/query.service'; import { Environment } from '../types/environment.types'; - - - export const queryConfig: DeployableItemConfig = { deploy: { singularLabel: 'Query', fields: [ { - name: 'updateDependenciesIfNeeded', - label: 'Update Dependencies If Needed', + name: 'deployCredential', + label: 'Overwrite Credentials', type: 'checkbox', defaultValue: false } @@ -24,8 +21,8 @@ export const queryConfig: DeployableItemConfig = { envId: sourceEnv.environmentId, targetEnvId: targetEnv.environmentId, queryId: item.id, - updateDependenciesIfNeeded: values.updateDependenciesIfNeeded, queryGid: item.gid, + deployCredential: values.deployCredential ?? false }; }, execute: (params: any) => deployQuery(params) diff --git a/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx b/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx index 6689931f9..e35423f2f 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx +++ b/client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx @@ -5,32 +5,23 @@ import { Environment } from '../types/environment.types'; import { deployWorkspace } from '../services/workspace.service'; import { Workspace } from '../types/workspace.types'; - - export const workspaceConfig: DeployableItemConfig = { // Deploy configuration deploy: { singularLabel: 'Workspace', fields: [ - { - name: 'deployCredential', - label: 'Overwrite Credentials', - type: 'checkbox', - defaultValue: false - } + // Removed deployCredential field as workspaces don't need credential overwrite ], prepareParams: (item: Workspace, values: any, sourceEnv: Environment, targetEnv: Environment) => { if (!item.gid) { console.error('Missing workspace.gid when deploying workspace:', item); } - console.log('item.gid', item.gid); return { envId: sourceEnv.environmentId, targetEnvId: targetEnv.environmentId, workspaceId: item.gid, - deployCredential: values.deployCredential ?? false }; }, execute: (params: any) => deployWorkspace(params) 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 e743179b0..2fc492028 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 @@ -21,7 +21,6 @@ export interface DeployDataSourceParams { targetEnvId: string; datasourceId: string; datasourceGid: string; - updateDependenciesIfNeeded?: boolean; deployCredential: boolean; } // Get data sources for a workspace - using your correct implementation @@ -158,7 +157,6 @@ export async function deployDataSource(params: DeployDataSourceParams): Promise< envId: params.envId, targetEnvId: params.targetEnvId, datasourceId: params.datasourceId, - updateDependenciesIfNeeded: params.updateDependenciesIfNeeded ?? false, deployCredential: params.deployCredential } }); 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 eb11609f5..b3ccb5314 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 @@ -136,6 +136,7 @@ export async function getEnvironmentById(id: string): Promise { envWithLicense.isLicensed = licenseInfo.isValid; envWithLicense.licenseStatus = licenseInfo.isValid ? 'licensed' : 'unlicensed'; envWithLicense.licenseError = licenseInfo.error; + envWithLicense.licenseDetails = licenseInfo.details; } else { envWithLicense.isLicensed = false; envWithLicense.licenseStatus = 'error'; @@ -556,6 +557,7 @@ export async function getEnvironmentsWithLicenseStatus(): Promise envWithLicense.isLicensed = licenseInfo.isValid; envWithLicense.licenseStatus = licenseInfo.isValid ? 'licensed' : 'unlicensed'; envWithLicense.licenseError = licenseInfo.error; + envWithLicense.licenseDetails = licenseInfo.details; } else { envWithLicense.isLicensed = false; envWithLicense.licenseStatus = 'error'; 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 index ff0be0ce6..ebc0ae46a 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/license.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/license.service.ts @@ -1,11 +1,11 @@ import axios from 'axios'; -import { EnvironmentLicense } from '../types/environment.types'; +import { EnvironmentLicense, DetailedLicenseInfo } from '../types/environment.types'; /** - * Check if license endpoint exists for an environment + * Check license and fetch detailed license information for an environment * @param apiServiceUrl - API service URL for the environment * @param apiKey - API key for the environment - * @returns Promise with license information + * @returns Promise with license information including detailed data */ export async function checkEnvironmentLicense( apiServiceUrl: string, @@ -25,8 +25,8 @@ export async function checkEnvironmentLicense( headers.Authorization = `Bearer ${apiKey}`; } - // Use GET request to check endpoint existence - await axios.get( + // Fetch detailed license information + const response = await axios.get( `${apiServiceUrl}/api/plugins/enterprise/license`, { headers, @@ -34,16 +34,84 @@ export async function checkEnvironmentLicense( } ); - // If we get a successful response, the endpoint exists + // Parse the license response + const licenseData = response.data; + + // Calculate total API calls limit and usage percentage + const totalAPICallsLimit = licenseData.eeLicenses?.reduce( + (sum: number, license: any) => sum + (license.apiCallsLimit || 0), + 0 + ) || 0; + + const apiCallsUsage = totalAPICallsLimit > 0 + ? Math.round(((totalAPICallsLimit - licenseData.remainingAPICalls) / totalAPICallsLimit) * 100) + : 0; + + const licenseDetails: DetailedLicenseInfo = { + eeActive: licenseData.eeActive || false, + remainingAPICalls: licenseData.remainingAPICalls || 0, + eeLicenses: licenseData.eeLicenses || [], + totalAPICallsLimit, + apiCallsUsage + }; + + // Determine if license is valid based on enterprise edition status and remaining calls + const isValid = licenseDetails.eeActive && licenseDetails.remainingAPICalls > 0; + return { - isValid: true + isValid, + details: licenseDetails }; } catch (error) { - // Any error means the endpoint doesn't exist or isn't accessible + // Determine the specific error type + let errorMessage = 'License information unavailable'; + + if (axios.isAxiosError(error)) { + if (error.code === 'ECONNABORTED') { + errorMessage = 'License check took too long'; + } else if (error.response?.status === 404) { + errorMessage = 'License service not available'; + } else if (error.response?.status === 401) { + errorMessage = 'Authentication required - please check API key'; + } else if (error.response && error.response.status >= 500) { + errorMessage = 'License service temporarily unavailable'; + } + } + return { isValid: false, - error: 'License not available' + error: errorMessage }; } +} + +/** + * Format API calls for display + * @param remaining - Remaining API calls + * @param total - Total API calls limit + * @returns Formatted string + */ +export function formatAPICalls(remaining: number, total: number): string { + const used = total - remaining; + const percentage = total > 0 ? Math.round((used / total) * 100) : 0; + + return `${remaining.toLocaleString()} remaining (${used.toLocaleString()}/${total.toLocaleString()} used, ${percentage}%)`; +} + +/** + * Get API calls status color based on usage percentage - using softer, less aggressive colors + * @param remainingCalls - Remaining API calls + * @param totalCalls - Total API calls limit + * @returns Color string for UI components + */ +export function getAPICallsStatusColor(remainingCalls: number, totalCalls: number): string { + if (totalCalls === 0) return '#d9d9d9'; // Unknown + + const usagePercentage = ((totalCalls - remainingCalls) / totalCalls) * 100; + + if (usagePercentage >= 90) return '#ff7875'; // Soft red - High usage + if (usagePercentage >= 75) return '#ffc53d'; // Soft orange - Moderate usage + if (usagePercentage >= 50) return '#40a9ff'; // Soft blue - Normal usage + return '#73d13d'; // Soft green - Low usage } \ 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 index 4cf7a29be..20b79f4ee 100644 --- a/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts +++ b/client/packages/lowcoder/src/pages/setting/environments/services/query.service.ts @@ -14,8 +14,8 @@ export interface MergedQueriesResult { envId: string; targetEnvId: string; queryId: string; - updateDependenciesIfNeeded?: boolean; queryGid: string; + deployCredential: boolean; } @@ -71,7 +71,7 @@ export interface MergedQueriesResult { envId: params.envId, targetEnvId: params.targetEnvId, queryId: params.queryId, - updateDependenciesIfNeeded: params.updateDependenciesIfNeeded ?? false + deployCredential: params.deployCredential } }); if (response.status === 200) { 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 dec7c7cf7..b7cf4a37c 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 @@ -84,7 +84,6 @@ export async function deployWorkspace(params: { envId: string; targetEnvId: string; workspaceId: string; - deployCredential: boolean; // Mandatory parameter }): Promise { try { // Use the new endpoint format with only essential parameters @@ -93,7 +92,6 @@ export async function deployWorkspace(params: { orgGid: params.workspaceId, // Using workspaceId as orgGid envId: params.envId, targetEnvId: params.targetEnvId, - deployCredential: params.deployCredential } }); @@ -107,7 +105,6 @@ export async function deployWorkspace(params: { ); } - return response.status === 200; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Failed to deploy workspace'; 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 4660ea38b..1cca58f7e 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 @@ -18,6 +18,8 @@ export interface Environment { isLicensed?: boolean; licenseStatus?: 'checking' | 'licensed' | 'unlicensed' | 'error'; licenseError?: string; + // Enhanced license details + licenseDetails?: DetailedLicenseInfo; } /** @@ -26,4 +28,28 @@ export interface Environment { export interface EnvironmentLicense { isValid: boolean; error?: string; + // Enhanced license details + details?: DetailedLicenseInfo; +} + +/** + * Interface representing detailed license information from the license endpoint + */ +export interface DetailedLicenseInfo { + eeActive: boolean; + remainingAPICalls: number; + eeLicenses: LicenseEntry[]; + // Calculated fields + totalAPICallsLimit?: number; + apiCallsUsage?: number; // percentage used +} + +/** + * Interface representing a single license entry + */ +export interface LicenseEntry { + uuid: string; + customerId: string; + customerName: string; + apiCallsLimit: number; } \ No newline at end of file pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy