diff --git a/client/packages/lowcoder/src/comps/comps/buttonComp/scannerComp.tsx b/client/packages/lowcoder/src/comps/comps/buttonComp/scannerComp.tsx index 8b061cb4c1..8900ea914c 100644 --- a/client/packages/lowcoder/src/comps/comps/buttonComp/scannerComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/buttonComp/scannerComp.tsx @@ -30,7 +30,7 @@ import React, { useState, useContext, } from "react"; -import { arrayStringExposingStateControl } from "comps/controls/codeStateControl"; +import { arrayStringExposingStateControl, stringExposingStateControl } from "comps/controls/codeStateControl"; import { BoolControl } from "comps/controls/boolControl"; import { RefControl } from "comps/controls/refControl"; import { EditorContext } from "comps/editorState"; @@ -120,6 +120,7 @@ const BarcodeScannerComponent = React.lazy( const ScannerTmpComp = (function () { const childrenMap = { data: arrayStringExposingStateControl("data"), + value: stringExposingStateControl("value"), text: withDefault(StringControl, trans("scanner.text")), continuous: BoolControl, uniqueData: withDefault(BoolControl, true), @@ -150,17 +151,27 @@ const ScannerTmpComp = (function () { }, [success, showModal]); const continuousValue = useRef([]); + const seenSetRef = useRef>(new Set()); const handleUpdate = (err: any, result: any) => { if (result) { if (props.continuous) { - continuousValue.current = [...continuousValue.current, result.text]; + const scannedText: string = result.text; + if (props.uniqueData && seenSetRef.current.has(scannedText)) { + return; + } + continuousValue.current = [...continuousValue.current, scannedText]; + if (props.uniqueData) { + seenSetRef.current.add(scannedText); + } const val = props.uniqueData ? [...new Set(continuousValue.current)] : continuousValue.current; + props.value.onChange(scannedText); props.data.onChange(val); props.onEvent("success"); } else { + props.value.onChange(result.text); props.data.onChange([result.text]); setShowModal(false); setSuccess(true); @@ -205,6 +216,7 @@ const ScannerTmpComp = (function () { props.onEvent("click"); setShowModal(true); continuousValue.current = []; + seenSetRef.current = new Set(); }} > {props.text} @@ -317,6 +329,7 @@ const ScannerTmpComp = (function () { export const ScannerComp = withExposingConfigs(ScannerTmpComp, [ new NameConfig("data", trans("data")), + new NameConfig("value", trans("value")), new NameConfig("text", trans("button.textDesc")), ...CommonNameConfig, ]); diff --git a/client/packages/lowcoder/src/comps/comps/formComp/formComp.tsx b/client/packages/lowcoder/src/comps/comps/formComp/formComp.tsx index 8255813932..bece24fe23 100644 --- a/client/packages/lowcoder/src/comps/comps/formComp/formComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/formComp/formComp.tsx @@ -390,15 +390,24 @@ let FormTmpComp = class extends FormBaseComp implements IForm { if (ret.children.initialData !== this.children.initialData) { // FIXME: kill setTimeout ? setTimeout(() => { - this.dispatch( - customAction( - { - type: "setData", - initialData: (action.value["initialData"] as ValueAndMsg).value || {}, - }, - false - ) - ); + const newInitialData = (action.value["initialData"] as ValueAndMsg) + .value; + // only setData when initialData has explicit keys. + if ( + newInitialData && + typeof newInitialData === "object" && + Object.keys(newInitialData).length > 0 + ) { + this.dispatch( + customAction( + { + type: "setData", + initialData: newInitialData, + }, + false + ) + ); + } }, 1000); } return ret; diff --git a/client/packages/lowcoder/src/comps/comps/navComp/components/NavItemsControl.tsx b/client/packages/lowcoder/src/comps/comps/navComp/components/NavItemsControl.tsx new file mode 100644 index 0000000000..ee0817b49a --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/navComp/components/NavItemsControl.tsx @@ -0,0 +1,77 @@ +import { BoolCodeControl, StringControl } from "comps/controls/codeControl"; +import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; +import { MultiCompBuilder } from "comps/generators/multi"; +import { dropdownControl } from "comps/controls/dropdownControl"; +import { mapOptionsControl } from "comps/controls/optionsControl"; +import { trans } from "i18n"; +import { navListComp } from "../navItemComp"; +import { controlItem } from "lowcoder-design"; +import { menuPropertyView } from "./MenuItemList"; + +export function createNavItemsControl() { + const OptionTypes = [ + { label: trans("prop.manual"), value: "manual" }, + { label: trans("prop.map"), value: "map" }, + ] as const; + + const NavMapOption = new MultiCompBuilder( + { + label: StringControl, + hidden: BoolCodeControl, + disabled: BoolCodeControl, + active: BoolCodeControl, + onEvent: eventHandlerControl([clickEvent]), + }, + (props) => props + ) + .setPropertyViewFn((children) => ( + <> + {children.label.propertyView({ label: trans("label"), placeholder: "{{item}}" })} + {children.active.propertyView({ label: trans("navItemComp.active") })} + {children.hidden.propertyView({ label: trans("hidden") })} + {children.disabled.propertyView({ label: trans("disabled") })} + {children.onEvent.getPropertyView()} + + )) + .build(); + + const TmpNavItemsControl = new MultiCompBuilder( + { + optionType: dropdownControl(OptionTypes, "manual"), + manual: navListComp(), + mapData: mapOptionsControl(NavMapOption), + }, + (props) => { + return props.optionType === "manual" ? props.manual : props.mapData; + } + ) + .setPropertyViewFn(() => { + throw new Error("Method not implemented."); + }) + .build(); + + return class NavItemsControl extends TmpNavItemsControl { + exposingNode() { + return this.children.optionType.getView() === "manual" + ? (this.children.manual as any).exposingNode() + : (this.children.mapData as any).exposingNode(); + } + + propertyView() { + const isManual = this.children.optionType.getView() === "manual"; + const content = isManual + ? menuPropertyView(this.children.manual as any) + : this.children.mapData.getPropertyView(); + + return controlItem( + { searchChild: true }, + <> + {this.children.optionType.propertyView({ radioButton: true, type: "oneline" })} + {content} + + ); + } + }; +} + + diff --git a/client/packages/lowcoder/src/comps/comps/navComp/navComp.tsx b/client/packages/lowcoder/src/comps/comps/navComp/navComp.tsx index e3727508ec..670f4bba91 100644 --- a/client/packages/lowcoder/src/comps/comps/navComp/navComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/navComp/navComp.tsx @@ -1,9 +1,10 @@ import { NameConfig, NameConfigHidden, withExposingConfigs } from "comps/generators/withExposing"; +import { MultiCompBuilder } from "comps/generators/multi"; import { UICompBuilder, withDefault } from "comps/generators"; import { Section, sectionNames } from "lowcoder-design"; import styled from "styled-components"; import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerControl"; -import { StringControl } from "comps/controls/codeControl"; +import { BoolCodeControl, StringControl } from "comps/controls/codeControl"; import { alignWithJustifyControl } from "comps/controls/alignControl"; import { navListComp } from "./navItemComp"; import { menuPropertyView } from "./components/MenuItemList"; @@ -22,6 +23,8 @@ import { trans } from "i18n"; import { useContext } from "react"; import { EditorContext } from "comps/editorState"; +import { controlItem } from "lowcoder-design"; +import { createNavItemsControl } from "./components/NavItemsControl"; type IProps = { $justify: boolean; @@ -63,11 +66,12 @@ const Item = styled.div<{ $padding: string; $textTransform:string; $textDecoration:string; + $disabled?: boolean; }>` height: 30px; line-height: 30px; padding: ${(props) => props.$padding ? props.$padding : '0 16px'}; - color: ${(props) => (props.$active ? props.$activeColor : props.$color)}; + color: ${(props) => props.$disabled ? `${props.$color}80` : (props.$active ? props.$activeColor : props.$color)}; font-weight: ${(props) => (props.$textWeight ? props.$textWeight : 500)}; font-family:${(props) => (props.$fontFamily ? props.$fontFamily : 'sans-serif')}; font-style:${(props) => (props.$fontStyle ? props.$fontStyle : 'normal')}; @@ -77,8 +81,8 @@ const Item = styled.div<{ margin:${(props) => props.$margin ? props.$margin : '0px'}; &:hover { - color: ${(props) => props.$activeColor}; - cursor: pointer; + color: ${(props) => props.$disabled ? (props.$active ? props.$activeColor : props.$color) : props.$activeColor}; + cursor: ${(props) => props.$disabled ? 'not-allowed' : 'pointer'}; } .anticon { @@ -131,41 +135,74 @@ function fixOldStyleData(oldData: any) { return oldData; } +function fixOldItemsData(oldData: any) { + if (Array.isArray(oldData)) { + return { + optionType: "manual", + manual: oldData, + }; + } + if (oldData && !oldData.optionType && Array.isArray(oldData.manual)) { + return { + optionType: "manual", + manual: oldData.manual, + }; + } + return oldData; +} + const childrenMap = { logoUrl: StringControl, logoEvent: withDefault(eventHandlerControl(logoEventHandlers), [{ name: "click" }]), horizontalAlignment: alignWithJustifyControl(), style: migrateOldData(styleControl(NavigationStyle, 'style'), fixOldStyleData), animationStyle: styleControl(AnimationStyle, 'animationStyle'), - items: withDefault(navListComp(), [ - { - label: trans("menuItem") + " 1", - }, - ]), + items: withDefault(migrateOldData(createNavItemsControl(), fixOldItemsData), { + optionType: "manual", + manual: [ + { + label: trans("menuItem") + " 1", + }, + ], + }), }; const NavCompBase = new UICompBuilder(childrenMap, (props) => { const data = props.items; const items = ( <> - {data.map((menuItem, idx) => { - const { hidden, label, items, active, onEvent } = menuItem.getView(); + {data.map((menuItem: any, idx: number) => { + const isCompItem = typeof menuItem?.getView === "function"; + const view = isCompItem ? menuItem.getView() : menuItem; + const hidden = !!view?.hidden; if (hidden) { return null; } - const subMenuItems: Array<{ key: string; label: string }> = []; + + const label = view?.label; + const active = !!view?.active; + const onEvent = view?.onEvent; + const disabled = !!view?.disabled; + const subItems = isCompItem ? view?.items : []; + + const subMenuItems: Array<{ key: string; label: any; disabled?: boolean }> = []; const subMenuSelectedKeys: Array = []; - items.forEach((subItem, originalIndex) => { - if (subItem.children.hidden.getView()) { - return; - } - const key = originalIndex + ""; - subItem.children.active.getView() && subMenuSelectedKeys.push(key); - subMenuItems.push({ - key: key, - label: subItem.children.label.getView(), + + if (Array.isArray(subItems)) { + subItems.forEach((subItem: any, originalIndex: number) => { + if (subItem.children.hidden.getView()) { + return; + } + const key = originalIndex + ""; + subItem.children.active.getView() && subMenuSelectedKeys.push(key); + subMenuItems.push({ + key: key, + label: subItem.children.label.getView(), + disabled: !!subItem.children.disabled.getView(), + }); }); - }); + } + const item = ( { $textTransform={props.style.textTransform} $textDecoration={props.style.textDecoration} $margin={props.style.margin} - onClick={() => onEvent("click")} + $disabled={disabled} + onClick={() => { if (!disabled && onEvent) onEvent("click"); }} > {label} - {items.length > 0 && } + {Array.isArray(subItems) && subItems.length > 0 && } ); if (subMenuItems.length > 0) { const subMenu = ( { - const { onEvent: onSubEvent } = items[Number(e.key)]?.getView(); - onSubEvent("click"); + if (disabled) return; + const subItem = subItems[Number(e.key)]; + const isSubDisabled = !!subItem?.children?.disabled?.getView?.(); + if (isSubDisabled) return; + const onSubEvent = subItem?.getView()?.onEvent; + onSubEvent && onSubEvent("click"); }} selectedKeys={subMenuSelectedKeys} items={subMenuItems} @@ -201,6 +243,7 @@ const NavCompBase = new UICompBuilder(childrenMap, (props) => { subMenu} + disabled={disabled} > {item} @@ -237,7 +280,7 @@ const NavCompBase = new UICompBuilder(childrenMap, (props) => { return ( <>
- {menuPropertyView(children.items)} + {children.items.propertyView()}
{(useContext(EditorContext).editorModeStatus === "logic" || useContext(EditorContext).editorModeStatus === "both") && ( diff --git a/client/packages/lowcoder/src/comps/comps/navComp/navItemComp.tsx b/client/packages/lowcoder/src/comps/comps/navComp/navItemComp.tsx index 565013ab4f..e8ce0f0118 100644 --- a/client/packages/lowcoder/src/comps/comps/navComp/navItemComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/navComp/navItemComp.tsx @@ -3,7 +3,7 @@ import { clickEvent, eventHandlerControl } from "comps/controls/eventHandlerCont import { list } from "comps/generators/list"; import { parseChildrenFromValueAndChildrenMap, ToViewReturn } from "comps/generators/multi"; import { withDefault } from "comps/generators/simpleGenerators"; -import { hiddenPropertyView } from "comps/utils/propertyUtils"; +import { disabledPropertyView, hiddenPropertyView } from "comps/utils/propertyUtils"; import { trans } from "i18n"; import _ from "lodash"; import { fromRecord, MultiBaseComp, Node, RecordNode, RecordNodeToValue } from "lowcoder-core"; @@ -14,6 +14,7 @@ const events = [clickEvent]; const childrenMap = { label: StringControl, hidden: BoolCodeControl, + disabled: BoolCodeControl, active: BoolCodeControl, onEvent: withDefault(eventHandlerControl(events), [ { @@ -29,6 +30,7 @@ const childrenMap = { type ChildrenType = { label: InstanceType; hidden: InstanceType; + disabled: InstanceType; active: InstanceType; onEvent: InstanceType>; items: InstanceType>; @@ -45,6 +47,7 @@ export class NavItemComp extends MultiBaseComp { {this.children.label.propertyView({ label: trans("label") })} {hiddenPropertyView(this.children)} {this.children.active.propertyView({ label: trans("navItemComp.active") })} + {disabledPropertyView(this.children)} {this.children.onEvent.propertyView({ inline: true })} ); @@ -69,6 +72,7 @@ export class NavItemComp extends MultiBaseComp { return fromRecord({ label: this.children.label.exposingNode(), hidden: this.children.hidden.exposingNode(), + disabled: this.children.disabled.exposingNode(), active: this.children.active.exposingNode(), items: this.children.items.exposingNode(), }); @@ -78,6 +82,7 @@ export class NavItemComp extends MultiBaseComp { type NavItemExposing = { label: Node; hidden: Node; + disabled: Node; active: Node; items: Node[]>; }; diff --git a/client/packages/lowcoder/src/comps/comps/numberInputComp/numberInputComp.tsx b/client/packages/lowcoder/src/comps/comps/numberInputComp/numberInputComp.tsx index 9573e2a3b1..fad9d36d14 100644 --- a/client/packages/lowcoder/src/comps/comps/numberInputComp/numberInputComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/numberInputComp/numberInputComp.tsx @@ -331,7 +331,7 @@ const CustomInputNumber = (props: RecordConstructorToView) = value = Number(defaultValue); } props.value.onChange(value); - }, [defaultValue]); + }, [defaultValue, props.allowNull]); const formatFn = (value: number) => format(value, props.allowNull, props.formatter, props.precision, props.thousandsSeparator); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/ResizeableTable.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/ResizeableTable.tsx index 55bf8d694b..3cdf9119a4 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/ResizeableTable.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/ResizeableTable.tsx @@ -165,6 +165,8 @@ function ResizeableTableComp(props: CustomTableProps< onClick: () => onCellClick(col.titleText, String(col.dataIndex)), loading: customLoading, customAlign: col.align, + className: col.columnClassName, + 'data-testid': col.columnDataTestId, }); }, [rowColorFn, rowHeightFn, columnsStyle, size, rowAutoHeight, onCellClick, customLoading]); @@ -182,6 +184,8 @@ function ResizeableTableComp(props: CustomTableProps< onResizeStop: (e: React.SyntheticEvent, { size }: { size: { width: number } }) => { handleResizeStop(size.width, index, col.onWidthResize); }, + className: col.columnClassName, + 'data-testid': col.columnDataTestId, }); }, [viewModeResizable, handleResize, handleResizeStop]); diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx index f9bedc7549..eaac533278 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/simpleColumnTypeComps.tsx @@ -4,8 +4,8 @@ import { BoolCodeControl, StringControl } from "comps/controls/codeControl"; import { dropdownControl } from "comps/controls/dropdownControl"; import { disabledPropertyView, loadingPropertyView } from "comps/utils/propertyUtils"; import { trans } from "i18n"; -import { useStyle } from "comps/controls/styleControl"; -import { ButtonStyle } from "comps/controls/styleControlConstants"; +import { styleControl, useStyle } from "comps/controls/styleControl"; +import { ButtonStyle, TableColumnButtonStyle } from "comps/controls/styleControlConstants"; import { Button100 } from "comps/comps/buttonComp/buttonCompConstants"; import { IconControl } from "comps/controls/iconControl"; import { hasIcon } from "comps/utils"; @@ -58,10 +58,11 @@ const childrenMap = { disabled: BoolCodeControl, prefixIcon: IconControl, suffixIcon: IconControl, + style: styleControl(TableColumnButtonStyle, 'style', { boldTitle: true }), }; const ButtonStyled = React.memo(({ props }: { props: ToViewReturn>}) => { - const style = useStyle(ButtonStyle); + const themeButtonStyle = useStyle(ButtonStyle); const hasText = !!props.text; const hasPrefixIcon = hasIcon(props.prefixIcon); const hasSuffixIcon = hasIcon(props.suffixIcon); @@ -85,7 +86,10 @@ const ButtonStyled = React.memo(({ props }: { props: ToViewReturn @@ -120,6 +124,7 @@ const ButtonCompTmp = (function () { })} {loadingPropertyView(children)} {disabledPropertyView(children)} + {children.style.getPropertyView()} {children.onClick.propertyView()} )) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx index 938983ac9e..f94b17816d 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnComp.tsx @@ -134,6 +134,9 @@ export const columnChildrenMap = { align: HorizontalAlignmentControl, tempHide: stateComp(false), fixed: dropdownControl(columnFixOptions, "close"), + // identifiers + className: StringControl, + dataTestId: StringControl, editable: BoolControl, background: withDefault(ColorControl, ""), margin: withDefault(RadiusControl, ""), @@ -162,6 +165,11 @@ const StyledFontFamilyIcon = styled(FontFamilyIcon)` width: 24px; margin: 0 8px const StyledTextWeightIcon = styled(TextWeightIcon)` width: 24px; margin: 0 8px 0 -3px; padding: 3px;`; const StyledBackgroundImageIcon = styled(ImageCompIcon)` width: 24px; margin: 0 0px 0 -12px;`; +const SectionHeading = styled.div` + font-weight: bold; + margin-bottom: 8px; +`; + /** * export for test. * Put it here temporarily to avoid circular dependencies @@ -283,11 +291,7 @@ const ColumnPropertyView = React.memo(({ {(columnType === 'link' || columnType === 'links') && ( <> - {controlItem({}, ( -
- {"Link Style"} -
- ))} + Link Style {comp.children.linkColor.propertyView({ label: trans('text') // trans('style.background'), })} @@ -300,11 +304,7 @@ const ColumnPropertyView = React.memo(({ )} - {controlItem({}, ( -
- {"Column Style"} -
- ))} + Column Style {comp.children.background.propertyView({ label: trans('style.background'), })} @@ -346,6 +346,14 @@ const ColumnPropertyView = React.memo(({ })} {comp.children.textOverflow.getPropertyView()} {comp.children.cellColor.getPropertyView()} + + Identifiers + {comp.children.className.propertyView({ + label: trans("prop.className"), + })} + {comp.children.dataTestId.propertyView({ + label: trans("prop.dataTestId"), + })} )} diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx index 725543effe..f050997a02 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx @@ -822,6 +822,55 @@ TableTmpComp = withMethodExposing(TableTmpComp, [ } }, } + , + { + method: { + name: "hideColumns", + description: "Hide specified columns by dataIndex or title", + params: [ + { name: "columns", type: "arrayString" }, + ], + }, + execute: (comp, values) => { + const columns = values[0]; + if (!isArray(columns)) { + return Promise.reject("hideColumns expects an array of strings, e.g. ['id','name']"); + } + const targets = new Set((columns as any[]).map((c) => String(c))); + comp.children.columns.getView().forEach((c) => { + const view = c.getView(); + if (targets.has(view.dataIndex) || targets.has(view.title)) { + // Ensure both persistent and temporary flags are updated + c.children.hide.dispatchChangeValueAction(true); + c.children.tempHide.dispatchChangeValueAction(true); + } + }); + }, + } + , + { + method: { + name: "showColumns", + description: "Show specified columns by dataIndex or title", + params: [ + { name: "columns", type: "arrayString" }, + ], + }, + execute: (comp, values) => { + const columns = values[0]; + if (!isArray(columns)) { + return Promise.reject("showColumns expects an array of strings, e.g. ['id','name']"); + } + const targets = new Set((columns as any[]).map((c) => String(c))); + comp.children.columns.getView().forEach((c) => { + const view = c.getView(); + if (targets.has(view.dataIndex) || targets.has(view.title)) { + c.children.hide.dispatchChangeValueAction(false); + c.children.tempHide.dispatchChangeValueAction(false); + } + }); + }, + } ]); // exposing data @@ -1052,6 +1101,30 @@ export const TableComp = withExposingConfigs(TableTmpComp, [ }, trans("table.displayDataDesc") ), + new CompDepsConfig( + "hiddenColumns", + (comp) => { + return { + dataIndexes: comp.children.columns.getColumnsNode("dataIndex"), + hides: comp.children.columns.getColumnsNode("hide"), + tempHides: comp.children.columns.getColumnsNode("tempHide"), + columnSetting: comp.children.toolbar.children.columnSetting.node(), + }; + }, + (input) => { + const hidden: string[] = []; + _.forEach(input.dataIndexes, (dataIndex, idx) => { + const isHidden = columnHide({ + hide: input.hides[idx].value, + tempHide: input.tempHides[idx], + enableColumnSetting: input.columnSetting.value, + }); + if (isHidden) hidden.push(dataIndex); + }); + return hidden; + }, + trans("table.displayDataDesc") + ), new DepsConfig( "filter", (children) => { diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx index 53d4231290..74d431f7ad 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx @@ -100,6 +100,8 @@ export const TableCompView = React.memo((props: { const onEvent = useMemo(() => compChildren.onEvent.getView(), [compChildren.onEvent]); const currentExpandedRows = useMemo(() => compChildren.currentExpandedRows.getView(), [compChildren.currentExpandedRows]); const dynamicColumn = compChildren.dynamicColumn.getView(); + const className = compChildren.className.getView(); + const dataTestId = compChildren.dataTestId.getView(); const dynamicColumnConfig = useMemo( () => compChildren.dynamicColumnConfig.getView(), @@ -360,6 +362,8 @@ export const TableCompView = React.memo((props: { suffixNode={toolbar.position === "below" && !toolbar.fixedToolbar && !(tableMode.isAutoMode && showHorizontalScrollbar) && toolbarView} > tooltip: trans("table.dynamicColumnConfigDesc"), })} + +
+ {comp.children.className.propertyView({ label: trans("prop.className") })} + {comp.children.dataTestId.propertyView({ label: trans("prop.dataTestId") })} +
)} diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableStyles.ts b/client/packages/lowcoder/src/comps/comps/tableComp/tableStyles.ts index 5e3cc6dcd7..54c856c14b 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableStyles.ts +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableStyles.ts @@ -122,7 +122,13 @@ export const BackgroundWrapper = styled.div<{ `; // TODO: find a way to limit the calc function for max-height only to first Margin value -export const TableWrapper = styled.div<{ +export const TableWrapper = styled.div.attrs<{ + className?: string; + "data-testid"?: string; +}>((props) => ({ + className: props.className, + "data-testid": props["data-testid"], +}))<{ $style: TableStyleType; $headerStyle: TableHeaderStyleType; $toolbarStyle: TableToolbarStyleType; diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx index d71ae5a8d4..fdc5c775df 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx @@ -333,6 +333,8 @@ export type CustomColumnType = ColumnType & { style: TableColumnStyleType; linkStyle: TableColumnLinkStyleType; cellColorFn: CellColorViewType; + columnClassName?: string; + columnDataTestId?: string; }; /** @@ -400,6 +402,8 @@ export function columnsToAntdFormat( align: column.align, width: column.autoWidth === "auto" ? 0 : column.width, fixed: column.fixed === "close" ? false : column.fixed, + columnClassName: column.className, + columnDataTestId: column.dataTestId, style: { background: column.background, margin: column.margin, diff --git a/client/packages/lowcoder/src/comps/comps/tableLiteComp/column/tableColumnComp.tsx b/client/packages/lowcoder/src/comps/comps/tableLiteComp/column/tableColumnComp.tsx index 4951dea4e2..fcfab56af1 100644 --- a/client/packages/lowcoder/src/comps/comps/tableLiteComp/column/tableColumnComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableLiteComp/column/tableColumnComp.tsx @@ -136,6 +136,9 @@ export const columnChildrenMap = { align: HorizontalAlignmentControl, tempHide: stateComp(false), fixed: dropdownControl(columnFixOptions, "close"), + // identifiers + className: StringControl, + dataTestId: StringControl, background: withDefault(ColorControl, ""), margin: withDefault(RadiusControl, ""), text: withDefault(ColorControl, ""), @@ -163,6 +166,11 @@ const StyledFontFamilyIcon = styled(FontFamilyIcon)` width: 24px; margin: 0 8px const StyledTextWeightIcon = styled(TextWeightIcon)` width: 24px; margin: 0 8px 0 -3px; padding: 3px;`; const StyledBackgroundImageIcon = styled(ImageCompIcon)` width: 24px; margin: 0 0px 0 -12px;`; +const SectionHeading = styled.div` + font-weight: bold; + margin-bottom: 8px; +`; + /** * export for test. * Put it here temporarily to avoid circular dependencies @@ -285,11 +293,7 @@ const ColumnPropertyView = React.memo(({ {(columnType === 'link' || columnType === 'links') && ( <> - {controlItem({}, ( -
- {"Link Style"} -
- ))} + Link Style {comp.children.linkColor.propertyView({ label: trans('text') // trans('style.background'), })} @@ -302,11 +306,7 @@ const ColumnPropertyView = React.memo(({ )} - {controlItem({}, ( -
- {"Column Style"} -
- ))} + Column Style {comp.children.background.propertyView({ label: trans('style.background'), })} @@ -348,6 +348,14 @@ const ColumnPropertyView = React.memo(({ })} {comp.children.textOverflow.getPropertyView()} {comp.children.cellColor.getPropertyView()} + + Identifiers + {comp.children.className.propertyView({ + label: trans("prop.className"), + })} + {comp.children.dataTestId.propertyView({ + label: trans("prop.dataTestId"), + })} )} diff --git a/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/BaseTable.tsx b/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/BaseTable.tsx index 31aa30f33e..fffde7f21f 100644 --- a/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/BaseTable.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/BaseTable.tsx @@ -106,6 +106,8 @@ import React, { onClick: () => onCellClick(col.titleText, String(col.dataIndex)), loading: customLoading, customAlign: col.align, + className: col.columnClassName, + 'data-testid': col.columnDataTestId, }); }, [ @@ -135,6 +137,8 @@ import React, { ) => { handleResizeStop(size.width, index, col.onWidthResize); }, + className: col.columnClassName, + 'data-testid': col.columnDataTestId, }); }, [viewModeResizable, handleResize, handleResizeStop] diff --git a/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/ResizeableTable.tsx b/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/ResizeableTable.tsx index c7a5d39c2b..16ca3a7d60 100644 --- a/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/ResizeableTable.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/ResizeableTable.tsx @@ -108,6 +108,8 @@ function ResizeableTableComp( onClick: () => onCellClick(col.titleText, String(col.dataIndex)), loading: customLoading, customAlign: col.align, + className: col.columnClassName, + 'data-testid': col.columnDataTestId, }); }, [ @@ -138,6 +140,8 @@ function ResizeableTableComp( ) => { handleResizeStop(size.width, index, col.onWidthResize); }, + className: col.columnClassName, + 'data-testid': col.columnDataTestId, }); }, [viewModeResizable, handleResize, handleResizeStop] diff --git a/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/TableContainer.tsx b/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/TableContainer.tsx index 1a44cfa7d7..247c0e0d47 100644 --- a/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/TableContainer.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableLiteComp/parts/TableContainer.tsx @@ -4,7 +4,13 @@ import styled from 'styled-components'; import SimpleBar from 'simplebar-react'; // import 'simplebar-react/dist/simplebar.min.css'; -const MainContainer = styled.div<{ +const MainContainer = styled.div.attrs<{ + className?: string; + "data-testid"?: string; +}>((props) => ({ + className: props.className, + "data-testid": props["data-testid"], +}))<{ $mode: 'AUTO' | 'FIXED'; $showHorizontalScrollbar: boolean; $showVerticalScrollbar: boolean; @@ -114,6 +120,8 @@ interface TableContainerProps { showVerticalScrollbar: boolean; showHorizontalScrollbar: boolean; virtual: boolean; + className?: string; + dataTestId?: string; } export const TableContainer: React.FC = ({ @@ -126,7 +134,9 @@ export const TableContainer: React.FC = ({ containerRef, showVerticalScrollbar, showHorizontalScrollbar, - virtual + virtual, + className, + dataTestId }) => { return ( = ({ $showHorizontalScrollbar={showHorizontalScrollbar} $showVerticalScrollbar={showVerticalScrollbar} $virtual={virtual} + className={className} + data-testid={dataTestId} > {/* Sticky above toolbar - always visible */} {stickyToolbar && toolbarPosition === 'above' && showToolbar && ( diff --git a/client/packages/lowcoder/src/comps/comps/tableLiteComp/tableCompView.tsx b/client/packages/lowcoder/src/comps/comps/tableLiteComp/tableCompView.tsx index c35fcc0ba3..4d792e0565 100644 --- a/client/packages/lowcoder/src/comps/comps/tableLiteComp/tableCompView.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableLiteComp/tableCompView.tsx @@ -34,6 +34,8 @@ export const TableCompView = React.memo((props: { const headerStyle = compChildren.headerStyle.getView(); const toolbarStyle = compChildren.toolbarStyle.getView(); const showHRowGridBorder = compChildren.showHRowGridBorder.getView(); + const className = compChildren.className.getView(); + const dataTestId = compChildren.dataTestId.getView(); const columns = useMemo(() => compChildren.columns.getView(), [compChildren.columns]); const columnViews = useMemo(() => columns.map((c) => c.getView()), [columns]); const data = comp.filterData; @@ -185,6 +187,8 @@ export const TableCompView = React.memo((props: { showHorizontalScrollbar={compChildren.showHorizontalScrollbar.getView()} virtual={virtualization.enabled} containerRef={containerRef} + className={className} + dataTestId={dataTestId} > @@ -206,6 +210,8 @@ export const TableCompView = React.memo((props: { showVerticalScrollbar={compChildren.showVerticalScrollbar.getView()} showHorizontalScrollbar={compChildren.showHorizontalScrollbar.getView()} virtual={virtualization.enabled} + className={className} + dataTestId={dataTestId} > diff --git a/client/packages/lowcoder/src/comps/comps/tableLiteComp/tablePropertyView.tsx b/client/packages/lowcoder/src/comps/comps/tableLiteComp/tablePropertyView.tsx index 62f3f1ffbc..dbe4edcf88 100644 --- a/client/packages/lowcoder/src/comps/comps/tableLiteComp/tablePropertyView.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableLiteComp/tablePropertyView.tsx @@ -586,6 +586,11 @@ export function compTablePropertyView tooltip: trans("table.dynamicColumnConfigDesc"), })} + +
+ {comp.children.className.propertyView({ label: trans("prop.className") })} + {comp.children.dataTestId.propertyView({ label: trans("prop.dataTestId") })} +
)} diff --git a/client/packages/lowcoder/src/comps/comps/tableLiteComp/tableUtils.tsx b/client/packages/lowcoder/src/comps/comps/tableLiteComp/tableUtils.tsx index 9297eded86..69f18ae83d 100644 --- a/client/packages/lowcoder/src/comps/comps/tableLiteComp/tableUtils.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableLiteComp/tableUtils.tsx @@ -286,6 +286,8 @@ export type CustomColumnType = ColumnType & { style: TableColumnStyleType; linkStyle: TableColumnLinkStyleType; cellColorFn: CellColorViewType; + columnClassName?: string; + columnDataTestId?: string; }; /** @@ -539,6 +541,8 @@ export function columnsToAntdFormat( fixed: column.fixed === "close" ? false : column.fixed, style, linkStyle, + columnClassName: column.className, + columnDataTestId: column.dataTestId, cellColorFn: column.cellColor, onWidthResize: column.onWidthResize, render: buildRenderFn( diff --git a/client/packages/lowcoder/src/comps/controls/styleControl.tsx b/client/packages/lowcoder/src/comps/controls/styleControl.tsx index 5d40a2db4d..a743d466dc 100644 --- a/client/packages/lowcoder/src/comps/controls/styleControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/styleControl.tsx @@ -937,11 +937,12 @@ function calcColors>( return res as ColorMap; } -const TitleDiv = styled.div` +const TitleDiv = styled.div<{ $boldTitle?: boolean }>` display: flex; justify-content: space-between; font-size: 13px; line-height: 1; + font-weight: ${(props) => (props.$boldTitle ? 600 : 400)}; span:nth-of-type(2) { cursor: pointer; @@ -1149,6 +1150,7 @@ const useThemeStyles = ( export function styleControl( colorConfigs: T, styleKey: string = '', + options?: { boldTitle?: boolean }, ) { type ColorMap = { [K in Names]: string }; const childrenMap: any = {}; @@ -1268,7 +1270,7 @@ export function styleControl( return ( <> - + {label} {showReset && ( ; export type TimeLineStyleType = StyleConfigType; export type AvatarStyleType = StyleConfigType; @@ -2437,6 +2449,7 @@ export type TableColumnStyleType = StyleConfigType; export type TableColumnLinkStyleType = StyleConfigType< typeof TableColumnLinkStyle >; +export type TableColumnButtonStyleType = StyleConfigType; export type TableSummaryRowStyleType = StyleConfigType; export type FileStyleType = StyleConfigType; export type FileViewerStyleType = StyleConfigType; diff --git a/client/packages/lowcoder/src/comps/hooks/drawerComp.tsx b/client/packages/lowcoder/src/comps/hooks/drawerComp.tsx index 3b33c3fb96..7f005d1aae 100644 --- a/client/packages/lowcoder/src/comps/hooks/drawerComp.tsx +++ b/client/packages/lowcoder/src/comps/hooks/drawerComp.tsx @@ -4,7 +4,7 @@ import { ContainerCompBuilder } from "comps/comps/containerBase/containerCompBui import { gridItemCompToGridItems, InnerGrid } from "comps/comps/containerComp/containerView"; import { AutoHeightControl } from "comps/controls/autoHeightControl"; import { BoolControl } from "comps/controls/boolControl"; -import { StringControl } from "comps/controls/codeControl"; +import { StringControl, NumberControl } from "comps/controls/codeControl"; import { booleanExposingStateControl } from "comps/controls/codeStateControl"; import { PositionControl, LeftRightControl, HorizontalAlignmentControl } from "comps/controls/dropdownControl"; import { eventHandlerControl } from "comps/controls/eventHandlerControl"; @@ -24,6 +24,8 @@ import styled from "styled-components"; import { useUserViewMode } from "util/hooks"; import { isNumeric } from "util/stringUtils"; import { NameConfig, withExposingConfigs } from "../generators/withExposing"; +import { IconControl } from "comps/controls/iconControl"; +import { hasIcon } from "comps/utils"; import { title } from "process"; import { SliderControl } from "../controls/sliderControl"; import clsx from "clsx"; @@ -122,6 +124,8 @@ const childrenMap = { showMask: withDefault(BoolControl, true), toggleClose:withDefault(BoolControl,true), escapeClosable: withDefault(BoolControl, true), + closeIcon: withDefault(IconControl, ""), + zIndex: withDefault(NumberControl, Layers.drawer), }; type ChildrenType = NewChildren> & { @@ -138,6 +142,9 @@ const DrawerPropertyView = React.memo((props: { {props.children.title.getView() && props.children.titleAlign.propertyView({ label: trans("drawer.titleAlign"), radioButton: true })} {props.children.closePosition.propertyView({ label: trans("drawer.closePosition"), radioButton: true })} {props.children.placement.propertyView({ label: trans("drawer.placement"), radioButton: true })} + {props.children.toggleClose.getView() && props.children.closeIcon.propertyView({ + label: trans("drawer.closeIcon"), + })} {["top", "bottom"].includes(props.children.placement.getView()) ? props.children.autoHeight.getPropertyView() : props.children.width.propertyView({ @@ -168,6 +175,9 @@ const DrawerPropertyView = React.memo((props: { {props.children.escapeClosable.propertyView({ label: trans("prop.escapeClose"), })} + {props.children.zIndex.propertyView({ + label: trans("prop.zIndex"), + })}
{props.children.onEvent.getPropertyView()}
{props.children.style.getPropertyView()}
@@ -251,7 +261,7 @@ const DrawerView = React.memo(( height={!props.autoHeight ? transToPxSize(props.height || DEFAULT_SIZE) : ""} onClose={onClose} afterOpenChange={afterOpenChange} - zIndex={Layers.drawer} + zIndex={props.zIndex} maskClosable={props.maskClosable} mask={true} className={clsx(`app-${appID}`, props.className)} @@ -262,7 +272,7 @@ const DrawerView = React.memo(( $closePosition={props.closePosition} onClick={onClose} > - + {hasIcon(props.closeIcon) ? props.closeIcon : } )}
{props.children.onEvent.getPropertyView()}
{props.children.style.getPropertyView()}
@@ -278,7 +282,7 @@ const ModalView = React.memo(( onCancel={handleCancel} afterClose={handleAfterClose} afterOpenChange={handleAfterOpenChange} - zIndex={Layers.modal} + zIndex={props.zIndex} modalRender={modalRender} mask={props.showMask} className={clsx(`app-${appID}`, props.className)} diff --git a/client/packages/lowcoder/src/comps/hooks/utilsComp.ts b/client/packages/lowcoder/src/comps/hooks/utilsComp.ts index 2a8689efcb..a7190b1f37 100644 --- a/client/packages/lowcoder/src/comps/hooks/utilsComp.ts +++ b/client/packages/lowcoder/src/comps/hooks/utilsComp.ts @@ -87,12 +87,12 @@ UtilsComp = withMethodExposing(UtilsComp, [ method: { name: "copyToClipboard", description: trans("utilsComp.copyToClipboard"), - params: [{ name: "url", type: "string" }], + params: [{ name: "text", type: "string" }], }, execute: (comp, params) => { const text = params?.[0]; if (typeof text === "string" && !isEmpty(text)) { - copy(text); + copy(text, { format: "text/plain" }); } }, }, diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 60095c1465..8f85af6140 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -26,6 +26,8 @@ export const en = { "text": "Text", "basic": "Basic", "label": "Label", + "hidden": "Hidden", + "disabled": "Disabled", "layout": "Layout", "color": "Color", "form": "Form", @@ -237,7 +239,8 @@ export const en = { "timeZone": "TimeZone", "pickerMode": "Picker Mode", "customTags": "Allow Custom Tags", - "customTagsTooltip": "Allow users to enter custom tags that are not in the options list." + "customTagsTooltip": "Allow users to enter custom tags that are not in the options list.", + "zIndex": "z-Index" }, "autoHeightProp": { "auto": "Auto", @@ -2363,7 +2366,9 @@ export const en = { "openDrawerDesc": "Open Drawer", "closeDrawerDesc": "Close Drawer", "width": "Drawer Width", - "height": "Drawer Height" + "height": "Drawer Height", + "closeIcon": "Close Icon", + }, "meeting": { "logLevel": "Agora SDK Log Level", @@ -3204,7 +3209,7 @@ export const en = { "logoURL": "Navigation Logo URL", "horizontalAlignment": "Horizontal Alignment", "logoURLDesc": "You can display a Logo on the left side by entering URI Value or Base64 String like data:image/png;base64,AAA... CCC", - "itemsDesc": "Hierarchical Navigation Menu Items" + "itemsDesc": "Menu Items" }, "droppadbleMenuItem": { "subMenu": "Submenu {number}" diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index ba589dffab..b332c19425 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -1,7 +1,7 @@ ## ## Build Lowcoder api-service application ## -FROM maven:3.9-eclipse-temurin-17 AS build-api-service +FROM maven:3-eclipse-temurin-24 AS build-api-service # Build lowcoder-api COPY ./server/api-service /lowcoder-server @@ -26,7 +26,7 @@ RUN chmod +x /lowcoder/api-service/*.sh ## To create a separate image out of it, build it with: ## DOCKER_BUILDKIT=1 docker build -f deploy/docker/Dockerfile -t lowcoderorg/lowcoder-ce-api-service --target lowcoder-ce-api-service . ## -FROM eclipse-temurin:17-jammy AS lowcoder-ce-api-service +FROM eclipse-temurin:24-noble AS lowcoder-ce-api-service LABEL maintainer="lowcoder" RUN apt-get update && apt-get install -y --no-install-recommends gosu \ @@ -51,14 +51,12 @@ CMD [ "/bin/bash" , "/lowcoder/api-service/entrypoint.sh" ] ## ## Build lowcoder node service ## -FROM ubuntu:jammy AS build-node-service +FROM ubuntu:noble AS build-node-service RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y curl ca-certificates build-essential gnupg # Add nodejs repo and keys -RUN mkdir -p /etc/apt/keyrings \ - && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ - && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list +RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - # Download nodejs and install yarn RUN apt-get update \ @@ -82,15 +80,13 @@ RUN chmod +x /lowcoder/node-service/*.sh ## To create a separate image out of it, build it with: ## DOCKER_BUILDKIT=1 docker build -f deploy/docker/Dockerfile -t lowcoderorg/lowcoder-ce-node-service --target lowcoder-ce-node-service . ## -FROM ubuntu:jammy AS lowcoder-ce-node-service +FROM ubuntu:noble AS lowcoder-ce-node-service LABEL maintainer="lowcoder" RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y curl ca-certificates gnupg # Add nodejs repo and keys -RUN mkdir -p /etc/apt/keyrings \ - && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ - && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list +RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - # Download nodejs and install yarn RUN apt-get update \ @@ -110,7 +106,7 @@ CMD [ "/bin/sh", "/lowcoder/node-service/entrypoint.sh" ] ## ## Build lowcoder client application ## -FROM node:20.2-slim AS build-client +FROM node:23.10-slim AS build-client # curl is required for yarn build to succeed, because it calls it while building client RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates @@ -147,7 +143,7 @@ RUN yarn build ## To create a separate image out of it, build it with: ## DOCKER_BUILDKIT=1 docker build -f deploy/docker/Dockerfile -t lowcoderorg/lowcoder-ce-frontend --target lowcoder-ce-frontend . ## -FROM nginx:1.27.1 AS lowcoder-ce-frontend +FROM nginx:1.29.1 AS lowcoder-ce-frontend LABEL maintainer="lowcoder" # Change default nginx user into lowcoder user and remove default nginx config @@ -188,7 +184,7 @@ EXPOSE 3443 ## ## Build Lowcoder all-in-one image ## -FROM ubuntu:jammy +FROM ubuntu:noble LABEL maintainer="lowcoder" # Install essential tools @@ -197,26 +193,30 @@ RUN apt-get update \ && rm -rf /var/cache/apt/lists /var/lib/apt/lists/* /var/log/dpkg.log \ && apt-get clean +# Install nodejs apt repo +RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - + # Add required apt repositories and signing keys -RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/nodesource-keyring.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ +RUN mkdir -p /usr/share/keyrings \ && curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg \ && echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb `lsb_release -cs` main" | tee /etc/apt/sources.list.d/redis.list \ - && curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] https://repo.mongodb.org/apt/ubuntu `lsb_release -cs`/mongodb-org/7.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-7.0.list \ + && curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] https://repo.mongodb.org/apt/ubuntu `lsb_release -cs`/mongodb-org/8.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-8.0.list \ && curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list + && echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list \ + && curl -fsSL https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /usr/share/keyrings/adoptium-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/adoptium-keyring.gpg] https://packages.adoptium.net/artifactory/deb `lsb_release -cs` main" | tee /etc/apt/sources.list.d/adoptium.list # Install required packages RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends -y \ - nginx=1.27.1-1~jammy \ + nginx=1.29.1-1~noble \ mongodb-org \ redis \ supervisor \ gosu \ nodejs \ - openjdk-17-jdk-headless \ + temurin-24-jdk \ && npm install -g yarn \ && rm -rf /var/cache/apt/lists /var/lib/apt/lists/* /var/log/dpkg.log \ && apt-get clean \ @@ -224,8 +224,8 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-ins # Use configuration setup from official nginx image RUN rm -rf /etc/nginx/nginx.conf -COPY --from=nginx:1.27.1 /docker-entrypoint.d /docker-entrypoint.d -COPY --from=nginx:1.27.1 /docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=nginx:1.29.1 /docker-entrypoint.d /docker-entrypoint.d +COPY --from=nginx:1.29.1 /docker-entrypoint.sh /docker-entrypoint.sh # Add lowcoder user RUN usermod --login lowcoder --uid 9001 nginx \ diff --git a/deploy/docker/default.env b/deploy/docker/default.env index 8b4445a3d4..d37f0ce817 100644 --- a/deploy/docker/default.env +++ b/deploy/docker/default.env @@ -116,8 +116,8 @@ LOWCODER_NODE_SERVICE_SECRET_SALT="lowcoder.org" ## ## Frontend parameters ## -# Lowcoder max request size -LOWCODER_MAX_REQUEST_SIZE=20m +# Lowcoder max request size in kb/mb/gb +LOWCODER_MAX_REQUEST_SIZE=20mb # Lowcoder max query timeout (in seconds) LOWCODER_MAX_QUERY_TIMEOUT=120 # Default lowcoder query timeout diff --git a/deploy/docker/frontend/01-update-nginx-conf.sh b/deploy/docker/frontend/01-update-nginx-conf.sh index 3499286b28..88e8928e91 100644 --- a/deploy/docker/frontend/01-update-nginx-conf.sh +++ b/deploy/docker/frontend/01-update-nginx-conf.sh @@ -18,12 +18,16 @@ else ln -s /etc/nginx/nginx-http.conf /etc/nginx/nginx.conf fi; -sed -i "s@__LOWCODER_MAX_REQUEST_SIZE__@${LOWCODER_MAX_REQUEST_SIZE:=20m}@" /etc/nginx/nginx.conf +# Normalize max. request size for usage with nginx +MAX_REQUEST_SIZE=$(echo "${LOWCODER_MAX_REQUEST_SIZE:=20m}" | perl -pe 's/^([ \t]*)(?\d+(\.\d+)?)([ \t]*)(?[kKmMgGtT]{1})?([bB \t]*)$/"$+{number}" . lc($+{unit})/e') + + +sed -i "s@__LOWCODER_MAX_REQUEST_SIZE__@${MAX_REQUEST_SIZE}@" /etc/nginx/nginx.conf sed -i "s@__LOWCODER_MAX_QUERY_TIMEOUT__@${LOWCODER_MAX_QUERY_TIMEOUT:=120}@" /etc/nginx/server.conf sed -i "s@__LOWCODER_API_SERVICE_URL__@${LOWCODER_API_SERVICE_URL:=http://localhost:8080}@" /etc/nginx/server.conf sed -i "s@__LOWCODER_NODE_SERVICE_URL__@${LOWCODER_NODE_SERVICE_URL:=http://localhost:6060}@" /etc/nginx/server.conf echo "nginx config updated with:" -echo " Lowcoder max upload size: ${LOWCODER_MAX_REQUEST_SIZE:=20m}" +echo " Lowcoder max upload size: ${MAX_REQUEST_SIZE:=20m}" echo " Lowcoder api service URL: ${LOWCODER_API_SERVICE_URL:=http://localhost:8080}" echo " Lowcoder node service URL: ${LOWCODER_NODE_SERVICE_URL:=http://localhost:6060}" diff --git a/deploy/helm/templates/api-service/secrets.yaml b/deploy/helm/templates/api-service/secrets.yaml index c1e45ced8e..c670b36c2e 100644 --- a/deploy/helm/templates/api-service/secrets.yaml +++ b/deploy/helm/templates/api-service/secrets.yaml @@ -31,6 +31,6 @@ stringData: LOWCODER_API_KEY_SECRET: "{{ .Values.global.config.apiKeySecret }}" LOWCODER_SUPERUSER_USERNAME: {{ .Values.global.config.superuser.username | default "admin@localhost" | quote }} LOWCODER_SUPERUSER_PASSWORD: {{ .Values.global.config.superuser.password | default "" | quote }} - LOWCODER_NODE_SERVICE_SECRET: {{ .values.global.config.nodeServiceSecret | default "62e348319ab9f5c43c3b5a380b4d82525cdb68740f21140e767989b509ab0aa2" | quote }} - LOWCODER_NODE_SERVICE_SECRET_SALT: {{ .values.global.config.nodeServiceSalt | default "lowcoder.org" | quote }} + LOWCODER_NODE_SERVICE_SECRET: {{ .Values.global.config.nodeServiceSecret | default "62e348319ab9f5c43c3b5a380b4d82525cdb68740f21140e767989b509ab0aa2" | quote }} + LOWCODER_NODE_SERVICE_SECRET_SALT: {{ .Values.global.config.nodeServiceSalt | default "lowcoder.org" | quote }} diff --git a/deploy/helm/templates/node-service/secrets.yaml b/deploy/helm/templates/node-service/secrets.yaml index 2af6cfa30b..88888d60f3 100644 --- a/deploy/helm/templates/node-service/secrets.yaml +++ b/deploy/helm/templates/node-service/secrets.yaml @@ -10,6 +10,6 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} stringData: - LOWCODER_NODE_SERVICE_SECRET: {{ .values.global.config.nodeServiceSecret | default "62e348319ab9f5c43c3b5a380b4d82525cdb68740f21140e767989b509ab0aa2" | quote }} - LOWCODER_NODE_SERVICE_SECRET_SALT: {{ .values.global.config.nodeServiceSalt | default "lowcoder.org" | quote }} + LOWCODER_NODE_SERVICE_SECRET: {{ .Values.global.config.nodeServiceSecret | default "62e348319ab9f5c43c3b5a380b4d82525cdb68740f21140e767989b509ab0aa2" | quote }} + LOWCODER_NODE_SERVICE_SECRET_SALT: {{ .Values.global.config.nodeServiceSalt | default "lowcoder.org" | quote }} diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index 3723fec4b4..ddfe76491a 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -34,7 +34,7 @@ global: nodeServiceSecret: "62e348319ab9f5c43c3b5a380b4d82525cdb68740f21140e767989b509ab0aa2" nodeServiceSalt: "lowcoder.org" maxQueryTimeout: 120 - maxRequestSize: "20m" + maxRequestSize: "20mb" snapshotRetentionTime: 30 marketplacePrivateMode: true cookie: diff --git a/server/api-service/lowcoder-dependencies/pom.xml b/server/api-service/lowcoder-dependencies/pom.xml index 0787bcfcf4..6a69dba570 100644 --- a/server/api-service/lowcoder-dependencies/pom.xml +++ b/server/api-service/lowcoder-dependencies/pom.xml @@ -18,7 +18,7 @@ org.springframework.boot spring-boot-dependencies - 3.2.4 + 3.4.5 pom import @@ -38,24 +38,24 @@ org.json json - 20240303 + 20250107 org.projectlombok lombok - 1.18.32 + 1.18.38 org.apache.commons commons-text - 1.11.0 + 1.13.1 commons-io commons-io - 2.16.0 + 2.19.0 org.glassfish @@ -76,18 +76,18 @@ jakarta.persistence jakarta.persistence-api - 3.1.0 + 3.2.0 org.apache.commons commons-collections4 - 4.4 + 4.5.0 com.google.guava guava - 33.1.0-jre + 33.4.8-jre @@ -109,7 +109,7 @@ com.github.ben-manes.caffeine caffeine - 3.1.8 + 3.2.0 es.moki.ratelimitj @@ -119,7 +119,7 @@ com.github.spullara.mustache.java compiler - 0.9.11 + 0.9.14 @@ -131,7 +131,7 @@ io.projectreactor reactor-core - 3.6.4 + 3.7.4 @@ -246,7 +246,7 @@ org.springdoc springdoc-openapi-starter-webflux-ui - 2.5.0 + 2.8.6 diff --git a/server/api-service/lowcoder-infra/src/main/java/org/lowcoder/infra/localcache/ReloadableCache.java b/server/api-service/lowcoder-infra/src/main/java/org/lowcoder/infra/localcache/ReloadableCache.java index 59b568d06c..edb685f798 100644 --- a/server/api-service/lowcoder-infra/src/main/java/org/lowcoder/infra/localcache/ReloadableCache.java +++ b/server/api-service/lowcoder-infra/src/main/java/org/lowcoder/infra/localcache/ReloadableCache.java @@ -5,9 +5,9 @@ import jakarta.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.lowcoder.sdk.destructor.DestructorUtil; +import org.springframework.lang.CheckReturnValue; import reactor.core.publisher.Mono; -import javax.annotation.CheckReturnValue; import java.time.Duration; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; diff --git a/server/api-service/lowcoder-plugins/clickHousePlugin/pom.xml b/server/api-service/lowcoder-plugins/clickHousePlugin/pom.xml index 0f4b417d37..c3d9525cfc 100644 --- a/server/api-service/lowcoder-plugins/clickHousePlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/clickHousePlugin/pom.xml @@ -15,7 +15,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} clickHouse-plugin @@ -39,18 +40,9 @@ - com.clickhouse clickhouse-jdbc - 0.3.2-patch11 - - all - - - * - * - - + 0.8.4 @@ -58,7 +50,11 @@ org.testcontainers testcontainers - 1.18.0 + test + + + org.testcontainers + clickhouse test @@ -69,30 +65,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/elasticSearchPlugin/pom.xml b/server/api-service/lowcoder-plugins/elasticSearchPlugin/pom.xml index d420375260..bb03509dfe 100644 --- a/server/api-service/lowcoder-plugins/elasticSearchPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/elasticSearchPlugin/pom.xml @@ -16,7 +16,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} es-plugin @@ -31,21 +32,19 @@ org.elasticsearch.client elasticsearch-rest-client - 8.2.3 + 9.0.0 org.testcontainers testcontainers - 1.18.0 test org.testcontainers elasticsearch - 1.16.3 test @@ -63,29 +62,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/googleSheetsPlugin/pom.xml b/server/api-service/lowcoder-plugins/googleSheetsPlugin/pom.xml index e50a0dbe63..5654782012 100644 --- a/server/api-service/lowcoder-plugins/googleSheetsPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/googleSheetsPlugin/pom.xml @@ -16,7 +16,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} googleSheets-plugin @@ -31,22 +32,22 @@ com.google.api-client google-api-client - 1.31.2 + 2.7.2 com.google.oauth-client google-oauth-client-jetty - 1.30.6 + 1.39.0 com.google.auth google-auth-library-oauth2-http - 1.3.0 + 1.33.1 com.google.apis google-api-services-sheets - v4-rev20230526-2.0.0 + v4-rev612-1.25.0 @@ -54,20 +55,19 @@ org.assertj assertj-core - 3.13.2 test org.springframework.boot spring-boot-starter-webflux - 3.0.6 + 3.4.5 test com.google.guava guava - 31.1-jre + 33.4.8-jre compile @@ -79,30 +79,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/graphqlPlugin/pom.xml b/server/api-service/lowcoder-plugins/graphqlPlugin/pom.xml index cc0aa2d7df..44953a5cbb 100644 --- a/server/api-service/lowcoder-plugins/graphqlPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/graphqlPlugin/pom.xml @@ -16,7 +16,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} graphql-plugin @@ -24,6 +25,7 @@ ${revision} service@lowcoder.org + 0.12.6 @@ -67,23 +69,22 @@ io.jsonwebtoken jjwt-api - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-impl - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-jackson - 0.11.2 + ${jjwt.version} org.assertj assertj-core - 3.13.2 test @@ -106,13 +107,13 @@ org.springframework.boot spring-boot-starter-webflux - 3.0.6 + 3.4.5 test org.springframework spring-test - 6.0.10 + 6.2.6 test @@ -133,30 +134,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - org.apache.maven.plugins diff --git a/server/api-service/lowcoder-plugins/mongoPlugin/pom.xml b/server/api-service/lowcoder-plugins/mongoPlugin/pom.xml index 4361d9f387..4b25143408 100644 --- a/server/api-service/lowcoder-plugins/mongoPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/mongoPlugin/pom.xml @@ -16,7 +16,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} mongo-plugin @@ -32,7 +33,6 @@ org.testcontainers testcontainers - 1.18.0 test @@ -53,29 +53,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-antrun-plugin diff --git a/server/api-service/lowcoder-plugins/mssqlPlugin/pom.xml b/server/api-service/lowcoder-plugins/mssqlPlugin/pom.xml index 790a695bdb..67fdd9523a 100644 --- a/server/api-service/lowcoder-plugins/mssqlPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/mssqlPlugin/pom.xml @@ -16,7 +16,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} mssql-plugin @@ -43,13 +44,11 @@ org.testcontainers testcontainers - 1.18.0 test org.testcontainers mssqlserver - 1.16.3 test @@ -65,39 +64,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/mysqlPlugin/pom.xml b/server/api-service/lowcoder-plugins/mysqlPlugin/pom.xml index 201618644d..669f14176e 100644 --- a/server/api-service/lowcoder-plugins/mysqlPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/mysqlPlugin/pom.xml @@ -16,7 +16,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} mysql-plugin @@ -29,11 +30,10 @@ - mysql - mysql-connector-java - 8.0.30 - - + com.mysql + mysql-connector-j + 9.2.0 + org.lowcoder sqlBasedPlugin compile @@ -42,14 +42,12 @@ org.testcontainers testcontainers - 1.18.0 test org.testcontainers mysql - 1.16.3 test @@ -59,29 +57,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/oraclePlugin/pom.xml b/server/api-service/lowcoder-plugins/oraclePlugin/pom.xml index a109e8a7ae..d4eaa56ac4 100644 --- a/server/api-service/lowcoder-plugins/oraclePlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/oraclePlugin/pom.xml @@ -16,9 +16,9 @@ UTF-8 UTF-8 - - 17 - 17 + ${jdk.version} + ${java.version} + ${java.version} oracle-plugin org.lowcoder.plugin.oracle.OraclePlugin @@ -37,7 +37,6 @@ org.testcontainers oracle-xe - 1.17.3 test @@ -57,30 +56,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/pom.xml b/server/api-service/lowcoder-plugins/pom.xml index e0535b582b..97d99876ec 100644 --- a/server/api-service/lowcoder-plugins/pom.xml +++ b/server/api-service/lowcoder-plugins/pom.xml @@ -21,6 +21,11 @@ + + ${jdk.version} + 1.21.0 + + org.pf4j @@ -164,6 +169,50 @@ snowflakePlugin ${revision} + + + + org.assertj + assertj-core + 3.27.3 + + + org.testcontainers + testcontainers + ${testcontainers.version} + + + org.testcontainers + clickhouse + ${testcontainers.version} + + + org.testcontainers + elasticsearch + ${testcontainers.version} + + + org.testcontainers + mssqlserver + ${testcontainers.version} + + + org.testcontainers + mysqlserver + ${testcontainers.version} + + + org.testcontainers + oracle-xe + ${testcontainers.version} + + + org.testcontainers + postgresql + ${testcontainers.version} + + + @@ -179,13 +228,14 @@ ${java.version} -parameters + -proc:full org.apache.maven.plugins maven-dependency-plugin - 3.6.1 + 3.8.1 org.apache.maven.plugins @@ -206,6 +256,35 @@ + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + shade-plugin-jar + package + + shade + + + false + + + + ${plugin.id} + ${plugin.class} + ${plugin.version} + ${plugin.provider} + + + + + + + + diff --git a/server/api-service/lowcoder-plugins/postgresPlugin/pom.xml b/server/api-service/lowcoder-plugins/postgresPlugin/pom.xml index 4d6c00086d..460c23ce6b 100644 --- a/server/api-service/lowcoder-plugins/postgresPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/postgresPlugin/pom.xml @@ -17,7 +17,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} postgres-plugin @@ -32,7 +33,7 @@ org.postgresql postgresql - 42.3.8 + 42.3.9 @@ -45,13 +46,11 @@ org.testcontainers testcontainers - 1.18.0 test org.testcontainers postgresql - 1.16.3 test @@ -61,29 +60,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/redisPlugin/pom.xml b/server/api-service/lowcoder-plugins/redisPlugin/pom.xml index f65a1d22d5..37b540666f 100644 --- a/server/api-service/lowcoder-plugins/redisPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/redisPlugin/pom.xml @@ -16,7 +16,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} redis-plugin @@ -45,10 +46,14 @@ org.testcontainers testcontainers - 1.18.0 test - + + com.redis + testcontainers-redis + 2.2.4 + test + @@ -56,30 +61,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/restApiPlugin/pom.xml b/server/api-service/lowcoder-plugins/restApiPlugin/pom.xml index 912902ad37..8a7fa7b99d 100644 --- a/server/api-service/lowcoder-plugins/restApiPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/restApiPlugin/pom.xml @@ -16,7 +16,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} restapi-plugin @@ -70,16 +71,6 @@ 1.5 - - - - - - - - - - org.assertj assertj-core @@ -103,30 +94,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/smtpPlugin/pom.xml b/server/api-service/lowcoder-plugins/smtpPlugin/pom.xml index add6d6d9b6..453b7efef7 100644 --- a/server/api-service/lowcoder-plugins/smtpPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/smtpPlugin/pom.xml @@ -16,7 +16,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} smtp-plugin @@ -46,7 +47,7 @@ io.netty netty-codec-http - 4.1.77.Final + 4.1.108.Final test @@ -56,7 +57,6 @@ org.testcontainers testcontainers - 1.18.0 test @@ -66,30 +66,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/snowflakePlugin/pom.xml b/server/api-service/lowcoder-plugins/snowflakePlugin/pom.xml index 9b114063b7..2c0717c16d 100644 --- a/server/api-service/lowcoder-plugins/snowflakePlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/snowflakePlugin/pom.xml @@ -13,9 +13,11 @@ snowflakePlugin - 17 - 17 - 17 + UTF-8 + UTF-8 + ${jdk.version} + ${java.version} + ${java.version} UTF-8 snowflake-plugin @@ -30,7 +32,7 @@ net.snowflake snowflake-jdbc - 3.22.0 + 3.23.1 org.lowcoder @@ -41,7 +43,6 @@ org.testcontainers testcontainers - 1.18.0 test @@ -51,29 +52,6 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 - - - shade-plugin-jar - package - - shade - - - false - - - - ${plugin.id} - ${plugin.class} - ${plugin.version} - ${plugin.provider} - - - - - - maven-dependency-plugin diff --git a/server/api-service/lowcoder-plugins/sqlBasedPlugin/pom.xml b/server/api-service/lowcoder-plugins/sqlBasedPlugin/pom.xml index 142aefebb3..c16cfd260b 100644 --- a/server/api-service/lowcoder-plugins/sqlBasedPlugin/pom.xml +++ b/server/api-service/lowcoder-plugins/sqlBasedPlugin/pom.xml @@ -13,7 +13,8 @@ UTF-8 - 17 + UTF-8 + ${jdk.version} ${java.version} ${java.version} diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/plugin/sqlcommand/GuiSqlCommand.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/plugin/sqlcommand/GuiSqlCommand.java index f1690bae13..df0fc907d6 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/plugin/sqlcommand/GuiSqlCommand.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/plugin/sqlcommand/GuiSqlCommand.java @@ -37,7 +37,7 @@ public List bindParams() { } static String parseTable(Map commandDetail) { - String table = MapUtils.getString(commandDetail, TABLE_KEY, null); + String table = MapUtils.getString(commandDetail, TABLE_KEY, (String)null); if (StringUtils.isBlank(table)) { throw new PluginException(INVALID_GUI_SETTINGS, "GUI_FIELD_EMPTY"); } diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/util/MustacheHelper.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/util/MustacheHelper.java index 25cc917a3c..0e9a4b8962 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/util/MustacheHelper.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/util/MustacheHelper.java @@ -173,7 +173,7 @@ private static List doTokenize(String template) { } - if (currentToken.length() > 0) { + if (!currentToken.isEmpty()) { tokens.add(currentToken.toString()); } @@ -243,7 +243,7 @@ public static List extractMustacheKeysInOrder(String template) { } private static void clearAndPushToken(StringBuilder tokenBuilder, List tokenList) { - if (tokenBuilder.length() > 0) { + if (!tokenBuilder.isEmpty()) { tokenList.add(tokenBuilder.toString()); tokenBuilder.setLength(0); } @@ -537,11 +537,11 @@ private static Range findQuotePair(String query, int startIndex) { start = i; } else { if (query.charAt(i) == quote) { - return Range.between(start, i + 1); + return Range.of(start, i + 1); } } } - return Range.between(-1, -1); + return Range.of(-1, -1); } private static boolean isQuote(String query, int index) { diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/ApiEventFilter.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/ApiEventFilter.java index dc084c1293..ae7f27cc31 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/ApiEventFilter.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/ApiEventFilter.java @@ -10,6 +10,7 @@ import org.lowcoder.plugin.api.event.LowcoderEvent; import org.lowcoder.sdk.constants.Authentication; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; @@ -38,7 +39,7 @@ public class ApiEventFilter implements WebFilter { public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { return chain.filter(exchange).then( Mono.deferContextual(contextView -> { - if (exchange.getResponse().getStatusCode().is2xxSuccessful()) { + if (exchange.getResponse().getStatusCode() != null && exchange.getResponse().getStatusCode().is2xxSuccessful()) { String token = contextView.get(VISITOR_TOKEN); ((Mono) contextView.get(CURRENT_ORG_MEMBER)) .flatMap(orgMember -> { @@ -54,7 +55,7 @@ public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { } private void emitEvent(ServerHttpRequest request, String token, OrgMember orgMember, ContextView contextView) { - MultiValueMap headers = writableHttpHeaders(request.getHeaders()); + MultiValueMap headers = new HttpHeaders(request.getHeaders()); headers.remove("Cookie"); Optional ipAddressOptional = headers.remove("X-Real-IP").stream().findFirst(); String ipAddress = ipAddressOptional.orElse(""); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/GlobalContextFilter.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/GlobalContextFilter.java index fe00a00653..36227a794b 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/GlobalContextFilter.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/filter/GlobalContextFilter.java @@ -13,6 +13,7 @@ import org.lowcoder.sdk.util.UriUtils; import org.springframework.context.i18n.LocaleContext; import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; @@ -33,7 +34,6 @@ import static org.lowcoder.sdk.constants.Authentication.isAnonymousUser; import static org.lowcoder.sdk.constants.GlobalContext.*; import static org.lowcoder.sdk.util.IDUtils.generate; -import static org.springframework.http.HttpHeaders.writableHttpHeaders; @Component @RequiredArgsConstructor @@ -111,7 +111,7 @@ private Map buildContextMap(ServerWebExchange serverWebExchange, contextMap.put(DOMAIN, UriUtils.getRefererDomainFromRequest(serverWebExchange)); //Analytics related fields - MultiValueMap headers = writableHttpHeaders(request.getHeaders()); + MultiValueMap headers = new HttpHeaders(request.getHeaders()); headers.remove("Cookie"); contextMap.put(HEADERS, headers); return contextMap; @@ -120,7 +120,7 @@ private Map buildContextMap(ServerWebExchange serverWebExchange, @SuppressWarnings("ConstantConditions") private String getOrCreateRequestId(final ServerHttpRequest request) { if (!request.getHeaders().containsKey(REQUEST_ID_HEADER)) { - request.mutate().header(REQUEST_ID_HEADER, generate()).build(); + request.getHeaders().add(REQUEST_ID_HEADER, generate()); } return request.getHeaders().get(REQUEST_ID_HEADER).get(0); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java index d8520a9d54..e7e840e010 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java @@ -344,7 +344,7 @@ public Flux getAllMarketplaceApplications(@Nulla return applicationFlux - .flatMap(application -> Mono.zip(Mono.just(application), userMapMono, orgMapMono)) + .flatMap(application -> Mono.zip(Mono.justOrEmpty(application), userMapMono, orgMapMono)) .flatMap(tuple2 -> { // build view Application application = tuple2.getT1(); @@ -356,24 +356,33 @@ public Flux getAllMarketplaceApplications(@Nulla .applicationType(application.getApplicationType()) .applicationStatus(application.getApplicationStatus()) .orgId(application.getOrganizationId()) - .orgName(orgMap.get(application.getOrganizationId()).getName()) + .orgName(Optional.ofNullable(orgMap.get(application.getOrganizationId())) + .map(Organization::getName) + .orElse("")) .creatorEmail(Optional.ofNullable(userMap.get(application.getCreatedBy())) .map(User::getName) .orElse("")) - .createAt(application.getCreatedAt().toEpochMilli()) + .createAt(Optional.ofNullable(application.getCreatedAt()) + .map(Instant::toEpochMilli) + .orElse(0L)) .createBy(application.getCreatedBy()) .build(); // marketplace specific fields return application.getPublishedApplicationDSL(applicationRecordService) - .map(pubishedApplicationDSL -> - (Map) new HashMap((Map) pubishedApplicationDSL.getOrDefault("settings", new HashMap<>()))) - .switchIfEmpty(Mono.just(new HashMap<>())) + .map(dsl -> { + Object settingsObj = dsl.getOrDefault("settings", new HashMap<>()); + if (!(settingsObj instanceof Map)) { + return new HashMap(); // fallback if not a map + } + return (Map) settingsObj; + }) + .defaultIfEmpty(new HashMap<>()) .map(settings -> { - marketplaceApplicationInfoView.setTitle((String)settings.getOrDefault("title", application.getName())); - marketplaceApplicationInfoView.setCategory((String)settings.get("category")); - marketplaceApplicationInfoView.setDescription((String)settings.get("description")); - marketplaceApplicationInfoView.setImage((String)settings.get("icon")); + marketplaceApplicationInfoView.setTitle((String) settings.getOrDefault("title", application.getName())); + marketplaceApplicationInfoView.setCategory((String) settings.get("category")); + marketplaceApplicationInfoView.setDescription((String) settings.get("description")); + marketplaceApplicationInfoView.setImage((String) settings.get("icon")); return marketplaceApplicationInfoView; }); }); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java index 42e8a7e27f..0879cb11ef 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/util/ApiCallEventPublisher.java @@ -5,7 +5,6 @@ import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.lowcoder.api.framework.filter.ReactiveRequestContextHolder; import org.lowcoder.api.home.SessionUserService; @@ -13,17 +12,14 @@ import org.lowcoder.infra.event.APICallEvent; import org.lowcoder.plugin.api.event.LowcoderEvent.EventType; import org.lowcoder.sdk.constants.Authentication; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; -import static org.springframework.http.HttpHeaders.writableHttpHeaders; - @Slf4j //@Aspect @RequiredArgsConstructor @@ -63,7 +59,7 @@ public Object handleAPICallEvent(ProceedingJoinPoint joinPoint) throws Throwable if (orgMember == OrgMember.NOT_EXIST) { return Mono.empty(); } - MultiValueMap headers = writableHttpHeaders(request.getHeaders()); + MultiValueMap headers = new HttpHeaders(request.getHeaders()); headers.remove("Cookie"); String ipAddress = headers.remove("X-Real-IP").stream().findFirst().get(); APICallEvent event = APICallEvent.builder() diff --git a/server/api-service/pom.xml b/server/api-service/pom.xml index 358f6aaf9d..9fc4567230 100644 --- a/server/api-service/pom.xml +++ b/server/api-service/pom.xml @@ -11,8 +11,9 @@ ${revision} - 2.7.5 - 17 + 2.7.4 + 24 + ${jdk.version} ${java.version} ${java.version} true @@ -97,6 +98,7 @@ ${java.version} -parameters + -proc:full