diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml index d075f1fdce..be06cf1a4b 100644 --- a/.github/workflows/docker-images.yml +++ b/.github/workflows/docker-images.yml @@ -80,18 +80,21 @@ jobs: # Image names ALLINONE_IMAGE_NAMES=lowcoderorg/lowcoder-ce:${IMAGE_TAG} FRONTEND_IMAGE_NAMES=lowcoderorg/lowcoder-ce-frontend:${IMAGE_TAG} + FRONTEND_EE_IMAGE_NAMES=lowcoderorg/lowcoder-enterprise-frontend:${IMAGE_TAG} APISERVICE_IMAGE_NAMES=lowcoderorg/lowcoder-ce-api-service:${IMAGE_TAG} NODESERVICE_IMAGE_NAMES=lowcoderorg/lowcoder-ce-node-service:${IMAGE_TAG} if [[ "${IS_LATEST}" == "true" ]]; then ALLINONE_IMAGE_NAMES="lowcoderorg/lowcoder-ce:latest,${ALLINONE_IMAGE_NAMES}" FRONTEND_IMAGE_NAMES="lowcoderorg/lowcoder-ce-frontend:latest,${FRONTEND_IMAGE_NAMES}" + FRONTEND_EE_IMAGE_NAMES="lowcoderorg/lowcoder-enterprise-frontend:latest,${FRONTEND_EE_IMAGE_NAMES}" APISERVICE_IMAGE_NAMES="lowcoderorg/lowcoder-ce-api-service:latest,${APISERVICE_IMAGE_NAMES}" NODESERVICE_IMAGE_NAMES="lowcoderorg/lowcoder-ce-node-service:latest,${NODESERVICE_IMAGE_NAMES}" fi; echo "ALLINONE_IMAGE_NAMES=${ALLINONE_IMAGE_NAMES}" >> "${GITHUB_ENV}" echo "FRONTEND_IMAGE_NAMES=${FRONTEND_IMAGE_NAMES}" >> "${GITHUB_ENV}" + echo "FRONTEND_EE_IMAGE_NAMES=${FRONTEND_EE_IMAGE_NAMES}" >> "${GITHUB_ENV}" echo "APISERVICE_IMAGE_NAMES=${APISERVICE_IMAGE_NAMES}" >> "${GITHUB_ENV}" echo "NODESERVICE_IMAGE_NAMES=${NODESERVICE_IMAGE_NAMES}" >> "${GITHUB_ENV}" @@ -146,6 +149,24 @@ jobs: push: true tags: ${{ env.FRONTEND_IMAGE_NAMES }} + - name: Build and push the enterprise edition frontend image + if: ${{ env.BUILD_FRONTEND == 'true' }} + uses: docker/build-push-action@v6 + env: + NODE_ENV: production + with: + file: ./deploy/docker/Dockerfile + target: lowcoder-enterprise-frontend + build-args: | + REACT_APP_ENV=production + REACT_APP_EDITION=enterprise + REACT_APP_COMMIT_ID="dev #${{ env.SHORT_SHA }}" + platforms: | + linux/amd64 + linux/arm64 + push: true + tags: ${{ env.FRONTEND_EE_IMAGE_NAMES }} + - name: Build and push the node service image if: ${{ env.BUILD_NODESERVICE == 'true' }} uses: docker/build-push-action@v6 diff --git a/client/VERSION b/client/VERSION index 9aa34646dc..e2bdf6e45a 100644 --- a/client/VERSION +++ b/client/VERSION @@ -1 +1 @@ -2.7.0 \ No newline at end of file +2.7.3 \ No newline at end of file diff --git a/client/package.json b/client/package.json index 1d539f23bc..32deab2481 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-frontend", - "version": "2.7.0", + "version": "2.7.3", "type": "module", "private": true, "workspaces": [ @@ -15,6 +15,7 @@ "start:ee": "REACT_APP_EDITION=enterprise yarn workspace lowcoder start", "translate": "node --loader ts-node/esm ./scripts/translate.js", "build": "yarn node ./scripts/build.js", + "build:ee": "REACT_APP_EDITION=enterprise yarn node ./scripts/build.js", "test": "jest && yarn workspace lowcoder-comps test", "prepare": "yarn workspace lowcoder prepare", "build:core": "yarn workspace lowcoder-core build", diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 2819fd79ce..c2a1a5b75b 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-comps", - "version": "2.7.1", + "version": "2.7.3", "type": "module", "license": "MIT", "dependencies": { diff --git a/client/packages/lowcoder-design/src/components/Modal/handler.tsx b/client/packages/lowcoder-design/src/components/Modal/handler.tsx index c51a6858f9..2293236d65 100644 --- a/client/packages/lowcoder-design/src/components/Modal/handler.tsx +++ b/client/packages/lowcoder-design/src/components/Modal/handler.tsx @@ -1,5 +1,5 @@ import styled, { css } from "styled-components"; -import { memo, useMemo } from "react"; +import { RefObject } from "react"; type ResizeHandleAxis = "s" | "w" | "e" | "n" | "sw" | "nw" | "se" | "ne"; type ReactRef = { @@ -84,10 +84,9 @@ const ResizeHandle = styled.div<{ $axis: string }>` ${(props) => (["sw", "nw", "se", "ne"].indexOf(props.$axis) >= 0 ? CornerHandle : "")}; `; -// Memoize Handle component -const Handle = memo((axis: ResizeHandleAxis, ref: ReactRef) => { - return ; -}); +const Handle = (resizeHandle: ResizeHandleAxis, ref: RefObject) => { + return ; +}; Handle.displayName = 'Handle'; diff --git a/client/packages/lowcoder-design/src/components/customSelect.tsx b/client/packages/lowcoder-design/src/components/customSelect.tsx index 2f13f0db8e..72864178ad 100644 --- a/client/packages/lowcoder-design/src/components/customSelect.tsx +++ b/client/packages/lowcoder-design/src/components/customSelect.tsx @@ -20,7 +20,8 @@ const SelectWrapper = styled.div<{ $border?: boolean }>` padding: ${(props) => (props.$border ? "0px" : "0 0 0 12px")}; height: 100%; align-items: center; - margin-right: 8px; + margin-right: 10px; + padding-right: 5px; background-color: #fff; .ant-select-selection-item { @@ -46,9 +47,9 @@ const SelectWrapper = styled.div<{ $border?: boolean }>` } .ant-select-arrow { - width: 20px; - height: 20px; - right: 8px; + width: 17px; + height: 17px; + right: 10px; top: 0; bottom: 0; margin: auto; diff --git a/client/packages/lowcoder-design/src/icons/index.tsx b/client/packages/lowcoder-design/src/icons/index.tsx index 94453db48b..9c00866feb 100644 --- a/client/packages/lowcoder-design/src/icons/index.tsx +++ b/client/packages/lowcoder-design/src/icons/index.tsx @@ -255,7 +255,7 @@ export { ReactComponent as RecyclerIcon } from "./remix/delete-bin-line.svg"; export { ReactComponent as MarketplaceIcon } from "./v1/icon-application-marketplace.svg"; export { ReactComponent as FavoritesIcon } from "./v1/icon-application-favorites.svg"; export { ReactComponent as HomeSettingIcon } from "./remix/settings-4-line.svg"; -export { ReactComponent as EnterpriseIcon } from "./remix/earth-line.svg"; +export { ReactComponent as EnterpriseIcon } from "./remix/shield-star-line.svg"; export { ReactComponent as VerticalIcon } from "./remix/vertical.svg"; export { ReactComponent as HorizontalIcon } from "./remix/horizontal.svg"; @@ -355,6 +355,7 @@ export { ReactComponent as VideoCameraStreamCompIconSmall } from "./v2/camera-st export { ReactComponent as VideoScreenshareCompIconSmall } from "./v2/screen-share-stream-s.svg"; // new export { ReactComponent as SignatureCompIconSmall } from "./v2/signature-s.svg"; export { ReactComponent as StepCompIconSmall } from "./v2/steps-s.svg"; +export { ReactComponent as TagsCompIconSmall } from "./v2/tags-s.svg" export { ReactComponent as CandlestickChartCompIconSmall } from "./v2/candlestick-chart-s.svg"; // new @@ -468,6 +469,7 @@ export { ReactComponent as SignatureCompIcon } from "./v2/signature-m.svg"; export { ReactComponent as GanttCompIcon } from "./v2/gantt-chart-m.svg"; export { ReactComponent as KanbanCompIconSmall } from "./v2/kanban-s.svg"; export { ReactComponent as KanbanCompIcon } from "./v2/kanban-m.svg"; +export { ReactComponent as TagsCompIcon } from "./v2/tags-l.svg"; export { ReactComponent as CandlestickChartCompIcon } from "./v2/candlestick-chart-m.svg"; export { ReactComponent as FunnelChartCompIcon } from "./v2/funnel-chart-m.svg"; diff --git a/client/packages/lowcoder-design/src/icons/v2/tags-l.svg b/client/packages/lowcoder-design/src/icons/v2/tags-l.svg new file mode 100644 index 0000000000..cd1d0368c3 --- /dev/null +++ b/client/packages/lowcoder-design/src/icons/v2/tags-l.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/client/packages/lowcoder-design/src/icons/v2/tags-s.svg b/client/packages/lowcoder-design/src/icons/v2/tags-s.svg new file mode 100644 index 0000000000..d45fcb0aa8 --- /dev/null +++ b/client/packages/lowcoder-design/src/icons/v2/tags-s.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/client/packages/lowcoder-sdk-webpack-bundle/package.json b/client/packages/lowcoder-sdk-webpack-bundle/package.json index b9266b7bd5..e0851a7e69 100644 --- a/client/packages/lowcoder-sdk-webpack-bundle/package.json +++ b/client/packages/lowcoder-sdk-webpack-bundle/package.json @@ -1,7 +1,7 @@ { "name": "lowcoder-sdk-webpack-bundle", "description": "", - "version": "2.7.0", + "version": "2.7.3", "main": "index.jsx", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", diff --git a/client/packages/lowcoder-sdk/package.json b/client/packages/lowcoder-sdk/package.json index e901e98aac..aeb3d8dd33 100644 --- a/client/packages/lowcoder-sdk/package.json +++ b/client/packages/lowcoder-sdk/package.json @@ -1,6 +1,6 @@ { "name": "lowcoder-sdk", - "version": "2.7.0", + "version": "2.7.3", "type": "module", "files": [ "src", diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json index f25be12c32..32e3a06a8d 100644 --- a/client/packages/lowcoder/package.json +++ b/client/packages/lowcoder/package.json @@ -1,12 +1,13 @@ { "name": "lowcoder", - "version": "2.7.0", + "version": "2.7.3", "private": true, "type": "module", "main": "src/index.sdk.ts", "types": "src/index.sdk.ts", "dependencies": { "@ant-design/icons": "^5.3.0", + "@bany/curl-to-json": "^1.2.8", "@codemirror/autocomplete": "^6.11.1", "@codemirror/commands": "^6.3.2", "@codemirror/lang-css": "^6.2.1", diff --git a/client/packages/lowcoder/src/api/applicationApi.ts b/client/packages/lowcoder/src/api/applicationApi.ts index 2411b50d80..8ed818b371 100644 --- a/client/packages/lowcoder/src/api/applicationApi.ts +++ b/client/packages/lowcoder/src/api/applicationApi.ts @@ -99,6 +99,7 @@ class ApplicationApi extends Api { static publicToMarketplaceURL = (applicationId: string) => `/applications/${applicationId}/public-to-marketplace`; static getMarketplaceAppURL = (applicationId: string) => `/applications/${applicationId}/view_marketplace`; static setAppEditingStateURL = (applicationId: string) => `/applications/editState/${applicationId}`; + static getAvailableGroupsMembersURL = (applicationId: string) => `/applications/${applicationId}/groups-members/available`; static serverSettingsURL = () => `/serverSettings`; static fetchHomeData(request: HomeDataPayload): AxiosPromise { @@ -217,6 +218,10 @@ class ApplicationApi extends Api { }); } + static getAvailableGroupsMembers(applicationId: string, search: string): AxiosPromise { + return Api.get(ApplicationApi.getAvailableGroupsMembersURL(applicationId), {search}) + } + /** * set app as public */ diff --git a/client/packages/lowcoder/src/api/datasourceApi.ts b/client/packages/lowcoder/src/api/datasourceApi.ts index 1be29e6469..278015a124 100644 --- a/client/packages/lowcoder/src/api/datasourceApi.ts +++ b/client/packages/lowcoder/src/api/datasourceApi.ts @@ -187,6 +187,10 @@ export class DatasourceApi extends Api { return Api.get(DatasourceApi.url + `/listByOrg?orgId=${orgId}`, {...res}); } + static getDatasourceById(id: string): AxiosPromise> { + return Api.get(`${DatasourceApi.url}/${id}`); + } + static createDatasource( datasourceConfig: Partial ): AxiosPromise> { diff --git a/client/packages/lowcoder/src/api/newsApi.ts b/client/packages/lowcoder/src/api/newsApi.ts index 9da9202609..2d8c822e2c 100644 --- a/client/packages/lowcoder/src/api/newsApi.ts +++ b/client/packages/lowcoder/src/api/newsApi.ts @@ -132,7 +132,7 @@ export const getHubspotContent = async () => { }; try { const result = await NewsApi.secureRequest(apiBody); - return result?.data[0]?.hubspot?.length > 0 ? result.data[0].hubspot as any[] : []; + return result?.data[0]?.results?.length > 0 ? result.data[0].results as any[] : []; } catch (error) { console.error("Error getting news:", error); throw error; diff --git a/client/packages/lowcoder/src/api/orgApi.ts b/client/packages/lowcoder/src/api/orgApi.ts index 588a20df51..379234b32e 100644 --- a/client/packages/lowcoder/src/api/orgApi.ts +++ b/client/packages/lowcoder/src/api/orgApi.ts @@ -62,6 +62,7 @@ export class OrgApi extends Api { static updateOrgURL = (orgId: string) => `/organizations/${orgId}/update`; static fetchUsage = (orgId: string) => `/organizations/${orgId}/api-usage`; static fetchOrgsByEmailURL = (email: string) => `organizations/byuser/${email}`; + static fetchGroupPotentialMembersURL = (groupId: string) => `/groups/${groupId}/potential-members`; static createGroup(request: { name: string }): AxiosPromise> { return Api.post(OrgApi.createGroupURL, request); @@ -110,6 +111,10 @@ export class OrgApi extends Api { return Api.get(OrgApi.fetchGroupUsersURL(groupId)); } + static fetchGroupPotentialMembers(searchName: string, groupId: string): AxiosPromise { + return Api.get(OrgApi.fetchGroupPotentialMembersURL(groupId), {searchName}) + } + static fetchGroupUsersPagination(request: fetchGroupUserRequestType): AxiosPromise { const {groupId, ...res} = request; return Api.get(OrgApi.fetchGroupUsersURL(groupId), {...res}); diff --git a/client/packages/lowcoder/src/api/userApi.ts b/client/packages/lowcoder/src/api/userApi.ts index c80d4b19dd..a65a72338c 100644 --- a/client/packages/lowcoder/src/api/userApi.ts +++ b/client/packages/lowcoder/src/api/userApi.ts @@ -1,6 +1,6 @@ import Api from "api/api"; import { AxiosPromise } from "axios"; -import { OrgAndRole } from "constants/orgConstants"; +import { Org, OrgAndRole } from "constants/orgConstants"; import { BaseUserInfo, CurrentUser } from "constants/userConstants"; import { MarkUserStatusPayload, UpdateUserPayload } from "redux/reduxActions/userActions"; import { ApiResponse, GenericApiResponse } from "./apiResponses"; @@ -60,10 +60,28 @@ export interface FetchApiKeysResponse extends ApiResponse { export type GetCurrentUserResponse = GenericApiResponse; +export interface GetMyOrgsResponse extends ApiResponse { + data: { + data: Array<{ + isCurrentOrg: boolean; + orgView: { + orgId: string; + orgName: string; + createdAt?: number; + updatedAt?: number; + }; + }>; + pageNum: number; + pageSize: number; + total: number; + }; +} + class UserApi extends Api { static thirdPartyLoginURL = "/auth/tp/login"; static thirdPartyBindURL = "/auth/tp/bind"; static usersURL = "/users"; + static myOrgsURL = "/users/myorg"; static sendVerifyCodeURL = "/auth/otp/send"; static logoutURL = "/auth/logout"; static userURL = "/users/me"; @@ -127,6 +145,19 @@ class UserApi extends Api { static getCurrentUser(): AxiosPromise { return Api.get(UserApi.currentUserURL); } + static getMyOrgs( + pageNum: number = 1, + pageSize: number = 20, + orgName?: string + ): AxiosPromise { + const params = new URLSearchParams({ + pageNum: pageNum.toString(), + pageSize: pageSize.toString(), + ...(orgName && { orgName }) + }); + + return Api.get(`${UserApi.myOrgsURL}?${params}`); + } static getRawCurrentUser(): AxiosPromise { return Api.get(UserApi.rawCurrentUserURL); diff --git a/client/packages/lowcoder/src/app.tsx b/client/packages/lowcoder/src/app.tsx index 1fb49720d4..a4857882ee 100644 --- a/client/packages/lowcoder/src/app.tsx +++ b/client/packages/lowcoder/src/app.tsx @@ -60,7 +60,6 @@ import GlobalInstances from 'components/GlobalInstances'; import { fetchHomeData, fetchServerSettingsAction } from "./redux/reduxActions/applicationActions"; import { getNpmPackageMeta } from "./comps/utils/remote"; import { packageMetaReadyAction, setLowcoderCompsLoading } from "./redux/reduxActions/npmPluginActions"; -import { fetchBrandingSetting } from "./redux/reduxActions/enterpriseActions"; import { EnterpriseProvider } from "./util/context/EnterpriseContext"; import { SimpleSubscriptionContextProvider } from "./util/context/SimpleSubscriptionContext"; import { getBrandingSetting } from "./redux/selectors/enterpriseSelectors"; @@ -137,7 +136,6 @@ type AppIndexProps = { defaultHomePage: string | null | undefined; fetchHomeDataFinished: boolean; fetchConfig: (orgId?: string) => void; - fetchBrandingSetting: (orgId?: string) => void; fetchHomeData: (currentUserAnonymous?: boolean | undefined) => void; fetchLowcoderCompVersions: () => void; getCurrentUser: () => void; @@ -167,7 +165,6 @@ class AppIndex extends React.Component { if (!this.props.currentUserAnonymous) { this.props.fetchHomeData(this.props.currentUserAnonymous); this.props.fetchLowcoderCompVersions(); - this.props.fetchBrandingSetting(this.props.currentOrgId); } } } @@ -521,7 +518,6 @@ const mapDispatchToProps = (dispatch: any) => ({ fetchHomeData: (currentUserAnonymous: boolean | undefined) => { dispatch(fetchHomeData({})); }, - fetchBrandingSetting: (orgId?: string) => dispatch(fetchBrandingSetting({ orgId, fallbackToGlobal: true })), fetchLowcoderCompVersions: async () => { try { dispatch(setLowcoderCompsLoading(true)); diff --git a/client/packages/lowcoder/src/components/CurlImport.tsx b/client/packages/lowcoder/src/components/CurlImport.tsx new file mode 100644 index 0000000000..a15d54bbf3 --- /dev/null +++ b/client/packages/lowcoder/src/components/CurlImport.tsx @@ -0,0 +1,97 @@ +import React, { useState } from "react"; +import { Modal, Input, Button, message } from "antd"; +import { trans } from "i18n"; +import parseCurl from "@bany/curl-to-json"; +const { TextArea } = Input; +interface CurlImportModalProps { + open: boolean; + onCancel: () => void; + onSuccess: (parsedData: any) => void; +} + +export function CurlImportModal(props: CurlImportModalProps) { + const { open, onCancel, onSuccess } = props; + const [curlCommand, setCurlCommand] = useState(""); + const [loading, setLoading] = useState(false); + + const handleImport = async () => { + if (!curlCommand.trim()) { + message.error("Please enter a cURL command"); + return; + } + + setLoading(true); + try { + // Parse the cURL command using the correct import + const parsedData = parseCurl(curlCommand); + + + + // Log the result for now as requested + // console.log("Parsed cURL data:", parsedData); + + // Call success callback with parsed data + onSuccess(parsedData); + + // Reset form and close modal + setCurlCommand(""); + onCancel(); + + message.success("cURL command imported successfully!"); + } catch (error: any) { + console.error("Error parsing cURL command:", error); + message.error(`Failed to parse cURL command: ${error.message}`); + } finally { + setLoading(false); + } + }; + + const handleCancel = () => { + setCurlCommand(""); + onCancel(); + }; + + return ( + + Cancel + , + , + ]} + width={600} + > +
+
+ Paste cURL Command Here +
+
+
+ Examples: +
+
+ GET: curl -X GET https://jsonplaceholder.typicode.com/posts/1 +
+
+ POST: curl -X POST https://jsonplaceholder.typicode.com/posts -H "Content-Type: application/json" -d '{"title":"foo","body":"bar","userId":1}' +
+
+ Users: curl -X GET https://jsonplaceholder.typicode.com/users +
+
+