();
+ const { scopes } = useContext(FilterContext) || {};
+ const { op, leftVar, rightVar } = field.value || {};
+ const data2value = useCallback(() => {
+ field.value = field.data.leftVar
+ ? {
+ op: field.data.operator?.value,
+ leftVar: field.data.leftVar,
+ rightVar: field.data?.rightVar,
+ }
+ : {};
+ }, [field]);
+
+ const value2data = () => {
+ /**
+ * 等待获取最新的scopes
+ */
+ setTimeout(() => {
+ const option = findOption(leftVar, scopes);
+ field.data = field.data || {};
+ if (!field.value) {
+ return;
+ }
+ const combOperators = uniqBy([...(field.data.operators || []), ...(option?.operators || [])], 'value');
+ field.data.operators = combOperators.length ? combOperators : operators;
+ field.data.leftVar = leftVar;
+ field.data.rightVar = rightVar;
+ const operator = combOperators?.find((v) => v.value === op);
+ field.data.operator = field.data.operator || operator;
+ const s1 = cloneDeep(option?.schema);
+ const s2 = cloneDeep(operator?.schema);
+ field.data.schema = field.data?.schema || merge(s1, s2);
+ }, 100);
+ };
+
+ useEffect(value2data, [field.value, scopes]);
+
+ const setLeftValue = useCallback(
+ (leftVar, paths) => {
+ const option: any = last(paths);
+ field.data = field.data || {};
+ field.data.operators = option?.operators || operators;
+ const operator = field.data.operators?.[0];
+ field.data.operator = operator;
+ const s1 = cloneDeep(option?.schema);
+ const s2 = cloneDeep(operator?.schema);
+ field.data.schema = merge(s1, s2);
+ field.data.leftVar = leftVar;
+ field.data.rightVar = operator?.noValue ? operator.default || true : undefined;
+ data2value();
+ },
+ [data2value, field],
+ );
+
+ const setOperator = useCallback(
+ (operatorValue) => {
+ const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
+ field.data.operator = operator;
+ const s1 = cloneDeep(field.data.schema);
+ const s2 = cloneDeep(operator?.schema);
+ field.data.schema = merge(s1, s2);
+ field.data.value = operator.noValue ? operator.default || true : undefined;
+ data2value();
+ },
+ [data2value, field.data],
+ );
+
+ const setRightValue = useCallback(
+ (rightVar) => {
+ field.data.rightVar = rightVar;
+ data2value();
+ },
+ [data2value, field.data],
+ );
+ return {
+ ...(field?.data || {}),
+ setLeftValue,
+ setOperator,
+ setRightValue,
+ };
+};
diff --git a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx
index 0a3ced0dc6..88e1ba38de 100644
--- a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx
+++ b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx
@@ -48,6 +48,7 @@ export type RemoteSelectProps = SelectProps
& {
CustomDropdownRender?: (v: any) => any;
optionFilter?: (option: any) => boolean;
toOptionsItem?: (data) => any;
+ onSuccess?: (data) => any;
};
const InternalRemoteSelect = withDynamicSchemaProps(
@@ -68,6 +69,7 @@ const InternalRemoteSelect = withDynamicSchemaProps(
dataSource: propsDataSource,
toOptionsItem = (value) => value,
popupMatchSelectWidth = false,
+ onSuccess,
...others
} = props;
const dataSource = useDataSourceKey();
@@ -178,6 +180,7 @@ const InternalRemoteSelect = withDynamicSchemaProps(
{
manual,
debounceWait: wait,
+ onSuccess,
...(service.defaultParams ? { defaultParams: [service.defaultParams] } : {}),
},
);
diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.Column.Decorator.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.Column.Decorator.tsx
index af6a055d9f..b3b3f35139 100644
--- a/packages/core/client/src/schema-component/antd/table-v2/Table.Column.Decorator.tsx
+++ b/packages/core/client/src/schema-component/antd/table-v2/Table.Column.Decorator.tsx
@@ -18,6 +18,8 @@ import {
useDesigner,
useFlag,
useSchemaComponentContext,
+ BlockContext,
+ useBlockContext,
} from '../../../';
import { useToken } from '../__builtins__';
import { designerCss } from './Table.Column.ActionBar';
@@ -77,6 +79,7 @@ export const TableColumnDecorator = (props) => {
const compile = useCompile();
const { isInSubTable } = useFlag() || {};
const { token } = useToken();
+ const { name } = useBlockContext?.() || {};
useEffect(() => {
if (field.title) {
@@ -110,11 +113,13 @@ export const TableColumnDecorator = (props) => {
})}
>
-
-
- {fieldSchema?.required && *}
- {field?.title || compile(uiSchema?.title)}
-
+
+
+
+ {fieldSchema?.required && *}
+ {field?.title || compile(uiSchema?.title)}
+
+
);
diff --git a/packages/core/client/src/schema-component/antd/variable/Input.tsx b/packages/core/client/src/schema-component/antd/variable/Input.tsx
index 23993aad16..c19f9465e1 100644
--- a/packages/core/client/src/schema-component/antd/variable/Input.tsx
+++ b/packages/core/client/src/schema-component/antd/variable/Input.tsx
@@ -186,6 +186,7 @@ export type VariableInputProps = {
className?: string;
parseOptions?: ParseOptions;
hideVariableButton?: boolean;
+ constantAbel?: boolean;
};
export function Input(props: VariableInputProps) {
@@ -202,6 +203,7 @@ export function Input(props: VariableInputProps) {
fieldNames,
parseOptions,
hideVariableButton,
+ constantAbel = true,
} = props;
const scope = typeof props.scope === 'function' ? props.scope() : props.scope;
const { wrapSSR, hashId, componentCls, rootPrefixCls } = useStyles({ hideVariableButton });
@@ -233,6 +235,7 @@ export function Input(props: VariableInputProps) {
);
const constantOption: DefaultOptionType & { component?: React.FC } = useMemo(() => {
+ if (!constantAbel) return null;
if (children) {
return {
value: '$',
diff --git a/packages/core/client/src/schema-component/common/utils/uitls.tsx b/packages/core/client/src/schema-component/common/utils/uitls.tsx
index bae058d2a0..ec2405dda5 100644
--- a/packages/core/client/src/schema-component/common/utils/uitls.tsx
+++ b/packages/core/client/src/schema-component/common/utils/uitls.tsx
@@ -57,6 +57,7 @@ export const getTargetField = (obj) => {
}
});
const result = keys.slice(0, index);
+
return result;
};
@@ -76,72 +77,44 @@ function getAllKeys(obj) {
return keys;
}
+const parseVariableValue = async (targetVariable, variables, localVariables) => {
+ const parsingResult = isVariable(targetVariable)
+ ? [variables.parseVariable(targetVariable, localVariables).then(({ value }) => value)]
+ : [targetVariable];
+
+ try {
+ const [value] = await Promise.all(parsingResult);
+ return value;
+ } catch (error) {
+ console.error('Error in parseVariableValue:', error);
+ throw error;
+ }
+};
+
export const conditionAnalyses = async (
{
ruleGroup,
variables,
localVariables,
variableNameOfLeftCondition,
+ conditionType,
}: {
ruleGroup;
variables: VariablesContextType;
localVariables: VariableOption[];
- /**
- * used to parse the variable name of the left condition value
- * @default '$nForm'
- */
variableNameOfLeftCondition?: string;
+ conditionType?: 'advanced' | 'basic';
},
jsonLogic: any,
) => {
const type = Object.keys(ruleGroup)[0] || '$and';
const conditions = ruleGroup[type];
- let results = conditions.map(async (condition) => {
- if ('$and' in condition || '$or' in condition) {
- return await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic);
- }
- const logicCalculation = getInnermostKeyAndValue(condition);
- const operator = logicCalculation?.key;
-
- if (!operator) {
- return true;
- }
-
- const targetVariableName = targetFieldToVariableString(getTargetField(condition), variableNameOfLeftCondition);
- const targetValue = variables
- .parseVariable(targetVariableName, localVariables, {
- doNotRequest: true,
- })
- .then(({ value }) => value);
-
- const parsingResult = isVariable(logicCalculation?.value)
- ? [variables.parseVariable(logicCalculation?.value, localVariables).then(({ value }) => value), targetValue]
- : [logicCalculation?.value, targetValue];
-
- try {
- const [value, targetValue] = await Promise.all(parsingResult);
- const targetCollectionField = await variables.getCollectionField(targetVariableName, localVariables);
- let currentInputValue = transformVariableValue(targetValue, { targetCollectionField });
- const comparisonValue = transformVariableValue(value, { targetCollectionField });
- if (
- targetCollectionField?.type &&
- ['datetime', 'date', 'datetimeNoTz', 'dateOnly', 'unixTimestamp'].includes(targetCollectionField.type) &&
- currentInputValue
- ) {
- const picker = inferPickerType(comparisonValue);
- const format = getPickerFormat(picker);
- currentInputValue = dayjs(currentInputValue).format(format);
- }
-
- return jsonLogic.apply({
- [operator]: [currentInputValue, comparisonValue],
- });
- } catch (error) {
- throw error;
- }
- });
- results = await Promise.all(results);
+ const results = await Promise.all(
+ conditions.map((condition) =>
+ processCondition(condition, variables, localVariables, variableNameOfLeftCondition, conditionType, jsonLogic),
+ ),
+ );
if (type === '$and') {
return every(results, (v) => v);
@@ -153,6 +126,67 @@ export const conditionAnalyses = async (
}
};
+const processCondition = async (
+ condition,
+ variables,
+ localVariables,
+ variableNameOfLeftCondition,
+ conditionType,
+ jsonLogic,
+) => {
+ if ('$and' in condition || '$or' in condition) {
+ return await conditionAnalyses({ ruleGroup: condition, variables, localVariables, conditionType }, jsonLogic);
+ }
+ return conditionType === 'advanced'
+ ? processAdvancedCondition(condition, variables, localVariables, jsonLogic)
+ : processBasicCondition(condition, variables, localVariables, variableNameOfLeftCondition, jsonLogic);
+};
+
+const processAdvancedCondition = async (condition, variables, localVariables, jsonLogic) => {
+ const operator = condition.op;
+ const rightValue = await parseVariableValue(condition.rightVar, variables, localVariables);
+ const leftValue = await parseVariableValue(condition.leftVar, variables, localVariables);
+ if (operator) {
+ return jsonLogic.apply({ [operator]: [leftValue, rightValue] });
+ }
+ return true;
+};
+
+const processBasicCondition = async (condition, variables, localVariables, variableNameOfLeftCondition, jsonLogic) => {
+ const logicCalculation = getInnermostKeyAndValue(condition);
+ const operator = logicCalculation?.key;
+ if (!operator) return true;
+
+ const targetVariableName = targetFieldToVariableString(getTargetField(condition), variableNameOfLeftCondition);
+ const targetValue = variables
+ .parseVariable(targetVariableName, localVariables, { doNotRequest: true })
+ .then(({ value }) => value);
+
+ const parsingResult = isVariable(logicCalculation?.value)
+ ? [variables.parseVariable(logicCalculation?.value, localVariables).then(({ value }) => value), targetValue]
+ : [logicCalculation?.value, targetValue];
+
+ try {
+ const [value, resolvedTargetValue] = await Promise.all(parsingResult);
+ const targetCollectionField = await variables.getCollectionField(targetVariableName, localVariables);
+ let currentInputValue = transformVariableValue(resolvedTargetValue, { targetCollectionField });
+ const comparisonValue = transformVariableValue(value, { targetCollectionField });
+
+ if (
+ targetCollectionField?.type &&
+ ['datetime', 'date', 'datetimeNoTz', 'dateOnly', 'unixTimestamp'].includes(targetCollectionField.type) &&
+ currentInputValue
+ ) {
+ const picker = inferPickerType(comparisonValue);
+ const format = getPickerFormat(picker);
+ currentInputValue = dayjs(currentInputValue).format(format);
+ }
+ return jsonLogic.apply({ [operator]: [currentInputValue, comparisonValue] });
+ } catch (error) {
+ throw error;
+ }
+};
+
/**
* 转化成变量字符串,方便解析出值
* @param targetField
diff --git a/packages/core/client/src/schema-initializer/components/CreateRecordAction.tsx b/packages/core/client/src/schema-initializer/components/CreateRecordAction.tsx
index 1cde756d2e..627aeaf7e3 100644
--- a/packages/core/client/src/schema-initializer/components/CreateRecordAction.tsx
+++ b/packages/core/client/src/schema-initializer/components/CreateRecordAction.tsx
@@ -89,6 +89,7 @@ const InternalCreateRecordAction = (props: any, ref) => {
condition: v.condition,
variables,
localVariables,
+ conditionType: v.conditionType,
},
app.jsonLogic,
);
@@ -208,6 +209,7 @@ export const CreateAction = observer(
condition: v.condition,
variables,
localVariables,
+ conditionType: v.conditionType,
},
app.jsonLogic,
);
diff --git a/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx b/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx
index b1bf08c57e..478c0562d1 100644
--- a/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx
+++ b/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx
@@ -26,7 +26,7 @@ import { VariableInput, getShouldChange } from '../../../schema-settings/Variabl
import { Option } from '../../../schema-settings/VariableInput/type';
import { formatVariableScop } from '../../../schema-settings/VariableInput/utils/formatVariableScop';
import { useLocalVariables, useVariables } from '../../../variables';
-
+import { BlockContext, useBlockContext } from '../../../block-provider';
interface AssignedFieldProps {
value: any;
onChange: (value: any) => void;
@@ -93,7 +93,7 @@ export enum AssignedFieldValueType {
DynamicValue = 'dynamicValue',
}
-export const AssignedField = (props: AssignedFieldProps) => {
+export const AssignedFieldInner = (props: AssignedFieldProps) => {
const { value, onChange } = props;
const { getCollectionFields, getAllCollectionsInheritChain } = useCollectionManager_deprecated();
const collection = useCollection_deprecated();
@@ -148,3 +148,13 @@ export const AssignedField = (props: AssignedFieldProps) => {
/>
);
};
+
+export const AssignedField = (props) => {
+ const { form } = useFormBlockContext();
+ const { name } = useBlockContext();
+ return (
+
+
+
+ );
+};
diff --git a/packages/core/client/src/schema-initializer/utils.ts b/packages/core/client/src/schema-initializer/utils.ts
index 66520a4110..5c80353fce 100644
--- a/packages/core/client/src/schema-initializer/utils.ts
+++ b/packages/core/client/src/schema-initializer/utils.ts
@@ -728,7 +728,7 @@ export const useCustomFormItemInitializerFields = (options?: any) => {
const remove = useRemoveGridFormItem();
return currentFields
?.filter((field) => {
- return field?.interface && field.interface !== 'snapshot' && field.type !== 'sequence';
+ return !field.inherit && field?.interface && field.interface !== 'snapshot' && field.type !== 'sequence';
})
?.map((field) => {
const interfaceConfig = getInterface(field.interface);
diff --git a/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts b/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts
index 6ae599c86e..0afde24761 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts
+++ b/packages/core/client/src/schema-settings/LinkageRules/bindLinkageRulesToFiled.ts
@@ -38,6 +38,7 @@ interface Props {
*/
variableNameOfLeftCondition?: string;
action?: any;
+ conditionType?: 'advanced' | 'basic';
}
export function bindLinkageRulesToFiled(
@@ -83,7 +84,6 @@ export function bindLinkageRulesToFiled(
() => {
// 获取条件中的字段值
const fieldValuesInCondition = getFieldValuesInCondition({ linkageRules, formValues });
-
// 获取条件中的变量值
const variableValuesInCondition = getVariableValuesInCondition({ linkageRules, localVariables });
@@ -132,20 +132,37 @@ function getVariableValuesInCondition({
return linkageRules.map((rule) => {
const type = Object.keys(rule.condition)[0] || '$and';
const conditions = rule.condition[type];
+ if (rule.conditionType === 'advanced') {
+ return conditions
+ .map((condition) => {
+ if (!condition) {
+ return null;
+ }
- return conditions
- .map((condition) => {
- const jsonlogic = getInnermostKeyAndValue(condition);
- if (!jsonlogic) {
- return null;
- }
- if (isVariable(jsonlogic.value)) {
- return getVariableValue(jsonlogic.value, localVariables);
- }
+ const resolveVariable = (varName) =>
+ isVariable(varName) ? getVariableValue(varName, localVariables) : varName;
- return jsonlogic.value;
- })
- .filter(Boolean);
+ return {
+ leftVar: resolveVariable(condition.leftVar),
+ rightVar: resolveVariable(condition.rightVar),
+ };
+ })
+ .filter(Boolean);
+ } else {
+ return conditions
+ .map((condition) => {
+ const jsonlogic = getInnermostKeyAndValue(condition);
+ if (!jsonlogic) {
+ return null;
+ }
+ if (isVariable(jsonlogic.value)) {
+ return getVariableValue(jsonlogic.value, localVariables);
+ }
+
+ return jsonlogic.value;
+ })
+ .filter(Boolean);
+ }
});
}
@@ -216,6 +233,7 @@ function getSubscriber(
localVariables,
variableNameOfLeftCondition,
action,
+ conditionType: rule.conditionType,
},
jsonLogic,
);
@@ -327,7 +345,17 @@ function getFieldNameByOperator(operator: ActionType) {
}
export const collectFieldStateOfLinkageRules = (
- { operator, value, field, condition, variables, localVariables, variableNameOfLeftCondition, action }: Props,
+ {
+ operator,
+ value,
+ field,
+ condition,
+ variables,
+ localVariables,
+ variableNameOfLeftCondition,
+ action,
+ conditionType,
+ }: Props,
jsonLogic: any,
) => {
const requiredResult = field?.stateOfLinkageRules?.required || [field?.initStateOfLinkageRules?.required];
@@ -336,7 +364,13 @@ export const collectFieldStateOfLinkageRules = (
const valueResult = field?.stateOfLinkageRules?.value || [field?.initStateOfLinkageRules?.value];
const optionsResult = field?.stateOfLinkageRules?.dataSource || [field?.initStateOfLinkageRules?.dataSource];
const { evaluate } = evaluators.get('formula.js');
- const paramsToGetConditionResult = { ruleGroup: condition, variables, localVariables, variableNameOfLeftCondition };
+ const paramsToGetConditionResult = {
+ ruleGroup: condition,
+ variables,
+ localVariables,
+ variableNameOfLeftCondition,
+ conditionType,
+ };
const dateScopeResult = field?.stateOfLinkageRules?.dateScope || [field?.initStateOfLinkageRules?.dateScope];
switch (operator) {
diff --git a/packages/core/client/src/schema-settings/LinkageRules/compute-rules.ts b/packages/core/client/src/schema-settings/LinkageRules/compute-rules.ts
index 12ca65e77f..ffece9a6d3 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/compute-rules.ts
+++ b/packages/core/client/src/schema-settings/LinkageRules/compute-rules.ts
@@ -38,7 +38,12 @@ const getSatisfiedActions = async ({ rules, variables, localVariables }, jsonLog
rules
.filter((k) => !k.disabled)
.map(async (rule) => {
- if (await conditionAnalyses({ ruleGroup: rule.condition, variables, localVariables }, jsonLogic)) {
+ if (
+ await conditionAnalyses(
+ { ruleGroup: rule.condition, variables, localVariables, conditionType: rule.conditionType },
+ jsonLogic,
+ )
+ ) {
return rule;
} else return null;
}),
diff --git a/packages/core/client/src/schema-settings/LinkageRules/index.tsx b/packages/core/client/src/schema-settings/LinkageRules/index.tsx
index c5b97bfb27..03acb9eca3 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/index.tsx
+++ b/packages/core/client/src/schema-settings/LinkageRules/index.tsx
@@ -10,7 +10,7 @@
import { css } from '@emotion/css';
import { observer, useFieldSchema } from '@formily/react';
import React, { useMemo } from 'react';
-import { useCollectionManager_deprecated } from '../../collection-manager';
+import { useCollectionManager_deprecated, useCollection_deprecated } from '../../collection-manager';
import { useCollectionParentRecordData } from '../../data-source/collection-record/CollectionRecordProvider';
import { CollectionProvider } from '../../data-source/collection/CollectionProvider';
import { withDynamicSchemaProps } from '../../hoc/withDynamicSchemaProps';
@@ -27,15 +27,75 @@ import { ArrayCollapse } from './components/LinkageHeader';
export interface Props {
dynamicComponent: any;
}
+function extractFieldPath(obj, path = []) {
+ if (typeof obj !== 'object' || obj === null) return null;
+
+ const [key, value] = Object.entries(obj)[0] || [];
+
+ if (typeof value === 'object' && value !== null && !key.startsWith('$')) {
+ return extractFieldPath(value, [...path, key]);
+ }
+
+ return [path.join('.'), obj];
+}
+type Condition = { [field: string]: { [op: string]: any } } | { $and: Condition[] } | { $or: Condition[] };
+type TransformedCondition =
+ | { leftVar: string; op: string; rightVar: any }
+ | { $and: TransformedCondition[] }
+ | { $or: TransformedCondition[] };
+function transformConditionData(condition: Condition, variableKey: '$nForm' | '$nRecord'): TransformedCondition {
+ if ('$and' in condition) {
+ return {
+ $and: condition.$and.map((c) => transformConditionData(c, variableKey)),
+ };
+ }
+
+ if ('$or' in condition) {
+ return {
+ $or: condition.$or.map((c) => transformConditionData(c, variableKey)),
+ };
+ }
+ const [field, expression] = extractFieldPath(condition || {}) || [];
+
+ const [op, value] = Object.entries(expression || {})[0] || [];
+ return {
+ leftVar: field ? `{{${variableKey}.${field}}}` : null,
+ op,
+ rightVar: value,
+ };
+}
+function getActiveContextName(contextList: { name: string; ctx: any }[]): string | null {
+ const priority = ['$nForm', '$nRecord'];
+ for (const name of priority) {
+ const item = contextList.find((ctx) => ctx.name === name && ctx.ctx);
+ if (item) return name;
+ }
+ return '$nRecord';
+}
+
+const transformDefaultValue = (values, variableKey) => {
+ return values.map((v) => {
+ if (v.conditionType !== 'advanced') {
+ const condition = transformConditionData(v.condition, variableKey);
+ return {
+ ...v,
+ condition: variableKey ? condition : v.condition,
+ conditionType: variableKey ? 'advanced' : 'basic',
+ };
+ }
+ return v;
+ });
+};
export const FormLinkageRules = withDynamicSchemaProps(
observer((props: Props) => {
const fieldSchema = useFieldSchema();
const { options, defaultValues, collectionName, form, variables, localVariables, record, dynamicComponent } =
useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
+ const { name } = useCollection_deprecated();
const { getAllCollectionsInheritChain } = useCollectionManager_deprecated();
const parentRecordData = useCollectionParentRecordData();
-
+ const variableKey = getActiveContextName(localVariables);
const components = useMemo(() => ({ ArrayCollapse }), []);
const schema = useMemo(
() => ({
@@ -43,7 +103,7 @@ export const FormLinkageRules = withDynamicSchemaProps(
properties: {
rules: {
type: 'array',
- default: defaultValues,
+ default: transformDefaultValue(defaultValues, variableKey),
'x-component': 'ArrayCollapse',
'x-decorator': 'FormItem',
'x-component-props': {
@@ -72,6 +132,20 @@ export const FormLinkageRules = withDynamicSchemaProps(
'x-content': '{{ t("Condition") }}',
},
condition: {
+ 'x-component': 'Input', // 仅作为数据存储
+ 'x-hidden': true, // 不显示
+ 'x-reactions': [
+ {
+ dependencies: ['.conditionType', '.conditionBasic', '.conditionAdvanced'],
+ fulfill: {
+ state: {
+ value: '{{$deps[0] === "basic" ? $deps[1] : $deps[2]}}',
+ },
+ },
+ },
+ ],
+ },
+ conditionBasic: {
'x-component': 'Filter',
'x-use-component-props': () => {
return {
@@ -83,6 +157,7 @@ export const FormLinkageRules = withDynamicSchemaProps(
`,
};
},
+ 'x-visible': '{{$deps[0] === "basic"}}',
'x-component-props': {
collectionName,
dynamicComponent: (props: DynamicComponentProps) => {
@@ -102,6 +177,38 @@ export const FormLinkageRules = withDynamicSchemaProps(
);
},
},
+ 'x-reactions': [
+ {
+ dependencies: ['.conditionType', '.condition'],
+ fulfill: {
+ state: {
+ visible: '{{$deps[0] === "basic"}}',
+ value: '{{$deps[0] === "basic" ? $deps[1] : undefined}}',
+ },
+ },
+ },
+ ],
+ },
+ conditionAdvanced: {
+ 'x-component': 'LinkageFilter',
+ 'x-visible': '{{$deps[0] === "advanced"}}',
+ 'x-reactions': [
+ {
+ dependencies: ['.conditionType', '.condition'],
+ fulfill: {
+ state: {
+ visible: '{{$deps[0] === "advanced"}}',
+ value: '{{$deps[0] === "advanced" ? $deps[1] : undefined}}',
+ },
+ },
+ },
+ ],
+ },
+ conditionType: {
+ type: 'string',
+ 'x-component': 'Input',
+ default: 'advanced',
+ 'x-hidden': true,
},
actions: {
'x-component': 'h4',
@@ -168,10 +275,10 @@ export const FormLinkageRules = withDynamicSchemaProps(
return (
// 这里使用 SubFormProvider 包裹,是为了让子表格的联动规则中 “当前对象” 的配置显示正确
-
+
-
+
diff --git a/packages/core/client/src/schema-settings/LinkageRules/type.ts b/packages/core/client/src/schema-settings/LinkageRules/type.ts
index 1f59a6a849..05abd99617 100644
--- a/packages/core/client/src/schema-settings/LinkageRules/type.ts
+++ b/packages/core/client/src/schema-settings/LinkageRules/type.ts
@@ -32,9 +32,11 @@ export enum ActionType {
export enum LinkageRuleCategory {
default = 'default',
style = 'style',
+ button = 'button',
}
export const LinkageRuleDataKeyMap: Record<`${LinkageRuleCategory}`, string> = {
[LinkageRuleCategory.style]: 'x-linkage-style-rules',
[LinkageRuleCategory.default]: 'x-linkage-rules',
+ [LinkageRuleCategory.button]: 'x-linkage-rules',
};
diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx
index 84e1ec4271..c4154f68b4 100644
--- a/packages/core/client/src/schema-settings/SchemaSettings.tsx
+++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx
@@ -1122,7 +1122,7 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
const getRules = useCallback(() => {
return gridSchema?.[dataKey] || fieldSchema?.[dataKey] || [];
}, [gridSchema, fieldSchema, dataKey]);
- const title = titleMap[category];
+ const title = titleMap[category] || t('Linkage rules');
const schema = useMemo(
() => ({
type: 'object',
@@ -1155,7 +1155,7 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
(v) => {
const rules = [];
for (const rule of v.fieldReaction.rules) {
- rules.push(_.pickBy(rule, _.identity));
+ rules.push(_.omit(_.pickBy(rule, _.identity), ['conditionBasic', 'conditionAdvanced']));
}
const templateId = gridSchema['x-component'] === 'BlockTemplate' && gridSchema['x-component-props']?.templateId;
const uid = (templateId && getTemplateById(templateId).uid) || gridSchema['x-uid'];
diff --git a/packages/core/client/src/schema-settings/VariableInput/VariableInput.tsx b/packages/core/client/src/schema-settings/VariableInput/VariableInput.tsx
index 1d98e03cdb..79a3c7c024 100644
--- a/packages/core/client/src/schema-settings/VariableInput/VariableInput.tsx
+++ b/packages/core/client/src/schema-settings/VariableInput/VariableInput.tsx
@@ -11,7 +11,7 @@ import { Form } from '@formily/core';
// @ts-ignore
import { Schema } from '@formily/json-schema';
import _ from 'lodash';
-import React, { useCallback } from 'react';
+import React, { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { CollectionFieldOptions_deprecated } from '../../collection-manager';
import { Variable, useVariableScope } from '../../schema-component';
@@ -72,6 +72,10 @@ type Props = {
*/
noDisabled?: boolean;
hideVariableButton?: boolean;
+ setScopes?: any; //更新scopes
+ nullable?: boolean;
+ constantAbel?: boolean;
+ changeOnSelect?: boolean;
};
/**
@@ -98,6 +102,10 @@ export const VariableInput = (props: Props) => {
targetFieldSchema,
noDisabled,
hideVariableButton,
+ setScopes,
+ nullable = true,
+ constantAbel = true,
+ changeOnSelect = true,
} = props;
const { name: blockCollectionName } = useBlockCollection();
const scope = useVariableScope();
@@ -127,31 +135,37 @@ export const VariableInput = (props: Props) => {
const handleChange = useCallback(
(value: any, optionPath: any[]) => {
if (!shouldChange) {
- return onChange(value);
+ return onChange(value, optionPath);
}
// `shouldChange` 这个函数的运算量比较大,会导致展开变量列表时有明显的卡顿感,在这里加个延迟能有效解决这个问题
setTimeout(async () => {
if (await shouldChange(value, optionPath)) {
- onChange(value);
+ onChange(value, optionPath);
}
});
},
[onChange, shouldChange],
);
+ const scopes = returnScope(
+ compatOldVariables(_.isEmpty(scope) ? variableOptions : scope, {
+ value,
+ }),
+ );
+ useEffect(() => {
+ setScopes?.(scopes);
+ }, [value, scope]);
return (
diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useAPITokenVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useAPITokenVariable.ts
index 3538a7d746..eac0d1dc6b 100644
--- a/packages/core/client/src/schema-settings/VariableInput/hooks/useAPITokenVariable.ts
+++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useAPITokenVariable.ts
@@ -9,6 +9,7 @@
import { useAPIClient } from '../../../api-client/hooks/useAPIClient';
import { useBaseVariable } from './useBaseVariable';
+import { string } from '../../../collection-manager/interfaces/properties/operators';
/**
* 变量:`当前 Token`
@@ -26,6 +27,7 @@ export const useAPITokenVariable = ({
title: 'API token',
noDisabled,
noChildren: true,
+ operators: string,
});
return {
diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useBaseVariable.tsx b/packages/core/client/src/schema-settings/VariableInput/hooks/useBaseVariable.tsx
index 36d3b9793f..b28bdb9a2d 100644
--- a/packages/core/client/src/schema-settings/VariableInput/hooks/useBaseVariable.tsx
+++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useBaseVariable.tsx
@@ -87,6 +87,8 @@ interface BaseProps {
*/
deprecated?: boolean;
tooltip?: string;
+ /**支持的操作符 */
+ operators?: any[];
}
interface BaseVariableProviderProps {
@@ -133,6 +135,8 @@ const getChildren = (
: isDisabled({ option, collectionField, uiSchema, targetFieldSchema, getCollectionField })),
isLeaf: true,
depth,
+ operators: option?.operators,
+ schema: option?.schema,
};
}
@@ -197,6 +201,7 @@ export const useBaseVariable = ({
returnFields = (fields) => fields,
deprecated,
tooltip,
+ operators = [],
}: BaseProps) => {
const compile = useCompile();
const getFilterOptions = useGetFilterOptions();
@@ -276,6 +281,7 @@ export const useBaseVariable = ({
children: [],
disabled: !!deprecated,
deprecated,
+ operators,
} as Option;
}, [uiSchema?.['x-component']]);
diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts
index 933938f120..f2e1f0f9e1 100644
--- a/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts
+++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts
@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
import { useOperators } from '../../../block-provider/CollectOperators';
import { useDatePickerContext } from '../../../schema-component/antd/date-picker/DatePicker';
import { getDateRanges } from '../../../schema-component/antd/date-picker/util';
-
+import { datetime } from '../../../collection-manager/interfaces/properties/operators';
interface Props {
operator?: {
value: string;
@@ -45,132 +45,155 @@ export const useDateVariable = ({ operator, schema, noDisabled }: Props) => {
value: 'now',
label: t('Current time'),
disabled: noDisabled ? false : schema?.['x-component'] !== 'DatePicker' || operatorValue === '$dateBetween',
+ operators: datetime,
+ schema: {},
},
{
key: 'yesterday',
value: 'yesterday',
label: t('Yesterday'),
disabled,
+ operators: datetime,
},
{
key: 'today',
value: 'today',
label: t('Today'),
disabled,
+ operators: datetime,
},
{
key: 'tomorrow',
value: 'tomorrow',
label: t('Tomorrow'),
disabled,
+ operators: datetime,
},
{
key: 'lastIsoWeek',
value: 'lastIsoWeek',
label: t('Last week'),
disabled,
+ operators: datetime,
},
{
key: 'thisIsoWeek',
value: 'thisIsoWeek',
label: t('This week'),
disabled,
+ operators: datetime,
},
{
key: 'nextIsoWeek',
value: 'nextIsoWeek',
label: t('Next week'),
disabled,
+ operators: datetime,
},
{
key: 'lastMonth',
value: 'lastMonth',
label: t('Last month'),
disabled,
+ operators: datetime,
},
{
key: 'thisMonth',
value: 'thisMonth',
label: t('This month'),
disabled,
+ operators: datetime,
},
{
key: 'nextMonth',
value: 'nextMonth',
label: t('Next month'),
disabled,
+ operators: datetime,
},
{
key: 'lastQuarter',
value: 'lastQuarter',
label: t('Last quarter'),
disabled,
+ operators: datetime,
},
{
key: 'thisQuarter',
value: 'thisQuarter',
label: t('This quarter'),
disabled,
+ operators: datetime,
},
{
key: 'nextQuarter',
value: 'nextQuarter',
label: t('Next quarter'),
disabled,
+ operators: datetime,
},
{
key: 'lastYear',
value: 'lastYear',
label: t('Last year'),
disabled,
+ operators: datetime,
},
{
key: 'thisYear',
value: 'thisYear',
label: t('This year'),
disabled,
+ operators: datetime,
},
{
key: 'nextYear',
value: 'nextYear',
label: t('Next year'),
disabled,
+ operators: datetime,
},
{
key: 'last7Days',
value: 'last7Days',
label: t('Last 7 days'),
disabled,
+ operators: datetime,
},
{
key: 'next7Days',
value: 'next7Days',
label: t('Next 7 days'),
disabled,
+ operators: datetime,
},
{
key: 'last30Days',
value: 'last30Days',
label: t('Last 30 days'),
disabled,
+ operators: datetime,
},
{
key: 'next30Days',
value: 'next30Days',
label: t('Next 30 days'),
disabled,
+ operators: datetime,
},
{
key: 'last90Days',
value: 'last90Days',
label: t('Last 90 days'),
disabled,
+ operators: datetime,
},
{
key: 'next90Days',
value: 'next90Days',
label: t('Next 90 days'),
disabled,
+ operators: datetime,
},
];
@@ -222,132 +245,154 @@ export const useDatetimeVariable = ({ operator, schema, noDisabled, targetFieldS
value: 'now',
label: t('Current time'),
disabled: noDisabled ? false : schema?.['x-component'] !== 'DatePicker' || operatorValue === '$dateBetween',
+ operators: datetime,
},
{
key: 'yesterday',
value: 'yesterday',
label: t('Yesterday'),
disabled,
+ operators: datetime,
},
{
key: 'today',
value: 'today',
label: t('Today'),
disabled,
+ operators: datetime,
},
{
key: 'tomorrow',
value: 'tomorrow',
label: t('Tomorrow'),
disabled,
+ operators: datetime,
},
{
key: 'lastIsoWeek',
value: 'lastIsoWeek',
label: t('Last week'),
disabled,
+ operators: datetime,
},
{
key: 'thisIsoWeek',
value: 'thisIsoWeek',
label: t('This week'),
disabled,
+ operators: datetime,
},
{
key: 'nextIsoWeek',
value: 'nextIsoWeek',
label: t('Next week'),
disabled,
+ operators: datetime,
},
{
key: 'lastMonth',
value: 'lastMonth',
label: t('Last month'),
disabled,
+ operators: datetime,
},
{
key: 'thisMonth',
value: 'thisMonth',
label: t('This month'),
disabled,
+ operators: datetime,
},
{
key: 'nextMonth',
value: 'nextMonth',
label: t('Next month'),
disabled,
+ operators: datetime,
},
{
key: 'lastQuarter',
value: 'lastQuarter',
label: t('Last quarter'),
disabled,
+ operators: datetime,
},
{
key: 'thisQuarter',
value: 'thisQuarter',
label: t('This quarter'),
disabled,
+ operators: datetime,
},
{
key: 'nextQuarter',
value: 'nextQuarter',
label: t('Next quarter'),
disabled,
+ operators: datetime,
},
{
key: 'lastYear',
value: 'lastYear',
label: t('Last year'),
disabled,
+ operators: datetime,
},
{
key: 'thisYear',
value: 'thisYear',
label: t('This year'),
disabled,
+ operators: datetime,
},
{
key: 'nextYear',
value: 'nextYear',
label: t('Next year'),
disabled,
+ operators: datetime,
},
{
key: 'last7Days',
value: 'last7Days',
label: t('Last 7 days'),
disabled,
+ operators: datetime,
},
{
key: 'next7Days',
value: 'next7Days',
label: t('Next 7 days'),
disabled,
+ operators: datetime,
},
{
key: 'last30Days',
value: 'last30Days',
label: t('Last 30 days'),
disabled,
+ operators: datetime,
},
{
key: 'next30Days',
value: 'next30Days',
label: t('Next 30 days'),
disabled,
+ operators: datetime,
},
{
key: 'last90Days',
value: 'last90Days',
label: t('Last 90 days'),
disabled,
+ operators: datetime,
},
{
key: 'next90Days',
value: 'next90Days',
label: t('Next 90 days'),
disabled,
+ operators: datetime,
},
];
diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useFormVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useFormVariable.ts
index f99c6b7644..ddf2f76f1f 100644
--- a/packages/core/client/src/schema-settings/VariableInput/hooks/useFormVariable.ts
+++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useFormVariable.ts
@@ -10,6 +10,7 @@
import { Form } from '@formily/core';
import { Schema } from '@formily/json-schema';
import { useTranslation } from 'react-i18next';
+import { useBlockContext } from '../../../block-provider';
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
import { CollectionFieldOptions_deprecated } from '../../../collection-manager';
import { useDataBlockRequestData, useDataSource } from '../../../data-source';
@@ -62,14 +63,6 @@ export const useFormVariable = ({ collectionName, collectionField, schema, noDis
return result;
};
-const useCurrentFormData = () => {
- const data = useDataBlockRequestData();
- if (data?.data?.length > 1) {
- return;
- }
- return data?.data?.[0] || data?.data;
-};
-
/**
* 变量:`当前表单` 相关的 hook
* @param param0
@@ -78,14 +71,14 @@ const useCurrentFormData = () => {
export const useCurrentFormContext = ({ form: _form }: Pick = {}) => {
const { form } = useFormBlockContext();
const { isVariableParsedInOtherContext } = useFlag();
-
+ const { name } = useBlockContext?.() || {};
const formInstance = _form || form;
-
return {
/** 变量值 */
currentFormCtx: formInstance?.values,
/** 用来判断是否可以显示`当前表单`变量 */
- shouldDisplayCurrentForm: formInstance && !formInstance.readPretty && !isVariableParsedInOtherContext,
+ shouldDisplayCurrentForm:
+ name === 'form' && formInstance && !formInstance.readPretty && !isVariableParsedInOtherContext,
};
};
diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useRecordVariable.tsx b/packages/core/client/src/schema-settings/VariableInput/hooks/useRecordVariable.tsx
index 33d123fa51..fe00105d3b 100644
--- a/packages/core/client/src/schema-settings/VariableInput/hooks/useRecordVariable.tsx
+++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useRecordVariable.tsx
@@ -90,7 +90,10 @@ export const useCurrentRecordContext = () => {
/** 变量值 */
currentRecordCtx: ctx?.recordData || formRecord?.data || recordData,
/** 用于判断是否需要显示配置项 */
- shouldDisplayCurrentRecord: !_.isEmpty(_.omit(recordData, ['__collectionName', '__parent'])) || !!formRecord?.data,
+ shouldDisplayCurrentRecord:
+ !_.isEmpty(_.omit(recordData, ['__collectionName', '__parent'])) ||
+ !!formRecord?.data ||
+ blockType === 'taleColumn',
/** 当前记录对应的 collection name */
collectionName: realCollectionName,
/** 块类型 */
diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useRoleVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useRoleVariable.ts
index 2b4bcec761..eed541fe37 100644
--- a/packages/core/client/src/schema-settings/VariableInput/hooks/useRoleVariable.ts
+++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useRoleVariable.ts
@@ -13,6 +13,9 @@ import { useAPIClient } from '../../../api-client';
import { CollectionFieldOptions_deprecated } from '../../../collection-manager';
import { CollectionFieldOptions } from '../../../data-source/collection/Collection';
import { useBaseVariable } from './useBaseVariable';
+import { string } from '../../../collection-manager/interfaces/properties/operators';
+import { useCurrentUserContext } from '../../../user/CurrentUserProvider';
+import { useCompile } from '../../../schema-component';
/**
* @deprecated
@@ -47,6 +50,7 @@ export const useRoleVariable = ({
noDisabled,
targetFieldSchema,
noChildren: true,
+ operators: string,
});
return result;
@@ -73,6 +77,9 @@ export const useCurrentRoleVariable = ({
} = {}) => {
const { t } = useTranslation();
const apiClient = useAPIClient();
+ const compile = useCompile();
+ const { data } = useCurrentUserContext() || {};
+ const roles = (data?.data?.roles || []).map(({ name, title }) => ({ name, title: compile(title) }));
const currentRoleSettings = useBaseVariable({
collectionField,
uiSchema,
@@ -83,12 +90,13 @@ export const useCurrentRoleVariable = ({
noDisabled,
targetFieldSchema,
noChildren: true,
+ operators: string,
});
return {
/** 变量配置项 */
currentRoleSettings,
/** 变量的值 */
- currentRoleCtx: apiClient.auth?.role,
+ currentRoleCtx: apiClient.auth?.role === '__union__' ? roles.map((v) => v.name) : apiClient.auth?.role,
};
};
diff --git a/packages/core/client/src/user/CurrentUserProvider.tsx b/packages/core/client/src/user/CurrentUserProvider.tsx
index e8e8f1969c..8df134eb79 100644
--- a/packages/core/client/src/user/CurrentUserProvider.tsx
+++ b/packages/core/client/src/user/CurrentUserProvider.tsx
@@ -27,7 +27,7 @@ export const useIsLoggedIn = () => {
export const useCurrentRoles = () => {
const { allowAnonymous } = useACLRoleContext();
- const { data } = useCurrentUserContext();
+ const { data } = useCurrentUserContext() || {};
const compile = useCompile();
const options = useMemo(() => {
const roles = (data?.data?.roles || []).map(({ name, title }) => ({ name, title: compile(title) }));
diff --git a/packages/core/create-nocobase-app/package.json b/packages/core/create-nocobase-app/package.json
index a4d2118149..92aacc28b8 100755
--- a/packages/core/create-nocobase-app/package.json
+++ b/packages/core/create-nocobase-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-nocobase-app",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "src/index.js",
"license": "AGPL-3.0",
"dependencies": {
@@ -8,6 +8,7 @@
"axios": "^1.7.0",
"chalk": "^4.1.1",
"commander": "^9.2.0",
+ "fs-extra": "^11.3.0",
"tar": "6.1.11"
},
"bin": {
diff --git a/packages/core/create-nocobase-app/src/cli.js b/packages/core/create-nocobase-app/src/cli.js
index 09fd152ddc..0fd40eb008 100644
--- a/packages/core/create-nocobase-app/src/cli.js
+++ b/packages/core/create-nocobase-app/src/cli.js
@@ -19,6 +19,7 @@ const cli = new Command('create-nocobase');
cli
.arguments('', 'directory of new NocoBase app')
.option('--quickstart', 'quickstart app creation')
+ .option('--skip-dev-dependencies')
.option('-a, --all-db-dialect', 'install all database dialect dependencies')
.option('-d, --db-dialect ', 'database dialect, current support sqlite/mysql/postgres', 'sqlite')
.option('-e, --env ', 'environment variables write into .env file', concat, [])
diff --git a/packages/core/create-nocobase-app/src/generator.js b/packages/core/create-nocobase-app/src/generator.js
index 260cbc308f..fc7ef0179d 100644
--- a/packages/core/create-nocobase-app/src/generator.js
+++ b/packages/core/create-nocobase-app/src/generator.js
@@ -9,7 +9,8 @@
const chalk = require('chalk');
const crypto = require('crypto');
-const { existsSync } = require('fs');
+const { existsSync, promises } = require('fs');
+const fs = require('fs-extra');
const { join, resolve } = require('path');
const { Generator } = require('@umijs/utils');
const { downloadPackageFromNpm, updateJsonFile } = require('./util');
@@ -191,6 +192,13 @@ class AppGenerator extends Generator {
this.checkDbEnv();
+ const skipDevDependencies = this.args.skipDevDependencies;
+ if (skipDevDependencies) {
+ const json = await fs.readJSON(join(this.cwd, 'package.json'), 'utf8');
+ delete json['devDependencies'];
+ await fs.writeJSON(join(this.cwd, 'package.json'), json, { encoding: 'utf8', spaces: 2 });
+ }
+
console.log('');
console.log(chalk.green(`$ cd ${name}`));
console.log(chalk.green(`$ yarn install`));
diff --git a/packages/core/create-nocobase-app/templates/app/package.json.tpl b/packages/core/create-nocobase-app/templates/app/package.json.tpl
index 392cfdbf4b..6245ac2fd6 100644
--- a/packages/core/create-nocobase-app/templates/app/package.json.tpl
+++ b/packages/core/create-nocobase-app/templates/app/package.json.tpl
@@ -29,6 +29,7 @@
"react-router-dom": "6.28.1",
"react-router": "6.28.1",
"antd": "5.24.2",
+ "async": "3.2.6",
"rollup": "4.24.0"
},
"dependencies": {
diff --git a/packages/core/data-source-manager/package.json b/packages/core/data-source-manager/package.json
index 46bb863423..a3cd0531b0 100644
--- a/packages/core/data-source-manager/package.json
+++ b/packages/core/data-source-manager/package.json
@@ -1,16 +1,16 @@
{
"name": "@nocobase/data-source-manager",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
- "@nocobase/actions": "1.7.0-beta.16",
- "@nocobase/cache": "1.7.0-beta.16",
- "@nocobase/database": "1.7.0-beta.16",
- "@nocobase/resourcer": "1.7.0-beta.16",
- "@nocobase/utils": "1.7.0-beta.16",
+ "@nocobase/actions": "1.7.0-beta.18",
+ "@nocobase/cache": "1.7.0-beta.18",
+ "@nocobase/database": "1.7.0-beta.18",
+ "@nocobase/resourcer": "1.7.0-beta.18",
+ "@nocobase/utils": "1.7.0-beta.18",
"@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1"
},
diff --git a/packages/core/database/package.json b/packages/core/database/package.json
index 7f36984bfe..1ce49e8c91 100644
--- a/packages/core/database/package.json
+++ b/packages/core/database/package.json
@@ -1,13 +1,13 @@
{
"name": "@nocobase/database",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
- "@nocobase/logger": "1.7.0-beta.16",
- "@nocobase/utils": "1.7.0-beta.16",
+ "@nocobase/logger": "1.7.0-beta.18",
+ "@nocobase/utils": "1.7.0-beta.18",
"async-mutex": "^0.3.2",
"chalk": "^4.1.1",
"cron-parser": "4.4.0",
@@ -20,12 +20,12 @@
"graphlib": "^2.1.8",
"lodash": "^4.17.21",
"mathjs": "^10.6.1",
- "nanoid": "^3.3.6",
+ "nanoid": "^3.3.11",
"node-fetch": "^2.6.7",
"node-sql-parser": "^4.18.0",
"qs": "^6.11.2",
"safe-json-stringify": "^1.2.0",
- "semver": "^7.3.7",
+ "semver": "^7.7.1",
"sequelize": "^6.26.0",
"umzug": "^3.1.1",
"uuid": "^9.0.1"
diff --git a/packages/core/devtools/package.json b/packages/core/devtools/package.json
index 0a70d980f0..9bfee29a37 100644
--- a/packages/core/devtools/package.json
+++ b/packages/core/devtools/package.json
@@ -1,13 +1,13 @@
{
"name": "@nocobase/devtools",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"description": "",
"license": "AGPL-3.0",
"main": "./src/index.js",
"dependencies": {
- "@nocobase/build": "1.7.0-beta.16",
- "@nocobase/client": "1.7.0-beta.16",
- "@nocobase/test": "1.7.0-beta.16",
+ "@nocobase/build": "1.7.0-beta.18",
+ "@nocobase/client": "1.7.0-beta.18",
+ "@nocobase/test": "1.7.0-beta.18",
"@types/koa": "^2.15.0",
"@types/koa-bodyparser": "^4.3.4",
"@types/lodash": "^4.14.177",
@@ -35,7 +35,7 @@
"react": "^18.0.0",
"react-dom": "^18.0.0",
"rimraf": "^3.0.0",
- "serve": "^13.0.2",
+ "serve": "^14.2.4",
"ts-loader": "^7.0.4",
"ts-node": "9.1.1",
"ts-node-dev": "1.1.8",
diff --git a/packages/core/evaluators/package.json b/packages/core/evaluators/package.json
index 73d9cf3f92..2312b20723 100644
--- a/packages/core/evaluators/package.json
+++ b/packages/core/evaluators/package.json
@@ -1,13 +1,13 @@
{
"name": "@nocobase/evaluators",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
"@formulajs/formulajs": "4.4.9",
- "@nocobase/utils": "1.7.0-beta.16",
+ "@nocobase/utils": "1.7.0-beta.18",
"mathjs": "^10.6.0"
},
"repository": {
diff --git a/packages/core/lock-manager/package.json b/packages/core/lock-manager/package.json
index 07cae242a1..d844a95dc1 100644
--- a/packages/core/lock-manager/package.json
+++ b/packages/core/lock-manager/package.json
@@ -1,10 +1,10 @@
{
"name": "@nocobase/lock-manager",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "lib/index.js",
"license": "AGPL-3.0",
"devDependencies": {
- "@nocobase/utils": "1.7.0-beta.16",
+ "@nocobase/utils": "1.7.0-beta.18",
"async-mutex": "^0.5.0"
}
}
diff --git a/packages/core/logger/package.json b/packages/core/logger/package.json
index d1c7132fe8..ec387f5a49 100644
--- a/packages/core/logger/package.json
+++ b/packages/core/logger/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/logger",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"description": "nocobase logging library",
"license": "AGPL-3.0",
"main": "./lib/index.js",
diff --git a/packages/core/resourcer/package.json b/packages/core/resourcer/package.json
index b695b16c64..41c7c03676 100644
--- a/packages/core/resourcer/package.json
+++ b/packages/core/resourcer/package.json
@@ -1,12 +1,12 @@
{
"name": "@nocobase/resourcer",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
- "@nocobase/utils": "1.7.0-beta.16",
+ "@nocobase/utils": "1.7.0-beta.18",
"deepmerge": "^4.2.2",
"koa-compose": "^4.1.0",
"lodash": "^4.17.21",
diff --git a/packages/core/sdk/package.json b/packages/core/sdk/package.json
index e49ea31027..f9954ed165 100644
--- a/packages/core/sdk/package.json
+++ b/packages/core/sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/sdk",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
diff --git a/packages/core/server/package.json b/packages/core/server/package.json
index 6579e38b63..de319f6dff 100644
--- a/packages/core/server/package.json
+++ b/packages/core/server/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/server",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
@@ -10,19 +10,19 @@
"@koa/cors": "^5.0.0",
"@koa/multer": "^3.0.2",
"@koa/router": "^9.4.0",
- "@nocobase/acl": "1.7.0-beta.16",
- "@nocobase/actions": "1.7.0-beta.16",
- "@nocobase/auth": "1.7.0-beta.16",
- "@nocobase/cache": "1.7.0-beta.16",
- "@nocobase/data-source-manager": "1.7.0-beta.16",
- "@nocobase/database": "1.7.0-beta.16",
- "@nocobase/evaluators": "1.7.0-beta.16",
- "@nocobase/lock-manager": "1.7.0-beta.16",
- "@nocobase/logger": "1.7.0-beta.16",
- "@nocobase/resourcer": "1.7.0-beta.16",
- "@nocobase/sdk": "1.7.0-beta.16",
- "@nocobase/telemetry": "1.7.0-beta.16",
- "@nocobase/utils": "1.7.0-beta.16",
+ "@nocobase/acl": "1.7.0-beta.18",
+ "@nocobase/actions": "1.7.0-beta.18",
+ "@nocobase/auth": "1.7.0-beta.18",
+ "@nocobase/cache": "1.7.0-beta.18",
+ "@nocobase/data-source-manager": "1.7.0-beta.18",
+ "@nocobase/database": "1.7.0-beta.18",
+ "@nocobase/evaluators": "1.7.0-beta.18",
+ "@nocobase/lock-manager": "1.7.0-beta.18",
+ "@nocobase/logger": "1.7.0-beta.18",
+ "@nocobase/resourcer": "1.7.0-beta.18",
+ "@nocobase/sdk": "1.7.0-beta.18",
+ "@nocobase/telemetry": "1.7.0-beta.18",
+ "@nocobase/utils": "1.7.0-beta.18",
"@types/decompress": "4.2.7",
"@types/ini": "^1.3.31",
"@types/koa-send": "^4.1.3",
@@ -31,6 +31,7 @@
"axios": "^1.7.0",
"chalk": "^4.1.1",
"commander": "^9.2.0",
+ "compression": "^1.8.0",
"cron": "^2.4.4",
"cronstrue": "^2.11.0",
"dayjs": "^1.11.8",
@@ -45,9 +46,9 @@
"koa-static": "^5.0.0",
"lodash": "^4.17.21",
"multer": "^1.4.2",
- "nanoid": "3.3.4",
- "semver": "^7.3.7",
- "serve-handler": "^6.1.5",
+ "nanoid": "^3.3.11",
+ "semver": "^7.7.1",
+ "serve-handler": "^6.1.6",
"ws": "^8.13.0",
"xpipe": "^1.0.5"
},
diff --git a/packages/core/telemetry/package.json b/packages/core/telemetry/package.json
index 43877a5f99..d5a6e826b1 100644
--- a/packages/core/telemetry/package.json
+++ b/packages/core/telemetry/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/telemetry",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"description": "nocobase telemetry library",
"license": "AGPL-3.0",
"main": "./lib/index.js",
@@ -11,7 +11,7 @@
"directory": "packages/telemetry"
},
"dependencies": {
- "@nocobase/utils": "1.7.0-beta.16",
+ "@nocobase/utils": "1.7.0-beta.18",
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/instrumentation": "^0.46.0",
"@opentelemetry/resources": "^1.19.0",
diff --git a/packages/core/test/package.json b/packages/core/test/package.json
index cf97f76b34..0cddbe03aa 100644
--- a/packages/core/test/package.json
+++ b/packages/core/test/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/test",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "lib/index.js",
"module": "./src/index.ts",
"types": "./lib/index.d.ts",
@@ -51,7 +51,7 @@
},
"dependencies": {
"@faker-js/faker": "8.1.0",
- "@nocobase/server": "1.7.0-beta.16",
+ "@nocobase/server": "1.7.0-beta.18",
"@playwright/test": "^1.45.3",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.0.0",
diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json
index e69f65aa96..26b9e7c2ad 100644
--- a/packages/core/utils/package.json
+++ b/packages/core/utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/utils",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
diff --git a/packages/plugins/@nocobase/plugin-acl/package.json b/packages/plugins/@nocobase/plugin-acl/package.json
index ab054c3083..b7e3d3da26 100644
--- a/packages/plugins/@nocobase/plugin-acl/package.json
+++ b/packages/plugins/@nocobase/plugin-acl/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "权限控制",
"description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.",
"description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/acl",
diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/setCurrentRole.ts b/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/setCurrentRole.ts
index 0a034f2f0a..4d06502ce3 100644
--- a/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/setCurrentRole.ts
+++ b/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/setCurrentRole.ts
@@ -48,9 +48,9 @@ export async function setCurrentRole(ctx: Context, next) {
const userRoles = Array.from(rolesMap.values());
ctx.state.currentUser.roles = userRoles;
const systemSettings = (await cache.wrap(`app:systemSettings`, () =>
- ctx.db.getRepository('systemSettings').findOne(),
+ ctx.db.getRepository('systemSettings').findOne({ raw: true }),
)) as Model;
- const roleMode = systemSettings?.get('roleMode') || SystemRoleMode.default;
+ const roleMode = systemSettings?.roleMode || SystemRoleMode.default;
if ([currentRole, ctx.state.currentRole].includes(UNION_ROLE_KEY) && roleMode === SystemRoleMode.default) {
currentRole = userRoles[0].name;
ctx.state.currentRole = userRoles[0].name;
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json b/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json
index f1001796b9..2382df243e 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-bulk-edit",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-edit",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit",
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditAction.Settings.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditAction.Settings.tsx
index 713d5f0c1b..b126f527e8 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditAction.Settings.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditAction.Settings.tsx
@@ -21,9 +21,10 @@ import {
SecondConFirm,
AfterSuccess,
RefreshDataBlockRequest,
+ SchemaSettingsLinkageRules,
+ useDataBlockProps,
} from '@nocobase/client';
import { ModalProps } from 'antd';
-import { isValid } from '@formily/shared';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -96,6 +97,16 @@ export const deprecatedBulkEditActionSettings = new SchemaSettings({
return buttonEditorProps;
},
},
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { linkageRulesProps } = useSchemaToolbar();
+ return {
+ ...linkageRulesProps,
+ };
+ },
+ },
{
name: 'openMode',
Component: SchemaInitializerOpenModeSchemaItems,
@@ -138,6 +149,16 @@ export const bulkEditActionSettings = new SchemaSettings({
return buttonEditorProps;
},
},
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { linkageRulesProps } = useSchemaToolbar();
+ return {
+ ...linkageRulesProps,
+ };
+ },
+ },
{
name: 'openMode',
Component: SchemaInitializerOpenModeSchemaItems,
@@ -158,6 +179,7 @@ export const bulkEditActionSettings = new SchemaSettings({
name: 'updateMode',
Component: UpdateMode,
},
+
{
name: 'remove',
sort: 100,
@@ -191,6 +213,17 @@ export const bulkEditFormSubmitActionSettings = new SchemaSettings({
name: 'afterSuccessfulSubmission',
Component: AfterSuccess,
},
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { linkageRulesProps } = useSchemaToolbar();
+
+ return {
+ ...linkageRulesProps,
+ };
+ },
+ },
{
name: 'refreshDataBlockRequest',
Component: RefreshDataBlockRequest,
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/package.json b/packages/plugins/@nocobase/plugin-action-bulk-update/package.json
index bf88c92864..4192615bbb 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-update/package.json
+++ b/packages/plugins/@nocobase/plugin-action-bulk-update/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-bulk-update",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-update",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update",
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateAction.Settings.tsx b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateAction.Settings.tsx
index eada045700..0d5b099677 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateAction.Settings.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/BulkUpdateAction.Settings.tsx
@@ -22,6 +22,10 @@ import {
useGlobalVariable,
BlocksSelector,
usePlugin,
+ SchemaSettingsLinkageRules,
+ useCollectionManager_deprecated,
+ useDataBlockProps,
+ useCollection_deprecated,
} from '@nocobase/client';
import { useTranslation } from 'react-i18next';
import React from 'react';
@@ -161,6 +165,21 @@ const schemaSettingsItems: SchemaSettingsItemType[] = [
return buttonEditorProps;
},
},
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { name } = useCollection_deprecated();
+ const { association } = useDataBlockProps() || {};
+ const { getCollectionField } = useCollectionManager_deprecated();
+ const associationField = getCollectionField(association);
+ const { linkageRulesProps } = useSchemaToolbar();
+ return {
+ ...linkageRulesProps,
+ collectionName: associationField?.collectionName || name,
+ };
+ },
+ },
{
name: 'updateMode',
Component: UpdateMode,
diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/package.json b/packages/plugins/@nocobase/plugin-action-custom-request/package.json
index d3330578cc..f32209ce00 100644
--- a/packages/plugins/@nocobase/plugin-action-custom-request/package.json
+++ b/packages/plugins/@nocobase/plugin-action-custom-request/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-custom-request",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-custom-request",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request",
diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/hooks/useCustomRequestVariableOptions.ts b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/hooks/useCustomRequestVariableOptions.ts
index 9980ba5625..30764dfd6c 100644
--- a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/hooks/useCustomRequestVariableOptions.ts
+++ b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/hooks/useCustomRequestVariableOptions.ts
@@ -15,6 +15,7 @@ import {
useCollectionRecordData,
useCompile,
useGlobalVariable,
+ useFormBlockContext,
} from '@nocobase/client';
import { useMemo } from 'react';
import { useTranslation } from '../locale';
@@ -27,6 +28,7 @@ export const useCustomRequestVariableOptions = () => {
const compile = useCompile();
const recordData = useCollectionRecordData();
const { name: blockType } = useBlockContext() || {};
+ const { form } = useFormBlockContext();
const [fields, userFields] = useMemo(() => {
return [compile(fieldsOptions), compile(userFieldOptions)];
}, [fieldsOptions, userFieldOptions]);
@@ -39,7 +41,7 @@ export const useCustomRequestVariableOptions = () => {
title: t('Current record', { ns: 'client' }),
children: [...fields],
},
- blockType === 'form' && {
+ (blockType === 'form' || form) && {
name: '$nForm',
title: t('Current form', { ns: 'client' }),
children: [...fields],
diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts
index ef4c43deb7..2a872ac122 100644
--- a/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts
+++ b/packages/plugins/@nocobase/plugin-action-custom-request/src/client/schemaSettings.ts
@@ -20,6 +20,8 @@ import {
useCollectionRecord,
useSchemaToolbar,
SchemaSettingAccessControl,
+ useDataBlockProps,
+ useCollectionManager_deprecated,
} from '@nocobase/client';
import { CustomRequestSettingsItem } from './components/CustomRequestActionDesigner';
@@ -40,17 +42,11 @@ export const customizeCustomRequestActionSettings = new SchemaSettings({
name: 'linkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
- const { name } = useCollection() || {};
const { linkageRulesProps } = useSchemaToolbar();
return {
...linkageRulesProps,
- collectionName: name,
};
},
- useVisible() {
- const record = useCollectionRecord();
- return record && record.data && !record?.isNew;
- },
},
{
name: 'secondConFirm',
diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/package.json b/packages/plugins/@nocobase/plugin-action-duplicate/package.json
index e8327ee090..4dea5f7bc8 100644
--- a/packages/plugins/@nocobase/plugin-action-duplicate/package.json
+++ b/packages/plugins/@nocobase/plugin-action-duplicate/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-duplicate",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-duplicate",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate",
diff --git a/packages/plugins/@nocobase/plugin-action-export/package.json b/packages/plugins/@nocobase/plugin-action-export/package.json
index abf26de107..6634081558 100644
--- a/packages/plugins/@nocobase/plugin-action-export/package.json
+++ b/packages/plugins/@nocobase/plugin-action-export/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导出记录",
"description": "Export filtered records to excel, you can configure which fields to export.",
"description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-export",
diff --git a/packages/plugins/@nocobase/plugin-action-export/src/client/schemaSettings.ts b/packages/plugins/@nocobase/plugin-action-export/src/client/schemaSettings.ts
index c1a066e16d..42fa2e46e8 100644
--- a/packages/plugins/@nocobase/plugin-action-export/src/client/schemaSettings.ts
+++ b/packages/plugins/@nocobase/plugin-action-export/src/client/schemaSettings.ts
@@ -9,7 +9,14 @@
import { ArrayItems } from '@formily/antd-v5';
import { ISchema, useField, useFieldSchema } from '@formily/react';
-import { ButtonEditor, SchemaSettings, useDesignable, useSchemaToolbar } from '@nocobase/client';
+import {
+ ButtonEditor,
+ SchemaSettings,
+ useDesignable,
+ useSchemaToolbar,
+ SchemaSettingsLinkageRules,
+ useDataBlockProps,
+} from '@nocobase/client';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useShared } from './useShared';
@@ -25,6 +32,16 @@ export const exportActionSchemaSettings = new SchemaSettings({
return buttonEditorProps;
},
},
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { linkageRulesProps } = useSchemaToolbar();
+ return {
+ ...linkageRulesProps,
+ };
+ },
+ },
{
name: 'exportableFields',
type: 'actionModal',
@@ -65,6 +82,7 @@ export const exportActionSchemaSettings = new SchemaSettings({
};
},
},
+
{
name: 'divider',
type: 'divider',
diff --git a/packages/plugins/@nocobase/plugin-action-import/package.json b/packages/plugins/@nocobase/plugin-action-import/package.json
index e03746c618..c0a5d30941 100644
--- a/packages/plugins/@nocobase/plugin-action-import/package.json
+++ b/packages/plugins/@nocobase/plugin-action-import/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导入记录",
"description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.",
"description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-import",
diff --git a/packages/plugins/@nocobase/plugin-action-import/src/client/schemaSettings.tsx b/packages/plugins/@nocobase/plugin-action-import/src/client/schemaSettings.tsx
index 968667acf0..78b7e7ad4f 100644
--- a/packages/plugins/@nocobase/plugin-action-import/src/client/schemaSettings.tsx
+++ b/packages/plugins/@nocobase/plugin-action-import/src/client/schemaSettings.tsx
@@ -9,14 +9,19 @@
import { ArrayItems } from '@formily/antd-v5';
import { ISchema, useField, useFieldSchema } from '@formily/react';
-import { ButtonEditor, SchemaSettings, type, useDesignable, useSchemaToolbar } from '@nocobase/client';
+import {
+ ButtonEditor,
+ SchemaSettings,
+ useDesignable,
+ useSchemaToolbar,
+ SchemaSettingsLinkageRules,
+} from '@nocobase/client';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useShared } from './useShared';
import { Button, Space } from 'antd';
import { Action } from '@nocobase/client';
import React from 'react';
-import { useDownloadXlsxTemplateAction } from './useImportAction';
export const importActionSchemaSettings = new SchemaSettings({
name: 'actionSettings:import',
@@ -29,6 +34,16 @@ export const importActionSchemaSettings = new SchemaSettings({
return buttonEditorProps;
},
},
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { linkageRulesProps } = useSchemaToolbar();
+ return {
+ ...linkageRulesProps,
+ };
+ },
+ },
{
name: 'importableFields',
type: 'actionModal',
@@ -72,6 +87,7 @@ export const importActionSchemaSettings = new SchemaSettings({
};
},
},
+
{
name: 'divider',
type: 'divider',
diff --git a/packages/plugins/@nocobase/plugin-action-print/package.json b/packages/plugins/@nocobase/plugin-action-print/package.json
index 9712ff390d..40b75af87d 100644
--- a/packages/plugins/@nocobase/plugin-action-print/package.json
+++ b/packages/plugins/@nocobase/plugin-action-print/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-print",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-print",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print",
diff --git a/packages/plugins/@nocobase/plugin-action-print/src/client/PrintAction.Settings.tsx b/packages/plugins/@nocobase/plugin-action-print/src/client/PrintAction.Settings.tsx
index b27fa4a78b..d2051158a6 100644
--- a/packages/plugins/@nocobase/plugin-action-print/src/client/PrintAction.Settings.tsx
+++ b/packages/plugins/@nocobase/plugin-action-print/src/client/PrintAction.Settings.tsx
@@ -12,7 +12,6 @@ import {
SchemaSettings,
SchemaSettingsItemType,
SchemaSettingsLinkageRules,
- useCollection_deprecated,
useSchemaToolbar,
} from '@nocobase/client';
@@ -35,11 +34,9 @@ const schemaSettingsItems: SchemaSettingsItemType[] = [
name: 'linkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
- const { name } = useCollection_deprecated();
const { linkageRulesProps } = useSchemaToolbar();
return {
...linkageRulesProps,
- collectionName: name,
};
},
},
diff --git a/packages/plugins/@nocobase/plugin-ai/package.json b/packages/plugins/@nocobase/plugin-ai/package.json
index 3d3604f7e1..94a93ee045 100644
--- a/packages/plugins/@nocobase/plugin-ai/package.json
+++ b/packages/plugins/@nocobase/plugin-ai/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "AI 集成",
"description": "Support integration with AI services, providing AI-related workflow nodes to enhance business processing capabilities.",
"description.zh-CN": "支持接入 AI 服务,提供 AI 相关的工作流节点,增强业务处理能力。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",
diff --git a/packages/plugins/@nocobase/plugin-api-doc/package.json b/packages/plugins/@nocobase/plugin-api-doc/package.json
index e0d5659d26..795f2c9920 100644
--- a/packages/plugins/@nocobase/plugin-api-doc/package.json
+++ b/packages/plugins/@nocobase/plugin-api-doc/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-api-doc",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "API documentation",
"displayName.zh-CN": "API 文档",
"description": "An OpenAPI documentation generator for NocoBase HTTP API.",
diff --git a/packages/plugins/@nocobase/plugin-api-keys/package.json b/packages/plugins/@nocobase/plugin-api-keys/package.json
index d2cc7c37a2..4aaa4fea9d 100644
--- a/packages/plugins/@nocobase/plugin-api-keys/package.json
+++ b/packages/plugins/@nocobase/plugin-api-keys/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "认证:API 密钥",
"description": "Allows users to use API key to access application's HTTP API",
"description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/api-keys",
diff --git a/packages/plugins/@nocobase/plugin-async-task-manager/package.json b/packages/plugins/@nocobase/plugin-async-task-manager/package.json
index f6fbe7530e..1b321ff11e 100644
--- a/packages/plugins/@nocobase/plugin-async-task-manager/package.json
+++ b/packages/plugins/@nocobase/plugin-async-task-manager/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "异步任务管理器",
"description": "Manage and monitor asynchronous tasks such as data import/export. Support task progress tracking and notification.",
"description.zh-CN": "管理和监控数据导入导出等异步任务。支持任务进度跟踪和通知。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",
diff --git a/packages/plugins/@nocobase/plugin-audit-logs/package.json b/packages/plugins/@nocobase/plugin-audit-logs/package.json
index ffbef68261..094180b6f6 100644
--- a/packages/plugins/@nocobase/plugin-audit-logs/package.json
+++ b/packages/plugins/@nocobase/plugin-audit-logs/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-audit-logs",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "Audit logs (deprecated)",
"displayName.zh-CN": "审计日志(废弃)",
"description": "This plugin is deprecated. There will be a new audit log plugin in the future.",
diff --git a/packages/plugins/@nocobase/plugin-auth-sms/package.json b/packages/plugins/@nocobase/plugin-auth-sms/package.json
index e0e70cc751..210a6f54f2 100644
--- a/packages/plugins/@nocobase/plugin-auth-sms/package.json
+++ b/packages/plugins/@nocobase/plugin-auth-sms/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "认证:短信",
"description": "SMS authentication.",
"description.zh-CN": "通过短信验证码认证身份。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth-sms",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth-sms",
diff --git a/packages/plugins/@nocobase/plugin-auth/package.json b/packages/plugins/@nocobase/plugin-auth/package.json
index d010cff83b..464f62a665 100644
--- a/packages/plugins/@nocobase/plugin-auth/package.json
+++ b/packages/plugins/@nocobase/plugin-auth/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-auth",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth",
diff --git a/packages/plugins/@nocobase/plugin-backup-restore/package.json b/packages/plugins/@nocobase/plugin-backup-restore/package.json
index 79f6f95fa1..9bd545d7b0 100644
--- a/packages/plugins/@nocobase/plugin-backup-restore/package.json
+++ b/packages/plugins/@nocobase/plugin-backup-restore/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "应用的备份与还原(废弃)",
"description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.",
"description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/backup-restore",
@@ -27,7 +27,7 @@
"mkdirp": "^1.0.4",
"object-path": "^0.11.8",
"react": "^18.2.0",
- "semver": "^7.5.4",
+ "semver": "^7.7.1",
"tar": "^6.1.13"
},
"peerDependencies": {
diff --git a/packages/plugins/@nocobase/plugin-block-iframe/package.json b/packages/plugins/@nocobase/plugin-block-iframe/package.json
index 16d9bd7f19..7d1d74bc3a 100644
--- a/packages/plugins/@nocobase/plugin-block-iframe/package.json
+++ b/packages/plugins/@nocobase/plugin-block-iframe/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "区块:iframe",
"description": "Create an iframe block on the page to embed and display external web pages or content.",
"description.zh-CN": "在页面上创建和管理iframe,用于嵌入和展示外部网页或内容。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-iframe",
diff --git a/packages/plugins/@nocobase/plugin-block-iframe/src/client/Iframe.tsx b/packages/plugins/@nocobase/plugin-block-iframe/src/client/Iframe.tsx
index 33394867d1..3c04d26b1e 100644
--- a/packages/plugins/@nocobase/plugin-block-iframe/src/client/Iframe.tsx
+++ b/packages/plugins/@nocobase/plugin-block-iframe/src/client/Iframe.tsx
@@ -17,7 +17,7 @@ import {
useRequest,
useVariables,
} from '@nocobase/client';
-import { Card, Spin } from 'antd';
+import { Card, Spin, theme } from 'antd';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import RIframe from 'react-iframe';
@@ -37,6 +37,7 @@ export const Iframe: any = observer(
const { url, htmlId, mode = 'url', height, html, params, engine, ...others } = props;
const field = useField();
const { t } = useTranslation();
+ const { token } = theme.useToken();
const targetHeight = useBlockHeight() || height;
const variables = useVariables();
const localVariables = useLocalVariables();
@@ -90,7 +91,9 @@ export const Iframe: any = observer(
}, [htmlContent, mode, url, variables, localVariables, params]);
if ((mode === 'url' && !url) || (mode === 'html' && !htmlId)) {
return (
-
+
{t('Please fill in the iframe URL')}
);
@@ -101,7 +104,7 @@ export const Iframe: any = observer(
diff --git a/packages/plugins/@nocobase/plugin-block-template/package.json b/packages/plugins/@nocobase/plugin-block-template/package.json
index 9055a88dea..8d1786640e 100644
--- a/packages/plugins/@nocobase/plugin-block-template/package.json
+++ b/packages/plugins/@nocobase/plugin-block-template/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "区块:模板",
"description": "Create and manage block templates for reuse on pages.",
"description.zh-CN": "创建和管理区块模板,用于在页面中重复使用。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-template",
diff --git a/packages/plugins/@nocobase/plugin-block-workbench/package.json b/packages/plugins/@nocobase/plugin-block-workbench/package.json
index a94bb75f45..7a65efc524 100644
--- a/packages/plugins/@nocobase/plugin-block-workbench/package.json
+++ b/packages/plugins/@nocobase/plugin-block-workbench/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-block-workbench",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "Block: Action panel",
"displayName.zh-CN": "区块:操作面板",
"description": "Centrally manages and displays various actions, allowing users to efficiently perform tasks. It supports extensibility, with current action types including pop-ups, links, scanning, and custom requests.",
diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchCustomRequestActionSchemaInitializerItem.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchCustomRequestActionSchemaInitializerItem.tsx
index 833cce7a8e..af444909af 100644
--- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchCustomRequestActionSchemaInitializerItem.tsx
+++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchCustomRequestActionSchemaInitializerItem.tsx
@@ -14,6 +14,8 @@ import {
useSchemaInitializer,
ModalActionSchemaInitializerItem,
SchemaSettingAccessControl,
+ SchemaSettingsLinkageRules,
+ useSchemaToolbar,
} from '@nocobase/client';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -27,6 +29,16 @@ export const workbenchActionSettingsCustomRequest = new SchemaSettings({
return { hasIconColor: true };
},
},
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { linkageRulesProps } = useSchemaToolbar();
+ return {
+ ...linkageRulesProps,
+ };
+ },
+ },
{
name: 'editLink',
Component: SchemaSettingsActionLinkItem,
diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchLinkActionSchemaInitializerItem.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchLinkActionSchemaInitializerItem.tsx
index 7b17a862f6..fb91d8aa3f 100644
--- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchLinkActionSchemaInitializerItem.tsx
+++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchLinkActionSchemaInitializerItem.tsx
@@ -15,6 +15,8 @@ import {
useSchemaInitializerItem,
ModalActionSchemaInitializerItem,
SchemaSettingAccessControl,
+ SchemaSettingsLinkageRules,
+ useSchemaToolbar,
} from '@nocobase/client';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -29,6 +31,16 @@ export const workbenchActionSettingsLink = new SchemaSettings({
return { hasIconColor: true };
},
},
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { linkageRulesProps } = useSchemaToolbar();
+ return {
+ ...linkageRulesProps,
+ };
+ },
+ },
{
name: 'editLink',
Component: SchemaSettingsActionLinkItem,
diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchPopupActionSchemaInitializerItem.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchPopupActionSchemaInitializerItem.tsx
index 3d1e7b09c5..d2f95e0f4d 100644
--- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchPopupActionSchemaInitializerItem.tsx
+++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchPopupActionSchemaInitializerItem.tsx
@@ -15,6 +15,8 @@ import {
useOpenModeContext,
ModalActionSchemaInitializerItem,
SchemaSettingAccessControl,
+ SchemaSettingsLinkageRules,
+ useSchemaToolbar,
} from '@nocobase/client';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -29,7 +31,16 @@ export const workbenchActionSettingsPopup = new SchemaSettings({
return { hasIconColor: true };
},
},
-
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { linkageRulesProps } = useSchemaToolbar();
+ return {
+ ...linkageRulesProps,
+ };
+ },
+ },
{
name: 'openMode',
Component: SchemaSettingOpenModeSchemaItems,
diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchScanActionSchemaInitializerItem.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchScanActionSchemaInitializerItem.tsx
index 47f2c87587..16a477bcd6 100644
--- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchScanActionSchemaInitializerItem.tsx
+++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchScanActionSchemaInitializerItem.tsx
@@ -15,6 +15,8 @@ import {
useSchemaInitializerItem,
ModalActionSchemaInitializerItem,
SchemaSettingAccessControl,
+ SchemaSettingsLinkageRules,
+ useSchemaToolbar,
} from '@nocobase/client';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -29,6 +31,16 @@ export const workbenchActionSettingsScanQrCode = new SchemaSettings({
return { hasIconColor: true };
},
},
+ {
+ name: 'linkageRules',
+ Component: SchemaSettingsLinkageRules,
+ useComponentProps() {
+ const { linkageRulesProps } = useSchemaToolbar();
+ return {
+ ...linkageRulesProps,
+ };
+ },
+ },
{
...SchemaSettingAccessControl,
useVisible() {
diff --git a/packages/plugins/@nocobase/plugin-calendar/package.json b/packages/plugins/@nocobase/plugin-calendar/package.json
index 7154b17090..8365ba971d 100644
--- a/packages/plugins/@nocobase/plugin-calendar/package.json
+++ b/packages/plugins/@nocobase/plugin-calendar/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-calendar",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "Calendar",
"displayName.zh-CN": "日历",
"description": "Provides callendar collection template and block for managing date data, typically for date/time related information such as events, appointments, tasks, and so on.",
diff --git a/packages/plugins/@nocobase/plugin-charts/package.json b/packages/plugins/@nocobase/plugin-charts/package.json
index af1539526e..99a6c6a143 100644
--- a/packages/plugins/@nocobase/plugin-charts/package.json
+++ b/packages/plugins/@nocobase/plugin-charts/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "图表(废弃)",
"description": "The plugin has been deprecated, please use the data visualization plugin instead.",
"description.zh-CN": "已废弃插件,请使用数据可视化插件代替。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"license": "AGPL-3.0",
"devDependencies": {
diff --git a/packages/plugins/@nocobase/plugin-client/package.json b/packages/plugins/@nocobase/plugin-client/package.json
index 71d3657172..63bc66b5f7 100644
--- a/packages/plugins/@nocobase/plugin-client/package.json
+++ b/packages/plugins/@nocobase/plugin-client/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "WEB 客户端",
"description": "Provides a client interface for the NocoBase server",
"description.zh-CN": "为 NocoBase 服务端提供客户端界面",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"license": "AGPL-3.0",
"devDependencies": {
diff --git a/packages/plugins/@nocobase/plugin-collection-sql/package.json b/packages/plugins/@nocobase/plugin-collection-sql/package.json
index 6513bb7a5a..1dff519df6 100644
--- a/packages/plugins/@nocobase/plugin-collection-sql/package.json
+++ b/packages/plugins/@nocobase/plugin-collection-sql/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表: SQL",
"description": "Provides SQL collection template",
"description.zh-CN": "提供 SQL 数据表模板",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"homepage": "https://docs-cn.nocobase.com/handbook/collection-sql",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/collection-sql",
"main": "dist/server/index.js",
diff --git a/packages/plugins/@nocobase/plugin-collection-tree/package.json b/packages/plugins/@nocobase/plugin-collection-tree/package.json
index b832b62a8b..9a8eff1d8d 100644
--- a/packages/plugins/@nocobase/plugin-collection-tree/package.json
+++ b/packages/plugins/@nocobase/plugin-collection-tree/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-collection-tree",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "Collection: Tree",
"displayName.zh-CN": "数据表:树",
"description": "Provides tree collection template",
diff --git a/packages/plugins/@nocobase/plugin-data-source-main/package.json b/packages/plugins/@nocobase/plugin-data-source-main/package.json
index a7034f5c6a..1384d7db81 100644
--- a/packages/plugins/@nocobase/plugin-data-source-main/package.json
+++ b/packages/plugins/@nocobase/plugin-data-source-main/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据源:主数据库",
"description": "NocoBase main database, supports relational databases such as PostgreSQL, MySQL, MariaDB and so on.",
"description.zh-CN": "NocoBase 主数据库,支持 PostgreSQL、MySQL、MariaDB 等关系型数据库。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/data-source-main",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-main",
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/package.json b/packages/plugins/@nocobase/plugin-data-source-manager/package.json
index 31427fce14..7e9e9f16d6 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/package.json
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-data-source-manager",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"displayName": "Data source manager",
"displayName.zh-CN": "数据源管理",
diff --git a/packages/plugins/@nocobase/plugin-data-visualization/package.json b/packages/plugins/@nocobase/plugin-data-visualization/package.json
index c4c94cdb7c..acc07c8cfb 100644
--- a/packages/plugins/@nocobase/plugin-data-visualization/package.json
+++ b/packages/plugins/@nocobase/plugin-data-visualization/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-data-visualization",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "Data visualization",
"displayName.zh-CN": "数据可视化",
"description": "Provides data visualization feature, including chart block and chart filter block, support line charts, area charts, bar charts and more than a dozen kinds of charts, you can also extend more chart types.",
diff --git a/packages/plugins/@nocobase/plugin-departments/.npmignore b/packages/plugins/@nocobase/plugin-departments/.npmignore
new file mode 100644
index 0000000000..65f5e8779f
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/.npmignore
@@ -0,0 +1,2 @@
+/node_modules
+/src
diff --git a/packages/plugins/@nocobase/plugin-departments/README.md b/packages/plugins/@nocobase/plugin-departments/README.md
new file mode 100644
index 0000000000..cb4fb63505
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/README.md
@@ -0,0 +1 @@
+# @nocobase/plugin-department
diff --git a/packages/plugins/@nocobase/plugin-departments/client.d.ts b/packages/plugins/@nocobase/plugin-departments/client.d.ts
new file mode 100644
index 0000000000..6c459cbac4
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/client.d.ts
@@ -0,0 +1,2 @@
+export * from './dist/client';
+export { default } from './dist/client';
diff --git a/packages/plugins/@nocobase/plugin-departments/client.js b/packages/plugins/@nocobase/plugin-departments/client.js
new file mode 100644
index 0000000000..b6e3be70e6
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/client.js
@@ -0,0 +1 @@
+module.exports = require('./dist/client/index.js');
diff --git a/packages/plugins/@nocobase/plugin-departments/package.json b/packages/plugins/@nocobase/plugin-departments/package.json
new file mode 100644
index 0000000000..c2aac785af
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@nocobase/plugin-departments",
+ "displayName": "Departments",
+ "displayName.zh-CN": "部门",
+ "description": "Organize users by departments, set hierarchical relationships, link roles to control permissions, and use departments as variables in workflows and expressions.",
+ "description.zh-CN": "以部门来组织用户,设定上下级关系,绑定角色控制权限,并支持作为变量用于工作流和表达式。",
+ "version": "1.7.0-beta.18",
+ "main": "dist/server/index.js",
+ "peerDependencies": {
+ "@nocobase/actions": "1.x",
+ "@nocobase/client": "1.x",
+ "@nocobase/plugin-user-data-sync": "1.x",
+ "@nocobase/server": "1.x",
+ "@nocobase/test": "1.x"
+ },
+ "keywords": [
+ "Users & permissions"
+ ],
+ "gitHead": "080fc78c1a744d47e010b3bbe5840446775800e4"
+}
diff --git a/packages/plugins/@nocobase/plugin-departments/server.d.ts b/packages/plugins/@nocobase/plugin-departments/server.d.ts
new file mode 100644
index 0000000000..c41081ddc6
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/server.d.ts
@@ -0,0 +1,2 @@
+export * from './dist/server';
+export { default } from './dist/server';
diff --git a/packages/plugins/@nocobase/plugin-departments/server.js b/packages/plugins/@nocobase/plugin-departments/server.js
new file mode 100644
index 0000000000..972842039a
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/server.js
@@ -0,0 +1 @@
+module.exports = require('./dist/server/index.js');
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/ResourcesProvider.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/ResourcesProvider.tsx
new file mode 100644
index 0000000000..2ad0daac83
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/ResourcesProvider.tsx
@@ -0,0 +1,110 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { CollectionProvider_deprecated, ResourceActionContext, TableBlockContext, useRequest } from '@nocobase/client';
+import React, { useContext, useEffect, useMemo } from 'react';
+import { departmentCollection } from './collections/departments';
+import { userCollection } from './collections/users';
+import { FormContext } from '@formily/react';
+import { createForm } from '@formily/core';
+
+export const ResourcesContext = React.createContext<{
+ user: any;
+ setUser?: (user: any) => void;
+ department: any; // department name
+ setDepartment?: (department: any) => void;
+ departmentsResource?: any;
+ usersResource?: any;
+}>({
+ user: {},
+ department: {},
+});
+
+export const ResourcesProvider: React.FC = (props) => {
+ const [user, setUser] = React.useState(null);
+ const [department, setDepartment] = React.useState(null);
+
+ const userService = useRequest({
+ resource: 'users',
+ action: 'list',
+ params: {
+ appends: ['departments', 'departments.parent(recursively=true)'],
+ filter: department
+ ? {
+ 'departments.id': department.id,
+ }
+ : {},
+ pageSize: 20,
+ },
+ });
+
+ useEffect(() => {
+ userService.run();
+ }, [department]);
+
+ const departmentRequest = {
+ resource: 'departments',
+ action: 'list',
+ params: {
+ paginate: false,
+ filter: {
+ parentId: null,
+ },
+ },
+ };
+ const departmentService = useRequest(departmentRequest);
+
+ return (
+
+ {props.children}
+
+ );
+};
+
+export const DepartmentsListProvider: React.FC = (props) => {
+ const { departmentsResource } = useContext(ResourcesContext);
+ const { service } = departmentsResource || {};
+ return (
+
+ {props.children}
+
+ );
+};
+
+export const UsersListProvider: React.FC = (props) => {
+ const { usersResource } = useContext(ResourcesContext);
+ const { service } = usersResource || {};
+ const form = useMemo(() => createForm(), []);
+ const field = form.createField({ name: 'table' });
+ return (
+
+
+ {props.children}
+
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/collections/departments.ts b/packages/plugins/@nocobase/plugin-departments/src/client/collections/departments.ts
new file mode 100644
index 0000000000..38105c0b2b
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/collections/departments.ts
@@ -0,0 +1,115 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+export const departmentCollection = {
+ name: 'departments',
+ fields: [
+ {
+ type: 'bigInt',
+ name: 'id',
+ primaryKey: true,
+ autoIncrement: true,
+ interface: 'id',
+ uiSchema: {
+ type: 'id',
+ title: '{{t("ID")}}',
+ },
+ },
+ {
+ name: 'title',
+ type: 'string',
+ interface: 'input',
+ uiSchema: {
+ type: 'string',
+ title: '{{t("Department name")}}',
+ 'x-component': 'Input',
+ required: true,
+ },
+ },
+ {
+ name: 'parent',
+ type: 'belongsTo',
+ interface: 'm2o',
+ collectionName: 'departments',
+ foreignKey: 'parentId',
+ target: 'departments',
+ targetKey: 'id',
+ treeParent: true,
+ uiSchema: {
+ title: '{{t("Superior department")}}',
+ 'x-component': 'DepartmentSelect',
+ // 'x-component-props': {
+ // multiple: false,
+ // fieldNames: {
+ // label: 'title',
+ // value: 'id',
+ // },
+ // },
+ },
+ },
+ {
+ interface: 'm2m',
+ type: 'belongsToMany',
+ name: 'roles',
+ target: 'roles',
+ collectionName: 'departments',
+ through: 'departmentsRoles',
+ foreignKey: 'departmentId',
+ otherKey: 'roleName',
+ targetKey: 'name',
+ sourceKey: 'id',
+ uiSchema: {
+ title: '{{t("Roles")}}',
+ 'x-component': 'AssociationField',
+ 'x-component-props': {
+ multiple: true,
+ fieldNames: {
+ label: 'title',
+ value: 'name',
+ },
+ },
+ },
+ },
+ {
+ interface: 'm2m',
+ type: 'belongsToMany',
+ name: 'owners',
+ collectionName: 'departments',
+ target: 'users',
+ through: 'departmentsUsers',
+ foreignKey: 'departmentId',
+ otherKey: 'userId',
+ targetKey: 'id',
+ sourceKey: 'id',
+ scope: {
+ isOwner: true,
+ },
+ uiSchema: {
+ title: '{{t("Owners")}}',
+ 'x-component': 'AssociationField',
+ 'x-component-props': {
+ multiple: true,
+ fieldNames: {
+ label: 'nickname',
+ value: 'id',
+ },
+ },
+ },
+ },
+ ],
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/collections/users.ts b/packages/plugins/@nocobase/plugin-departments/src/client/collections/users.ts
new file mode 100644
index 0000000000..ba309455a7
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/collections/users.ts
@@ -0,0 +1,142 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+export const userCollection = {
+ name: 'users',
+ fields: [
+ {
+ name: 'id',
+ type: 'bigInt',
+ autoIncrement: true,
+ primaryKey: true,
+ allowNull: false,
+ uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true },
+ interface: 'id',
+ },
+ {
+ interface: 'input',
+ type: 'string',
+ name: 'nickname',
+ uiSchema: {
+ type: 'string',
+ title: '{{t("Nickname")}}',
+ 'x-component': 'Input',
+ },
+ },
+ {
+ interface: 'input',
+ type: 'string',
+ name: 'username',
+ unique: true,
+ uiSchema: {
+ type: 'string',
+ title: '{{t("Username")}}',
+ 'x-component': 'Input',
+ 'x-validator': { username: true },
+ required: true,
+ },
+ },
+ {
+ interface: 'email',
+ type: 'string',
+ name: 'email',
+ unique: true,
+ uiSchema: {
+ type: 'string',
+ title: '{{t("Email")}}',
+ 'x-component': 'Input',
+ 'x-validator': 'email',
+ required: true,
+ },
+ },
+ {
+ interface: 'phone',
+ type: 'string',
+ name: 'phone',
+ unique: true,
+ uiSchema: {
+ type: 'string',
+ title: '{{t("Phone")}}',
+ 'x-component': 'Input',
+ 'x-validator': 'phone',
+ required: true,
+ },
+ },
+ {
+ interface: 'm2m',
+ type: 'belongsToMany',
+ name: 'roles',
+ target: 'roles',
+ foreignKey: 'userId',
+ otherKey: 'roleName',
+ onDelete: 'CASCADE',
+ sourceKey: 'id',
+ targetKey: 'name',
+ through: 'rolesUsers',
+ uiSchema: {
+ type: 'array',
+ title: '{{t("Roles")}}',
+ 'x-component': 'AssociationField',
+ 'x-component-props': {
+ multiple: true,
+ fieldNames: {
+ label: 'title',
+ value: 'name',
+ },
+ },
+ },
+ },
+ {
+ name: 'departments',
+ type: 'belongsToMany',
+ interface: 'm2m',
+ target: 'departments',
+ foreignKey: 'userId',
+ otherKey: 'departmentId',
+ onDelete: 'CASCADE',
+ sourceKey: 'id',
+ targetKey: 'id',
+ through: 'departmentsUsers',
+ uiSchema: {
+ type: 'array',
+ title: '{{t("Departments")}}',
+ 'x-component': 'DepartmentField',
+ },
+ },
+ {
+ interface: 'm2m',
+ type: 'belongsToMany',
+ name: 'mainDepartment',
+ target: 'departments',
+ foreignKey: 'userId',
+ otherKey: 'departmentId',
+ onDelete: 'CASCADE',
+ sourceKey: 'id',
+ targetKey: 'id',
+ through: 'departmentsUsers',
+ throughScope: {
+ isMain: true,
+ },
+ uiSchema: {
+ type: 'array',
+ title: '{{t("Main department")}}',
+ 'x-component': 'DepartmentField',
+ },
+ },
+ ],
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/components/DepartmentOwnersField.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/components/DepartmentOwnersField.tsx
new file mode 100644
index 0000000000..afcb651250
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/components/DepartmentOwnersField.tsx
@@ -0,0 +1,35 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { SchemaSettings } from '@nocobase/client';
+import { enableLink, fieldComponent, titleField } from './fieldSettings';
+
+export const DepartmentOwnersFieldSettings = new SchemaSettings({
+ name: 'fieldSettings:component:DepartmentOwnersField',
+ items: [
+ {
+ ...fieldComponent,
+ },
+ {
+ ...titleField,
+ },
+ {
+ ...enableLink,
+ },
+ ],
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/components/ReadOnlyAssociationField.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/components/ReadOnlyAssociationField.tsx
new file mode 100644
index 0000000000..d843d62847
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/components/ReadOnlyAssociationField.tsx
@@ -0,0 +1,27 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React from 'react';
+import { useDepartmentTranslation } from '../locale';
+import { AssociationField } from '@nocobase/client';
+import { connect, mapReadPretty } from '@formily/react';
+
+export const ReadOnlyAssociationField = connect(() => {
+ const { t } = useDepartmentTranslation();
+ return {t('This field is currently not supported for use in form blocks.')}
;
+}, mapReadPretty(AssociationField.ReadPretty));
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/components/UserDepartmentsField.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/components/UserDepartmentsField.tsx
new file mode 100644
index 0000000000..e866411db2
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/components/UserDepartmentsField.tsx
@@ -0,0 +1,35 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { SchemaSettings } from '@nocobase/client';
+import { enableLink, fieldComponent, titleField } from './fieldSettings';
+
+export const UserDepartmentsFieldSettings = new SchemaSettings({
+ name: 'fieldSettings:component:UserDepartmentsField',
+ items: [
+ {
+ ...fieldComponent,
+ },
+ {
+ ...titleField,
+ },
+ {
+ ...enableLink,
+ },
+ ],
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/components/UserMainDepartmentField.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/components/UserMainDepartmentField.tsx
new file mode 100644
index 0000000000..0e8355e408
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/components/UserMainDepartmentField.tsx
@@ -0,0 +1,35 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { SchemaSettings } from '@nocobase/client';
+import { enableLink, fieldComponent, titleField } from './fieldSettings';
+
+export const UserMainDepartmentFieldSettings = new SchemaSettings({
+ name: 'fieldSettings:component:UserMainDepartmentField',
+ items: [
+ {
+ ...fieldComponent,
+ },
+ {
+ ...titleField,
+ },
+ {
+ ...enableLink,
+ },
+ ],
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/components/fieldSettings.ts b/packages/plugins/@nocobase/plugin-departments/src/client/components/fieldSettings.ts
new file mode 100644
index 0000000000..bc637afa40
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/components/fieldSettings.ts
@@ -0,0 +1,185 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import {
+ useCollectionField,
+ useCollectionManager_deprecated,
+ useCollection_deprecated,
+ useCompile,
+ useDesignable,
+ useFieldComponentName,
+ useFieldModeOptions,
+ useIsAddNewForm,
+ useTitleFieldOptions,
+} from '@nocobase/client';
+import { useDepartmentTranslation } from '../locale';
+import { Field } from '@formily/core';
+import { useField, useFieldSchema, ISchema } from '@formily/react';
+
+export const titleField: any = {
+ name: 'titleField',
+ type: 'select',
+ useComponentProps() {
+ const { t } = useDepartmentTranslation();
+ const field = useField();
+ const { dn } = useDesignable();
+ const options = useTitleFieldOptions();
+ const { uiSchema, fieldSchema: tableColumnSchema, collectionField: tableColumnField } = useColumnSchema();
+ const schema = useFieldSchema();
+ const fieldSchema = tableColumnSchema || schema;
+ const targetCollectionField = useCollectionField();
+ const collectionField = tableColumnField || targetCollectionField;
+ const fieldNames = {
+ ...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'],
+ ...field?.componentProps?.fieldNames,
+ ...fieldSchema?.['x-component-props']?.['fieldNames'],
+ };
+ return {
+ title: t('Title field'),
+ options,
+ value: fieldNames?.label,
+ onChange(label) {
+ const schema = {
+ ['x-uid']: fieldSchema['x-uid'],
+ };
+ const newFieldNames = {
+ ...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'],
+ ...fieldSchema['x-component-props']?.['fieldNames'],
+ label,
+ };
+ fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
+ fieldSchema['x-component-props']['fieldNames'] = newFieldNames;
+ schema['x-component-props'] = fieldSchema['x-component-props'];
+ field.componentProps.fieldNames = fieldSchema['x-component-props']?.fieldNames;
+ const path = field.path?.splice(field.path?.length - 1, 1);
+ field.form.query(`${path.concat(`*.` + fieldSchema.name)}`).forEach((f) => {
+ f.componentProps.fieldNames = fieldNames;
+ });
+ dn.emit('patch', {
+ schema,
+ });
+ dn.refresh();
+ },
+ };
+ },
+};
+
+export const isCollectionFieldComponent = (schema: ISchema) => {
+ return schema['x-component'] === 'CollectionField';
+};
+
+const useColumnSchema = () => {
+ const { getField } = useCollection_deprecated();
+ const compile = useCompile();
+ const columnSchema = useFieldSchema();
+ const { getCollectionJoinField } = useCollectionManager_deprecated();
+ const fieldSchema = columnSchema.reduceProperties((buf, s) => {
+ if (isCollectionFieldComponent(s)) {
+ return s;
+ }
+ return buf;
+ }, null);
+ if (!fieldSchema) {
+ return {};
+ }
+
+ const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
+ return { columnSchema, fieldSchema, collectionField, uiSchema: compile(collectionField?.uiSchema) };
+};
+
+export const enableLink = {
+ name: 'enableLink',
+ type: 'switch',
+ useVisible() {
+ const field = useField();
+ return field.readPretty;
+ },
+ useComponentProps() {
+ const { t } = useDepartmentTranslation();
+ const field = useField();
+ const { fieldSchema: tableColumnSchema } = useColumnSchema();
+ const schema = useFieldSchema();
+ const fieldSchema = tableColumnSchema || schema;
+ const { dn } = useDesignable();
+ return {
+ title: t('Enable link'),
+ checked: fieldSchema['x-component-props']?.enableLink !== false,
+ onChange(flag) {
+ fieldSchema['x-component-props'] = {
+ ...fieldSchema?.['x-component-props'],
+ enableLink: flag,
+ };
+ field.componentProps['enableLink'] = flag;
+ dn.emit('patch', {
+ schema: {
+ 'x-uid': fieldSchema['x-uid'],
+ 'x-component-props': {
+ ...fieldSchema?.['x-component-props'],
+ },
+ },
+ });
+ dn.refresh();
+ },
+ };
+ },
+};
+
+export const fieldComponent: any = {
+ name: 'fieldComponent',
+ type: 'select',
+ useComponentProps() {
+ const { t } = useDepartmentTranslation();
+ const field = useField();
+ const { fieldSchema: tableColumnSchema, collectionField } = useColumnSchema();
+ const schema = useFieldSchema();
+ const fieldSchema = tableColumnSchema || schema;
+ const fieldModeOptions = useFieldModeOptions({ fieldSchema: tableColumnSchema, collectionField });
+ // const isAddNewForm = useIsAddNewForm();
+ // const fieldMode = useFieldComponentName();
+ const { dn } = useDesignable();
+ return {
+ title: t('Field component'),
+ options: fieldModeOptions,
+ value: 'Select',
+ onChange(mode) {
+ const schema = {
+ ['x-uid']: fieldSchema['x-uid'],
+ };
+ fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
+ fieldSchema['x-component-props']['mode'] = mode;
+ schema['x-component-props'] = fieldSchema['x-component-props'];
+ field.componentProps = field.componentProps || {};
+ field.componentProps.mode = mode;
+
+ // 子表单状态不允许设置默认值
+ // if (isSubMode(fieldSchema) && isAddNewForm) {
+ // // @ts-ignore
+ // schema.default = null;
+ // fieldSchema.default = null;
+ // field?.setInitialValue?.(null);
+ // field?.setValue?.(null);
+ // }
+
+ void dn.emit('patch', {
+ schema,
+ });
+ dn.refresh();
+ },
+ };
+ },
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/components/index.ts b/packages/plugins/@nocobase/plugin-departments/src/client/components/index.ts
new file mode 100644
index 0000000000..a9a4c4c641
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/components/index.ts
@@ -0,0 +1,22 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+export * from './ReadOnlyAssociationField';
+export * from './UserDepartmentsField';
+export * from './UserMainDepartmentField';
+export * from './DepartmentOwnersField';
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/AggregateSearch.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/AggregateSearch.tsx
new file mode 100644
index 0000000000..fccec99e2b
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/AggregateSearch.tsx
@@ -0,0 +1,216 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React, { useContext } from 'react';
+import { Input, Button, Empty, MenuProps, Dropdown, theme } from 'antd';
+import { useDepartmentTranslation } from '../locale';
+import { createStyles, useAPIClient, useRequest } from '@nocobase/client';
+import { ResourcesContext } from '../ResourcesProvider';
+
+const useStyles = createStyles(({ css }) => {
+ return {
+ searchDropdown: css`
+ .ant-dropdown-menu {
+ max-height: 500px;
+ overflow-y: scroll;
+ }
+ `,
+ };
+});
+
+export const AggregateSearch: React.FC = () => {
+ const { t } = useDepartmentTranslation();
+ const { token } = theme.useToken();
+ const { setDepartment, setUser } = useContext(ResourcesContext);
+ const [open, setOpen] = React.useState(false);
+ const [keyword, setKeyword] = React.useState('');
+ const [users, setUsers] = React.useState([]);
+ const [departments, setDepartments] = React.useState([]);
+ const [moreUsers, setMoreUsers] = React.useState(true);
+ const [moreDepartments, setMoreDepartments] = React.useState(true);
+ const { styles } = useStyles();
+ const limit = 10;
+
+ const api = useAPIClient();
+ const service = useRequest(
+ (params) =>
+ api
+ .resource('departments')
+ .aggregateSearch(params)
+ .then((res) => res?.data?.data),
+ {
+ manual: true,
+ onSuccess: (data, params) => {
+ const {
+ values: { type },
+ } = params[0] || {};
+ if (!data) {
+ return;
+ }
+ if ((!type || type === 'user') && data['users'].length < limit) {
+ setMoreUsers(false);
+ }
+ if ((!type || type === 'department') && data['departments'].length < limit) {
+ setMoreDepartments(false);
+ }
+ setUsers((users) => [...users, ...data['users']]);
+ setDepartments((departments) => [...departments, ...data['departments']]);
+ },
+ },
+ );
+ const { run } = service;
+
+ const handleSearch = (keyword: string) => {
+ setKeyword(keyword);
+ setUsers([]);
+ setDepartments([]);
+ setMoreUsers(true);
+ setMoreDepartments(true);
+ if (!keyword) {
+ return;
+ }
+ run({
+ values: { keyword, limit },
+ });
+ setOpen(true);
+ };
+
+ const handleChange = (e) => {
+ if (e.target.value) {
+ return;
+ }
+ setUser(null);
+ setKeyword('');
+ setOpen(false);
+ service.mutate({});
+ setUsers([]);
+ setDepartments([]);
+ };
+
+ const getTitle = (department: any) => {
+ const title = department.title;
+ const parent = department.parent;
+ if (parent) {
+ return getTitle(parent) + ' / ' + title;
+ }
+ return title;
+ };
+
+ const LoadMore: React.FC<{ type: string; last: number }> = (props) => {
+ return (
+
+ );
+ };
+
+ const getItems = () => {
+ const items: MenuProps['items'] = [];
+ if (!users.length && !departments.length) {
+ return [
+ {
+ key: '0',
+ label: ,
+ disabled: true,
+ },
+ ];
+ }
+ if (users.length) {
+ items.push({
+ key: '0',
+ type: 'group',
+ label: t('Users'),
+ children: users.map((user: { nickname: string; username: string; phone?: string; email?: string }) => ({
+ key: user.username,
+ label: (
+ setUser(user)}>
+
{user.nickname || user.username}
+
+ {`${user.username}${user.phone ? ' | ' + user.phone : ''}${user.email ? ' | ' + user.email : ''}`}
+
+
+ ),
+ })),
+ });
+ if (moreUsers) {
+ items.push({
+ type: 'group',
+ key: '0-loadMore',
+ label: ,
+ });
+ }
+ }
+ if (departments.length) {
+ items.push({
+ key: '1',
+ type: 'group',
+ label: t('Departments'),
+ children: departments.map((department: any) => ({
+ key: department.id,
+ label: setDepartment(department)}>{getTitle(department)}
,
+ })),
+ });
+ if (moreDepartments) {
+ items.push({
+ type: 'group',
+ key: '1-loadMore',
+ label: ,
+ });
+ }
+ }
+ return items;
+ };
+
+ return (
+ setOpen(open)}
+ >
+ {
+ if (!keyword) {
+ setOpen(false);
+ }
+ }}
+ onFocus={() => setDepartment(null)}
+ onSearch={handleSearch}
+ onChange={handleChange}
+ placeholder={t('Search for departments, users')}
+ style={{ marginBottom: '20px' }}
+ />
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/Department.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/Department.tsx
new file mode 100644
index 0000000000..b8673777ab
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/Department.tsx
@@ -0,0 +1,149 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React, { createContext, useContext, useState } from 'react';
+import { Row, Button, Divider, theme } from 'antd';
+import { UserOutlined } from '@ant-design/icons';
+import { useDepartmentTranslation } from '../locale';
+import { NewDepartment } from './NewDepartment';
+import { DepartmentTree } from './DepartmentTree';
+import { ResourcesContext } from '../ResourcesProvider';
+import { AggregateSearch } from './AggregateSearch';
+import { useDepartmentManager } from '../hooks';
+import {
+ ActionContextProvider,
+ RecordProvider,
+ SchemaComponent,
+ SchemaComponentOptions,
+ useAPIClient,
+ useActionContext,
+ useRecord,
+ useResourceActionContext,
+} from '@nocobase/client';
+import { useForm, useField } from '@formily/react';
+import { DepartmentOwnersField } from './DepartmentOwnersField';
+
+export const DepartmentTreeContext = createContext({} as ReturnType);
+
+export const useCreateDepartment = () => {
+ const form = useForm();
+ const field = useField();
+ const ctx = useActionContext();
+ const { refreshAsync } = useResourceActionContext();
+ const api = useAPIClient();
+ const { expandedKeys, setLoadedKeys, setExpandedKeys } = useContext(DepartmentTreeContext);
+ return {
+ async run() {
+ try {
+ await form.submit();
+ field.data = field.data || {};
+ field.data.loading = true;
+ await api.resource('departments').create({ values: form.values });
+ ctx.setVisible(false);
+ await form.reset();
+ field.data.loading = false;
+ const expanded = [...expandedKeys];
+ setLoadedKeys([]);
+ setExpandedKeys([]);
+ await refreshAsync();
+ setExpandedKeys(expanded);
+ } catch (error) {
+ if (field.data) {
+ field.data.loading = false;
+ }
+ }
+ },
+ };
+};
+
+export const useUpdateDepartment = () => {
+ const field = useField();
+ const form = useForm();
+ const ctx = useActionContext();
+ const { refreshAsync } = useResourceActionContext();
+ const api = useAPIClient();
+ const { id: filterByTk } = useRecord() as any;
+ const { expandedKeys, setLoadedKeys, setExpandedKeys } = useContext(DepartmentTreeContext);
+ const { department, setDepartment } = useContext(ResourcesContext);
+ return {
+ async run() {
+ await form.submit();
+ field.data = field.data || {};
+ field.data.loading = true;
+ try {
+ await api.resource('departments').update({ filterByTk, values: form.values });
+ setDepartment({ department, ...form.values });
+ ctx.setVisible(false);
+ await form.reset();
+ const expanded = [...expandedKeys];
+ setLoadedKeys([]);
+ setExpandedKeys([]);
+ await refreshAsync();
+ setExpandedKeys(expanded);
+ } catch (e) {
+ console.log(e);
+ } finally {
+ field.data.loading = false;
+ }
+ },
+ };
+};
+
+export const Department: React.FC = () => {
+ const { t } = useDepartmentTranslation();
+ const [visible, setVisible] = useState(false);
+ const [drawer, setDrawer] = useState({} as any);
+ const { department, setDepartment } = useContext(ResourcesContext);
+ const { token } = theme.useToken();
+ const departmentManager = useDepartmentManager({
+ label: ({ node }) => ,
+ });
+
+ return (
+
+
+
+
+ }
+ style={{
+ textAlign: 'left',
+ marginBottom: '5px',
+ background: department ? '' : token.colorBgTextHover,
+ }}
+ onClick={() => {
+ setDepartment(null);
+ }}
+ block
+ >
+ {t('All users')}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentBlock.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentBlock.tsx
new file mode 100644
index 0000000000..01b379d59a
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentBlock.tsx
@@ -0,0 +1,40 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React from 'react';
+import { SchemaComponent } from '@nocobase/client';
+import { DepartmentManagement } from './DepartmentManagement';
+import { uid } from '@formily/shared';
+
+export const DepartmentBlock: React.FC = () => {
+ return (
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentField.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentField.tsx
new file mode 100644
index 0000000000..76763a15bb
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentField.tsx
@@ -0,0 +1,48 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React, { useContext } from 'react';
+import { useField } from '@formily/react';
+import { Field } from '@formily/core';
+import { ResourcesContext } from '../ResourcesProvider';
+import { getDepartmentTitle } from '../utils';
+import { EllipsisWithTooltip } from '@nocobase/client';
+
+export const DepartmentField: React.FC = () => {
+ const { setDepartment } = useContext(ResourcesContext);
+ const field = useField();
+ const values = field.value || [];
+ const deptsMap = values.reduce((mp: { [id: number]: any }, dept: any) => {
+ mp[dept.id] = dept;
+ return mp;
+ }, {});
+ const depts = values.map((dept: { id: number; title: string }, index: number) => (
+
+ {
+ e.preventDefault();
+ setDepartment(deptsMap[dept.id]);
+ }}
+ >
+ {getDepartmentTitle(dept)}
+
+ {index !== values.length - 1 ? , : ''}
+
+ ));
+ return {depts};
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentManagement.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentManagement.tsx
new file mode 100644
index 0000000000..5bae972bf8
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentManagement.tsx
@@ -0,0 +1,44 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React from 'react';
+import { Col, Row } from 'antd';
+import { Department } from './Department';
+import { Member } from './Member';
+import { SchemaComponentOptions } from '@nocobase/client';
+import { SuperiorDepartmentSelect, DepartmentSelect } from './DepartmentTreeSelect';
+import { DepartmentsListProvider, UsersListProvider } from '../ResourcesProvider';
+
+export const DepartmentManagement: React.FC = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentOwnersField.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentOwnersField.tsx
new file mode 100644
index 0000000000..6faffb38f3
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentOwnersField.tsx
@@ -0,0 +1,115 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import {
+ ActionContextProvider,
+ ResourceActionProvider,
+ SchemaComponent,
+ useActionContext,
+ useRecord,
+} from '@nocobase/client';
+import React, { useEffect, useRef, useState } from 'react';
+import { Select } from 'antd';
+import { Field } from '@formily/core';
+import { useField } from '@formily/react';
+import { departmentOwnersSchema } from './schemas/departments';
+
+export const DepartmentOwnersField: React.FC = () => {
+ const [visible, setVisible] = useState(false);
+ const department = useRecord() as any;
+ const field = useField();
+ const [value, setValue] = useState([]);
+ const selectedRows = useRef([]);
+ const handleSelect = (_: number[], rows: any[]) => {
+ selectedRows.current = rows;
+ };
+
+ const useSelectOwners = () => {
+ const { setVisible } = useActionContext();
+ return {
+ run() {
+ const selected = field.value || [];
+ field.setValue([...selected, ...selectedRows.current]);
+ selectedRows.current = [];
+ setVisible(false);
+ },
+ };
+ };
+
+ useEffect(() => {
+ if (!field.value) {
+ return;
+ }
+ setValue(
+ field.value.map((owner: any) => ({
+ value: owner.id,
+ label: owner.nickname || owner.username,
+ })),
+ );
+ }, [field.value]);
+
+ const RequestProvider: React.FC = (props) => (
+ owner.id),
+ },
+ }
+ : {},
+ },
+ }}
+ >
+ {props.children}
+
+ );
+
+ return (
+
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentTable.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentTable.tsx
new file mode 100644
index 0000000000..e1e3a768d2
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentTable.tsx
@@ -0,0 +1,263 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import {
+ CollectionContext,
+ CollectionProvider_deprecated,
+ ResourceActionContext,
+ SchemaComponent,
+ mergeFilter,
+ removeNullCondition,
+ useFilterFieldOptions,
+ useFilterFieldProps,
+ useResourceActionContext,
+} from '@nocobase/client';
+import React, { createContext, useContext, useEffect, useState } from 'react';
+import { useDepartmentManager } from '../hooks';
+import { Table, TablePaginationConfig, TableProps } from 'antd';
+import { departmentCollection } from '../collections/departments';
+import { useDepartmentTranslation } from '../locale';
+import { useField } from '@formily/react';
+import { Field } from '@formily/core';
+import { uid } from '@formily/shared';
+import { getDepartmentTitle } from '../utils';
+
+const ExpandMetaContext = createContext({});
+
+export const useFilterActionProps = () => {
+ const { setHasFilter, setExpandedKeys } = useContext(ExpandMetaContext);
+ const { t } = useDepartmentTranslation();
+ const collection = useContext(CollectionContext);
+ const options = useFilterFieldOptions(collection.fields);
+ const service = useResourceActionContext();
+ const { run, defaultRequest } = service;
+ const field = useField();
+ const { params } = defaultRequest || {};
+
+ return {
+ options,
+ onSubmit: async (values: any) => {
+ // filter parameter for the block
+ const defaultFilter = params.filter;
+ // filter parameter for the filter action
+ const filter = removeNullCondition(values?.filter);
+ run({
+ ...params,
+ page: 1,
+ pageSize: 10,
+ filter: mergeFilter([filter, defaultFilter]),
+ });
+ const items = filter?.$and || filter?.$or;
+ if (items?.length) {
+ field.title = t('{{count}} filter items', { count: items?.length || 0 });
+ setHasFilter(true);
+ } else {
+ field.title = t('Filter');
+ setHasFilter(false);
+ }
+ },
+ onReset() {
+ run({
+ ...(params || {}),
+ filter: {
+ ...(params?.filter || {}),
+ parentId: null,
+ },
+ page: 1,
+ pageSize: 10,
+ });
+ field.title = t('Filter');
+ setHasFilter(false);
+ setExpandedKeys([]);
+ },
+ };
+};
+
+const useDefaultDisabled = () => {
+ return {
+ disabled: () => false,
+ };
+};
+
+const InternalDepartmentTable: React.FC<{
+ useDisabled?: () => {
+ disabled: (record: any) => boolean;
+ };
+}> = ({ useDisabled = useDefaultDisabled }) => {
+ const { t } = useDepartmentTranslation();
+ const ctx = useResourceActionContext();
+ console.log(ctx);
+ const { run, data, loading, defaultRequest } = ctx;
+ const { resource, resourceOf, params } = defaultRequest || {};
+ const { treeData, initData, loadData } = useDepartmentManager({
+ resource,
+ resourceOf,
+ params,
+ });
+ const field = useField();
+ const { disabled } = useDisabled();
+ const { hasFilter, expandedKeys, setExpandedKeys } = useContext(ExpandMetaContext);
+
+ useEffect(() => {
+ if (hasFilter) {
+ return;
+ }
+ initData(data?.data);
+ }, [data, initData, loading, hasFilter]);
+
+ const pagination: TablePaginationConfig = {};
+ if (params?.pageSize) {
+ pagination.defaultPageSize = params.pageSize;
+ }
+ if (!pagination.total && data?.meta) {
+ const { count, page, pageSize } = data.meta;
+ pagination.total = count;
+ pagination.current = page;
+ pagination.pageSize = pageSize;
+ }
+
+ return (
+ (hasFilter ? getDepartmentTitle(record) : text),
+ },
+ ] as TableProps['columns']
+ }
+ rowSelection={{
+ selectedRowKeys: (field?.value || []).map((dept: any) => dept.id),
+ onChange: (keys, depts) => field?.setValue?.(depts),
+ getCheckboxProps: (record: any) => ({
+ disabled: disabled(record),
+ }),
+ }}
+ pagination={{
+ showSizeChanger: true,
+ ...pagination,
+ onChange(page, pageSize) {
+ run({
+ ...(ctx?.params?.[0] || {}),
+ page,
+ pageSize,
+ });
+ },
+ }}
+ dataSource={hasFilter ? data?.data || [] : treeData}
+ expandable={{
+ onExpand: (expanded, record) => {
+ loadData({
+ key: record.id,
+ children: record.children,
+ });
+ },
+ expandedRowKeys: expandedKeys,
+ onExpandedRowsChange: (keys) => setExpandedKeys(keys),
+ }}
+ />
+ );
+};
+
+const RequestProvider: React.FC<{
+ useDataSource: any;
+}> = (props) => {
+ const [expandedKeys, setExpandedKeys] = useState([]);
+ const [hasFilter, setHasFilter] = useState(false);
+ const { useDataSource } = props;
+ const service = useDataSource({
+ manual: true,
+ });
+ useEffect(() => {
+ service.run({
+ filter: {
+ parentId: null,
+ },
+ pageSize: 10,
+ });
+ }, []);
+ return (
+
+
+
+ {props.children}
+
+
+
+ );
+};
+
+export const DepartmentTable: React.FC<{
+ useDataSource: any;
+ useDisabled?: (record: any) => boolean;
+}> = ({ useDataSource, useDisabled }) => {
+ return (
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentTree.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentTree.tsx
new file mode 100644
index 0000000000..03ac4080b6
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentTree.tsx
@@ -0,0 +1,181 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React, { useContext, useEffect } from 'react';
+import { Tree, Dropdown, App, Empty } from 'antd';
+import { MoreOutlined } from '@ant-design/icons';
+import { useAPIClient, useResourceActionContext } from '@nocobase/client';
+import { useDepartmentTranslation } from '../locale';
+import { editDepartmentSchema, newSubDepartmentSchema } from './schemas/departments';
+import { ResourcesContext } from '../ResourcesProvider';
+import { DepartmentTreeContext } from './Department';
+import { css } from '@emotion/css';
+
+type DepartmentTreeProps = {
+ node: {
+ id: number;
+ title: string;
+ parent?: any;
+ };
+ setVisible: (visible: boolean) => void;
+ setDrawer: (schema: any) => void;
+};
+
+export const DepartmentTree: React.FC & {
+ Item: React.FC;
+} = () => {
+ const { data, loading } = useResourceActionContext();
+ const { department, setDepartment, setUser } = useContext(ResourcesContext);
+ const { treeData, nodeMap, loadData, loadedKeys, setLoadedKeys, initData, expandedKeys, setExpandedKeys } =
+ useContext(DepartmentTreeContext);
+ const handleSelect = (keys: number[]) => {
+ if (!keys.length) {
+ return;
+ }
+ const node = nodeMap[keys[0]];
+ setDepartment(node);
+ setUser(null);
+ };
+
+ const handleExpand = (keys: number[]) => {
+ setExpandedKeys(keys);
+ };
+
+ const handleLoad = (keys: number[]) => {
+ setLoadedKeys(keys);
+ };
+
+ useEffect(() => {
+ initData(data?.data);
+ }, [data, initData, loading]);
+
+ useEffect(() => {
+ if (!department) {
+ return;
+ }
+ const getIds = (node: any) => {
+ if (node.parent) {
+ return [node.parent.id, ...getIds(node.parent)];
+ }
+ return [];
+ };
+ const newKeys = getIds(department);
+ setExpandedKeys((keys) => Array.from(new Set([...keys, ...newKeys])));
+ }, [department, setExpandedKeys]);
+
+ return (
+
+ {treeData?.length ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+DepartmentTree.Item = function DepartmentTreeItem({ node, setVisible, setDrawer }: DepartmentTreeProps) {
+ const { t } = useDepartmentTranslation();
+ const { refreshAsync } = useResourceActionContext();
+ const { setLoadedKeys, expandedKeys, setExpandedKeys } = useContext(DepartmentTreeContext);
+ const { modal, message } = App.useApp();
+ const api = useAPIClient();
+ const deleteDepartment = () => {
+ modal.confirm({
+ title: t('Delete'),
+ content: t('Are you sure you want to delete it?'),
+ onOk: async () => {
+ await api.resource('departments').destroy({ filterByTk: node.id });
+ message.success(t('Deleted successfully'));
+ setExpandedKeys((keys) => keys.filter((k) => k !== node.id));
+ const expanded = [...expandedKeys];
+ setLoadedKeys([]);
+ setExpandedKeys([]);
+ await refreshAsync();
+ setExpandedKeys(expanded);
+ },
+ });
+ };
+ const openDrawer = (schema: any) => {
+ setDrawer({ schema, node });
+ setVisible(true);
+ };
+ const handleClick = ({ key, domEvent }) => {
+ domEvent.stopPropagation();
+ switch (key) {
+ case 'new-sub':
+ openDrawer(newSubDepartmentSchema);
+ break;
+ case 'edit':
+ openDrawer(editDepartmentSchema);
+ break;
+ case 'delete':
+ deleteDepartment();
+ }
+ };
+ return (
+
+
{node.title}
+
+
+
+
+
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentTreeSelect.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentTreeSelect.tsx
new file mode 100644
index 0000000000..b66b511548
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/DepartmentTreeSelect.tsx
@@ -0,0 +1,136 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React, { useCallback, useContext, useEffect } from 'react';
+import { TreeSelect } from 'antd';
+import { useField } from '@formily/react';
+import { Field } from '@formily/core';
+import { useRecord } from '@nocobase/client';
+import { ResourcesContext } from '../ResourcesProvider';
+import { useDepartmentManager } from '../hooks/departments-manager';
+
+export const DepartmentTreeSelect: React.FC<{
+ originData: any;
+ treeData: any[];
+ [key: string]: any;
+}> = (props) => {
+ const field = useField();
+ const [value, setValue] = React.useState({ label: null, value: null });
+ const { treeData, initData, getByKeyword, loadData, loadedKeys, setLoadedKeys, originData } = props;
+
+ const handleSearch = async (keyword: string) => {
+ if (!keyword) {
+ initData(originData);
+ return;
+ }
+ await getByKeyword(keyword);
+ };
+
+ const getTitle = useCallback((record: any) => {
+ const title = record.title;
+ const parent = record.parent;
+ if (parent) {
+ return getTitle(parent) + ' / ' + title;
+ }
+ return title;
+ }, []);
+
+ useEffect(() => {
+ initData(originData);
+ }, [originData, initData]);
+
+ useEffect(() => {
+ if (!field.value) {
+ setValue({ label: null, value: null });
+ return;
+ }
+ setValue({
+ label: getTitle(field.value) || field.value.label,
+ value: field.value.id,
+ });
+ }, [field.value, getTitle]);
+
+ return (
+ {
+ field.setValue(node);
+ }}
+ onChange={(value: any) => {
+ if (!value) {
+ field.setValue(null);
+ }
+ }}
+ treeData={treeData}
+ treeLoadedKeys={loadedKeys}
+ onTreeLoad={(keys: any[]) => setLoadedKeys(keys)}
+ loadData={(node: any) => loadData({ key: node.id, children: node.children })}
+ fieldNames={{
+ value: 'id',
+ }}
+ showSearch
+ allowClear
+ treeNodeFilterProp="title"
+ onSearch={handleSearch}
+ labelInValue={true}
+ />
+ );
+};
+
+export const DepartmentSelect: React.FC = () => {
+ const departmentManager = useDepartmentManager();
+ const { departmentsResource } = useContext(ResourcesContext);
+ const {
+ service: { data },
+ } = departmentsResource || {};
+ return ;
+};
+
+export const SuperiorDepartmentSelect: React.FC = () => {
+ const departmentManager = useDepartmentManager();
+ const { setTreeData, getChildrenIds } = departmentManager;
+ const record = useRecord() as any;
+ const { departmentsResource } = useContext(ResourcesContext);
+ const {
+ service: { data },
+ } = departmentsResource || {};
+
+ useEffect(() => {
+ if (!record.id) {
+ return;
+ }
+ const childrenIds = getChildrenIds(record.id);
+ childrenIds.push(record.id);
+ setTreeData((treeData) => {
+ const setDisabled = (treeData: any[]) => {
+ return treeData.map((node) => {
+ if (childrenIds.includes(node.id)) {
+ node.disabled = true;
+ }
+ if (node.children) {
+ node.children = setDisabled(node.children);
+ }
+ return node;
+ });
+ };
+ return setDisabled(treeData);
+ });
+ }, [setTreeData, record.id, getChildrenIds]);
+
+ return ;
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/IsOwnerField.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/IsOwnerField.tsx
new file mode 100644
index 0000000000..e685d2d89a
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/IsOwnerField.tsx
@@ -0,0 +1,30 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React, { useContext } from 'react';
+import { useDepartmentTranslation } from '../locale';
+import { Checkbox, useRecord } from '@nocobase/client';
+import { ResourcesContext } from '../ResourcesProvider';
+
+export const IsOwnerField: React.FC = () => {
+ const { department } = useContext(ResourcesContext);
+ const record = useRecord() as any;
+ const dept = (record.departments || []).find((dept: any) => dept?.id === department?.id);
+
+ return ;
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/Member.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/Member.tsx
new file mode 100644
index 0000000000..a23aa2bde8
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/Member.tsx
@@ -0,0 +1,235 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React, { useContext, useRef, useEffect, useMemo } from 'react';
+import { useDepartmentTranslation } from '../locale';
+import {
+ CollectionContext,
+ ResourceActionProvider,
+ SchemaComponent,
+ useAPIClient,
+ useActionContext,
+ useFilterFieldOptions,
+ useFilterFieldProps,
+ useRecord,
+ useResourceActionContext,
+ useTableBlockContext,
+} from '@nocobase/client';
+import { membersActionSchema, addMembersSchema, rowRemoveActionSchema, getMembersSchema } from './schemas/users';
+import { App } from 'antd';
+import { DepartmentField } from './DepartmentField';
+import { IsOwnerField } from './IsOwnerField';
+import { UserDepartmentsField } from './UserDepartmentsField';
+import { ResourcesContext } from '../ResourcesProvider';
+import { useTableBlockProps } from '../hooks/useTableBlockProps';
+
+const AddMembersListProvider: React.FC = (props) => {
+ const { department } = useContext(ResourcesContext);
+ return (
+
+ {props.children}
+
+ );
+};
+
+const useAddMembersFilterActionProps = () => {
+ const collection = useContext(CollectionContext);
+ const options = useFilterFieldOptions(collection.fields);
+ const service = useResourceActionContext();
+ return useFilterFieldProps({
+ options,
+ params: service.state?.params?.[0] || service.params,
+ service,
+ });
+};
+
+export const AddMembers: React.FC = () => {
+ const { department } = useContext(ResourcesContext);
+ // This resource is the list of members of the current department.
+ const {
+ service: { refresh },
+ } = useTableBlockContext();
+ const selectedKeys = useRef([]);
+ const api = useAPIClient();
+
+ const useAddMembersActionProps = () => {
+ const { department } = useContext(ResourcesContext);
+ const { setVisible } = useActionContext();
+ return {
+ async onClick() {
+ const selected = selectedKeys.current;
+ if (!selected?.length) {
+ return;
+ }
+ await api.resource('departments.members', department.id).add({
+ values: selected,
+ });
+ selectedKeys.current = [];
+ refresh();
+ setVisible?.(false);
+ },
+ };
+ };
+
+ const handleSelect = (keys: any[]) => {
+ selectedKeys.current = keys;
+ };
+
+ return (
+
+ );
+};
+
+const useBulkRemoveMembersAction = () => {
+ const { t } = useDepartmentTranslation();
+ const { message } = App.useApp();
+ const api = useAPIClient();
+ const {
+ service: { refresh },
+ field,
+ } = useTableBlockContext();
+ const { department } = useContext(ResourcesContext);
+ return {
+ async run() {
+ const selected = field?.data?.selectedRowKeys;
+ if (!selected?.length) {
+ message.warning(t('Please select members'));
+ return;
+ }
+ await api.resource('departments.members', department.id).remove({
+ values: selected,
+ });
+ field.data.selectedRowKeys = [];
+ refresh();
+ },
+ };
+};
+
+const useRemoveMemberAction = () => {
+ const api = useAPIClient();
+ const { department } = useContext(ResourcesContext);
+ const { id } = useRecord() as any;
+ const {
+ service: { refresh },
+ } = useTableBlockContext();
+ return {
+ async run() {
+ await api.resource('departments.members', department.id).remove({
+ values: [id],
+ });
+ refresh();
+ },
+ };
+};
+
+const useShowTotal = () => {
+ const {
+ service: { data },
+ } = useTableBlockContext();
+ const { t } = useDepartmentTranslation();
+ return t('Total {{count}} members', { count: data?.meta?.count });
+};
+
+const useRefreshActionProps = () => {
+ const { service } = useTableBlockContext();
+ return {
+ async onClick() {
+ service?.refresh?.();
+ },
+ };
+};
+
+const RowRemoveAction = () => {
+ const { department } = useContext(ResourcesContext);
+ return department ? : null;
+};
+
+const MemberActions = () => {
+ const { department } = useContext(ResourcesContext);
+ return department ? : null;
+};
+
+const useMemberFilterActionProps = () => {
+ const collection = useContext(CollectionContext);
+ const options = useFilterFieldOptions(collection.fields);
+ const { service } = useTableBlockContext();
+ return useFilterFieldProps({
+ options,
+ params: service.state?.params?.[0] || service.params,
+ service,
+ });
+};
+
+export const Member: React.FC = () => {
+ const { t } = useDepartmentTranslation();
+ const { department, user } = useContext(ResourcesContext);
+ const {
+ service: { data, setState },
+ } = useTableBlockContext();
+
+ useEffect(() => {
+ setState?.({ selectedRowKeys: [] });
+ }, [data, setState]);
+
+ const schema = useMemo(() => getMembersSchema(department, user), [department, user]);
+
+ return (
+ <>
+ {!user ? {t(department?.title || 'All users')}
: {t('Search results')}
}
+
+ >
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/NewDepartment.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/NewDepartment.tsx
new file mode 100644
index 0000000000..08f4e78a9f
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/NewDepartment.tsx
@@ -0,0 +1,97 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { SchemaComponent } from '@nocobase/client';
+import React from 'react';
+import { useDepartmentTranslation } from '../locale';
+
+export const NewDepartment: React.FC = () => {
+ const { t } = useDepartmentTranslation();
+ return (
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/UserDepartmentsField.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/departments/UserDepartmentsField.tsx
new file mode 100644
index 0000000000..e72869046a
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/UserDepartmentsField.tsx
@@ -0,0 +1,273 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import {
+ ActionContextProvider,
+ SchemaComponent,
+ useAPIClient,
+ useRecord,
+ useRequest,
+ useResourceActionContext,
+ useTableBlockContext,
+} from '@nocobase/client';
+import React, { useState } from 'react';
+import { Tag, Button, Dropdown, App } from 'antd';
+import { PlusOutlined, MoreOutlined } from '@ant-design/icons';
+import { Field } from '@formily/core';
+import { useField, useForm } from '@formily/react';
+import { userDepartmentsSchema } from './schemas/users';
+import { getDepartmentTitle } from '../utils';
+import { useDepartmentTranslation } from '../locale';
+import { DepartmentTable } from './DepartmentTable';
+
+const useDataSource = (options?: any) => {
+ const defaultRequest = {
+ resource: 'departments',
+ action: 'list',
+ params: {
+ appends: ['parent(recursively=true)'],
+ // filter: {
+ // parentId: null,
+ // },
+ sort: ['createdAt'],
+ },
+ };
+ const service = useRequest(defaultRequest, options);
+ return {
+ ...service,
+ defaultRequest,
+ };
+};
+
+export const UserDepartmentsField: React.FC = () => {
+ const { modal, message } = App.useApp();
+ const { t } = useDepartmentTranslation();
+ const [visible, setVisible] = useState(false);
+ const user = useRecord() as any;
+ const field = useField();
+ const {
+ service: { refresh },
+ } = useTableBlockContext();
+
+ const formatData = (data: any[]) => {
+ if (!data?.length) {
+ return [];
+ }
+
+ return data.map((department) => ({
+ ...department,
+ isMain: department.departmentsUsers?.isMain,
+ isOwner: department.departmentsUsers?.isOwner,
+ title: getDepartmentTitle(department),
+ }));
+ };
+
+ const api = useAPIClient();
+ useRequest(
+ () =>
+ api
+ .resource(`users.departments`, user.id)
+ .list({
+ appends: ['parent(recursively=true)'],
+ paginate: false,
+ })
+ .then((res) => {
+ const data = formatData(res?.data?.data);
+ field.setValue(data);
+ }),
+ {
+ ready: user.id,
+ },
+ );
+
+ const useAddDepartments = () => {
+ const api = useAPIClient();
+ const drawerForm = useForm();
+ const { departments } = drawerForm.values || {};
+ return {
+ async run() {
+ await api.resource('users.departments', user.id).add({
+ values: departments.map((dept: any) => dept.id),
+ });
+ drawerForm.reset();
+ field.setValue([
+ ...field.value,
+ ...departments.map((dept: any, index: number) => ({
+ ...dept,
+ isMain: index === 0 && field.value.length === 0,
+ title: getDepartmentTitle(dept),
+ })),
+ ]);
+ setVisible(false);
+ refresh();
+ },
+ };
+ };
+
+ const removeDepartment = (dept: any) => {
+ modal.confirm({
+ title: t('Remove department'),
+ content: t('Are you sure you want to remove it?'),
+ onOk: async () => {
+ await api.resource('users.departments', user.id).remove({ values: [dept.id] });
+ message.success(t('Deleted successfully'));
+ field.setValue(
+ field.value
+ .filter((d: any) => d.id !== dept.id)
+ .map((d: any, index: number) => ({
+ ...d,
+ isMain: (dept.isMain && index === 0) || d.isMain,
+ })),
+ );
+ refresh();
+ },
+ });
+ };
+
+ const setMainDepartment = async (dept: any) => {
+ await api.resource('users').setMainDepartment({
+ values: {
+ userId: user.id,
+ departmentId: dept.id,
+ },
+ });
+ message.success(t('Set successfully'));
+ field.setValue(
+ field.value.map((d: any) => ({
+ ...d,
+ isMain: d.id === dept.id,
+ })),
+ );
+ refresh();
+ };
+
+ const setOwner = async (dept: any) => {
+ await api.resource('departments').setOwner({
+ values: {
+ userId: user.id,
+ departmentId: dept.id,
+ },
+ });
+ message.success(t('Set successfully'));
+ field.setValue(
+ field.value.map((d: any) => ({
+ ...d,
+ isOwner: d.id === dept.id ? true : d.isOwner,
+ })),
+ );
+ refresh();
+ };
+
+ const removeOwner = async (dept: any) => {
+ await api.resource('departments').removeOwner({
+ values: {
+ userId: user.id,
+ departmentId: dept.id,
+ },
+ });
+ message.success(t('Set successfully'));
+ field.setValue(
+ field.value.map((d: any) => ({
+ ...d,
+ isOwner: d.id === dept.id ? false : d.isOwner,
+ })),
+ );
+ refresh();
+ };
+
+ const handleClick = (key: string, dept: any) => {
+ switch (key) {
+ case 'setMain':
+ setMainDepartment(dept);
+ break;
+ case 'setOwner':
+ setOwner(dept);
+ break;
+ case 'removeOwner':
+ removeOwner(dept);
+ break;
+ case 'remove':
+ removeDepartment(dept);
+ }
+ };
+
+ const useDisabled = () => ({
+ disabled: (record: any) => {
+ return field.value.some((dept: any) => dept.id === record.id);
+ },
+ });
+
+ return (
+
+ <>
+ {(field?.value || []).map((dept) => (
+
+ {dept.title}
+ {dept.isMain ? (
+
+ {t('Main')}
+
+ ) : (
+ ''
+ )}
+ {/* {dept.isOwner ? ( */}
+ {/* */}
+ {/* {t('Owner')} */}
+ {/* */}
+ {/* ) : ( */}
+ {/* '' */}
+ {/* )} */}
+ handleClick(key, dept),
+ }}
+ >
+
+
+
+
+
+ ))}
+ } onClick={() => setVisible(true)} />
+ >
+
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/schemas/departments.ts b/packages/plugins/@nocobase/plugin-departments/src/client/departments/schemas/departments.ts
new file mode 100644
index 0000000000..32272647c7
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/schemas/departments.ts
@@ -0,0 +1,291 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { useEffect } from 'react';
+import { uid } from '@formily/shared';
+import { useAPIClient, useActionContext, useRecord, useRequest } from '@nocobase/client';
+
+export const newSubDepartmentSchema = {
+ type: 'object',
+ properties: {
+ [uid()]: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'Form',
+ 'x-decorator-props': {
+ useValues(options: any) {
+ const ctx = useActionContext();
+ const record = useRecord();
+ return useRequest(() => Promise.resolve({ data: { parent: { ...record } } }), {
+ ...options,
+ refreshDeps: [ctx.visible],
+ });
+ },
+ },
+ title: '{{t("New sub department")}}',
+ properties: {
+ title: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ parent: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ 'x-collection-field': 'departments.parent',
+ 'x-component-props': {
+ component: 'DepartmentSelect',
+ },
+ },
+ roles: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ 'x-collection-field': 'departments.roles',
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ submit: {
+ title: '{{t("Submit")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction: '{{ useCreateDepartment }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+export const editDepartmentSchema = {
+ type: 'object',
+ properties: {
+ [uid()]: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'Form',
+ 'x-decorator-props': {
+ useValues(options: any) {
+ const api = useAPIClient();
+ const ctx = useActionContext();
+ const record = useRecord();
+ const result = useRequest(
+ () =>
+ api
+ .resource('departments')
+ .get({
+ filterByTk: record['id'],
+ appends: ['parent(recursively=true)', 'roles', 'owners'],
+ })
+ .then((res: any) => res?.data),
+ { ...options, manual: true },
+ );
+ useEffect(() => {
+ if (ctx.visible) {
+ result.run();
+ }
+ }, [ctx.visible]);
+ return result;
+ },
+ },
+ title: '{{t("Edit department")}}',
+ properties: {
+ title: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ parent: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ 'x-collection-field': 'departments.parent',
+ 'x-component-props': {
+ component: 'SuperiorDepartmentSelect',
+ },
+ },
+ roles: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ 'x-collection-field': 'departments.roles',
+ },
+ owners: {
+ title: '{{t("Owners")}}',
+ 'x-component': 'DepartmentOwnersField',
+ 'x-decorator': 'FormItem',
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ submit: {
+ title: '{{t("Submit")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction: '{{ useUpdateDepartment }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+export const departmentOwnersSchema = {
+ type: 'void',
+ properties: {
+ drawer: {
+ title: '{{t("Select Owners")}}',
+ 'x-component': 'Action.Drawer',
+ properties: {
+ resource: {
+ type: 'void',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'RequestProvider',
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'ActionBar',
+ 'x-component-props': {
+ style: {
+ marginBottom: 16,
+ },
+ },
+ properties: {
+ filter: {
+ type: 'void',
+ title: '{{ t("Filter") }}',
+ default: {
+ $and: [{ username: { $includes: '' } }, { nickname: { $includes: '' } }],
+ },
+ 'x-action': 'filter',
+ 'x-component': 'Filter.Action',
+ 'x-use-component-props': 'useFilterActionProps',
+ 'x-component-props': {
+ icon: 'FilterOutlined',
+ },
+ 'x-align': 'left',
+ },
+ },
+ },
+ table: {
+ type: 'void',
+ 'x-component': 'Table.Void',
+ 'x-component-props': {
+ rowKey: 'id',
+ rowSelection: {
+ type: 'checkbox',
+ onChange: '{{ handleSelect }}',
+ },
+ useDataSource: '{{ cm.useDataSourceFromRAC }}',
+ },
+ properties: {
+ username: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ username: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ nickname: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ nickname: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ phone: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ phone: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ email: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ email: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ confirm: {
+ title: '{{t("Confirm")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction: '{{ useSelectOwners }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/departments/schemas/users.ts b/packages/plugins/@nocobase/plugin-departments/src/client/departments/schemas/users.ts
new file mode 100644
index 0000000000..66c2115d0e
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/departments/schemas/users.ts
@@ -0,0 +1,464 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { uid } from '@formily/shared';
+
+export const membersActionSchema = {
+ type: 'void',
+ 'x-component': 'Space',
+ properties: {
+ remove: {
+ type: 'void',
+ title: '{{t("Remove")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ icon: 'UserDeleteOutlined',
+ confirm: {
+ title: "{{t('Remove members')}}",
+ content: "{{t('Are you sure you want to remove these members?')}}",
+ },
+ style: {
+ marginRight: 8,
+ },
+ useAction: '{{ useBulkRemoveMembersAction }}',
+ },
+ },
+ create: {
+ type: 'void',
+ title: '{{t("Add members")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ icon: 'UserAddOutlined',
+ },
+ properties: {
+ drawer: {
+ type: 'void',
+ 'x-component': 'AddMembers',
+ },
+ },
+ },
+ },
+};
+
+export const rowRemoveActionSchema = {
+ type: 'void',
+ properties: {
+ remove: {
+ title: '{{ t("Remove") }}',
+ 'x-component': 'Action.Link',
+ 'x-component-props': {
+ confirm: {
+ title: "{{t('Remove member')}}",
+ content: "{{t('Are you sure you want to remove it?')}}",
+ },
+ useAction: '{{ useRemoveMemberAction }}',
+ },
+ },
+ },
+};
+
+export const getMembersSchema = (department: any, user: any) => ({
+ type: 'void',
+ 'x-component': 'CardItem',
+ 'x-component-props': {
+ heightMode: 'fullHeight',
+ },
+ properties: {
+ ...(!user
+ ? {
+ actions: {
+ type: 'void',
+ 'x-component': 'ActionBar',
+ 'x-component-props': {
+ style: {
+ marginBottom: 16,
+ },
+ },
+ properties: {
+ [uid()]: {
+ type: 'void',
+ title: '{{ t("Filter") }}',
+ 'x-action': 'filter',
+ 'x-component': 'Filter.Action',
+ 'x-use-component-props': 'useMemberFilterActionProps',
+ 'x-component-props': {
+ icon: 'FilterOutlined',
+ },
+ 'x-align': 'left',
+ },
+ refresh: {
+ type: 'void',
+ title: '{{ t("Refresh") }}',
+ 'x-action': 'refresh',
+ 'x-component': 'Action',
+ 'x-use-component-props': 'useRefreshActionProps',
+ 'x-component-props': {
+ icon: 'ReloadOutlined',
+ },
+ },
+ actions: {
+ type: 'void',
+ 'x-component': 'MemberActions',
+ },
+ },
+ },
+ }
+ : {}),
+ table: {
+ type: 'array',
+ 'x-component': 'TableV2',
+ 'x-use-component-props': 'useTableBlockProps',
+ 'x-component-props': {
+ rowKey: 'id',
+ rowSelection: {
+ type: 'checkbox',
+ },
+ pagination: {
+ showTotal: '{{ useShowTotal }}',
+ },
+ },
+ properties: {
+ nickname: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ nickname: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ username: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ username: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ departments: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ departments: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ ...(department
+ ? {
+ isOwner: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ 'x-component-props': {
+ style: {
+ minWidth: 100,
+ },
+ },
+ title: '{{t("Owner")}}',
+ properties: {
+ isOwner: {
+ type: 'boolean',
+ 'x-component': 'IsOwnerField',
+ },
+ },
+ },
+ }
+ : {}),
+ phone: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ phone: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ email: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ email: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ actions: {
+ type: 'void',
+ title: '{{t("Actions")}}',
+ 'x-component': 'Table.Column',
+ 'x-component-props': {
+ fixed: 'right',
+ },
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'Space',
+ 'x-component-props': {
+ split: '|',
+ },
+ properties: {
+ update: {
+ type: 'void',
+ title: '{{t("Configure")}}',
+ 'x-component': 'Action.Link',
+ 'x-component-props': {
+ type: 'primary',
+ },
+ properties: {
+ drawer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'FormV2',
+ title: '{{t("Configure")}}',
+ properties: {
+ departments: {
+ title: '{{t("Departments")}}',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'UserDepartmentsField',
+ },
+ // footer: {
+ // type: 'void',
+ // 'x-component': 'Action.Drawer.Footer',
+ // properties: {
+ // cancel: {
+ // title: '{{t("Cancel")}}',
+ // 'x-component': 'Action',
+ // 'x-component-props': {
+ // useAction: '{{ cm.useCancelAction }}',
+ // },
+ // },
+ // submit: {
+ // title: '{{t("Submit")}}',
+ // 'x-component': 'Action',
+ // 'x-component-props': {
+ // type: 'primary',
+ // // useAction: '{{ useSetDepartments }}',
+ // },
+ // },
+ // },
+ // },
+ },
+ },
+ },
+ },
+ ...(department
+ ? {
+ remove: {
+ type: 'void',
+ 'x-component': 'RowRemoveAction',
+ },
+ }
+ : {}),
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+});
+
+export const addMembersSchema = {
+ type: 'object',
+ properties: {
+ drawer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'Form',
+ title: '{{t("Add members")}}',
+ properties: {
+ resource: {
+ type: 'void',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'AddMembersListProvider',
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'ActionBar',
+ 'x-component-props': {
+ style: {
+ marginBottom: 16,
+ },
+ },
+ properties: {
+ filter: {
+ type: 'void',
+ title: '{{ t("Filter") }}',
+ default: {
+ $and: [{ username: { $includes: '' } }, { nickname: { $includes: '' } }],
+ },
+ 'x-action': 'filter',
+ 'x-component': 'Filter.Action',
+ 'x-use-component-props': 'useAddMembersFilterActionProps',
+ 'x-component-props': {
+ icon: 'FilterOutlined',
+ },
+ 'x-align': 'left',
+ },
+ },
+ },
+ table: {
+ type: 'void',
+ 'x-component': 'Table.Void',
+ 'x-component-props': {
+ rowKey: 'id',
+ rowSelection: {
+ type: 'checkbox',
+ onChange: '{{ handleSelect }}',
+ },
+ useDataSource: '{{ cm.useDataSourceFromRAC }}',
+ },
+ properties: {
+ username: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ username: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ nickname: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ nickname: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ phone: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ phone: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ email: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ email: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ submit: {
+ title: '{{t("Submit")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ },
+ 'x-use-component-props': 'useAddMembersActionProps',
+ },
+ },
+ },
+ },
+ },
+ },
+};
+
+export const userDepartmentsSchema = {
+ type: 'void',
+ properties: {
+ drawer: {
+ title: '{{t("Select Departments")}}',
+ 'x-decorator': 'Form',
+ 'x-component': 'Action.Drawer',
+ properties: {
+ table: {
+ type: 'void',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'DepartmentTable',
+ 'x-component-props': {
+ useDataSource: '{{ useDataSource }}',
+ useDisabled: '{{ useDisabled }}',
+ },
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ confirm: {
+ title: '{{t("Submit")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction: '{{ useAddDepartments }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/hooks/departments-manager.ts b/packages/plugins/@nocobase/plugin-departments/src/client/hooks/departments-manager.ts
new file mode 100644
index 0000000000..57d99ee9aa
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/hooks/departments-manager.ts
@@ -0,0 +1,75 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { useAPIClient } from '@nocobase/client';
+import { TreeManagerOptions, useTreeManager } from './tree-manager';
+import deepmerge from 'deepmerge';
+
+type DepartmentManagerOptions = {
+ resource?: string;
+ resourceOf?: string;
+ params?: any;
+} & TreeManagerOptions;
+
+export const useDepartmentManager = (options?: DepartmentManagerOptions) => {
+ const { resource = 'departments', resourceOf, params = {} } = options || {};
+ const api = useAPIClient();
+ const resourceAPI = api.resource(resource, resourceOf);
+ const treeManager = useTreeManager(options);
+ const { setTreeData, updateTreeData, setLoadedKeys, initData } = treeManager;
+ const loadData = async ({ key, children }) => {
+ if (children?.length) {
+ return;
+ }
+ const { data } = await resourceAPI.list(
+ deepmerge(params, {
+ paginate: false,
+ appends: ['parent(recursively=true)'],
+ filter: {
+ parentId: key,
+ },
+ }),
+ );
+ if (!data?.data?.length) {
+ return;
+ }
+ setTreeData(updateTreeData(key, data?.data));
+ };
+
+ const getByKeyword = async (keyword: string) => {
+ const { data } = await resourceAPI.list(
+ deepmerge(params, {
+ paginate: false,
+ filter: {
+ title: {
+ $includes: keyword,
+ },
+ },
+ appends: ['parent(recursively=true)'],
+ pageSize: 100,
+ }),
+ );
+ initData(data?.data);
+ };
+
+ return {
+ ...treeManager,
+ loadData,
+ getByKeyword,
+ };
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/hooks/index.ts b/packages/plugins/@nocobase/plugin-departments/src/client/hooks/index.ts
new file mode 100644
index 0000000000..72af4ce551
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/hooks/index.ts
@@ -0,0 +1,68 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import {
+ CollectionContext,
+ useActionContext,
+ useFilterFieldOptions,
+ useFilterFieldProps,
+ useResourceActionContext,
+ useResourceContext,
+} from '@nocobase/client';
+import { useContext } from 'react';
+import { useForm, useField } from '@formily/react';
+
+export const useCreateAction = () => {
+ const form = useForm();
+ const field = useField();
+ const ctx = useActionContext();
+ const { refresh } = useResourceActionContext();
+ const { resource } = useResourceContext();
+ return {
+ async run() {
+ try {
+ await form.submit();
+ field.data = field.data || {};
+ field.data.loading = true;
+ await resource.create({ values: form.values });
+ ctx.setVisible(false);
+ await form.reset();
+ field.data.loading = false;
+ refresh();
+ } catch (error) {
+ if (field.data) {
+ field.data.loading = false;
+ }
+ }
+ },
+ };
+};
+
+export const useFilterActionProps = () => {
+ const collection = useContext(CollectionContext);
+ const options = useFilterFieldOptions(collection.fields);
+ const service = useResourceActionContext();
+ return useFilterFieldProps({
+ options,
+ params: service.state?.params?.[0] || service.params,
+ service,
+ });
+};
+
+export * from './tree-manager';
+export * from './departments-manager';
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/hooks/tree-manager.ts b/packages/plugins/@nocobase/plugin-departments/src/client/hooks/tree-manager.ts
new file mode 100644
index 0000000000..d2ef32c348
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/hooks/tree-manager.ts
@@ -0,0 +1,142 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React, { useCallback, useState } from 'react';
+
+export type TreeManagerOptions = {
+ label?: React.FC<{ node: any }>;
+};
+
+export const useTreeManager = (options?: TreeManagerOptions) => {
+ const { label } = options || {};
+ const [treeData, setTreeData] = useState([]);
+ const [nodeMap, setNodeMap] = useState({});
+ const [expandedKeys, setExpandedKeys] = useState([]);
+ const [loadedKeys, setLoadedKeys] = useState([]);
+
+ const buildNodeMap = useCallback((data: any[]) => {
+ const mp = {};
+ const setNodeMapFromChild = (node: any) => {
+ let child = node ? { ...node } : null;
+ while (child) {
+ const parentId = child.parentId || 'root';
+ if (mp[parentId]) {
+ mp[parentId].childrenMap[child.id] = child;
+ } else {
+ mp[parentId] = {
+ ...(child.parent || { id: parentId }),
+ childrenMap: {
+ [child.id]: child,
+ },
+ };
+ }
+ child = child.parent;
+ }
+ };
+ const setNodeMapFromParent = (node: any) => {
+ const childrenMap = {};
+ if (node.children && node.children.length) {
+ node.children.forEach((child: any) => {
+ childrenMap[child.id] = child;
+ setNodeMapFromParent(child);
+ });
+ }
+ mp[node.id] = {
+ ...node,
+ childrenMap,
+ };
+ };
+ if (!(data && data.length)) {
+ return mp;
+ }
+ data.forEach((node) => {
+ setNodeMapFromChild(node);
+ setNodeMapFromParent(node);
+ });
+ return mp;
+ }, []);
+
+ const constructTreeData = useCallback((nodeMap: { [parentId: string | number]: any }) => {
+ const getChildren = (id: any) => {
+ if (!nodeMap[id]) {
+ return null;
+ }
+ if (nodeMap[id].isLeaf) {
+ return null;
+ }
+ return Object.values(nodeMap[id]?.childrenMap || {}).map((node: any) => {
+ return {
+ ...node,
+ title: label ? React.createElement(label, { node }) : node.title,
+ children: getChildren(node.id),
+ };
+ });
+ };
+ return getChildren('root');
+ }, []);
+
+ const initData = useCallback(
+ (data: any[]) => {
+ const mp = buildNodeMap(data);
+ setNodeMap(mp);
+ const treeData = constructTreeData(mp) || [];
+ setTreeData(treeData);
+ // setLoadedKeys([]);
+ },
+ [setTreeData, buildNodeMap, constructTreeData],
+ );
+
+ const updateTreeData = (key: any, children: any[]) => {
+ const mp = buildNodeMap(children);
+ const newMap = { ...mp, ...nodeMap };
+ children.forEach((node) => {
+ newMap[key].childrenMap[node.id] = node;
+ });
+ setNodeMap(newMap);
+ return constructTreeData(newMap);
+ };
+
+ const getChildrenIds = useCallback(
+ (id: any) => {
+ if (!nodeMap[id]) {
+ return [];
+ }
+ const ids = [];
+ ids.push(...Object.keys(nodeMap[id].childrenMap).map((id) => Number(id)));
+ Object.keys(nodeMap[id].childrenMap).forEach((id) => {
+ ids.push(...getChildrenIds(id));
+ });
+ return ids;
+ },
+ [nodeMap],
+ );
+
+ return {
+ initData,
+ treeData,
+ setTreeData,
+ nodeMap,
+ updateTreeData,
+ constructTreeData,
+ getChildrenIds,
+ loadedKeys,
+ setLoadedKeys,
+ expandedKeys,
+ setExpandedKeys,
+ };
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/hooks/useTableBlockProps.ts b/packages/plugins/@nocobase/plugin-departments/src/client/hooks/useTableBlockProps.ts
new file mode 100644
index 0000000000..fe5b35eb8a
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/hooks/useTableBlockProps.ts
@@ -0,0 +1,175 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { ArrayField } from '@formily/core';
+import { useField, useFieldSchema } from '@formily/react';
+import { isEqual } from 'lodash';
+import { useCallback, useEffect, useRef } from 'react';
+import {
+ useTableBlockContext,
+ findFilterTargets,
+ DataBlock,
+ useFilterBlock,
+ mergeFilter,
+ removeNullCondition,
+} from '@nocobase/client';
+
+export const useTableBlockProps = () => {
+ const field = useField();
+ const fieldSchema = useFieldSchema();
+ const ctx = useTableBlockContext();
+ const { getDataBlocks } = useFilterBlock();
+ const isLoading = ctx?.service?.loading;
+
+ const ctxRef = useRef(null);
+ ctxRef.current = ctx;
+
+ useEffect(() => {
+ if (!isLoading) {
+ const serviceResponse = ctx?.service?.data;
+ const data = serviceResponse?.data || [];
+ const meta = serviceResponse?.meta || {};
+ const selectedRowKeys = ctx?.field?.data?.selectedRowKeys;
+
+ if (!isEqual(field.value, data)) {
+ field.value = data;
+ field?.setInitialValue(data);
+ }
+ field.data = field.data || {};
+
+ if (!isEqual(field.data.selectedRowKeys, selectedRowKeys)) {
+ field.data.selectedRowKeys = selectedRowKeys;
+ }
+
+ field.componentProps.pagination = field.componentProps.pagination || {};
+ field.componentProps.pagination.pageSize = meta?.pageSize;
+ field.componentProps.pagination.total = meta?.count;
+ field.componentProps.pagination.current = meta?.page;
+ }
+ }, [field, ctx?.service?.data, isLoading, ctx?.field?.data?.selectedRowKeys]);
+
+ return {
+ bordered: ctx.bordered,
+ childrenColumnName: ctx.childrenColumnName,
+ loading: ctx?.service?.loading,
+ showIndex: ctx.showIndex,
+ dragSort: ctx.dragSort && ctx.dragSortBy,
+ rowKey: ctx.rowKey || fieldSchema?.['x-component-props']?.rowKey || 'id',
+ pagination: fieldSchema?.['x-component-props']?.pagination === false ? false : field.componentProps.pagination,
+ onRowSelectionChange: useCallback((selectedRowKeys, selectedRowData) => {
+ ctx.field.data = ctx?.field?.data || {};
+ ctx.field.data.selectedRowKeys = selectedRowKeys;
+ ctx.field.data.selectedRowData = selectedRowData;
+ ctx?.field?.onRowSelect?.(selectedRowKeys);
+ }, []),
+ onRowDragEnd: useCallback(
+ async ({ from, to }) => {
+ await ctx.resource.move({
+ sourceId: from[ctx.rowKey || 'id'],
+ targetId: to[ctx.rowKey || 'id'],
+ sortField: ctx.dragSort && ctx.dragSortBy,
+ });
+ ctx.service.refresh();
+ // ctx.resource
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ },
+ [ctx.rowKey, ctx.dragSort, ctx.dragSortBy],
+ ),
+ onChange: useCallback(
+ ({ current, pageSize }, filters, sorter) => {
+ const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort'];
+ const sort = sorter.order
+ ? sorter.order === `ascend`
+ ? [sorter.field]
+ : [`-${sorter.field}`]
+ : globalSort || ctxRef.current.dragSortBy;
+ const currentPageSize = pageSize || fieldSchema.parent?.['x-decorator-props']?.['params']?.pageSize;
+ const args = { ...ctxRef.current?.service?.params?.[0], page: current || 1, pageSize: currentPageSize };
+ if (sort) {
+ args['sort'] = sort;
+ }
+ ctxRef.current?.service.run(args);
+ },
+ [fieldSchema.parent],
+ ),
+ onClickRow: useCallback(
+ (record, setSelectedRow, selectedRow) => {
+ const { targets, uid } = findFilterTargets(fieldSchema);
+ const dataBlocks = getDataBlocks();
+
+ // 如果是之前创建的区块是没有 x-filter-targets 属性的,所以这里需要判断一下避免报错
+ if (!targets || !targets.some((target) => dataBlocks.some((dataBlock) => dataBlock.uid === target.uid))) {
+ // 当用户已经点击过某一行,如果此时再把相连接的区块给删除的话,行的高亮状态就会一直保留。
+ // 这里暂时没有什么比较好的方法,只是在用户再次点击的时候,把高亮状态给清除掉。
+ setSelectedRow((prev) => (prev.length ? [] : prev));
+ return;
+ }
+
+ const currentBlock = dataBlocks.find((block) => block.uid === fieldSchema.parent['x-uid']);
+
+ dataBlocks.forEach((block) => {
+ const target = targets.find((target) => target.uid === block.uid);
+ if (!target) return;
+
+ const isForeignKey = block.foreignKeyFields?.some((field) => field.name === target.field);
+ const sourceKey = getSourceKey(currentBlock, target.field);
+ const recordKey = isForeignKey ? sourceKey : ctx.rowKey;
+ const value = [record[recordKey]];
+
+ const param = block.service.params?.[0] || {};
+ // 保留原有的 filter
+ const storedFilter = block.service.params?.[1]?.filters || {};
+
+ if (selectedRow.includes(record[ctx.rowKey])) {
+ if (block.dataLoadingMode === 'manual') {
+ return block.clearData();
+ }
+ delete storedFilter[uid];
+ } else {
+ storedFilter[uid] = {
+ $and: [
+ {
+ [target.field || ctx.rowKey]: {
+ [target.field ? '$in' : '$eq']: value,
+ },
+ },
+ ],
+ };
+ }
+
+ const mergedFilter = mergeFilter([
+ ...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
+ block.defaultFilter,
+ ]);
+
+ return block.doFilter(
+ {
+ ...param,
+ page: 1,
+ filter: mergedFilter,
+ },
+ { filters: storedFilter },
+ );
+ });
+
+ // 更新表格的选中状态
+ setSelectedRow((prev) => (prev?.includes(record[ctx.rowKey]) ? [] : [record[ctx.rowKey]]));
+ },
+ [ctx.rowKey, fieldSchema, getDataBlocks],
+ ),
+ onExpand: useCallback((expanded, record) => {
+ ctx?.field.onExpandClick?.(expanded, record);
+ }, []),
+ };
+};
+
+function getSourceKey(currentBlock: DataBlock, field: string) {
+ const associationField = currentBlock?.associatedFields?.find((item) => item.foreignKey === field);
+ return associationField?.sourceKey || 'id';
+}
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/index.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/index.tsx
new file mode 100644
index 0000000000..798fddecd6
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/index.tsx
@@ -0,0 +1,76 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Plugin, SchemaComponentContext, useSchemaComponentContext } from '@nocobase/client';
+import { tval } from '@nocobase/utils/client';
+import { DepartmentBlock } from './departments/DepartmentBlock';
+import React from 'react';
+import { ResourcesProvider } from './ResourcesProvider';
+import ACLPlugin from '@nocobase/plugin-acl/client';
+import { RoleDepartmentsManager } from './roles/RoleDepartmentsManager';
+import {
+ UserDepartmentsFieldSettings,
+ ReadOnlyAssociationField,
+ UserMainDepartmentFieldSettings,
+ DepartmentOwnersFieldSettings,
+} from './components';
+
+export class PluginDepartmentsClient extends Plugin {
+ async afterAdd() {
+ // await this.app.pm.add()
+ }
+
+ async beforeLoad() {}
+
+ // You can get and modify the app instance here
+ async load() {
+ this.app.addComponents({
+ UserDepartmentsField: ReadOnlyAssociationField,
+ UserMainDepartmentField: ReadOnlyAssociationField,
+ DepartmentOwnersField: ReadOnlyAssociationField,
+ });
+ this.app.schemaSettingsManager.add(UserDepartmentsFieldSettings);
+ this.app.schemaSettingsManager.add(UserMainDepartmentFieldSettings);
+ this.app.schemaSettingsManager.add(DepartmentOwnersFieldSettings);
+
+ this.app.pluginSettingsManager.add('users-permissions.departments', {
+ icon: 'ApartmentOutlined',
+ title: tval('Departments', { ns: 'departments' }),
+ Component: () => {
+ const scCtx = useSchemaComponentContext();
+ return (
+
+
+
+
+
+ );
+ },
+ sort: 2,
+ aclSnippet: 'pm.departments',
+ });
+
+ const acl = this.app.pm.get(ACLPlugin);
+ acl.rolesManager.add('departments', {
+ title: tval('Departments', { ns: 'departments' }),
+ Component: RoleDepartmentsManager,
+ });
+ }
+}
+
+export default PluginDepartmentsClient;
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/locale.ts b/packages/plugins/@nocobase/plugin-departments/src/client/locale.ts
new file mode 100644
index 0000000000..2899c172d4
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/locale.ts
@@ -0,0 +1,23 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { useTranslation } from 'react-i18next';
+
+export function useDepartmentTranslation() {
+ return useTranslation(['departments', 'client'], { nsMode: 'fallback' });
+}
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/roles/RoleDepartmentsManager.tsx b/packages/plugins/@nocobase/plugin-departments/src/client/roles/RoleDepartmentsManager.tsx
new file mode 100644
index 0000000000..30d8badc1d
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/roles/RoleDepartmentsManager.tsx
@@ -0,0 +1,178 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React, { useContext, useEffect, useMemo } from 'react';
+import { App } from 'antd';
+import { useDepartmentTranslation } from '../locale';
+import {
+ CollectionManagerContext,
+ CollectionProvider_deprecated,
+ ResourceActionContext,
+ SchemaComponent,
+ useAPIClient,
+ useActionContext,
+ useRecord,
+ useRequest,
+ useResourceActionContext,
+} from '@nocobase/client';
+import { RolesManagerContext } from '@nocobase/plugin-acl/client';
+import { departmentCollection } from '../collections/departments';
+import { getDepartmentsSchema } from './schemas/departments';
+import { useFilterActionProps } from '../hooks';
+import { DepartmentTable } from '../departments/DepartmentTable';
+import { useForm } from '@formily/react';
+
+const useRemoveDepartment = () => {
+ const api = useAPIClient();
+ const { role } = useContext(RolesManagerContext);
+ const { id } = useRecord();
+ const { refresh } = useResourceActionContext();
+ return {
+ async run() {
+ await api.resource(`roles/${role?.name}/departments`).remove({
+ values: [id],
+ });
+ refresh();
+ },
+ };
+};
+
+const useBulkRemoveDepartments = () => {
+ const { t } = useDepartmentTranslation();
+ const { message } = App.useApp();
+ const api = useAPIClient();
+ const { state, setState, refresh } = useResourceActionContext();
+ const { role } = useContext(RolesManagerContext);
+
+ return {
+ async run() {
+ const selected = state?.selectedRowKeys;
+ if (!selected?.length) {
+ message.warning(t('Please select departments'));
+ return;
+ }
+ await api.resource(`roles/${role?.name}/departments`).remove({
+ values: selected,
+ });
+ setState?.({ selectedRowKeys: [] });
+ refresh();
+ },
+ };
+};
+
+const DepartmentTitle: React.FC = () => {
+ const record = useRecord();
+ const getTitle = (record: any) => {
+ const title = record.title;
+ const parent = record.parent;
+ if (parent) {
+ return getTitle(parent) + ' / ' + title;
+ }
+ return title;
+ };
+
+ return <>{getTitle(record)}>;
+};
+
+const useDataSource = (options?: any) => {
+ const defaultRequest = {
+ resource: 'departments',
+ action: 'list',
+ params: {
+ // filter: {
+ // parentId: null,
+ // },
+ appends: ['roles', 'parent(recursively=true)'],
+ sort: ['createdAt'],
+ },
+ };
+ const service = useRequest(defaultRequest, options);
+ return {
+ ...service,
+ defaultRequest,
+ };
+};
+
+const useDisabled = () => {
+ const { role } = useContext(RolesManagerContext);
+ return {
+ disabled: (record: any) => record?.roles?.some((r: { name: string }) => r.name === role?.name),
+ };
+};
+
+const useAddDepartments = () => {
+ const { role } = useContext(RolesManagerContext);
+ const api = useAPIClient();
+ const form = useForm();
+ const { setVisible } = useActionContext();
+ const { refresh } = useResourceActionContext();
+ const { departments } = form.values || {};
+ return {
+ async run() {
+ await api.resource('roles.departments', role.name).add({
+ values: departments.map((dept: any) => dept.id),
+ });
+ form.reset();
+ setVisible(false);
+ refresh();
+ },
+ };
+};
+
+export const RoleDepartmentsManager: React.FC = () => {
+ const { t } = useDepartmentTranslation();
+ const { role } = useContext(RolesManagerContext);
+ const service = useRequest(
+ {
+ resource: `roles/${role?.name}/departments`,
+ action: 'list',
+ params: {
+ appends: ['parent', 'parent.parent(recursively=true)'],
+ },
+ },
+ {
+ ready: !!role,
+ },
+ );
+
+ useEffect(() => {
+ service.run();
+ }, [role]);
+
+ const schema = useMemo(() => getDepartmentsSchema(), [role]);
+
+ return (
+
+
+
+
+
+ );
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/roles/schemas/departments.ts b/packages/plugins/@nocobase/plugin-departments/src/client/roles/schemas/departments.ts
new file mode 100644
index 0000000000..240a37635c
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/roles/schemas/departments.ts
@@ -0,0 +1,171 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { uid } from '@formily/shared';
+
+export const getDepartmentsSchema = () => ({
+ type: 'void',
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'ActionBar',
+ 'x-component-props': {
+ style: {
+ marginBottom: 16,
+ },
+ },
+ properties: {
+ [uid()]: {
+ type: 'void',
+ title: '{{ t("Filter") }}',
+ 'x-action': 'filter',
+ 'x-component': 'Filter.Action',
+ 'x-use-component-props': 'useFilterActionProps',
+ 'x-component-props': {
+ icon: 'FilterOutlined',
+ },
+ 'x-align': 'left',
+ },
+ actions: {
+ type: 'void',
+ 'x-component': 'Space',
+ properties: {
+ remove: {
+ type: 'void',
+ title: '{{t("Remove")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ icon: 'MinusOutlined',
+ confirm: {
+ title: "{{t('Remove')}}",
+ content: "{{t('Are you sure you want to remove these departments?')}}",
+ },
+ style: {
+ marginRight: 8,
+ },
+ useAction: '{{ useBulkRemoveDepartments }}',
+ },
+ },
+ create: {
+ type: 'void',
+ title: '{{t("Add departments")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ icon: 'PlusOutlined',
+ },
+ properties: {
+ drawer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'FormV2',
+ title: '{{t("Add departments")}}',
+ properties: {
+ table: {
+ type: 'void',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'DepartmentTable',
+ 'x-component-props': {
+ useDataSource: '{{ useDataSource }}',
+ useDisabled: '{{ useDisabled }}',
+ },
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ submit: {
+ title: '{{t("Submit")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction: '{{ useAddDepartments }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ table: {
+ type: 'void',
+ 'x-component': 'Table.Void',
+ 'x-component-props': {
+ rowKey: 'id',
+ rowSelection: {
+ type: 'checkbox',
+ },
+ useDataSource: '{{ cm.useDataSourceFromRAC }}',
+ },
+ properties: {
+ title: {
+ type: 'void',
+ title: '{{t("Department name")}}',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ title: {
+ type: 'string',
+ 'x-component': 'DepartmentTitle',
+ },
+ },
+ },
+ actions: {
+ type: 'void',
+ title: '{{t("Actions")}}',
+ 'x-component': 'Table.Column',
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'Space',
+ 'x-component-props': {
+ split: '|',
+ },
+ properties: {
+ remove: {
+ type: 'void',
+ title: '{{ t("Remove") }}',
+ 'x-component': 'Action.Link',
+ 'x-component-props': {
+ confirm: {
+ title: "{{t('Remove department')}}",
+ content: "{{t('Are you sure you want to remove it?')}}",
+ },
+ useAction: '{{ useRemoveDepartment }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/client/utils.ts b/packages/plugins/@nocobase/plugin-departments/src/client/utils.ts
new file mode 100644
index 0000000000..52f6fb7663
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/client/utils.ts
@@ -0,0 +1,26 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+export const getDepartmentTitle = (record: any) => {
+ const title = record.title;
+ const parent = record.parent;
+ if (parent) {
+ return getDepartmentTitle(parent) + ' / ' + title;
+ }
+ return title;
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/index.ts b/packages/plugins/@nocobase/plugin-departments/src/index.ts
new file mode 100644
index 0000000000..7d69462f4f
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/index.ts
@@ -0,0 +1,20 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+export * from './server';
+export { default } from './server';
diff --git a/packages/plugins/@nocobase/plugin-departments/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-departments/src/locale/en-US.json
new file mode 100644
index 0000000000..f872121a10
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/locale/en-US.json
@@ -0,0 +1,35 @@
+{
+ "Department": "Department",
+ "All users": "All users",
+ "New department": "New department",
+ "Add department": "Add department",
+ "Add departments": "Add departments",
+ "New sub department": "New sub department",
+ "Edit department": "Edit department",
+ "Delete department": "Delete department",
+ "Departments": "Departments",
+ "Main department": "Main department",
+ "Owner": "Owner",
+ "Department name": "Department name",
+ "Superior department": "Superior department",
+ "Owners": "Owners",
+ "Add members": "Add members",
+ "Search for departments, users": "Search for departments, users",
+ "Search results": "Search results",
+ "Departments management": "Departments management",
+ "Roles management": "Roles management",
+ "Remove members": "Remove members",
+ "Remove member": "Remove member",
+ "Remove departments": "Remove departments",
+ "Remove department": "Remove department",
+ "Are you sure you want to remove it?": "Are you sure you want to remove it?",
+ "Are you sure you want to remove these members?": "Are you sure you want to remove these members?",
+ "Are you sure you want to remove these departments?": "Are you sure you want to remove these departments?",
+ "Please select members": "Please select members",
+ "Please select departments": "Please select departments",
+ "The department has sub-departments, please delete them first": "The department has sub-departments, please delete them first",
+ "The department has members, please remove them first": "The department has members, please remove them first",
+ "Main": "Main",
+ "Set as main department": "Set as main department",
+ "This field is currently not supported for use in form blocks.": "This field is currently not supported for use in form blocks."
+}
diff --git a/packages/plugins/@nocobase/plugin-departments/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-departments/src/locale/zh-CN.json
new file mode 100644
index 0000000000..be5393b399
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/locale/zh-CN.json
@@ -0,0 +1,35 @@
+{
+ "Department": "部门",
+ "All users": "所有用户",
+ "New department": "新建部门",
+ "New sub department": "新建子部门",
+ "Add department": "添加部门",
+ "Add departments": "添加部门",
+ "Edit department": "编辑部门",
+ "Delete department": "删除部门",
+ "Departments": "部门",
+ "Main department": "主属部门",
+ "Owner": "负责人",
+ "Department name": "部门名称",
+ "Superior department": "上级部门",
+ "Owners": "负责人",
+ "Add members": "添加成员",
+ "Search for departments, users": "搜索部门、用户",
+ "Search results": "搜索结果",
+ "Departments management": "部门管理",
+ "Roles management": "角色管理",
+ "Remove members": "移除成员",
+ "Remove member": "移除成员",
+ "Remove departments": "移除部门",
+ "Remove department": "移除部门",
+ "Are you sure you want to remove it?": "你确定要移除吗?",
+ "Are you sure you want to remove these members?": "你确定要移除这些成员吗?",
+ "Are you sure you want to remove these departments?": "你确定要移除这些部门吗?",
+ "Please select members": "请选择成员",
+ "Please select departments": "请选择部门",
+ "The department has sub-departments, please delete them first": "部门下有子部门,请先删除子部门",
+ "The department has members, please remove them first": "部门下有成员,请先移除",
+ "Main": "主属部门",
+ "Set as main department": "设置为主属部门",
+ "This field is currently not supported for use in form blocks.": "该字段目前不支持在表单区块中使用。"
+}
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/actions.test.ts b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/actions.test.ts
new file mode 100644
index 0000000000..1cc536139b
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/actions.test.ts
@@ -0,0 +1,125 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Database, Repository } from '@nocobase/database';
+import { MockServer, createMockServer } from '@nocobase/test';
+
+describe('actions', () => {
+ let app: MockServer;
+ let db: Database;
+ let repo: Repository;
+ let agent: any;
+
+ beforeAll(async () => {
+ app = await createMockServer({
+ plugins: ['field-sort', 'users', 'departments'],
+ });
+ db = app.db;
+ repo = db.getRepository('departments');
+ agent = app.agent();
+ });
+
+ afterAll(async () => {
+ await app.destroy();
+ });
+
+ afterEach(async () => {
+ await repo.destroy({ truncate: true });
+ });
+
+ it('should list users exclude department', async () => {
+ const dept = await repo.create({
+ values: {
+ title: 'Test department',
+ members: [1],
+ },
+ });
+ const res = await agent.resource('users').listExcludeDept({
+ departmentId: dept.id,
+ });
+ expect(res.status).toBe(200);
+ expect(res.body.data.length).toBe(0);
+ });
+
+ it('should list users exclude department with filter', async () => {
+ let res = await agent.resource('users').listExcludeDept({
+ departmentId: 1,
+ });
+ expect(res.status).toBe(200);
+ expect(res.body.data.length).toBe(1);
+
+ res = await agent.resource('users').listExcludeDept({
+ departmentId: 1,
+ filter: {
+ id: 1,
+ },
+ });
+ expect(res.status).toBe(200);
+ expect(res.body.data.length).toBe(1);
+
+ res = await agent.resource('users').listExcludeDept({
+ departmentId: 1,
+ filter: {
+ id: 2,
+ },
+ });
+ expect(res.status).toBe(200);
+ expect(res.body.data.length).toBe(0);
+ });
+
+ it('should set main department', async () => {
+ const depts = await repo.create({
+ values: [
+ {
+ title: 'Dept1',
+ members: [1],
+ },
+ {
+ title: 'Dept2',
+ members: [1],
+ },
+ ],
+ });
+ const deptUsers = db.getRepository('departmentsUsers');
+ await deptUsers.update({
+ filter: {
+ departmentId: depts[0].id,
+ userId: 1,
+ },
+ values: {
+ isMain: true,
+ },
+ });
+ const res = await agent.resource('users').setMainDepartment({
+ values: {
+ userId: 1,
+ departmentId: depts[1].id,
+ },
+ });
+ expect(res.status).toBe(200);
+ const records = await deptUsers.find({
+ filter: {
+ userId: 1,
+ },
+ });
+ const dept1 = records.find((record: any) => record.departmentId === depts[0].id);
+ const dept2 = records.find((record: any) => record.departmentId === depts[1].id);
+ expect(dept1.isMain).toBe(false);
+ expect(dept2.isMain).toBe(true);
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/data-sync.test.ts b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/data-sync.test.ts
new file mode 100644
index 0000000000..0471a66632
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/data-sync.test.ts
@@ -0,0 +1,278 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import PluginUserDataSyncServer, { UserDataResourceManager } from '@nocobase/plugin-user-data-sync';
+import { MockDatabase, MockServer, createMockServer } from '@nocobase/test';
+
+describe('department data sync', async () => {
+ let app: MockServer;
+ let db: MockDatabase;
+ let resourceManager: UserDataResourceManager;
+
+ beforeEach(async () => {
+ app = await createMockServer({
+ plugins: ['field-sort', 'user-data-sync', 'users', 'departments'],
+ });
+ db = app.db;
+ const plugin = app.pm.get('user-data-sync') as PluginUserDataSyncServer;
+ resourceManager = plugin.resourceManager;
+ });
+
+ afterEach(async () => {
+ await db.clean({ drop: true });
+ await app.destroy();
+ });
+
+ it('should create department', async () => {
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'department',
+ records: [
+ {
+ uid: '1',
+ title: 'test',
+ },
+ {
+ uid: '2',
+ title: 'sub-test',
+ parentUid: '1',
+ },
+ ],
+ });
+ const department = await db.getRepository('departments').findOne({
+ filter: {
+ title: 'test',
+ },
+ appends: ['children'],
+ });
+ expect(department).toBeTruthy();
+ expect(department.title).toBe('test');
+ expect(department.children).toHaveLength(1);
+ expect(department.children[0].title).toBe('sub-test');
+ });
+
+ it('should update department', async () => {
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'department',
+ records: [
+ {
+ uid: '1',
+ title: 'test',
+ },
+ ],
+ });
+ const department = await db.getRepository('departments').findOne({
+ filter: {
+ title: 'test',
+ },
+ appends: ['children'],
+ });
+ expect(department).toBeTruthy();
+ expect(department.children).toHaveLength(0);
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'department',
+ records: [
+ {
+ uid: '1',
+ title: 'test2',
+ },
+ {
+ uid: '2',
+ title: 'sub-test',
+ parentUid: '1',
+ },
+ ],
+ });
+ const department2 = await db.getRepository('departments').findOne({
+ filter: {
+ id: department.id,
+ },
+ appends: ['children'],
+ });
+ expect(department2).toBeTruthy();
+ expect(department2.title).toBe('test2');
+ expect(department2.children).toHaveLength(1);
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'department',
+ records: [
+ {
+ uid: '2',
+ title: 'sub-test',
+ },
+ ],
+ });
+ const department3 = await db.getRepository('departments').findOne({
+ filter: {
+ id: department2.children[0].id,
+ },
+ appends: ['parent'],
+ });
+ expect(department3).toBeTruthy();
+ expect(department3.parent).toBeNull();
+ });
+
+ it('should update user department', async () => {
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'department',
+ records: [
+ {
+ uid: '12',
+ title: 'test',
+ },
+ ],
+ });
+ const department = await db.getRepository('departments').findOne({
+ filter: {
+ title: 'test',
+ },
+ });
+ expect(department).toBeTruthy();
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'user',
+ records: [
+ {
+ uid: '1',
+ nickname: 'test',
+ email: 'test@nocobase.com',
+ departments: ['12'],
+ },
+ ],
+ });
+ const user = await db.getRepository('users').findOne({
+ filter: {
+ email: 'test@nocobase.com',
+ },
+ appends: ['departments'],
+ });
+ expect(user).toBeTruthy();
+ expect(user.departments).toHaveLength(1);
+ expect(user.departments[0].id).toBe(department.id);
+ const departmentUser = await db.getRepository('departmentsUsers').findOne({
+ filter: {
+ departmentId: department.id,
+ userId: user.id,
+ },
+ });
+ expect(departmentUser).toBeTruthy();
+ expect(departmentUser.isOwner).toBe(false);
+ expect(departmentUser.isMain).toBe(false);
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'user',
+ records: [
+ {
+ uid: '1',
+ nickname: 'test',
+ email: 'test@nocobase.com',
+ departments: [
+ {
+ uid: '12',
+ isOwner: true,
+ isMain: true,
+ },
+ ],
+ },
+ ],
+ });
+ const departmentUser2 = await db.getRepository('departmentsUsers').findOne({
+ filter: {
+ departmentId: department.id,
+ userId: user.id,
+ },
+ });
+ expect(departmentUser2).toBeTruthy();
+ expect(departmentUser2.isOwner).toBe(true);
+ expect(departmentUser2.isMain).toBe(true);
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'user',
+ records: [
+ {
+ uid: '2',
+ nickname: 'test2',
+ email: 'test2@nocobase.com',
+ departments: [
+ {
+ uid: '12',
+ isOwner: true,
+ isMain: false,
+ },
+ ],
+ },
+ ],
+ });
+ const user2 = await db.getRepository('users').findOne({
+ filter: {
+ email: 'test2@nocobase.com',
+ },
+ appends: ['departments'],
+ });
+ expect(user2).toBeTruthy();
+ expect(user2.departments).toHaveLength(1);
+ expect(user2.departments[0].id).toBe(department.id);
+ const departmentUser3 = await db.getRepository('departmentsUsers').findOne({
+ filter: {
+ departmentId: department.id,
+ userId: user2.id,
+ },
+ });
+ expect(departmentUser3).toBeTruthy();
+ expect(departmentUser3.isOwner).toBe(true);
+ expect(departmentUser3.isMain).toBe(false);
+ });
+
+ it('should update department custom field', async () => {
+ const departmemntCollection = db.getCollection('departments');
+ departmemntCollection.addField('customField', { type: 'string' });
+ await db.sync({
+ alter: true,
+ });
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'department',
+ records: [
+ {
+ uid: '1',
+ title: 'test',
+ customField: 'testField',
+ },
+ ],
+ });
+ const department = await db.getRepository('departments').findOne({
+ filter: {
+ title: 'test',
+ },
+ });
+ expect(department).toBeTruthy();
+ expect(department.customField).toBe('testField');
+ await resourceManager.updateOrCreate({
+ sourceName: 'test',
+ dataType: 'department',
+ records: [
+ {
+ uid: '1',
+ title: 'test',
+ customField: 'testField2',
+ },
+ ],
+ });
+ const department2 = await db.getRepository('departments').findOne({
+ filter: {
+ id: department.id,
+ },
+ });
+ expect(department2).toBeTruthy();
+ expect(department2.customField).toBe('testField2');
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/destroy-department-check.test.ts b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/destroy-department-check.test.ts
new file mode 100644
index 0000000000..80355b551e
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/destroy-department-check.test.ts
@@ -0,0 +1,72 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Database, Repository } from '@nocobase/database';
+import { MockServer, createMockServer } from '@nocobase/test';
+
+describe('destroy department check', () => {
+ let app: MockServer;
+ let db: Database;
+ let repo: Repository;
+ let agent: any;
+
+ beforeAll(async () => {
+ app = await createMockServer({
+ plugins: ['field-sort', 'users', 'departments'],
+ });
+ db = app.db;
+ repo = db.getRepository('departments');
+ agent = app.agent();
+ });
+
+ afterAll(async () => {
+ await app.destroy();
+ });
+
+ afterEach(async () => {
+ await repo.destroy({ truncate: true });
+ });
+
+ it('should check if it has sub departments', async () => {
+ const dept = await repo.create({
+ values: {
+ title: 'Department',
+ children: [{ title: 'Sub department' }],
+ },
+ });
+ const res = await agent.resource('departments').destroy({
+ filterByTk: dept.id,
+ });
+ expect(res.status).toBe(400);
+ expect(res.text).toBe('The department has sub-departments, please delete them first');
+ });
+
+ it('should check if it has members', async () => {
+ const dept = await repo.create({
+ values: {
+ title: 'Department',
+ members: [1],
+ },
+ });
+ const res = await agent.resource('departments').destroy({
+ filterByTk: dept.id,
+ });
+ expect(res.status).toBe(400);
+ expect(res.text).toBe('The department has members, please remove them first');
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/set-department-owners.test.ts b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/set-department-owners.test.ts
new file mode 100644
index 0000000000..7775b6e873
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/set-department-owners.test.ts
@@ -0,0 +1,91 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Database, Repository } from '@nocobase/database';
+import { MockServer, createMockServer } from '@nocobase/test';
+
+describe('set department owners', () => {
+ let app: MockServer;
+ let db: Database;
+ let repo: Repository;
+ let agent: any;
+
+ beforeAll(async () => {
+ app = await createMockServer({
+ plugins: ['field-sort', 'users', 'departments'],
+ });
+ db = app.db;
+ repo = db.getRepository('departments');
+ agent = app.agent();
+ });
+
+ afterAll(async () => {
+ await app.destroy();
+ });
+
+ afterEach(async () => {
+ await repo.destroy({ truncate: true });
+ });
+
+ it('should set department owners', async () => {
+ await db.getRepository('users').create({
+ values: {
+ username: 'test',
+ },
+ });
+ const dept = await repo.create({
+ values: {
+ title: 'Department',
+ members: [1, 2],
+ },
+ });
+ await agent.resource('departments').update({
+ filterByTk: dept.id,
+ values: {
+ owners: [{ id: 1 }],
+ },
+ });
+ const deptUser = await db.getRepository('departmentsUsers').findOne({
+ filter: {
+ userId: 1,
+ departmentId: dept.id,
+ },
+ });
+ expect(deptUser.isOwner).toBe(true);
+ await agent.resource('departments').update({
+ filterByTk: dept.id,
+ values: {
+ owners: [{ id: 2 }],
+ },
+ });
+ const deptUser1 = await db.getRepository('departmentsUsers').findOne({
+ filter: {
+ userId: 1,
+ departmentId: dept.id,
+ },
+ });
+ expect(deptUser1.isOwner).toBe(false);
+ const deptUser2 = await db.getRepository('departmentsUsers').findOne({
+ filter: {
+ userId: 2,
+ departmentId: dept.id,
+ },
+ });
+ expect(deptUser2.isOwner).toBe(true);
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/set-departments-info.test.ts b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/set-departments-info.test.ts
new file mode 100644
index 0000000000..b6417fbefa
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/set-departments-info.test.ts
@@ -0,0 +1,73 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Database, Repository } from '@nocobase/database';
+import { MockServer, createMockServer } from '@nocobase/test';
+import { setDepartmentsInfo } from '../middlewares';
+
+describe('set departments info', () => {
+ let app: MockServer;
+ let db: Database;
+ let repo: Repository;
+ let agent: any;
+ let ctx: any;
+
+ beforeAll(async () => {
+ app = await createMockServer({
+ plugins: ['field-sort', 'users', 'departments', 'acl', 'data-source-manager'],
+ });
+ db = app.db;
+ repo = db.getRepository('departments');
+ agent = app.agent();
+ ctx = {
+ db,
+ cache: app.cache,
+ state: {},
+ };
+ });
+
+ afterAll(async () => {
+ await app.destroy();
+ });
+
+ afterEach(async () => {
+ await repo.destroy({ truncate: true });
+ });
+
+ it('should set departments roles', async () => {
+ ctx.state.currentUser = await db.getRepository('users').findOne({
+ filterByTk: 1,
+ });
+ const role = await db.getRepository('roles').create({
+ values: {
+ name: 'test-role',
+ title: 'Test role',
+ },
+ });
+ await repo.create({
+ values: {
+ title: 'Department',
+ roles: [role.name],
+ members: [1],
+ },
+ });
+ await setDepartmentsInfo(ctx, () => {});
+ expect(ctx.state.attachRoles.length).toBe(1);
+ expect(ctx.state.attachRoles[0].name).toBe('test-role');
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/set-main-department.test.ts b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/set-main-department.test.ts
new file mode 100644
index 0000000000..46a5b9d310
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/set-main-department.test.ts
@@ -0,0 +1,183 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Database, Repository } from '@nocobase/database';
+import { MockServer, createMockServer } from '@nocobase/test';
+
+describe('set main department', () => {
+ let app: MockServer;
+ let db: Database;
+ let repo: Repository;
+ let agent: any;
+
+ beforeAll(async () => {
+ app = await createMockServer({
+ plugins: ['field-sort', 'users', 'departments'],
+ });
+ db = app.db;
+ repo = db.getRepository('departments');
+ agent = app.agent();
+ });
+
+ afterAll(async () => {
+ await app.destroy();
+ });
+
+ afterEach(async () => {
+ await repo.destroy({ truncate: true });
+ await db.getRepository('departmentsUsers').destroy({ truncate: true });
+ });
+
+ it('should set main department when add department members', async () => {
+ const dept = await repo.create({
+ values: {
+ title: 'Department',
+ },
+ });
+ await db.getRepository('users').create({
+ values: {
+ username: 'test',
+ },
+ });
+ await agent.resource('departments.members', dept.id).add({
+ values: [1, 2],
+ });
+ const throughRepo = db.getRepository('departmentsUsers');
+ const deptUsers = await throughRepo.find({
+ filter: {
+ userId: {
+ $in: [1, 2],
+ },
+ departmentId: dept.id,
+ },
+ });
+ for (const item of deptUsers) {
+ expect(item.isMain).toBe(true);
+ }
+
+ const dept2 = await repo.create({
+ values: {
+ title: 'Department2',
+ },
+ });
+ await agent.resource('departments.members', dept2.id).add({
+ values: [2],
+ });
+ const deptUsers2 = await throughRepo.find({
+ filter: {
+ userId: 2,
+ },
+ });
+ expect(deptUsers2.length).toBe(2);
+ expect(deptUsers2.find((i: any) => i.departmentId === dept.id).isMain).toBe(true);
+ expect(deptUsers2.find((i: any) => i.departmentId === dept2.id).isMain).toBe(false);
+ });
+
+ it('should set main department when remove department members', async () => {
+ const depts = await repo.create({
+ values: [
+ {
+ title: 'Department',
+ },
+ {
+ title: 'Department2',
+ },
+ ],
+ });
+ await agent.resource('departments.members', depts[0].id).add({
+ values: [1],
+ });
+ await agent.resource('departments.members', depts[1].id).add({
+ values: [1],
+ });
+ const throughRepo = db.getRepository('departmentsUsers');
+ const deptUsers = await throughRepo.find({
+ filter: {
+ userId: 1,
+ },
+ });
+ expect(deptUsers.length).toBe(2);
+ expect(deptUsers.find((i: any) => i.departmentId === depts[0].id).isMain).toBe(true);
+ expect(deptUsers.find((i: any) => i.departmentId === depts[1].id).isMain).toBe(false);
+
+ await agent.resource('departments.members', depts[0].id).remove({
+ values: [1],
+ });
+ const deptUsers2 = await throughRepo.find({
+ filter: {
+ userId: 1,
+ },
+ });
+ expect(deptUsers2.length).toBe(1);
+ expect(deptUsers2[0].departmentId).toBe(depts[1].id);
+ expect(deptUsers2[0].isMain).toBe(true);
+ });
+
+ it('should set main department when add user departments', async () => {
+ const depts = await repo.create({
+ values: [
+ {
+ title: 'Department',
+ },
+ {
+ title: 'Department2',
+ },
+ ],
+ });
+ await agent.resource('users.departments', 1).add({
+ values: depts.map((dept: any) => dept.id),
+ });
+ const throughRepo = db.getRepository('departmentsUsers');
+ const deptUsers = await throughRepo.find({
+ filter: {
+ userId: 1,
+ },
+ });
+ expect(deptUsers.length).toBe(2);
+ expect(deptUsers.find((i: any) => i.departmentId === depts[0].id).isMain).toBe(true);
+ expect(deptUsers.find((i: any) => i.departmentId === depts[1].id).isMain).toBe(false);
+ });
+
+ it('should set main department when remove user departments', async () => {
+ const depts = await repo.create({
+ values: [
+ {
+ title: 'Department',
+ },
+ {
+ title: 'Department2',
+ },
+ ],
+ });
+ await agent.resource('users.departments', 1).add({
+ values: depts.map((dept: any) => dept.id),
+ });
+ await agent.resource('users.departments', 1).remove({
+ values: [depts[0].id],
+ });
+ const throughRepo = db.getRepository('departmentsUsers');
+ const deptUsers = await throughRepo.find({
+ filter: {
+ userId: 1,
+ },
+ });
+ expect(deptUsers.length).toBe(1);
+ expect(deptUsers[0].departmentId).toBe(depts[1].id);
+ expect(deptUsers[0].isMain).toBe(true);
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/update-department-is-leaf.test.ts b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/update-department-is-leaf.test.ts
new file mode 100644
index 0000000000..3f3803c485
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/__tests__/update-department-is-leaf.test.ts
@@ -0,0 +1,102 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Database, Repository } from '@nocobase/database';
+import { MockServer, createMockServer } from '@nocobase/test';
+
+describe('update department isLeaf', () => {
+ let app: MockServer;
+ let db: Database;
+ let repo: Repository;
+ let agent: any;
+
+ beforeAll(async () => {
+ app = await createMockServer({
+ plugins: ['field-sort', 'users', 'departments'],
+ });
+ db = app.db;
+ repo = db.getRepository('departments');
+ agent = app.agent();
+ });
+
+ afterAll(async () => {
+ await app.destroy();
+ });
+
+ afterEach(async () => {
+ await repo.destroy({ truncate: true });
+ });
+
+ it('should update isLeaf when create sub department', async () => {
+ const res = await agent.resource('departments').create({
+ values: {
+ title: 'Department',
+ },
+ });
+ const dept = res.body.data;
+ expect(dept).toBeTruthy();
+ expect(dept.isLeaf).toBe(true);
+
+ await agent.resource('departments').create({
+ values: {
+ title: 'Sub Department',
+ parent: dept,
+ },
+ });
+ const record = await repo.findOne({
+ filterByTk: dept.id,
+ });
+ expect(record.isLeaf).toBe(false);
+ });
+
+ it('should update isLeaf when update department', async () => {
+ const res = await agent.resource('departments').create({
+ values: {
+ title: 'Department',
+ },
+ });
+ const res2 = await agent.resource('departments').create({
+ values: {
+ title: 'Department2',
+ },
+ });
+ const dept1 = res.body.data;
+ const dept2 = res2.body.data;
+ const res3 = await agent.resource('departments').create({
+ values: {
+ title: 'Sub Department',
+ parent: dept1,
+ },
+ });
+ const subDept = res3.body.data;
+ await agent.resource('departments').update({
+ filterByTk: subDept.id,
+ values: {
+ parent: dept2,
+ },
+ });
+ const record1 = await repo.findOne({
+ filterByTk: dept1.id,
+ });
+ expect(record1.isLeaf).toBe(true);
+ const record2 = await repo.findOne({
+ filterByTk: dept2.id,
+ });
+ expect(record2.isLeaf).toBe(false);
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/actions/departments.ts b/packages/plugins/@nocobase/plugin-departments/src/server/actions/departments.ts
new file mode 100644
index 0000000000..280fd8ab18
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/actions/departments.ts
@@ -0,0 +1,97 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Context, Next } from '@nocobase/actions';
+import { DepartmentModel } from '../models/department';
+
+export const getAppendsOwners = async (ctx: Context, next: Next) => {
+ const { filterByTk, appends } = ctx.action.params;
+ const repo = ctx.db.getRepository('departments');
+ const department: DepartmentModel = await repo.findOne({
+ filterByTk,
+ appends,
+ });
+ const owners = await department.getOwners();
+ department.setDataValue('owners', owners);
+ ctx.body = department;
+ await next();
+};
+
+export const aggregateSearch = async (ctx: Context, next: Next) => {
+ const { keyword, type, last = 0, limit = 10 } = ctx.action.params.values || {};
+ let users = [];
+ let departments = [];
+ if (!type || type === 'user') {
+ const repo = ctx.db.getRepository('users');
+ users = await repo.find({
+ filter: {
+ id: { $gt: last },
+ $or: [
+ { username: { $includes: keyword } },
+ { nickname: { $includes: keyword } },
+ { phone: { $includes: keyword } },
+ { email: { $includes: keyword } },
+ ],
+ },
+ limit,
+ });
+ }
+ if (!type || type === 'department') {
+ const repo = ctx.db.getRepository('departments');
+ departments = await repo.find({
+ filter: {
+ id: { $gt: last },
+ title: { $includes: keyword },
+ },
+ appends: ['parent(recursively=true)'],
+ limit,
+ });
+ }
+ ctx.body = { users, departments };
+ await next();
+};
+
+export const setOwner = async (ctx: Context, next: Next) => {
+ const { userId, departmentId } = ctx.action.params.values || {};
+ const throughRepo = ctx.db.getRepository('departmentsUsers');
+ await throughRepo.update({
+ filter: {
+ userId,
+ departmentId,
+ },
+ values: {
+ isOwner: true,
+ },
+ });
+ await next();
+};
+
+export const removeOwner = async (ctx: Context, next: Next) => {
+ const { userId, departmentId } = ctx.action.params.values || {};
+ const throughRepo = ctx.db.getRepository('departmentsUsers');
+ await throughRepo.update({
+ filter: {
+ userId,
+ departmentId,
+ },
+ values: {
+ isOwner: false,
+ },
+ });
+ await next();
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/actions/users.ts b/packages/plugins/@nocobase/plugin-departments/src/server/actions/users.ts
new file mode 100644
index 0000000000..38681fa574
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/actions/users.ts
@@ -0,0 +1,133 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Context, DEFAULT_PAGE, DEFAULT_PER_PAGE, Next } from '@nocobase/actions';
+
+export const listExcludeDept = async (ctx: Context, next: Next) => {
+ const { departmentId, page = DEFAULT_PAGE, pageSize = DEFAULT_PER_PAGE } = ctx.action.params;
+ const repo = ctx.db.getRepository('users');
+ const members = await repo.find({
+ fields: ['id'],
+ filter: {
+ 'departments.id': departmentId,
+ },
+ });
+ const memberIds = members.map((member: { id: number }) => member.id);
+ if (memberIds.length) {
+ ctx.action.mergeParams({
+ filter: {
+ id: {
+ $notIn: memberIds,
+ },
+ },
+ });
+ }
+ const { filter } = ctx.action.params;
+ const [rows, count] = await repo.findAndCount({
+ context: ctx,
+ offset: (page - 1) * pageSize,
+ limit: +pageSize,
+ filter,
+ });
+ ctx.body = {
+ count,
+ rows,
+ page: Number(page),
+ pageSize: Number(pageSize),
+ totalPage: Math.ceil(count / pageSize),
+ };
+ await next();
+};
+
+export const setDepartments = async (ctx: Context, next: Next) => {
+ const { values = {} } = ctx.action.params;
+ const { userId, departments = [] } = values;
+ const repo = ctx.db.getRepository('users');
+ const throughRepo = ctx.db.getRepository('departmentsUsers');
+ const user = await repo.findOne({ filterByTk: userId });
+ if (!user) {
+ ctx.throw(400, ctx.t('User does not exist'));
+ }
+ const departmentIds = departments.map((department: any) => department.id);
+ const main = departments.find((department: any) => department.isMain);
+ const owners = departments.filter((department: any) => department.isOwner);
+ await ctx.db.sequelize.transaction(async (t) => {
+ await user.setDepartments(departmentIds, {
+ through: {
+ isMain: false,
+ isOwner: false,
+ },
+ transaction: t,
+ });
+ if (main) {
+ await throughRepo.update({
+ filter: {
+ userId,
+ departmentId: main.id,
+ },
+ values: {
+ isMain: true,
+ },
+ transaction: t,
+ });
+ }
+ if (owners.length) {
+ await throughRepo.update({
+ filter: {
+ userId,
+ departmentId: {
+ $in: owners.map((owner: any) => owner.id),
+ },
+ },
+ values: {
+ isOwner: true,
+ },
+ transaction: t,
+ });
+ }
+ });
+ await next();
+};
+
+export const setMainDepartment = async (ctx: Context, next: Next) => {
+ const { userId, departmentId } = ctx.action.params.values || {};
+ const throughRepo = ctx.db.getRepository('departmentsUsers');
+ await ctx.db.sequelize.transaction(async (t) => {
+ await throughRepo.update({
+ filter: {
+ userId,
+ isMain: true,
+ },
+ values: {
+ isMain: false,
+ },
+ transaction: t,
+ });
+ await throughRepo.update({
+ filter: {
+ userId,
+ departmentId,
+ },
+ values: {
+ isMain: true,
+ },
+ transaction: t,
+ });
+ });
+ await next();
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/collections/.gitkeep b/packages/plugins/@nocobase/plugin-departments/src/server/collections/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/collections/departmentRoles.ts b/packages/plugins/@nocobase/plugin-departments/src/server/collections/departmentRoles.ts
new file mode 100644
index 0000000000..b0f44497c2
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/collections/departmentRoles.ts
@@ -0,0 +1,25 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { defineCollection } from '@nocobase/database';
+
+export default defineCollection({
+ name: 'departmentsRoles',
+ dumpRules: 'required',
+ migrationRules: ['overwrite'],
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/collections/departments.ts b/packages/plugins/@nocobase/plugin-departments/src/server/collections/departments.ts
new file mode 100644
index 0000000000..a5881b35c9
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/collections/departments.ts
@@ -0,0 +1,156 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { defineCollection } from '@nocobase/database';
+
+export const ownersField = {
+ interface: 'm2m',
+ type: 'belongsToMany',
+ name: 'owners',
+ collectionName: 'departments',
+ target: 'users',
+ through: 'departmentsUsers',
+ foreignKey: 'departmentId',
+ otherKey: 'userId',
+ targetKey: 'id',
+ sourceKey: 'id',
+ throughScope: {
+ isOwner: true,
+ },
+ uiSchema: {
+ type: 'm2m',
+ title: '{{t("Owners")}}',
+ 'x-component': 'DepartmentOwnersField',
+ 'x-component-props': {
+ multiple: true,
+ fieldNames: {
+ label: 'nickname',
+ value: 'id',
+ },
+ },
+ },
+};
+
+export default defineCollection({
+ name: 'departments',
+ migrationRules: ['overwrite'],
+ title: '{{t("Departments")}}',
+ dumpRules: 'required',
+ tree: 'adjacency-list',
+ template: 'tree',
+ shared: true,
+ sortable: true,
+ model: 'DepartmentModel',
+ createdBy: true,
+ updatedBy: true,
+ logging: true,
+ fields: [
+ {
+ type: 'bigInt',
+ name: 'id',
+ primaryKey: true,
+ autoIncrement: true,
+ interface: 'id',
+ uiSchema: {
+ type: 'number',
+ title: '{{t("ID")}}',
+ 'x-component': 'InputNumber',
+ 'x-read-pretty': true,
+ },
+ },
+ {
+ type: 'string',
+ name: 'title',
+ interface: 'input',
+ uiSchema: {
+ type: 'string',
+ title: '{{t("Department name")}}',
+ 'x-component': 'Input',
+ },
+ },
+ {
+ type: 'boolean',
+ name: 'isLeaf',
+ },
+ {
+ type: 'belongsTo',
+ name: 'parent',
+ target: 'departments',
+ foreignKey: 'parentId',
+ treeParent: true,
+ onDelete: 'CASCADE',
+ interface: 'm2o',
+ uiSchema: {
+ type: 'm2o',
+ title: '{{t("Superior department")}}',
+ 'x-component': 'AssociationField',
+ 'x-component-props': {
+ multiple: false,
+ fieldNames: {
+ label: 'title',
+ value: 'id',
+ },
+ },
+ },
+ },
+ {
+ type: 'hasMany',
+ name: 'children',
+ target: 'departments',
+ foreignKey: 'parentId',
+ treeChildren: true,
+ onDelete: 'CASCADE',
+ },
+ {
+ type: 'belongsToMany',
+ name: 'members',
+ target: 'users',
+ through: 'departmentsUsers',
+ foreignKey: 'departmentId',
+ otherKey: 'userId',
+ targetKey: 'id',
+ sourceKey: 'id',
+ onDelete: 'CASCADE',
+ },
+ {
+ interface: 'm2m',
+ type: 'belongsToMany',
+ name: 'roles',
+ target: 'roles',
+ through: 'departmentsRoles',
+ foreignKey: 'departmentId',
+ otherKey: 'roleName',
+ targetKey: 'name',
+ sourceKey: 'id',
+ onDelete: 'CASCADE',
+ uiSchema: {
+ type: 'm2m',
+ title: '{{t("Roles")}}',
+ 'x-component': 'AssociationField',
+ 'x-component-props': {
+ multiple: true,
+ fieldNames: {
+ label: 'title',
+ value: 'name',
+ },
+ },
+ },
+ },
+ ownersField,
+ ],
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/collections/departmentsUsers.ts b/packages/plugins/@nocobase/plugin-departments/src/server/collections/departmentsUsers.ts
new file mode 100644
index 0000000000..70d5534d2c
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/collections/departmentsUsers.ts
@@ -0,0 +1,39 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { defineCollection } from '@nocobase/database';
+
+export default defineCollection({
+ name: 'departmentsUsers',
+ dumpRules: 'required',
+ migrationRules: ['schema-only'],
+ fields: [
+ {
+ type: 'boolean',
+ name: 'isOwner', // Weather the user is the owner of the department
+ allowNull: false,
+ defaultValue: false,
+ },
+ {
+ type: 'boolean',
+ name: 'isMain', // Weather this is the main department of the user
+ allowNull: false,
+ defaultValue: false,
+ },
+ ],
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/collections/roles.ts b/packages/plugins/@nocobase/plugin-departments/src/server/collections/roles.ts
new file mode 100644
index 0000000000..bac06fa60b
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/collections/roles.ts
@@ -0,0 +1,36 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { extendCollection } from '@nocobase/database';
+
+export default extendCollection({
+ name: 'roles',
+ fields: [
+ {
+ type: 'belongsToMany',
+ name: 'departments',
+ target: 'departments',
+ foreignKey: 'roleName',
+ otherKey: 'departmentId',
+ onDelete: 'CASCADE',
+ sourceKey: 'name',
+ targetKey: 'id',
+ through: 'departmentsRoles',
+ },
+ ],
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/collections/users.ts b/packages/plugins/@nocobase/plugin-departments/src/server/collections/users.ts
new file mode 100644
index 0000000000..c5bcf488cc
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/collections/users.ts
@@ -0,0 +1,79 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { extendCollection } from '@nocobase/database';
+
+export const departmentsField = {
+ collectionName: 'users',
+ interface: 'm2m',
+ type: 'belongsToMany',
+ name: 'departments',
+ target: 'departments',
+ foreignKey: 'userId',
+ otherKey: 'departmentId',
+ onDelete: 'CASCADE',
+ sourceKey: 'id',
+ targetKey: 'id',
+ through: 'departmentsUsers',
+ uiSchema: {
+ type: 'm2m',
+ title: '{{t("Departments")}}',
+ 'x-component': 'UserDepartmentsField',
+ 'x-component-props': {
+ multiple: true,
+ fieldNames: {
+ label: 'title',
+ value: 'name',
+ },
+ },
+ },
+};
+
+export const mainDepartmentField = {
+ collectionName: 'users',
+ interface: 'm2m',
+ type: 'belongsToMany',
+ name: 'mainDepartment',
+ target: 'departments',
+ foreignKey: 'userId',
+ otherKey: 'departmentId',
+ onDelete: 'CASCADE',
+ sourceKey: 'id',
+ targetKey: 'id',
+ through: 'departmentsUsers',
+ throughScope: {
+ isMain: true,
+ },
+ uiSchema: {
+ type: 'm2m',
+ title: '{{t("Main department")}}',
+ 'x-component': 'UserMainDepartmentField',
+ 'x-component-props': {
+ multiple: false,
+ fieldNames: {
+ label: 'title',
+ value: 'name',
+ },
+ },
+ },
+};
+
+export default extendCollection({
+ name: 'users',
+ fields: [departmentsField, mainDepartmentField],
+});
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/department-data-sync-resource.ts b/packages/plugins/@nocobase/plugin-departments/src/server/department-data-sync-resource.ts
new file mode 100644
index 0000000000..69d0d414df
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/department-data-sync-resource.ts
@@ -0,0 +1,333 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { Model } from '@nocobase/database';
+import lodash from 'lodash';
+import {
+ FormatDepartment,
+ FormatUserDepartment,
+ OriginRecord,
+ PrimaryKey,
+ RecordResourceChanged,
+ SyncAccept,
+ UserDataResource,
+} from '@nocobase/plugin-user-data-sync';
+
+export class DepartmentDataSyncResource extends UserDataResource {
+ name = 'departments';
+ accepts: SyncAccept[] = ['user', 'department'];
+
+ get userRepo() {
+ return this.db.getRepository('users');
+ }
+
+ get deptRepo() {
+ return this.db.getRepository('departments');
+ }
+
+ get deptUserRepo() {
+ return this.db.getRepository('departmentsUsers');
+ }
+
+ getFlteredSourceDepartment(sourceDepartment: FormatDepartment) {
+ const deleteProps = [
+ 'id',
+ 'uid',
+ 'createdAt',
+ 'updatedAt',
+ 'sort',
+ 'createdById',
+ 'updatedById',
+ 'isDeleted',
+ 'parentId',
+ 'parentUid',
+ ];
+ return lodash.omit(sourceDepartment, deleteProps);
+ }
+
+ async update(record: OriginRecord, resourcePks: PrimaryKey[]): Promise {
+ const { dataType, metaData, sourceName } = record;
+ if (dataType === 'user') {
+ const sourceUser = metaData;
+ if (sourceUser.isDeleted) {
+ if (!resourcePks || !resourcePks.length) {
+ return [];
+ } else {
+ return resourcePks.map((id) => ({ resourcesPk: id, isDeleted: true }));
+ }
+ }
+ const resources = record.resources.filter((r) => r.resource === 'users');
+ if (!resources.length) {
+ return [];
+ }
+ const user = await this.userRepo.findOne({
+ filterByTk: resources[0].resourcePk,
+ });
+ if (!user) {
+ if (!resourcePks || !resourcePks.length) {
+ return [];
+ } else {
+ return resourcePks.map((id) => ({ resourcesPk: id, isDeleted: true }));
+ }
+ } else {
+ return await this.updateUserDepartments(user, resourcePks, sourceUser.departments, sourceName);
+ }
+ } else if (dataType === 'department') {
+ const sourceDepartment = metaData;
+ const department = await this.deptRepo.findOne({
+ filterByTk: resourcePks[0],
+ });
+ if (!department) {
+ if (sourceDepartment.isDeleted) {
+ return [{ resourcesPk: resourcePks[0], isDeleted: true }];
+ }
+ const result = await this.create(record);
+ return [...result, { resourcesPk: resourcePks[0], isDeleted: true }];
+ }
+ await this.updateDepartment(department, sourceDepartment, sourceName);
+ } else {
+ this.logger.warn(`update department: unsupported data type: ${dataType}`);
+ }
+ return [];
+ }
+
+ async create(record: OriginRecord): Promise {
+ const { dataType, metaData, sourceName } = record;
+ if (dataType === 'user') {
+ const sourceUser = metaData;
+ if (sourceUser.isDeleted) {
+ return [];
+ }
+ const resources = record.resources.filter((r) => r.resource === 'users');
+ if (!resources.length) {
+ return [];
+ }
+ const user = await this.userRepo.findOne({
+ filterByTk: resources[0].resourcePk,
+ });
+ return await this.updateUserDepartments(user, [], sourceUser.departments, sourceName);
+ } else if (dataType === 'department') {
+ const sourceDepartment = metaData;
+ const newDepartmentId = await this.createDepartment(sourceDepartment, sourceName);
+ return [{ resourcesPk: newDepartmentId, isDeleted: false }];
+ } else {
+ this.logger.warn(`create department: unsupported data type: ${dataType}`);
+ }
+ return [];
+ }
+
+ async getDepartmentIdsBySourceUks(sourceUks: PrimaryKey[], sourceName: string) {
+ const syncDepartmentRecords = await this.syncRecordRepo.find({
+ filter: {
+ sourceName,
+ dataType: 'department',
+ sourceUk: { $in: sourceUks },
+ 'resources.resource': this.name,
+ },
+ appends: ['resources'],
+ });
+ const departmentIds = syncDepartmentRecords
+ .filter((record) => record.resources?.length)
+ .map((record) => record.resources[0].resourcePk);
+ return departmentIds;
+ }
+
+ async getDepartmentIdBySourceUk(sourceUk: PrimaryKey, sourceName: string) {
+ const syncDepartmentRecord = await this.syncRecordRepo.findOne({
+ filter: {
+ sourceName,
+ dataType: 'department',
+ sourceUk,
+ 'resources.resource': this.name,
+ },
+ appends: ['resources'],
+ });
+ if (syncDepartmentRecord && syncDepartmentRecord.resources?.length) {
+ return syncDepartmentRecord.resources[0].resourcePk;
+ }
+ }
+
+ async updateUserDepartments(
+ user: any,
+ currentDepartmentIds: PrimaryKey[],
+ sourceDepartments: (PrimaryKey | FormatUserDepartment)[],
+ sourceName: string,
+ ): Promise {
+ if (!this.deptRepo) {
+ return [];
+ }
+ if (!sourceDepartments || !sourceDepartments.length) {
+ const userDepartments = await user.getDepartments();
+ if (userDepartments.length) {
+ await user.removeDepartments(userDepartments);
+ }
+ if (currentDepartmentIds && currentDepartmentIds.length) {
+ return currentDepartmentIds.map((id) => ({ resourcesPk: id, isDeleted: true }));
+ } else {
+ return [];
+ }
+ } else {
+ const sourceDepartmentIds = sourceDepartments.map((sourceDepartment) => {
+ if (typeof sourceDepartment === 'string' || typeof sourceDepartment === 'number') {
+ return sourceDepartment;
+ }
+ return sourceDepartment.uid;
+ });
+ const newDepartmentIds = await this.getDepartmentIdsBySourceUks(sourceDepartmentIds, sourceName);
+ const newDepartments = await this.deptRepo.find({
+ filter: { id: { $in: newDepartmentIds } },
+ });
+ const realCurrentDepartments = await user.getDepartments();
+ // 需要删除的部门
+ const toRealRemoveDepartments = realCurrentDepartments.filter((currnetDepartment) => {
+ return !newDepartments.find((newDepartment) => newDepartment.id === currnetDepartment.id);
+ });
+ if (toRealRemoveDepartments.length) {
+ await user.removeDepartments(toRealRemoveDepartments);
+ }
+ // 需要添加的部门
+ const toRealAddDepartments = newDepartments.filter((newDepartment) => {
+ if (realCurrentDepartments.length === 0) {
+ return true;
+ }
+ return !realCurrentDepartments.find((currentDepartment) => currentDepartment.id === newDepartment.id);
+ });
+ if (toRealAddDepartments.length) {
+ await user.addDepartments(toRealAddDepartments);
+ }
+ // 更新部门主管和主部门
+ for (const sourceDepartment of sourceDepartments) {
+ this.logger.debug('update dept owner: ' + JSON.stringify(sourceDepartment));
+ let isOwner = false;
+ let isMain = false;
+ let uid;
+ if (typeof sourceDepartment !== 'string' && typeof sourceDepartment !== 'number') {
+ isOwner = sourceDepartment.isOwner || false;
+ isMain = sourceDepartment.isMain || false;
+ uid = sourceDepartment.uid;
+ } else {
+ uid = sourceDepartment;
+ }
+ const deptId = await this.getDepartmentIdBySourceUk(uid, sourceName);
+ this.logger.debug('update dept owner: ' + JSON.stringify({ deptId, isOwner, isMain, userId: user.id }));
+ if (!deptId) {
+ continue;
+ }
+ await this.deptUserRepo.update({
+ filter: {
+ userId: user.id,
+ departmentId: deptId,
+ },
+ values: {
+ isOwner,
+ isMain,
+ },
+ });
+ }
+ const recordResourceChangeds: RecordResourceChanged[] = [];
+ if (currentDepartmentIds !== undefined && currentDepartmentIds.length > 0) {
+ // 需要删除的部门ID
+ const toRemoveDepartmentIds = currentDepartmentIds.filter(
+ (currentDepartmentId) => !newDepartmentIds.includes(currentDepartmentId),
+ );
+ recordResourceChangeds.push(
+ ...toRemoveDepartmentIds.map((departmentId) => {
+ return { resourcesPk: departmentId, isDeleted: true };
+ }),
+ );
+ // 需要添加的部门ID
+ const toAddDepartmentIds = newDepartmentIds.filter(
+ (newDepartmentId) => !currentDepartmentIds.includes(newDepartmentId),
+ );
+ recordResourceChangeds.push(
+ ...toAddDepartmentIds.map((departmentId) => {
+ return { resourcesPk: departmentId, isDeleted: false };
+ }),
+ );
+ } else {
+ recordResourceChangeds.push(
+ ...toRealAddDepartments.map((department) => {
+ return {
+ resourcesPk: department.id,
+ isDeleted: false,
+ };
+ }),
+ );
+ }
+ return recordResourceChangeds;
+ }
+ }
+
+ async updateDepartment(department: Model, sourceDepartment: FormatDepartment, sourceName: string) {
+ if (sourceDepartment.isDeleted) {
+ // 删除部门
+ await department.destroy();
+ return;
+ }
+ let dataChanged = false;
+ const filteredSourceDepartment = this.getFlteredSourceDepartment(sourceDepartment);
+ lodash.forOwn(filteredSourceDepartment, (value, key) => {
+ if (department[key] !== value) {
+ department[key] = value;
+ dataChanged = true;
+ }
+ });
+ if (dataChanged) {
+ await department.save();
+ }
+ await this.updateParentDepartment(department, sourceDepartment.parentUid, sourceName);
+ }
+
+ async createDepartment(sourceDepartment: FormatDepartment, sourceName: string): Promise {
+ const filteredSourceDepartment = this.getFlteredSourceDepartment(sourceDepartment);
+ const department = await this.deptRepo.create({
+ values: filteredSourceDepartment,
+ });
+ await this.updateParentDepartment(department, sourceDepartment.parentUid, sourceName);
+ return department.id;
+ }
+
+ async updateParentDepartment(department: Model, parentUid: string, sourceName: string) {
+ if (!parentUid) {
+ const parentDepartment = await department.getParent();
+ if (parentDepartment) {
+ await department.setParent(null);
+ }
+ } else {
+ const syncDepartmentRecord = await this.syncRecordRepo.findOne({
+ filter: {
+ sourceName,
+ dataType: 'department',
+ sourceUk: parentUid,
+ 'resources.resource': this.name,
+ },
+ appends: ['resources'],
+ });
+ if (syncDepartmentRecord && syncDepartmentRecord.resources?.length) {
+ const parentDepartment = await this.deptRepo.findOne({
+ filterByTk: syncDepartmentRecord.resources[0].resourcePk,
+ });
+ if (!parentDepartment) {
+ await department.setParent(null);
+ return;
+ }
+ const parent = await department.getParent();
+ if (parent) {
+ if (parentDepartment.id !== parent.id) {
+ await department.setParent(parentDepartment);
+ }
+ } else {
+ await department.setParent(parentDepartment);
+ }
+ } else {
+ await department.setParent(null);
+ }
+ }
+ }
+}
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/index.ts b/packages/plugins/@nocobase/plugin-departments/src/server/index.ts
new file mode 100644
index 0000000000..61787e8e89
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/index.ts
@@ -0,0 +1,19 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+export { default } from './plugin';
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/destroy-department-check.ts b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/destroy-department-check.ts
new file mode 100644
index 0000000000..ade251e3d9
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/destroy-department-check.ts
@@ -0,0 +1,48 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Context, Next } from '@nocobase/actions';
+
+const destroyCheck = async (ctx: Context) => {
+ const { filterByTk } = ctx.action.params;
+ const repo = ctx.db.getRepository('departments');
+ const children = await repo.count({
+ filter: {
+ parentId: filterByTk,
+ },
+ });
+ if (children) {
+ ctx.throw(400, ctx.t('The department has sub-departments, please delete them first', { ns: 'departments' }));
+ }
+ const members = await ctx.db.getRepository('departmentsUsers').count({
+ filter: {
+ departmentId: filterByTk,
+ },
+ });
+ if (members) {
+ ctx.throw(400, ctx.t('The department has members, please remove them first', { ns: 'departments' }));
+ }
+};
+
+export const destroyDepartmentCheck = async (ctx: Context, next: Next) => {
+ const { resourceName, actionName } = ctx.action.params;
+ if (resourceName === 'departments' && actionName === 'destroy') {
+ await destroyCheck(ctx as any);
+ }
+ await next();
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/index.ts b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/index.ts
new file mode 100644
index 0000000000..8d2e037c65
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/index.ts
@@ -0,0 +1,24 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+export * from './destroy-department-check';
+export * from './reset-user-departments-cache';
+export * from './set-department-owners';
+export * from './update-department-isleaf';
+export * from './set-departments-roles';
+export * from './set-main-department';
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/reset-user-departments-cache.ts b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/reset-user-departments-cache.ts
new file mode 100644
index 0000000000..0606093be1
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/reset-user-departments-cache.ts
@@ -0,0 +1,41 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Context, Next } from '@nocobase/actions';
+import { Cache } from '@nocobase/cache';
+
+export const resetUserDepartmentsCache = async (ctx: Context, next: Next) => {
+ await next();
+ const { associatedName, resourceName, associatedIndex, actionName, values } = ctx.action.params;
+ const cache = ctx.app.cache as Cache;
+ if (
+ associatedName === 'departments' &&
+ resourceName === 'members' &&
+ ['add', 'remove', 'set'].includes(actionName) &&
+ values?.length
+ ) {
+ // Delete cache when the members of a department changed
+ for (const memberId of values) {
+ await cache.del(`departments:${memberId}`);
+ }
+ }
+
+ if (associatedName === 'users' && resourceName === 'departments' && ['add', 'remove', 'set'].includes(actionName)) {
+ await cache.del(`departments:${associatedIndex}`);
+ }
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/set-department-owners.ts b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/set-department-owners.ts
new file mode 100644
index 0000000000..56a0da774f
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/set-department-owners.ts
@@ -0,0 +1,59 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Context, Next } from '@nocobase/actions';
+import lodash from 'lodash';
+
+const setOwners = async (ctx: Context, filterByTk: any, owners: any[]) => {
+ const throughRepo = ctx.db.getRepository('departmentsUsers');
+ await ctx.db.sequelize.transaction(async (t) => {
+ await throughRepo.update({
+ filter: {
+ departmentId: filterByTk,
+ },
+ values: {
+ isOwner: false,
+ },
+ transaction: t,
+ });
+ await throughRepo.update({
+ filter: {
+ departmentId: filterByTk,
+ userId: {
+ $in: owners.map((owner: any) => owner.id),
+ },
+ },
+ values: {
+ isOwner: true,
+ },
+ transaction: t,
+ });
+ });
+};
+
+export const setDepartmentOwners = async (ctx: Context, next: Next) => {
+ const { filterByTk, values = {}, resourceName, actionName } = ctx.action.params;
+ const { owners } = values;
+ if (resourceName === 'departments' && actionName === 'update' && owners) {
+ ctx.action.params.values = lodash.omit(values, ['owners']);
+ await next();
+ await setOwners(ctx as any, filterByTk, owners);
+ } else {
+ return next();
+ }
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/set-departments-roles.ts b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/set-departments-roles.ts
new file mode 100644
index 0000000000..5171d69ae0
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/set-departments-roles.ts
@@ -0,0 +1,60 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Context, Next } from '@nocobase/actions';
+import { Cache } from '@nocobase/cache';
+import { Model, Repository } from '@nocobase/database';
+
+export const setDepartmentsInfo = async (ctx: Context, next: Next) => {
+ const currentUser = ctx.state.currentUser;
+ if (!currentUser) {
+ return next();
+ }
+
+ const cache = ctx.cache as Cache;
+ const repo = ctx.db.getRepository('users.departments', currentUser.id) as unknown as Repository;
+ const departments = (await cache.wrap(`departments:${currentUser.id}`, () =>
+ repo.find({
+ appends: ['owners', 'roles', 'parent(recursively=true)'],
+ raw: true,
+ }),
+ )) as Model[];
+ if (!departments.length) {
+ return next();
+ }
+ ctx.state.currentUser.departments = departments;
+ ctx.state.currentUser.mainDeparmtent = departments.find((dept) => dept.isMain);
+
+ const departmentIds = departments.map((dept) => dept.id);
+ const roleRepo = ctx.db.getRepository('roles');
+ const roles = await roleRepo.find({
+ filter: {
+ 'departments.id': {
+ $in: departmentIds,
+ },
+ },
+ });
+ if (!roles.length) {
+ return next();
+ }
+ const rolesMap = new Map();
+ roles.forEach((role: any) => rolesMap.set(role.name, role));
+ ctx.state.attachRoles = Array.from(rolesMap.values());
+
+ await next();
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/set-main-department.ts b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/set-main-department.ts
new file mode 100644
index 0000000000..b5f7083fa3
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/set-main-department.ts
@@ -0,0 +1,101 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Context, Next } from '@nocobase/actions';
+
+export const setMainDepartment = async (ctx: Context, next: Next) => {
+ await next();
+ const { associatedName, resourceName, associatedIndex, actionName, values } = ctx.action.params;
+ if (associatedName === 'departments' && resourceName === 'members' && values?.length) {
+ const throughRepo = ctx.db.getRepository('departmentsUsers');
+ const usersHasMain = await throughRepo.find({
+ filter: {
+ userId: {
+ $in: values,
+ },
+ isMain: true,
+ },
+ });
+ const userIdsHasMain = usersHasMain.map((item) => item.userId);
+ if (actionName === 'add' || actionName === 'set') {
+ await throughRepo.update({
+ filter: {
+ userId: {
+ $in: values.filter((id) => !userIdsHasMain.includes(id)),
+ },
+ departmentId: associatedIndex,
+ },
+ values: {
+ isMain: true,
+ },
+ });
+ return;
+ }
+
+ if (actionName === 'remove') {
+ const userIdsHasNoMain = values.filter((id) => !userIdsHasMain.includes(id));
+ for (const userId of userIdsHasNoMain) {
+ const firstDept = await throughRepo.findOne({
+ filter: {
+ userId,
+ },
+ });
+ if (firstDept) {
+ await throughRepo.update({
+ filter: {
+ userId,
+ departmentId: firstDept.departmentId,
+ },
+ values: {
+ isMain: true,
+ },
+ });
+ }
+ }
+ }
+ }
+
+ if (associatedName === 'users' && resourceName === 'departments' && ['add', 'remove', 'set'].includes(actionName)) {
+ const throughRepo = ctx.db.getRepository('departmentsUsers');
+ const hasMain = await throughRepo.findOne({
+ filter: {
+ userId: associatedIndex,
+ isMain: true,
+ },
+ });
+ if (hasMain) {
+ return;
+ }
+ const firstDept = await throughRepo.findOne({
+ filter: {
+ userId: associatedIndex,
+ },
+ });
+ if (firstDept) {
+ await throughRepo.update({
+ filter: {
+ userId: associatedIndex,
+ departmentId: firstDept.departmentId,
+ },
+ values: {
+ isMain: true,
+ },
+ });
+ }
+ }
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/update-department-isleaf.ts b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/update-department-isleaf.ts
new file mode 100644
index 0000000000..9d0cbdf947
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/middlewares/update-department-isleaf.ts
@@ -0,0 +1,88 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Context, Next } from '@nocobase/actions';
+import { Repository } from '@nocobase/database';
+
+const updateIsLeafWhenAddChild = async (repo: Repository, parent: any) => {
+ if (parent && parent.isLeaf !== false) {
+ await repo.update({
+ filter: {
+ id: parent.id,
+ },
+ values: {
+ isLeaf: false,
+ },
+ });
+ }
+};
+
+const updateIsLeafWhenChangeChild = async (
+ repo: Repository,
+ oldParentId: number | null,
+ newParentId: number | null,
+) => {
+ if (oldParentId && oldParentId !== newParentId) {
+ const hasChild = await repo.count({
+ filter: {
+ parentId: oldParentId,
+ },
+ });
+ if (!hasChild) {
+ await repo.update({
+ filter: {
+ id: oldParentId,
+ },
+ values: {
+ isLeaf: true,
+ },
+ });
+ }
+ }
+};
+
+export const updateDepartmentIsLeaf = async (ctx: Context, next: Next) => {
+ const { filterByTk, values = {}, resourceName, actionName } = ctx.action.params;
+ const repo = ctx.db.getRepository('departments');
+ const { parent } = values;
+ if (resourceName === 'departments' && actionName === 'create') {
+ ctx.action.params.values = { ...values, isLeaf: true };
+ await next();
+ await updateIsLeafWhenAddChild(repo, parent);
+ return;
+ }
+
+ if (resourceName === 'departments' && actionName === 'update') {
+ const department = await repo.findOne({ filterByTk });
+ await next();
+ await Promise.all([
+ updateIsLeafWhenChangeChild(repo, department.parentId, parent?.id),
+ updateIsLeafWhenAddChild(repo, parent),
+ ]);
+ return;
+ }
+
+ if (resourceName === 'departments' && actionName === 'destroy') {
+ const department = await repo.findOne({ filterByTk });
+ await next();
+ await updateIsLeafWhenChangeChild(repo, department.parentId, null);
+ return;
+ }
+
+ return next();
+};
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/migrations/update-field-uischemas-20240307124823.ts b/packages/plugins/@nocobase/plugin-departments/src/server/migrations/update-field-uischemas-20240307124823.ts
new file mode 100644
index 0000000000..542d5865bf
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/migrations/update-field-uischemas-20240307124823.ts
@@ -0,0 +1,96 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Migration } from '@nocobase/server';
+import { departmentsField, mainDepartmentField } from '../collections/users';
+import { ownersField } from '../collections/departments';
+
+export default class UpdateFieldUISchemasMigration extends Migration {
+ async up() {
+ const result = await this.app.version.satisfies('<=0.20.0-alpha.6');
+
+ if (!result) {
+ return;
+ }
+
+ const fieldRepo = this.db.getRepository('fields');
+ const departmentsFieldInstance = await fieldRepo.findOne({
+ filter: {
+ name: 'departments',
+ collectionName: 'users',
+ },
+ });
+ if (departmentsFieldInstance) {
+ const options = {
+ ...departmentsFieldInstance.options,
+ uiSchema: departmentsField.uiSchema,
+ };
+ await fieldRepo.update({
+ filter: {
+ name: 'departments',
+ collectionName: 'users',
+ },
+ values: {
+ options,
+ },
+ });
+ }
+ const mainDepartmentFieldInstance = await fieldRepo.findOne({
+ filter: {
+ name: 'mainDepartment',
+ collectionName: 'users',
+ },
+ });
+ if (mainDepartmentFieldInstance) {
+ const options = {
+ ...mainDepartmentFieldInstance.options,
+ uiSchema: mainDepartmentField.uiSchema,
+ };
+ await fieldRepo.update({
+ filter: {
+ name: 'mainDepartment',
+ collectionName: 'users',
+ },
+ values: {
+ options,
+ },
+ });
+ }
+ const ownersFieldInstance = await fieldRepo.findOne({
+ filter: {
+ name: 'owners',
+ collectionName: 'departments',
+ },
+ });
+ if (ownersFieldInstance) {
+ const options = {
+ ...ownersFieldInstance.options,
+ uiSchema: ownersField.uiSchema,
+ };
+ await fieldRepo.update({
+ filter: {
+ name: 'owners',
+ collectionName: 'departments',
+ },
+ values: {
+ options,
+ },
+ });
+ }
+ }
+}
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/models/department.ts b/packages/plugins/@nocobase/plugin-departments/src/server/models/department.ts
new file mode 100644
index 0000000000..fddd8fc0ce
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/models/department.ts
@@ -0,0 +1,31 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Model } from '@nocobase/database';
+
+export class DepartmentModel extends Model {
+ getOwners() {
+ return this.getMembers({
+ through: {
+ where: {
+ isOwner: true,
+ },
+ },
+ });
+ }
+}
diff --git a/packages/plugins/@nocobase/plugin-departments/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-departments/src/server/plugin.ts
new file mode 100644
index 0000000000..6aeaf51fe3
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-departments/src/server/plugin.ts
@@ -0,0 +1,156 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Cache } from '@nocobase/cache';
+import { InstallOptions, Plugin } from '@nocobase/server';
+import { aggregateSearch, removeOwner, setOwner } from './actions/departments';
+import { listExcludeDept, setMainDepartment } from './actions/users';
+import { departmentsField, mainDepartmentField } from './collections/users';
+import {
+ destroyDepartmentCheck,
+ resetUserDepartmentsCache,
+ setDepartmentOwners,
+ setMainDepartment as setMainDepartmentMiddleware,
+ updateDepartmentIsLeaf,
+} from './middlewares';
+import { setDepartmentsInfo } from './middlewares/set-departments-roles';
+import { DepartmentModel } from './models/department';
+import { DepartmentDataSyncResource } from './department-data-sync-resource';
+import PluginUserDataSyncServer from '@nocobase/plugin-user-data-sync';
+import { DataSource } from '@nocobase/data-source-manager';
+
+export class PluginDepartmentsServer extends Plugin {
+ afterAdd() {}
+
+ beforeLoad() {
+ this.app.db.registerModels({ DepartmentModel });
+
+ this.app.acl.addFixedParams('collections', 'destroy', () => {
+ return {
+ filter: {
+ 'name.$notIn': ['departments', 'departmentsUsers', 'departmentsRoles'],
+ },
+ };
+ });
+ }
+
+ async load() {
+ this.app.resourceManager.registerActionHandlers({
+ 'users:listExcludeDept': listExcludeDept,
+ 'users:setMainDepartment': setMainDepartment,
+ 'departments:aggregateSearch': aggregateSearch,
+ 'departments:setOwner': setOwner,
+ 'departments:removeOwner': removeOwner,
+ });
+
+ this.app.acl.allow('users', ['setMainDepartment', 'listExcludeDept'], 'loggedIn');
+ this.app.acl.registerSnippet({
+ name: `pm.${this.name}`,
+ actions: [
+ 'departments:*',
+ 'roles:list',
+ 'users:list',
+ 'users:listExcludeDept',
+ 'users:setMainDepartment',
+ 'users.departments:*',
+ 'roles.departments:*',
+ 'departments.members:*',
+ ],
+ });
+
+ this.app.resourceManager.use(setDepartmentsInfo, {
+ tag: 'setDepartmentsInfo',
+ before: 'setCurrentRole',
+ after: 'auth',
+ });
+ this.app.dataSourceManager.afterAddDataSource((dataSource: DataSource) => {
+ dataSource.resourceManager.use(setDepartmentsInfo, {
+ tag: 'setDepartmentsInfo',
+ before: 'setCurrentRole',
+ after: 'auth',
+ });
+ });
+
+ this.app.resourceManager.use(setDepartmentOwners);
+ this.app.resourceManager.use(destroyDepartmentCheck);
+ this.app.resourceManager.use(updateDepartmentIsLeaf);
+ this.app.resourceManager.use(resetUserDepartmentsCache);
+ this.app.resourceManager.use(setMainDepartmentMiddleware);
+
+ // Delete cache when the departments of a user changed
+ this.app.db.on('departmentsUsers.afterSave', async (model) => {
+ const cache = this.app.cache as Cache;
+ await cache.del(`departments:${model.get('userId')}`);
+ });
+ this.app.db.on('departmentsUsers.afterDestroy', async (model) => {
+ const cache = this.app.cache as Cache;
+ await cache.del(`departments:${model.get('userId')}`);
+ });
+ this.app.on('beforeSignOut', ({ userId }) => {
+ this.app.cache.del(`departments:${userId}`);
+ });
+
+ const userDataSyncPlugin = this.app.pm.get('user-data-sync') as PluginUserDataSyncServer;
+ if (userDataSyncPlugin && userDataSyncPlugin.enabled) {
+ userDataSyncPlugin.resourceManager.registerResource(new DepartmentDataSyncResource(this.db, this.app.logger), {
+ // write department records after writing user records
+ after: 'users',
+ });
+ }
+ }
+
+ async install(options?: InstallOptions) {
+ const collectionRepo = this.db.getRepository('collections');
+ if (collectionRepo) {
+ await collectionRepo.db2cm('departments');
+ }
+ const fieldRepo = this.db.getRepository('fields');
+ if (fieldRepo) {
+ const isDepartmentsFieldExists = await fieldRepo.count({
+ filter: {
+ name: 'departments',
+ collectionName: 'users',
+ },
+ });
+ if (!isDepartmentsFieldExists) {
+ await fieldRepo.create({
+ values: departmentsField,
+ });
+ }
+ const isMainDepartmentFieldExists = await fieldRepo.count({
+ filter: {
+ name: 'mainDepartment',
+ collectionName: 'users',
+ },
+ });
+ if (!isMainDepartmentFieldExists) {
+ await fieldRepo.create({
+ values: mainDepartmentField,
+ });
+ }
+ }
+ }
+
+ async afterEnable() {}
+
+ async afterDisable() {}
+
+ async remove() {}
+}
+
+export default PluginDepartmentsServer;
diff --git a/packages/plugins/@nocobase/plugin-disable-pm-add/package.json b/packages/plugins/@nocobase/plugin-disable-pm-add/package.json
index d99aedbe09..474392f852 100644
--- a/packages/plugins/@nocobase/plugin-disable-pm-add/package.json
+++ b/packages/plugins/@nocobase/plugin-disable-pm-add/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-disable-pm-add",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",
diff --git a/packages/plugins/@nocobase/plugin-environment-variables/package.json b/packages/plugins/@nocobase/plugin-environment-variables/package.json
index 8084933602..8469032bbe 100644
--- a/packages/plugins/@nocobase/plugin-environment-variables/package.json
+++ b/packages/plugins/@nocobase/plugin-environment-variables/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-environment-variables",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",
diff --git a/packages/plugins/@nocobase/plugin-error-handler/package.json b/packages/plugins/@nocobase/plugin-error-handler/package.json
index cca0ee086d..978e9471aa 100644
--- a/packages/plugins/@nocobase/plugin-error-handler/package.json
+++ b/packages/plugins/@nocobase/plugin-error-handler/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "错误处理器",
"description": "Handling application errors and exceptions.",
"description.zh-CN": "处理应用程序中的错误和异常。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"devDependencies": {
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/.npmignore b/packages/plugins/@nocobase/plugin-field-attachment-url/.npmignore
new file mode 100644
index 0000000000..65f5e8779f
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/.npmignore
@@ -0,0 +1,2 @@
+/node_modules
+/src
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/README.md b/packages/plugins/@nocobase/plugin-field-attachment-url/README.md
new file mode 100644
index 0000000000..806a995fd7
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/README.md
@@ -0,0 +1 @@
+# @nocobase/plugin-field-attachment-url
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/client.d.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/client.d.ts
new file mode 100644
index 0000000000..6c459cbac4
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/client.d.ts
@@ -0,0 +1,2 @@
+export * from './dist/client';
+export { default } from './dist/client';
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/client.js b/packages/plugins/@nocobase/plugin-field-attachment-url/client.js
new file mode 100644
index 0000000000..b6e3be70e6
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/client.js
@@ -0,0 +1 @@
+module.exports = require('./dist/client/index.js');
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/package.json b/packages/plugins/@nocobase/plugin-field-attachment-url/package.json
new file mode 100644
index 0000000000..9a53df602e
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@nocobase/plugin-field-attachment-url",
+ "version": "1.7.0-beta.18",
+ "main": "dist/server/index.js",
+ "displayName": "Collection field: Attachment(URL)",
+ "displayName.zh-CN": "数据表字段:附件(URL)",
+ "description": "Supports attachments in URL format.",
+ "description.zh-CN": "支持 URL 格式的附件。",
+ "peerDependencies": {
+ "@nocobase/client": "1.x",
+ "@nocobase/plugin-file-manager": "1.x",
+ "@nocobase/server": "1.x",
+ "@nocobase/test": "1.x"
+ },
+ "keywords": [
+ "Collection fields"
+ ]
+}
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/server.d.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/server.d.ts
new file mode 100644
index 0000000000..c41081ddc6
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/server.d.ts
@@ -0,0 +1,2 @@
+export * from './dist/server';
+export { default } from './dist/server';
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/server.js b/packages/plugins/@nocobase/plugin-field-attachment-url/server.js
new file mode 100644
index 0000000000..972842039a
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/server.js
@@ -0,0 +1 @@
+module.exports = require('./dist/server/index.js');
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/__e2e__/createField.test.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/__e2e__/createField.test.ts
new file mode 100644
index 0000000000..89e1432ff4
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/__e2e__/createField.test.ts
@@ -0,0 +1,28 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { expect, test } from '@nocobase/test/e2e';
+
+test('create Attachment (URL) field', async ({ page }) => {
+ await page.goto('/admin/settings/data-source-manager/main/collections?type=main');
+ await page.getByLabel('action-Action.Link-Configure fields-collections-users', { exact: true }).click();
+ await page.getByRole('button', { name: 'plus Add field' }).click();
+ await page.getByRole('menuitem', { name: 'Attachment (URL)' }).click();
+ const displayName = `a${Math.random().toString(36).substring(7)}`;
+ const name = `a${Math.random().toString(36).substring(7)}`;
+ await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').fill(displayName);
+ await page.getByLabel('block-item-Input-fields-Field name').getByRole('textbox').fill(name);
+ await expect(page.getByLabel('block-item-RemoteSelect-')).toBeVisible();
+ await page.getByLabel('action-Action-Submit-fields-').click();
+ await expect(page.getByText(name)).toBeVisible();
+
+ // 删除
+ await page.getByLabel(`action-CollectionFields-Delete-fields-${name}`).click();
+ await page.getByRole('button', { name: 'OK', exact: true }).click();
+});
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/client.d.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/client.d.ts
new file mode 100644
index 0000000000..4e96f83fa1
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/client.d.ts
@@ -0,0 +1,249 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+// CSS modules
+type CSSModuleClasses = { readonly [key: string]: string };
+
+declare module '*.module.css' {
+ const classes: CSSModuleClasses;
+ export default classes;
+}
+declare module '*.module.scss' {
+ const classes: CSSModuleClasses;
+ export default classes;
+}
+declare module '*.module.sass' {
+ const classes: CSSModuleClasses;
+ export default classes;
+}
+declare module '*.module.less' {
+ const classes: CSSModuleClasses;
+ export default classes;
+}
+declare module '*.module.styl' {
+ const classes: CSSModuleClasses;
+ export default classes;
+}
+declare module '*.module.stylus' {
+ const classes: CSSModuleClasses;
+ export default classes;
+}
+declare module '*.module.pcss' {
+ const classes: CSSModuleClasses;
+ export default classes;
+}
+declare module '*.module.sss' {
+ const classes: CSSModuleClasses;
+ export default classes;
+}
+
+// CSS
+declare module '*.css' { }
+declare module '*.scss' { }
+declare module '*.sass' { }
+declare module '*.less' { }
+declare module '*.styl' { }
+declare module '*.stylus' { }
+declare module '*.pcss' { }
+declare module '*.sss' { }
+
+// Built-in asset types
+// see `src/node/constants.ts`
+
+// images
+declare module '*.apng' {
+ const src: string;
+ export default src;
+}
+declare module '*.png' {
+ const src: string;
+ export default src;
+}
+declare module '*.jpg' {
+ const src: string;
+ export default src;
+}
+declare module '*.jpeg' {
+ const src: string;
+ export default src;
+}
+declare module '*.jfif' {
+ const src: string;
+ export default src;
+}
+declare module '*.pjpeg' {
+ const src: string;
+ export default src;
+}
+declare module '*.pjp' {
+ const src: string;
+ export default src;
+}
+declare module '*.gif' {
+ const src: string;
+ export default src;
+}
+declare module '*.svg' {
+ const src: string;
+ export default src;
+}
+declare module '*.ico' {
+ const src: string;
+ export default src;
+}
+declare module '*.webp' {
+ const src: string;
+ export default src;
+}
+declare module '*.avif' {
+ const src: string;
+ export default src;
+}
+
+// media
+declare module '*.mp4' {
+ const src: string;
+ export default src;
+}
+declare module '*.webm' {
+ const src: string;
+ export default src;
+}
+declare module '*.ogg' {
+ const src: string;
+ export default src;
+}
+declare module '*.mp3' {
+ const src: string;
+ export default src;
+}
+declare module '*.wav' {
+ const src: string;
+ export default src;
+}
+declare module '*.flac' {
+ const src: string;
+ export default src;
+}
+declare module '*.aac' {
+ const src: string;
+ export default src;
+}
+declare module '*.opus' {
+ const src: string;
+ export default src;
+}
+declare module '*.mov' {
+ const src: string;
+ export default src;
+}
+declare module '*.m4a' {
+ const src: string;
+ export default src;
+}
+declare module '*.vtt' {
+ const src: string;
+ export default src;
+}
+
+// fonts
+declare module '*.woff' {
+ const src: string;
+ export default src;
+}
+declare module '*.woff2' {
+ const src: string;
+ export default src;
+}
+declare module '*.eot' {
+ const src: string;
+ export default src;
+}
+declare module '*.ttf' {
+ const src: string;
+ export default src;
+}
+declare module '*.otf' {
+ const src: string;
+ export default src;
+}
+
+// other
+declare module '*.webmanifest' {
+ const src: string;
+ export default src;
+}
+declare module '*.pdf' {
+ const src: string;
+ export default src;
+}
+declare module '*.txt' {
+ const src: string;
+ export default src;
+}
+
+// wasm?init
+declare module '*.wasm?init' {
+ const initWasm: (options?: WebAssembly.Imports) => Promise;
+ export default initWasm;
+}
+
+// web worker
+declare module '*?worker' {
+ const workerConstructor: {
+ new(options?: { name?: string }): Worker;
+ };
+ export default workerConstructor;
+}
+
+declare module '*?worker&inline' {
+ const workerConstructor: {
+ new(options?: { name?: string }): Worker;
+ };
+ export default workerConstructor;
+}
+
+declare module '*?worker&url' {
+ const src: string;
+ export default src;
+}
+
+declare module '*?sharedworker' {
+ const sharedWorkerConstructor: {
+ new(options?: { name?: string }): SharedWorker;
+ };
+ export default sharedWorkerConstructor;
+}
+
+declare module '*?sharedworker&inline' {
+ const sharedWorkerConstructor: {
+ new(options?: { name?: string }): SharedWorker;
+ };
+ export default sharedWorkerConstructor;
+}
+
+declare module '*?sharedworker&url' {
+ const src: string;
+ export default src;
+}
+
+declare module '*?raw' {
+ const src: string;
+ export default src;
+}
+
+declare module '*?url' {
+ const src: string;
+ export default src;
+}
+
+declare module '*?inline' {
+ const src: string;
+ export default src;
+}
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/component/AttachmentUrl.tsx b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/component/AttachmentUrl.tsx
new file mode 100644
index 0000000000..f8eb0f2c89
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/component/AttachmentUrl.tsx
@@ -0,0 +1,183 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { RecursionField, connect, mapReadPretty, useField, useFieldSchema } from '@formily/react';
+import React, { useContext, useEffect, useState } from 'react';
+import {
+ FormProvider,
+ RecordPickerContext,
+ RecordPickerProvider,
+ SchemaComponentOptions,
+ useActionContext,
+ TableSelectorParamsProvider,
+ useTableSelectorProps as useTsp,
+ EllipsisWithTooltip,
+ CollectionProvider_deprecated,
+ useCollection_deprecated,
+ useCollectionManager_deprecated,
+ Upload,
+ useFieldNames,
+ ActionContextProvider,
+ AssociationField,
+ Input,
+} from '@nocobase/client';
+import schema from '../schema';
+import { useInsertSchema } from '../hook';
+
+const defaultToValueItem = (data) => {
+ return data?.thumbnailRule ? `${data?.url}${data?.thumbnailRule}` : data?.url;
+};
+
+const InnerAttachmentUrl = (props) => {
+ const { value, onChange, toValueItem = defaultToValueItem, disabled, underFilter, ...others } = props;
+ const fieldSchema = useFieldSchema();
+ const [visibleSelector, setVisibleSelector] = useState(false);
+ const [selectedRows, setSelectedRows] = useState([]);
+ const insertSelector = useInsertSchema('Selector');
+ const fieldNames = useFieldNames(props);
+ const field: any = useField();
+ const [options, setOptions] = useState();
+ const { getField } = useCollection_deprecated();
+ const collectionField = getField(field.props.name);
+ const { modalProps } = useActionContext();
+ const handleSelect = (ev) => {
+ ev.stopPropagation();
+ ev.preventDefault();
+ insertSelector(schema.Selector);
+ setVisibleSelector(true);
+ setSelectedRows([]);
+ };
+
+ useEffect(() => {
+ if (value && Object.keys(value).length > 0) {
+ setOptions(value);
+ } else {
+ setOptions(null);
+ }
+ }, [value, fieldNames?.label]);
+
+ const pickerProps = {
+ size: 'small',
+ fieldNames,
+ multiple: false,
+ association: {
+ target: collectionField?.target,
+ },
+ options,
+ onChange: props?.onChange,
+ selectedRows,
+ setSelectedRows,
+ collectionField,
+ };
+ const usePickActionProps = () => {
+ const { setVisible } = useActionContext();
+ const { selectedRows, onChange } = useContext(RecordPickerContext);
+ return {
+ onClick() {
+ onChange(toValueItem(selectedRows?.[0]) || null);
+ setVisible(false);
+ },
+ };
+ };
+ const useTableSelectorProps = () => {
+ const {
+ multiple,
+ options,
+ setSelectedRows,
+ selectedRows: rcSelectRows = [],
+ onChange,
+ } = useContext(RecordPickerContext);
+ const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp();
+ const { setVisible } = useActionContext();
+ return {
+ ...others,
+ rowKey,
+ rowSelection: {
+ type: multiple ? 'checkbox' : 'radio',
+ selectedRowKeys: rcSelectRows?.filter((item) => options?.[rowKey] !== item[rowKey]).map((item) => item[rowKey]),
+ },
+ onRowSelectionChange(selectedRowKeys, selectedRows) {
+ setSelectedRows?.(selectedRows);
+ onRowSelectionChange?.(selectedRowKeys, selectedRows);
+ onChange(toValueItem(selectedRows?.[0]) || null);
+ setVisible(false);
+ },
+ };
+ };
+ if (underFilter) {
+ return ;
+ }
+ return (
+
+
+
+ {collectionField?.target && collectionField?.target !== 'attachments' && (
+
+
+
+
+
+ {
+ return s['x-component'] === 'AssociationField.Selector';
+ }}
+ />
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+const FileManageReadPretty = connect((props) => {
+ const { value } = props;
+ const fieldSchema = useFieldSchema();
+ const componentMode = fieldSchema?.['x-component-props']?.['componentMode'];
+ const { getField } = useCollection_deprecated();
+ const { getCollectionJoinField } = useCollectionManager_deprecated();
+ const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema['x-collection-field']);
+ if (componentMode === 'url') {
+ return {value};
+ }
+ return (
+ {collectionField ? : null}
+ );
+});
+
+export const AttachmentUrl = connect(InnerAttachmentUrl, mapReadPretty(FileManageReadPretty));
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/hook/index.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/hook/index.ts
new file mode 100644
index 0000000000..59389c8486
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/hook/index.ts
@@ -0,0 +1,84 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { useFieldSchema, useField } from '@formily/react';
+import { useCollectionField, useDesignable, useRequest } from '@nocobase/client';
+import { cloneDeep, uniqBy } from 'lodash';
+import { useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+
+function useStorageRules(storage) {
+ const name = storage ?? '';
+ const { loading, data } = useRequest(
+ {
+ url: `storages:getBasicInfo/${name}`,
+ },
+ {
+ refreshDeps: [name],
+ },
+ );
+ return (!loading && data?.data) || null;
+}
+export function useAttachmentUrlFieldProps(props) {
+ const field = useCollectionField();
+ const rules = useStorageRules(field?.storage);
+ return {
+ ...props,
+ rules,
+ action: `${field.target}:create${field.storage ? `?attachmentField=${field.collectionName}.${field.name}` : ''}`,
+ toValueItem: (data) => {
+ return data?.thumbnailRule ? `${data?.url}${data?.thumbnailRule}` : data?.url;
+ },
+ getThumbnailURL: (file) => {
+ return file?.url;
+ },
+ };
+}
+
+export const useInsertSchema = (component) => {
+ const fieldSchema = useFieldSchema();
+ const { insertAfterBegin } = useDesignable();
+ const insert = useCallback(
+ (ss) => {
+ const schema = fieldSchema.reduceProperties((buf, s) => {
+ if (s['x-component'] === 'AssociationField.' + component) {
+ return s;
+ }
+ return buf;
+ }, null);
+ if (!schema) {
+ insertAfterBegin(cloneDeep(ss));
+ }
+ },
+ [component, fieldSchema, insertAfterBegin],
+ );
+ return insert;
+};
+
+export const useAttachmentTargetProps = () => {
+ const { t } = useTranslation();
+ const field = useField();
+ return {
+ service: {
+ resource: 'collections:listFileCollectionsWithPublicStorage',
+ params: {
+ paginate: false,
+ },
+ },
+ manual: false,
+ fieldNames: {
+ label: 'title',
+ value: 'name',
+ },
+ onSuccess: (data) => {
+ field.data = field.data || {};
+ field.data.options = data?.data;
+ },
+ };
+};
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/index.tsx b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/index.tsx
new file mode 100644
index 0000000000..c535c97f67
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/index.tsx
@@ -0,0 +1,38 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { Plugin, lazy } from '@nocobase/client';
+import { AttachmentURLFieldInterface } from './interfaces/attachment-url';
+import { useAttachmentUrlFieldProps } from './hook';
+// import { AttachmentUrl } from './component/AttachmentUrl';
+const { AttachmentUrl } = lazy(() => import('./component/AttachmentUrl'), 'AttachmentUrl');
+
+import { attachmentUrlComponentFieldSettings } from './settings';
+export class PluginFieldAttachmentUrlClient extends Plugin {
+ async afterAdd() {
+ // await this.app.pm.add()
+ }
+
+ async beforeLoad() {}
+
+ // You can get and modify the app instance here
+ async load() {
+ this.app.dataSourceManager.addFieldInterfaces([AttachmentURLFieldInterface]);
+ this.app.addScopes({ useAttachmentUrlFieldProps });
+
+ this.app.addComponents({ AttachmentUrl });
+ this.app.schemaSettingsManager.add(attachmentUrlComponentFieldSettings);
+
+ // this.app.addProvider()
+ // this.app.addProviders()
+ // this.app.router.add()
+ }
+}
+
+export default PluginFieldAttachmentUrlClient;
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/interfaces/attachment-url.tsx b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/interfaces/attachment-url.tsx
new file mode 100644
index 0000000000..464f2a8c57
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/interfaces/attachment-url.tsx
@@ -0,0 +1,85 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { CollectionFieldInterface, interfacesProperties } from '@nocobase/client';
+import { ISchema } from '@formily/react';
+import { useAttachmentTargetProps } from '../hook';
+import { tStr } from '../locale';
+
+const { defaultProps, operators } = interfacesProperties;
+
+export const defaultToolbar = [
+ 'headings',
+ 'bold',
+ 'italic',
+ 'strike',
+ 'link',
+ 'list',
+ 'ordered-list',
+ 'check',
+ 'quote',
+ 'line',
+ 'code',
+ 'inline-code',
+ 'upload',
+ 'fullscreen',
+];
+
+export class AttachmentURLFieldInterface extends CollectionFieldInterface {
+ name = 'attachmentURL';
+ type = 'object';
+ group = 'media';
+ title = tStr('Attachment (URL)');
+ default = {
+ type: 'string',
+ // name,
+ uiSchema: {
+ type: 'string',
+ // title,
+ 'x-component': 'AttachmentUrl',
+ 'x-use-component-props': 'useAttachmentUrlFieldProps',
+ },
+ };
+ availableTypes = ['string', 'text'];
+ properties = {
+ ...defaultProps,
+ target: {
+ required: true,
+ type: 'string',
+ title: tStr('Which file collection should it be uploaded to'),
+ 'x-decorator': 'FormItem',
+ 'x-component': 'RemoteSelect',
+ 'x-use-component-props': useAttachmentTargetProps,
+ 'x-reactions': (field) => {
+ const options = field.data?.options || [];
+ const hasAttachments = options.some((opt) => opt?.name === 'attachments');
+ if (hasAttachments) {
+ !field.initialValue && field.setInitialValue('attachments');
+ }
+ },
+ },
+ targetKey: {
+ 'x-hidden': true,
+ default: 'id',
+ type: 'string',
+ },
+ };
+ schemaInitialize(schema: ISchema, { block }) {
+ schema['x-component-props'] = schema['x-component-props'] || {};
+ schema['x-component-props']['mode'] = 'AttachmentUrl';
+ if (['Table', 'Kanban'].includes(block)) {
+ schema['x-component-props']['ellipsis'] = true;
+ schema['x-component-props']['size'] = 'small';
+ }
+ }
+ filterable = {
+ operators: operators.bigField,
+ };
+ titleUsable = true;
+}
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/locale.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/locale.ts
new file mode 100644
index 0000000000..a26dd0158f
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/locale.ts
@@ -0,0 +1,30 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+// @ts-ignore
+import pkg from '../../package.json';
+import { useApp } from '@nocobase/client';
+import { useTranslation } from 'react-i18next';
+
+export const NAMESPACE = 'attachmentUrl';
+
+export function useT() {
+ const app = useApp();
+ return (str: string) => app.i18n.t(str, { ns: [pkg.name, 'client'] });
+}
+
+export function tStr(key: string) {
+ return `{{t(${JSON.stringify(key)}, { ns: ['${pkg.name}', 'client'], nsMode: 'fallback' })}}`;
+}
+
+export function useAttachmentUrlTranslation() {
+ return useTranslation([NAMESPACE, 'client'], {
+ nsMode: 'fallback',
+ });
+}
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/schema.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/schema.ts
new file mode 100644
index 0000000000..f5ae80d0ce
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/schema.ts
@@ -0,0 +1,53 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+export default {
+ Selector: {
+ type: 'void',
+ 'x-component': 'AssociationField.Selector',
+ title: '{{ t("Select record") }}',
+ 'x-component-props': {
+ className: 'nb-record-picker-selector',
+ },
+ properties: {
+ grid: {
+ type: 'void',
+ 'x-component': 'Grid',
+ 'x-initializer': 'popup:tableSelector:addBlock',
+ properties: {},
+ },
+ footer: {
+ 'x-component': 'Action.Container.Footer',
+ 'x-component-props': {},
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'ActionBar',
+ 'x-component-props': {},
+ properties: {
+ submit: {
+ title: '{{ t("Submit") }}',
+ 'x-action': 'submit',
+ 'x-component': 'Action',
+ 'x-use-component-props': 'usePickActionProps',
+ // 'x-designer': 'Action.Designer',
+ 'x-toolbar': 'ActionSchemaToolbar',
+ 'x-settings': 'actionSettings:submit',
+ 'x-component-props': {
+ type: 'primary',
+ htmlType: 'submit',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/settings/index.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/settings/index.ts
new file mode 100644
index 0000000000..52463073c2
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/client/settings/index.ts
@@ -0,0 +1,171 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { Field } from '@formily/core';
+import { useField, useFieldSchema, useForm } from '@formily/react';
+import { useTranslation } from 'react-i18next';
+import { useColumnSchema, useIsFieldReadPretty, SchemaSettings, useDesignable } from '@nocobase/client';
+
+const fieldComponent: any = {
+ name: 'fieldComponent',
+ type: 'select',
+ useComponentProps() {
+ const { t } = useTranslation();
+ const field = useField();
+ const { fieldSchema: tableColumnSchema } = useColumnSchema();
+ const schema = useFieldSchema();
+ const fieldSchema = tableColumnSchema || schema;
+ const { dn } = useDesignable();
+
+ return {
+ title: t('Field component'),
+ options: [
+ { label: t('URL'), value: 'url' },
+ { label: t('Preview'), value: 'preview' },
+ ],
+ value: fieldSchema['x-component-props']['componentMode'] || 'preview',
+ onChange(componentMode) {
+ const schema = {
+ ['x-uid']: fieldSchema['x-uid'],
+ };
+ fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
+ fieldSchema['x-component-props']['componentMode'] = componentMode;
+ schema['x-component-props'] = fieldSchema['x-component-props'];
+ field.componentProps = field.componentProps || {};
+ field.componentProps.componentMode = componentMode;
+ void dn.emit('patch', {
+ schema,
+ });
+ dn.refresh();
+ },
+ };
+ },
+ useVisible() {
+ const readPretty = useIsFieldReadPretty();
+ const { fieldSchema: tableColumnSchema } = useColumnSchema();
+ return readPretty;
+ },
+};
+
+export const attachmentUrlComponentFieldSettings = new SchemaSettings({
+ name: 'fieldSettings:component:AttachmentUrl',
+ items: [
+ {
+ name: 'quickUpload',
+ type: 'switch',
+ useComponentProps() {
+ const { t } = useTranslation();
+ const field = useField();
+ const { fieldSchema: tableColumnSchema } = useColumnSchema();
+ const schema = useFieldSchema();
+ const fieldSchema = tableColumnSchema || schema;
+ const { dn, refresh } = useDesignable();
+ return {
+ title: t('Quick upload'),
+ checked: fieldSchema['x-component-props']?.quickUpload !== (false as boolean),
+ onChange(value) {
+ const schema = {
+ ['x-uid']: fieldSchema['x-uid'],
+ };
+ field.componentProps.quickUpload = value;
+ fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
+ fieldSchema['x-component-props'].quickUpload = value;
+ schema['x-component-props'] = fieldSchema['x-component-props'];
+ dn.emit('patch', {
+ schema,
+ });
+ refresh();
+ },
+ };
+ },
+ useVisible() {
+ const { fieldSchema: tableColumnSchema } = useColumnSchema();
+ const field = useField();
+ const form = useForm();
+ const isReadPretty = tableColumnSchema?.['x-read-pretty'] || field.readPretty || form.readPretty;
+ return !isReadPretty && !field.componentProps.underFilter;
+ },
+ },
+ {
+ name: 'selectFile',
+ type: 'switch',
+ useComponentProps() {
+ const { t } = useTranslation();
+ const field = useField();
+ const { fieldSchema: tableColumnSchema } = useColumnSchema();
+ const schema = useFieldSchema();
+ const fieldSchema = tableColumnSchema || schema;
+ const { dn, refresh } = useDesignable();
+ return {
+ title: t('Select file'),
+ checked: fieldSchema['x-component-props']?.selectFile !== (false as boolean),
+ onChange(value) {
+ const schema = {
+ ['x-uid']: fieldSchema['x-uid'],
+ };
+ field.componentProps.selectFile = value;
+ fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
+ fieldSchema['x-component-props'].selectFile = value;
+ schema['x-component-props'] = fieldSchema['x-component-props'];
+ dn.emit('patch', {
+ schema,
+ });
+ refresh();
+ },
+ };
+ },
+ useVisible() {
+ const { fieldSchema: tableColumnSchema } = useColumnSchema();
+ const field = useField();
+ const form = useForm();
+ const isReadPretty = tableColumnSchema?.['x-read-pretty'] || field.readPretty || form.readPretty;
+ return !isReadPretty && !field.componentProps.underFilter;
+ },
+ },
+ fieldComponent,
+ {
+ name: 'size',
+ type: 'select',
+ useVisible() {
+ const readPretty = useIsFieldReadPretty();
+ const { fieldSchema: tableColumnSchema } = useColumnSchema();
+ return readPretty && !tableColumnSchema;
+ },
+ useComponentProps() {
+ const { t } = useTranslation();
+ const field = useField();
+ const fieldSchema = useFieldSchema();
+ const { dn } = useDesignable();
+ return {
+ title: t('Size'),
+ options: [
+ { label: t('Large'), value: 'large' },
+ { label: t('Default'), value: 'default' },
+ { label: t('Small'), value: 'small' },
+ ],
+ value: field?.componentProps?.size || 'default',
+ onChange(size) {
+ const schema = {
+ ['x-uid']: fieldSchema['x-uid'],
+ };
+ fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
+ fieldSchema['x-component-props']['size'] = size;
+ schema['x-component-props'] = fieldSchema['x-component-props'];
+ field.componentProps = field.componentProps || {};
+ field.componentProps.size = size;
+ dn.emit('patch', {
+ schema,
+ });
+ dn.refresh();
+ },
+ };
+ },
+ },
+ ],
+});
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/index.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/src/index.ts
new file mode 100644
index 0000000000..be99a2ff1a
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/index.ts
@@ -0,0 +1,11 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+export * from './server';
+export { default } from './server';
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-field-attachment-url/src/locale/en-US.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/locale/en-US.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-field-attachment-url/src/locale/zh-CN.json
new file mode 100644
index 0000000000..78def6ea14
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/locale/zh-CN.json
@@ -0,0 +1,4 @@
+{
+ "Which file collection should it be uploaded to":"上传到文件表",
+ "Attachment (URL)":"附件 (URL)"
+}
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/server/collections/.gitkeep b/packages/plugins/@nocobase/plugin-field-attachment-url/src/server/collections/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/server/index.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/src/server/index.ts
new file mode 100644
index 0000000000..be989de7c3
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/server/index.ts
@@ -0,0 +1,10 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+export { default } from './plugin';
diff --git a/packages/plugins/@nocobase/plugin-field-attachment-url/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-field-attachment-url/src/server/plugin.ts
new file mode 100644
index 0000000000..648ed0cec0
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-field-attachment-url/src/server/plugin.ts
@@ -0,0 +1,65 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { Plugin } from '@nocobase/server';
+import PluginFileManagerServer from '@nocobase/plugin-file-manager';
+
+export class PluginFieldAttachmentUrlServer extends Plugin {
+ async afterAdd() {}
+
+ async beforeLoad() {}
+
+ async load() {
+ this.app.resourceManager.registerActionHandlers({
+ 'collections:listFileCollectionsWithPublicStorage': async (ctx, next) => {
+ const fileCollections = await this.db.getRepository('collections').find({
+ filter: {
+ 'options.template': 'file',
+ },
+ });
+
+ const filePlugin = this.pm.get('file-manager') as PluginFileManagerServer | any;
+
+ const options = [];
+
+ const fileCollection = this.db.getCollection('attachments');
+
+ if (await filePlugin.isPublicAccessStorage(fileCollection?.options?.storage)) {
+ options.push({
+ title: '{{t("Attachments")}}',
+ name: 'attachments',
+ });
+ }
+
+ for (const fileCollection of fileCollections) {
+ if (await filePlugin.isPublicAccessStorage(fileCollection?.options?.storage)) {
+ options.push({
+ name: fileCollection.name,
+ title: fileCollection.title,
+ });
+ }
+ }
+
+ ctx.body = options;
+
+ await next();
+ },
+ });
+ }
+
+ async install() {}
+
+ async afterEnable() {}
+
+ async afterDisable() {}
+
+ async remove() {}
+}
+
+export default PluginFieldAttachmentUrlServer;
diff --git a/packages/plugins/@nocobase/plugin-field-china-region/package.json b/packages/plugins/@nocobase/plugin-field-china-region/package.json
index f0b8aa841c..201c9fd0e8 100644
--- a/packages/plugins/@nocobase/plugin-field-china-region/package.json
+++ b/packages/plugins/@nocobase/plugin-field-china-region/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-field-china-region",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "Collection field: administrative divisions of China",
"displayName.zh-CN": "数据表字段:中国行政区划",
"description": "Provides data and field type for administrative divisions of China.",
diff --git a/packages/plugins/@nocobase/plugin-field-formula/package.json b/packages/plugins/@nocobase/plugin-field-formula/package.json
index b532259e66..8aaaf8b80f 100644
--- a/packages/plugins/@nocobase/plugin-field-formula/package.json
+++ b/packages/plugins/@nocobase/plugin-field-formula/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:公式",
"description": "Configure and store the results of calculations between multiple field values in the same record, supporting both Math.js and Excel formula functions.",
"description.zh-CN": "可以配置并存储同一条记录的多字段值之间的计算结果,支持 Math.js 和 Excel formula functions 两种引擎",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-formula",
diff --git a/packages/plugins/@nocobase/plugin-field-m2m-array/package.json b/packages/plugins/@nocobase/plugin-field-m2m-array/package.json
index f96493a20b..a1212639a6 100644
--- a/packages/plugins/@nocobase/plugin-field-m2m-array/package.json
+++ b/packages/plugins/@nocobase/plugin-field-m2m-array/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:多对多 (数组)",
"description": "Allows to create many to many relationships between two models by storing an array of unique keys of the target model.",
"description.zh-CN": "支持通过在数组中存储目标表唯一键的方式建立多对多关系。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",
diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json b/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json
index 9df462c4c7..ef9b3401ef 100644
--- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json
+++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:Markdown(Vditor)",
"description": "Used to store Markdown and render it using Vditor editor, supports common Markdown syntax such as list, code, quote, etc., and supports uploading images, recordings, etc.It also allows for instant rendering, where what you see is what you get.",
"description.zh-CN": "用于存储 Markdown,并使用 Vditor 编辑器渲染,支持常见 Markdown 语法,如列表,代码,引用等,并支持上传图片,录音等。同时可以做到即时渲染,所见即所得。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-markdown-vditor",
diff --git a/packages/plugins/@nocobase/plugin-field-sequence/package.json b/packages/plugins/@nocobase/plugin-field-sequence/package.json
index 2ba9ce3019..f9f2c794e4 100644
--- a/packages/plugins/@nocobase/plugin-field-sequence/package.json
+++ b/packages/plugins/@nocobase/plugin-field-sequence/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:自动编码",
"description": "Automatically generate codes based on configured rules, supporting combinations of dates, numbers, and text.",
"description.zh-CN": "根据配置的规则自动生成编码,支持日期、数字、文本的组合。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-sequence",
diff --git a/packages/plugins/@nocobase/plugin-field-sort/package.json b/packages/plugins/@nocobase/plugin-field-sort/package.json
index c012d48c5b..25323eb756 100644
--- a/packages/plugins/@nocobase/plugin-field-sort/package.json
+++ b/packages/plugins/@nocobase/plugin-field-sort/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-field-sort",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"displayName": "Collection field: Sort",
"displayName.zh-CN": "数据表字段:排序",
diff --git a/packages/plugins/@nocobase/plugin-file-manager/package.json b/packages/plugins/@nocobase/plugin-file-manager/package.json
index 125bb52be2..cfe1d453e8 100644
--- a/packages/plugins/@nocobase/plugin-file-manager/package.json
+++ b/packages/plugins/@nocobase/plugin-file-manager/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-file-manager",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "File manager",
"displayName.zh-CN": "文件管理器",
"description": "Provides files storage services with files collection template and attachment field.",
diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts
index f30aadafbc..2b74a580ee 100644
--- a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts
+++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts
@@ -20,7 +20,7 @@ import { useStorageUploadProps } from './useStorageUploadProps';
export const useUploadFiles = () => {
const { getDataBlockRequest } = useDataBlockRequestGetter();
- const { association } = useDataBlockProps();
+ const { association } = useDataBlockProps() || {};
const { setVisible } = useActionContext();
const collection = useCollection();
const sourceId = useSourceId();
diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts
index d581ba8772..f7903a5db9 100644
--- a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts
+++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts
@@ -313,6 +313,27 @@ export class PluginFileManagerServer extends Plugin {
const storageType = this.storageTypes.get(storage.type);
return new storageType(storage).getFileURL(file, preview ? storage.options.thumbnailRule : '');
}
+ async isPublicAccessStorage(storageName) {
+ const storageRepository = this.db.getRepository('storages');
+ const storages = await storageRepository.findOne({
+ filter: { default: true },
+ });
+ let storage;
+ if (!storageName) {
+ storage = storages;
+ } else {
+ storage = await storageRepository.findOne({
+ filter: {
+ name: storageName,
+ },
+ });
+ }
+ storage = this.parseStorage(storage);
+ if (['local', 'ali-oss', 's3', 'tx-cos'].includes(storage.type)) {
+ return true;
+ }
+ return !!storage.options?.public;
+ }
}
export default PluginFileManagerServer;
diff --git a/packages/plugins/@nocobase/plugin-gantt/package.json b/packages/plugins/@nocobase/plugin-gantt/package.json
index 1dd1192248..a6e509b5b3 100644
--- a/packages/plugins/@nocobase/plugin-gantt/package.json
+++ b/packages/plugins/@nocobase/plugin-gantt/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-gantt",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "Block: Gantt",
"displayName.zh-CN": "区块:甘特图",
"description": "Provides Gantt block.",
diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json b/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json
index 95aa918447..a1cbc012ed 100644
--- a/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json
+++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "可视化数据表管理",
"description": "An ER diagram-like tool. Currently only the Master database is supported.",
"description.zh-CN": "类似 ER 图的工具,目前只支持主数据库。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/graph-collection-manager",
diff --git a/packages/plugins/@nocobase/plugin-kanban/package.json b/packages/plugins/@nocobase/plugin-kanban/package.json
index b51f79751a..a9f66415f9 100644
--- a/packages/plugins/@nocobase/plugin-kanban/package.json
+++ b/packages/plugins/@nocobase/plugin-kanban/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-kanban",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-kanban",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-kanban",
diff --git a/packages/plugins/@nocobase/plugin-locale-tester/package.json b/packages/plugins/@nocobase/plugin-locale-tester/package.json
index 121bf658fd..81c1bedb0b 100644
--- a/packages/plugins/@nocobase/plugin-locale-tester/package.json
+++ b/packages/plugins/@nocobase/plugin-locale-tester/package.json
@@ -2,7 +2,7 @@
"name": "@nocobase/plugin-locale-tester",
"displayName": "Locale tester",
"displayName.zh-CN": "翻译测试工具",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"homepage": "https://github.com/nocobase/locales",
"main": "dist/server/index.js",
"peerDependencies": {
diff --git a/packages/plugins/@nocobase/plugin-localization/package.json b/packages/plugins/@nocobase/plugin-localization/package.json
index 53334aea2d..4220fcceee 100644
--- a/packages/plugins/@nocobase/plugin-localization/package.json
+++ b/packages/plugins/@nocobase/plugin-localization/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-localization",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/localization-management",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/localization-management",
diff --git a/packages/plugins/@nocobase/plugin-logger/package.json b/packages/plugins/@nocobase/plugin-logger/package.json
index d8eedd3331..6bd9c9e82f 100644
--- a/packages/plugins/@nocobase/plugin-logger/package.json
+++ b/packages/plugins/@nocobase/plugin-logger/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "日志",
"description": "Server-side logs, mainly including API request logs and system runtime logs, and allows to package and download log files.",
"description.zh-CN": "服务端日志,主要包括接口请求日志和系统运行日志,并支持打包和下载日志文件。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/logger",
diff --git a/packages/plugins/@nocobase/plugin-map/package.json b/packages/plugins/@nocobase/plugin-map/package.json
index 054e550cd9..f27a5c27f4 100644
--- a/packages/plugins/@nocobase/plugin-map/package.json
+++ b/packages/plugins/@nocobase/plugin-map/package.json
@@ -2,7 +2,7 @@
"name": "@nocobase/plugin-map",
"displayName": "Block: Map",
"displayName.zh-CN": "区块:地图",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"description": "Map block, support Gaode map and Google map, you can also extend more map types.",
"description.zh-CN": "地图区块,支持高德地图和 Google 地图,你也可以扩展更多地图类型。",
"license": "AGPL-3.0",
diff --git a/packages/plugins/@nocobase/plugin-mobile-client/package.json b/packages/plugins/@nocobase/plugin-mobile-client/package.json
index c324768cf7..f21a37a5cc 100644
--- a/packages/plugins/@nocobase/plugin-mobile-client/package.json
+++ b/packages/plugins/@nocobase/plugin-mobile-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-mobile-client",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/mobile-client",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/mobile-client",
diff --git a/packages/plugins/@nocobase/plugin-mobile/package.json b/packages/plugins/@nocobase/plugin-mobile/package.json
index 439d9503ee..10e32dadc3 100644
--- a/packages/plugins/@nocobase/plugin-mobile/package.json
+++ b/packages/plugins/@nocobase/plugin-mobile/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-mobile",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/mobile",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/mobile",
diff --git a/packages/plugins/@nocobase/plugin-mock-collections/package.json b/packages/plugins/@nocobase/plugin-mock-collections/package.json
index 0d9ac2da3a..9d15142097 100644
--- a/packages/plugins/@nocobase/plugin-mock-collections/package.json
+++ b/packages/plugins/@nocobase/plugin-mock-collections/package.json
@@ -2,7 +2,7 @@
"name": "@nocobase/plugin-mock-collections",
"displayName": "mock-collections",
"description": "mock-collections",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"license": "AGPL-3.0",
"peerDependencies": {
diff --git a/packages/plugins/@nocobase/plugin-multi-app-manager/package.json b/packages/plugins/@nocobase/plugin-multi-app-manager/package.json
index 4aaaa5db1c..982f7e68a4 100644
--- a/packages/plugins/@nocobase/plugin-multi-app-manager/package.json
+++ b/packages/plugins/@nocobase/plugin-multi-app-manager/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "多应用管理器",
"description": "Dynamically create multiple apps without separate deployments.",
"description.zh-CN": "无需单独部署即可动态创建多个应用。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/multi-app-manager",
diff --git a/packages/plugins/@nocobase/plugin-multi-app-share-collection/package.json b/packages/plugins/@nocobase/plugin-multi-app-share-collection/package.json
index 896fe1e549..082c9cb898 100644
--- a/packages/plugins/@nocobase/plugin-multi-app-share-collection/package.json
+++ b/packages/plugins/@nocobase/plugin-multi-app-share-collection/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "多应用数据表共享",
"description": "",
"description.zh-CN": "",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"devDependencies": {
"@formily/react": "2.x",
diff --git a/packages/plugins/@nocobase/plugin-notification-email/package.json b/packages/plugins/@nocobase/plugin-notification-email/package.json
index b106c11297..333134ac28 100644
--- a/packages/plugins/@nocobase/plugin-notification-email/package.json
+++ b/packages/plugins/@nocobase/plugin-notification-email/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-notification-email",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "Notification: Email",
"displayName.zh-CN": "通知:电子邮件",
"description": "Used for sending email notifications with built-in SMTP transport.",
diff --git a/packages/plugins/@nocobase/plugin-notification-in-app-message/package.json b/packages/plugins/@nocobase/plugin-notification-in-app-message/package.json
index 60bd3bf354..9239f2c8ab 100644
--- a/packages/plugins/@nocobase/plugin-notification-in-app-message/package.json
+++ b/packages/plugins/@nocobase/plugin-notification-in-app-message/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-notification-in-app-message",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"displayName": "Notification: In-app message",
"displayName.zh-CN": "通知:站内信",
"description": "It supports users in receiving real-time message notifications within the NocoBase application.",
diff --git a/packages/plugins/@nocobase/plugin-notification-manager/package.json b/packages/plugins/@nocobase/plugin-notification-manager/package.json
index 76363b247a..6da844123c 100644
--- a/packages/plugins/@nocobase/plugin-notification-manager/package.json
+++ b/packages/plugins/@nocobase/plugin-notification-manager/package.json
@@ -4,7 +4,7 @@
"description": "Provides a unified management service that includes channel configuration, logging, and other features, supporting the configuration of various notification channels, including in-app message and email.",
"displayName.zh-CN": "通知管理",
"description.zh-CN": "提供统一的管理服务,涵盖渠道配置、日志记录等功能,支持多种通知渠道的配置,包括站内信和电子邮件等。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"homepage": "https://docs.nocobase.com/handbook/notification-manager",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/notification-manager",
"main": "dist/server/index.js",
diff --git a/packages/plugins/@nocobase/plugin-notifications/package.json b/packages/plugins/@nocobase/plugin-notifications/package.json
index d0a589ff0a..81b2e58e58 100644
--- a/packages/plugins/@nocobase/plugin-notifications/package.json
+++ b/packages/plugins/@nocobase/plugin-notifications/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-notifications",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"description": "",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
diff --git a/packages/plugins/@nocobase/plugin-public-forms/package.json b/packages/plugins/@nocobase/plugin-public-forms/package.json
index fe2fbeef6f..1d07c2fee4 100644
--- a/packages/plugins/@nocobase/plugin-public-forms/package.json
+++ b/packages/plugins/@nocobase/plugin-public-forms/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-public-forms",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"displayName": "Public forms",
"displayName.zh-CN": "公开表单",
diff --git a/packages/plugins/@nocobase/plugin-sample-hello/package.json b/packages/plugins/@nocobase/plugin-sample-hello/package.json
index bd9b3a25c8..aa8732dabb 100644
--- a/packages/plugins/@nocobase/plugin-sample-hello/package.json
+++ b/packages/plugins/@nocobase/plugin-sample-hello/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-sample-hello",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "./dist/server/index.js",
"displayName": "Hello",
"displayName.zh-CN": "Hello",
diff --git a/packages/plugins/@nocobase/plugin-snapshot-field/package.json b/packages/plugins/@nocobase/plugin-snapshot-field/package.json
index 04a794532c..7ba92aa78a 100644
--- a/packages/plugins/@nocobase/plugin-snapshot-field/package.json
+++ b/packages/plugins/@nocobase/plugin-snapshot-field/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:关系快照",
"description": "When adding a new record, create a snapshot for its relational record and save in the new record. The snapshot will not be updated when the relational record is updated.",
"description.zh-CN": "在添加数据时,为它的关系数据创建快照,并保存在当前的数据中。关系数据更新时,快照不会更新。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-snapshot",
diff --git a/packages/plugins/@nocobase/plugin-system-settings/package.json b/packages/plugins/@nocobase/plugin-system-settings/package.json
index 5652a03386..a38d782aa4 100644
--- a/packages/plugins/@nocobase/plugin-system-settings/package.json
+++ b/packages/plugins/@nocobase/plugin-system-settings/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "系统设置",
"description": "Used to adjust the system title, logo, language, etc.",
"description.zh-CN": "用于调整系统的标题、LOGO、语言等。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/system-settings",
diff --git a/packages/plugins/@nocobase/plugin-theme-editor/package.json b/packages/plugins/@nocobase/plugin-theme-editor/package.json
index e472a6f91c..c33805ff9a 100644
--- a/packages/plugins/@nocobase/plugin-theme-editor/package.json
+++ b/packages/plugins/@nocobase/plugin-theme-editor/package.json
@@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-theme-editor",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/theme-editor",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/theme-editor",
diff --git a/packages/plugins/@nocobase/plugin-ui-schema-storage/package.json b/packages/plugins/@nocobase/plugin-ui-schema-storage/package.json
index 0ef150b764..457c09e1fd 100644
--- a/packages/plugins/@nocobase/plugin-ui-schema-storage/package.json
+++ b/packages/plugins/@nocobase/plugin-ui-schema-storage/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "UI schema 存储服务",
"description": "Provides centralized UI schema storage service.",
"description.zh-CN": "提供中心化的 UI schema 存储服务。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/ui-schema-storage",
diff --git a/packages/plugins/@nocobase/plugin-user-data-sync/package.json b/packages/plugins/@nocobase/plugin-user-data-sync/package.json
index 7770c0a029..47b204a6ce 100644
--- a/packages/plugins/@nocobase/plugin-user-data-sync/package.json
+++ b/packages/plugins/@nocobase/plugin-user-data-sync/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "用户数据同步",
"description": "Reigster and manage extensible user data synchronization sources, with HTTP API provided by default. Support for synchronizing data to resources such as users and departments.",
"description.zh-CN": "注册和管理可扩展的用户数据同步来源,默认提供 HTTP API。支持向用户和部门等资源同步数据。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"main": "dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",
diff --git a/packages/plugins/@nocobase/plugin-users/package.json b/packages/plugins/@nocobase/plugin-users/package.json
index 5112a2f47b..3031a2e31b 100644
--- a/packages/plugins/@nocobase/plugin-users/package.json
+++ b/packages/plugins/@nocobase/plugin-users/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "用户",
"description": "Provides basic user model, as well as created by and updated by fields.",
"description.zh-CN": "提供了基础的用户模型,以及创建人和最后更新人字段。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/users",
diff --git a/packages/plugins/@nocobase/plugin-verification/package.json b/packages/plugins/@nocobase/plugin-verification/package.json
index 46ab970edd..4190a22f8b 100644
--- a/packages/plugins/@nocobase/plugin-verification/package.json
+++ b/packages/plugins/@nocobase/plugin-verification/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "验证",
"description": "User identity verification management, including SMS, TOTP authenticator, with extensibility.",
"description.zh-CN": "用户身份验证管理,包含短信、TOTP 认证器等,可扩展。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/verification",
diff --git a/packages/plugins/@nocobase/plugin-workflow-action-trigger/package.json b/packages/plugins/@nocobase/plugin-workflow-action-trigger/package.json
index df946e5dc6..9273c83917 100644
--- a/packages/plugins/@nocobase/plugin-workflow-action-trigger/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-action-trigger/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:操作后事件",
"description": "Triggered after the completion of a request initiated through an action button or API, such as after adding, updating, deleting data, or \"submit to workflow\". Suitable for data processing, sending notifications, etc., after actions are completed.",
"description.zh-CN": "通过操作按钮或 API 发起请求并在执行完成后触发,比如新增、更新、删除数据或者“提交至工作流”之后。适用于在操作完成后进行数据处理、发送通知等。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/plugins/workflow-action-trigger",
diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/package.json b/packages/plugins/@nocobase/plugin-workflow-aggregate/package.json
index 106797c329..201387fbdb 100644
--- a/packages/plugins/@nocobase/plugin-workflow-aggregate/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:聚合查询节点",
"description": "Used to aggregate data against the database in workflow, such as: statistics, sum, average, etc.",
"description.zh-CN": "可用于在工作流中对数据库进行聚合查询,如:统计数量、求和、平均值等。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-aggregate",
diff --git a/packages/plugins/@nocobase/plugin-workflow-delay/package.json b/packages/plugins/@nocobase/plugin-workflow-delay/package.json
index 6bc367aa5f..874b59727c 100644
--- a/packages/plugins/@nocobase/plugin-workflow-delay/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-delay/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:延时节点",
"description": "Could be used in workflow parallel branch for waiting other branches.",
"description.zh-CN": "可用于工作流并行分支中等待其他分支执行完成。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-delay",
diff --git a/packages/plugins/@nocobase/plugin-workflow-dynamic-calculation/package.json b/packages/plugins/@nocobase/plugin-workflow-dynamic-calculation/package.json
index 08a326862d..4cfa89a145 100644
--- a/packages/plugins/@nocobase/plugin-workflow-dynamic-calculation/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-dynamic-calculation/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:动态表达式计算节点",
"description": "Useful plugin for doing dynamic calculation based on expression collection records in workflow.",
"description.zh-CN": "用于在工作流中进行基于数据行的动态表达式计算。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-dynamic-calculation",
diff --git a/packages/plugins/@nocobase/plugin-workflow-loop/package.json b/packages/plugins/@nocobase/plugin-workflow-loop/package.json
index 59832fd8fa..8445cd6a5f 100644
--- a/packages/plugins/@nocobase/plugin-workflow-loop/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-loop/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:循环节点",
"description": "Used to repeat the sub-process processing of each value in an array, and can also be used for fixed times of sub-process processing.",
"description.zh-CN": "用于对一个数组中的每个值进行重复的子流程处理,也可用于固定次数的重复子流程处理。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-loop",
diff --git a/packages/plugins/@nocobase/plugin-workflow-mailer/package.json b/packages/plugins/@nocobase/plugin-workflow-mailer/package.json
index 5bceecf17a..45e9fe77d6 100644
--- a/packages/plugins/@nocobase/plugin-workflow-mailer/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-mailer/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:邮件发送节点",
"description": "Send email in workflow.",
"description.zh-CN": "可用于在工作流中发送电子邮件。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-smtp-mailer",
diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/package.json b/packages/plugins/@nocobase/plugin-workflow-manual/package.json
index 2fe7fef96e..e5c80f4bb3 100644
--- a/packages/plugins/@nocobase/plugin-workflow-manual/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-manual/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:人工处理节点",
"description": "Could be used for workflows which some of decisions are made by users.",
"description.zh-CN": "用于人工控制部分决策的流程。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-manual",
diff --git a/packages/plugins/@nocobase/plugin-workflow-notification/package.json b/packages/plugins/@nocobase/plugin-workflow-notification/package.json
index ac1953a8d9..2474535dc5 100644
--- a/packages/plugins/@nocobase/plugin-workflow-notification/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-notification/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:通知节点",
"description": "Send notification in workflow.",
"description.zh-CN": "可用于在工作流中发送各类通知。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-smtp-mailer",
diff --git a/packages/plugins/@nocobase/plugin-workflow-parallel/package.json b/packages/plugins/@nocobase/plugin-workflow-parallel/package.json
index 2f52b3b231..3dd3024223 100644
--- a/packages/plugins/@nocobase/plugin-workflow-parallel/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-parallel/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:并行分支节点",
"description": "Could be used for parallel execution of branch processes in the workflow.",
"description.zh-CN": "用于在工作流中需要并行执行的分支流程。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-parallel",
diff --git a/packages/plugins/@nocobase/plugin-workflow-request/package.json b/packages/plugins/@nocobase/plugin-workflow-request/package.json
index 2eed33cda4..88676beb3e 100644
--- a/packages/plugins/@nocobase/plugin-workflow-request/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-request/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:HTTP 请求节点",
"description": "Send HTTP requests to any HTTP service for data interaction in workflow.",
"description.zh-CN": "可用于在工作流中向任意 HTTP 服务发送请求,进行数据交互。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-request",
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/.npmignore b/packages/plugins/@nocobase/plugin-workflow-response-message/.npmignore
new file mode 100644
index 0000000000..65f5e8779f
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/.npmignore
@@ -0,0 +1,2 @@
+/node_modules
+/src
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/README.md b/packages/plugins/@nocobase/plugin-workflow-response-message/README.md
new file mode 100644
index 0000000000..8ced21e948
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/README.md
@@ -0,0 +1 @@
+# @nocobase/plugin-workflow-response-message
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/client.d.ts b/packages/plugins/@nocobase/plugin-workflow-response-message/client.d.ts
new file mode 100644
index 0000000000..6c459cbac4
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/client.d.ts
@@ -0,0 +1,2 @@
+export * from './dist/client';
+export { default } from './dist/client';
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/client.js b/packages/plugins/@nocobase/plugin-workflow-response-message/client.js
new file mode 100644
index 0000000000..b6e3be70e6
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/client.js
@@ -0,0 +1 @@
+module.exports = require('./dist/client/index.js');
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/package.json b/packages/plugins/@nocobase/plugin-workflow-response-message/package.json
new file mode 100644
index 0000000000..f8833d6adc
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "@nocobase/plugin-workflow-response-message",
+ "version": "1.7.0-beta.18",
+ "displayName": "Workflow: Response message",
+ "displayName.zh-CN": "工作流:响应消息",
+ "description": "Used for assemble response message and showing to client in form event and request interception workflows.",
+ "description.zh-CN": "用于在表单事件和请求拦截工作流中组装并向客户端显示响应消息。",
+ "main": "dist/server/index.js",
+ "homepage": "https://docs.nocobase.com/handbook/workflow-response-message",
+ "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/workflow-response-message",
+ "peerDependencies": {
+ "@nocobase/client": "1.x",
+ "@nocobase/plugin-workflow": "1.x",
+ "@nocobase/server": "1.x",
+ "@nocobase/test": "1.x",
+ "@nocobase/utils": "1.x"
+ },
+ "keywords": [
+ "Workflow"
+ ],
+ "gitHead": "080fc78c1a744d47e010b3bbe5840446775800e4"
+}
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/server.d.ts b/packages/plugins/@nocobase/plugin-workflow-response-message/server.d.ts
new file mode 100644
index 0000000000..c41081ddc6
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/server.d.ts
@@ -0,0 +1,2 @@
+export * from './dist/server';
+export { default } from './dist/server';
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/server.js b/packages/plugins/@nocobase/plugin-workflow-response-message/server.js
new file mode 100644
index 0000000000..972842039a
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/server.js
@@ -0,0 +1 @@
+module.exports = require('./dist/server/index.js');
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/client/ResponseMessageInstruction.tsx b/packages/plugins/@nocobase/plugin-workflow-response-message/src/client/ResponseMessageInstruction.tsx
new file mode 100644
index 0000000000..efdd6a7241
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/client/ResponseMessageInstruction.tsx
@@ -0,0 +1,87 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import React from 'react';
+import { InfoCircleOutlined } from '@ant-design/icons';
+import { Alert, Space } from 'antd';
+
+import {
+ Instruction,
+ RadioWithTooltip,
+ WorkflowVariableInput,
+ WorkflowVariableTextArea,
+} from '@nocobase/plugin-workflow/client';
+
+import { NAMESPACE } from '../locale';
+
+export default class extends Instruction {
+ title = `{{t("Response message", { ns: "${NAMESPACE}" })}}`;
+ type = 'response-message';
+ group = 'extended';
+ description = `{{t("Add response message, will be send to client when process of request ends.", { ns: "${NAMESPACE}" })}}`;
+ icon = ();
+ fieldset = {
+ message: {
+ type: 'string',
+ title: `{{t("Message content", { ns: "${NAMESPACE}" })}}`,
+ description: `{{t('Supports variables in template.', { ns: "${NAMESPACE}", name: '{{name}}' })}}`,
+ 'x-decorator': 'FormItem',
+ 'x-component': 'WorkflowVariableTextArea',
+ },
+ info: {
+ type: 'void',
+ 'x-component': 'Space',
+ 'x-component-props': {
+ direction: 'vertical',
+ },
+ properties: {
+ success: {
+ type: 'void',
+ 'x-component': 'Alert',
+ 'x-component-props': {
+ type: 'success',
+ showIcon: true,
+ description: `{{t('If the workflow ends normally, the response message will return a success status by default.', { ns: "${NAMESPACE}" })}}`,
+ },
+ },
+ failure: {
+ type: 'void',
+ 'x-component': 'Alert',
+ 'x-component-props': {
+ type: 'error',
+ showIcon: true,
+ description: `{{t('If you want to return a failure status, please add an "End Process" node downstream to terminate the workflow.', { ns: "${NAMESPACE}" })}}`,
+ },
+ },
+ },
+ },
+ };
+ scope = {};
+ components = {
+ RadioWithTooltip,
+ WorkflowVariableTextArea,
+ WorkflowVariableInput,
+ Alert,
+ Space,
+ };
+ isAvailable({ workflow, upstream, branchIndex }) {
+ return (
+ workflow.type === 'request-interception' || (['action', 'custom-action'].includes(workflow.type) && workflow.sync)
+ );
+ }
+}
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/client/index.tsx b/packages/plugins/@nocobase/plugin-workflow-response-message/src/client/index.tsx
new file mode 100644
index 0000000000..258f6c8d8b
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/client/index.tsx
@@ -0,0 +1,31 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Plugin } from '@nocobase/client';
+import WorkflowPlugin from '@nocobase/plugin-workflow/client';
+
+import ResponseMessageInstruction from './ResponseMessageInstruction';
+
+export class PluginWorkflowResponseMessageClient extends Plugin {
+ async load() {
+ const workflowPlugin = this.app.pm.get('workflow') as WorkflowPlugin;
+ workflowPlugin.registerInstruction('response-message', ResponseMessageInstruction);
+ }
+}
+
+export default PluginWorkflowResponseMessageClient;
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/index.ts b/packages/plugins/@nocobase/plugin-workflow-response-message/src/index.ts
new file mode 100644
index 0000000000..7d69462f4f
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/index.ts
@@ -0,0 +1,20 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+export * from './server';
+export { default } from './server';
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-workflow-response-message/src/locale/en-US.json
new file mode 100644
index 0000000000..11832d6610
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/locale/en-US.json
@@ -0,0 +1,6 @@
+{
+ "Response message": "Response message",
+ "Add response message, will be send to client when process of request ends.": "Add response message, will be send to client when process of request ends.",
+ "Message content": "Message content",
+ "Supports variables in template.": "Supports variables in template."
+}
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/locale/index.ts b/packages/plugins/@nocobase/plugin-workflow-response-message/src/locale/index.ts
new file mode 100644
index 0000000000..543b392f21
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/locale/index.ts
@@ -0,0 +1,25 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { i18n } from '@nocobase/client';
+
+export const NAMESPACE = '@nocobase/plugin-workflow-response-message';
+
+export function lang(key: string, options = {}) {
+ return i18n.t(key, { ...options, ns: NAMESPACE });
+}
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-workflow-response-message/src/locale/zh-CN.json
new file mode 100644
index 0000000000..c8b986cace
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/locale/zh-CN.json
@@ -0,0 +1,8 @@
+{
+ "Response message": "响应消息",
+ "Add response message, will be send to client when process of request ends.": "添加响应消息,将在请求处理结束时发送给客户端。",
+ "Message content": "消息内容",
+ "Supports variables in template.": "支持模板变量。",
+ "If the workflow ends normally, the response message will return a success status by default.": "如果工作流正常结束,响应消息默认返回成功状态。",
+ "If you want to return a failure status, please add an \"End Process\" node downstream to terminate the workflow.": "如果希望返回失败状态,请在下游添加“结束流程”节点终止工作流。"
+}
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/Plugin.ts b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/Plugin.ts
new file mode 100644
index 0000000000..cdd6eece72
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/Plugin.ts
@@ -0,0 +1,31 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Plugin } from '@nocobase/server';
+import PluginWorkflowServer from '@nocobase/plugin-workflow';
+
+import ResponseMessageInstruction from './ResponseMessageInstruction';
+
+export class PluginWorkflowResponseMessageServer extends Plugin {
+ async load() {
+ const workflowPlugin = this.app.pm.get(PluginWorkflowServer) as PluginWorkflowServer;
+ workflowPlugin.registerInstruction('response-message', ResponseMessageInstruction);
+ }
+}
+
+export default PluginWorkflowResponseMessageServer;
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/ResponseMessageInstruction.ts b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/ResponseMessageInstruction.ts
new file mode 100644
index 0000000000..e277a44e35
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/ResponseMessageInstruction.ts
@@ -0,0 +1,55 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import { Instruction, Processor, JOB_STATUS, FlowNodeModel } from '@nocobase/plugin-workflow';
+
+interface Config {
+ message?: string;
+}
+
+export default class extends Instruction {
+ async run(node: FlowNodeModel, prevJob, processor: Processor) {
+ const { httpContext } = processor.options;
+
+ if (!httpContext) {
+ return {
+ status: JOB_STATUS.RESOLVED,
+ result: null,
+ };
+ }
+
+ if (!httpContext.state) {
+ httpContext.state = {};
+ }
+
+ if (!httpContext.state.messages) {
+ httpContext.state.messages = [];
+ }
+
+ const message = processor.getParsedValue(node.config.message, node.id);
+
+ if (message) {
+ httpContext.state.messages.push({ message });
+ }
+
+ return {
+ status: JOB_STATUS.RESOLVED,
+ result: message,
+ };
+ }
+}
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/__tests__/instruction.test.ts b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/__tests__/instruction.test.ts
new file mode 100644
index 0000000000..500a70cefe
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/__tests__/instruction.test.ts
@@ -0,0 +1,346 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import Database from '@nocobase/database';
+import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
+import { getApp } from '@nocobase/plugin-workflow-test';
+import { MockServer } from '@nocobase/test';
+
+import Plugin from '..';
+
+describe.skip('workflow > instructions > response-message', () => {
+ let app: MockServer;
+ let db: Database;
+ let PostRepo;
+ let WorkflowModel;
+ let workflow;
+ let users;
+ let userAgents;
+
+ beforeEach(async () => {
+ app = await getApp({
+ plugins: ['users', 'auth', 'error-handler', 'workflow-request-interceptor', Plugin],
+ });
+
+ db = app.db;
+
+ PostRepo = db.getCollection('posts').repository;
+
+ WorkflowModel = db.getModel('workflows');
+ workflow = await WorkflowModel.create({
+ enabled: true,
+ type: 'request-interception',
+ config: {
+ global: true,
+ actions: ['create'],
+ collection: 'posts',
+ },
+ });
+
+ const UserModel = db.getCollection('users').model;
+ users = await UserModel.bulkCreate([
+ { id: 2, nickname: 'a' },
+ { id: 3, nickname: 'b' },
+ ]);
+
+ userAgents = await Promise.all(users.map((user) => app.agent().login(user)));
+ });
+
+ afterEach(() => app.destroy());
+
+ describe('no end, pass flow', () => {
+ it('no message', async () => {
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body).toMatchObject({ data: { title: 't1' } });
+ expect(res1.body.messages).toBeUndefined();
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeDefined();
+ expect(post.title).toBe('t1');
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(0);
+ });
+
+ it('has node, but null message', async () => {
+ const n1 = await workflow.createNode({
+ type: 'response-message',
+ });
+
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body).toMatchObject({ data: { title: 't1' } });
+ expect(res1.body.messages).toBeUndefined();
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeDefined();
+ expect(post.title).toBe('t1');
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(1);
+ });
+
+ it('has node, but empty message', async () => {
+ const n1 = await workflow.createNode({
+ type: 'response-message',
+ config: {
+ message: '',
+ },
+ });
+
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body).toMatchObject({ data: { title: 't1' } });
+ expect(res1.body.messages).toBeUndefined();
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeDefined();
+ expect(post.title).toBe('t1');
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(1);
+ });
+
+ it('single static message', async () => {
+ const n1 = await workflow.createNode({
+ type: 'response-message',
+ config: {
+ message: 'm1',
+ },
+ });
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body).toMatchObject({ data: { title: 't1' } });
+ expect(res1.body.messages).toEqual([{ message: 'm1' }]);
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeDefined();
+ expect(post.title).toBe('t1');
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(1);
+ });
+
+ it('multiple static messages', async () => {
+ const n1 = await workflow.createNode({
+ type: 'response-message',
+ config: {
+ message: 'm1',
+ },
+ });
+ const n2 = await workflow.createNode({
+ type: 'response-message',
+ config: {
+ message: 'm2',
+ },
+ upstreamId: n1.id,
+ });
+ await n1.setDownstream(n2);
+
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body).toMatchObject({ data: { title: 't1' } });
+ expect(res1.body.messages).toEqual([{ message: 'm1' }, { message: 'm2' }]);
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeDefined();
+ expect(post.title).toBe('t1');
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(2);
+ });
+
+ it('single dynamic message', async () => {
+ const n1 = await workflow.createNode({
+ type: 'response-message',
+ config: {
+ message: 'new post "{{ $context.params.values.title }}" by {{ $context.user.nickname }}',
+ },
+ });
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body).toMatchObject({ data: { title: 't1' } });
+ expect(res1.body.messages).toEqual([{ message: `new post "t1" by ${users[0].nickname}` }]);
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeDefined();
+ expect(post.title).toBe('t1');
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(1);
+ });
+ });
+
+ describe('end as success', () => {
+ it('no message', async () => {
+ const n1 = await workflow.createNode({
+ type: 'end',
+ config: {
+ endStatus: JOB_STATUS.RESOLVED,
+ },
+ });
+
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body.data).toBeUndefined();
+ expect(res1.body.messages).toBeUndefined();
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeNull();
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(1);
+
+ const posts = await PostRepo.find();
+ expect(posts.length).toBe(0);
+ });
+
+ it('single static message', async () => {
+ const n1 = await workflow.createNode({
+ type: 'response-message',
+ config: {
+ message: 'm1',
+ },
+ });
+
+ const n2 = await workflow.createNode({
+ type: 'end',
+ config: {
+ endStatus: JOB_STATUS.RESOLVED,
+ },
+ upstreamId: n1.id,
+ });
+
+ await n1.setDownstream(n2);
+
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body.data).toBeUndefined();
+ expect(res1.body.messages).toEqual([{ message: 'm1' }]);
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeNull();
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(2);
+ });
+ });
+
+ describe('end as failure', () => {
+ it('no message', async () => {
+ const n1 = await workflow.createNode({
+ type: 'end',
+ config: {
+ endStatus: JOB_STATUS.FAILED,
+ },
+ });
+
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(400);
+ expect(res1.body.data).toBeUndefined();
+ expect(res1.body.messages).toBeUndefined();
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeNull();
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.FAILED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(1);
+ });
+
+ it('single static message', async () => {
+ const n1 = await workflow.createNode({
+ type: 'response-message',
+ config: {
+ message: 'm1',
+ },
+ });
+
+ const n2 = await workflow.createNode({
+ type: 'end',
+ config: {
+ endStatus: JOB_STATUS.FAILED,
+ },
+ upstreamId: n1.id,
+ });
+
+ await n1.setDownstream(n2);
+
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ });
+
+ expect(res1.status).toBe(400);
+ expect(res1.body.data).toBeUndefined();
+ expect(res1.body.errors).toEqual([{ message: 'm1' }]);
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeNull();
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.FAILED);
+ const jobs = await e1.getJobs();
+ expect(jobs.length).toBe(2);
+ });
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/__tests__/multiple.test.ts b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/__tests__/multiple.test.ts
new file mode 100644
index 0000000000..21f207c449
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/__tests__/multiple.test.ts
@@ -0,0 +1,185 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+import Database from '@nocobase/database';
+import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
+import { getApp } from '@nocobase/plugin-workflow-test';
+import { MockServer } from '@nocobase/test';
+
+import Plugin from '..';
+
+describe.skip('workflow > multiple workflows', () => {
+ let app: MockServer;
+ let db: Database;
+ let PostRepo;
+ let WorkflowModel;
+ let workflow;
+ let users;
+ let userAgents;
+
+ beforeEach(async () => {
+ app = await getApp({
+ plugins: ['users', 'auth', 'error-handler', 'workflow-request-interceptor', Plugin],
+ });
+
+ db = app.db;
+
+ PostRepo = db.getCollection('posts').repository;
+
+ WorkflowModel = db.getModel('workflows');
+ workflow = await WorkflowModel.create({
+ enabled: true,
+ type: 'request-interception',
+ config: {
+ global: true,
+ actions: ['create'],
+ collection: 'posts',
+ },
+ });
+
+ const UserModel = db.getCollection('users').model;
+ users = await UserModel.bulkCreate([
+ { id: 2, nickname: 'a' },
+ { id: 3, nickname: 'b' },
+ ]);
+
+ userAgents = await Promise.all(users.map((user) => app.agent().login(user)));
+ });
+
+ afterEach(() => app.destroy());
+
+ describe('order', () => {
+ it('workflow 2 run first and pass, workflow 1 ends as success', async () => {
+ const n1 = await workflow.createNode({
+ type: 'response-message',
+ config: {
+ message: 'm1',
+ },
+ });
+ const n2 = await workflow.createNode({
+ type: 'end',
+ config: {
+ endStatus: JOB_STATUS.RESOLVED,
+ },
+ upstreamId: n1.id,
+ });
+ await n1.setDownstream(n2);
+
+ const w2 = await WorkflowModel.create({
+ enabled: true,
+ type: 'request-interception',
+ config: {
+ action: 'create',
+ collection: 'posts',
+ },
+ });
+
+ const n3 = await w2.createNode({
+ type: 'response-message',
+ config: {
+ message: 'm2',
+ },
+ });
+
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ triggerWorkflows: w2.key,
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body.data).toBeUndefined();
+ expect(res1.body.messages).toEqual([{ message: 'm2' }, { message: 'm1' }]);
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeNull();
+
+ const [e1] = await workflow.getExecutions();
+ expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const j1s = await e1.getJobs();
+ expect(j1s.length).toBe(2);
+
+ const [e2] = await w2.getExecutions();
+ expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const j2s = await e2.getJobs();
+ expect(j2s.length).toBe(1);
+ });
+
+ it('local workflow in trigger key order', async () => {
+ const w1 = await WorkflowModel.create({
+ enabled: true,
+ type: 'request-interception',
+ config: {
+ action: 'create',
+ collection: 'posts',
+ },
+ });
+
+ const n1 = await w1.createNode({
+ type: 'response-message',
+ config: {
+ message: 'm1',
+ },
+ });
+
+ const w2 = await WorkflowModel.create({
+ enabled: true,
+ type: 'request-interception',
+ config: {
+ action: 'create',
+ collection: 'posts',
+ },
+ });
+
+ const n2 = await w2.createNode({
+ type: 'response-message',
+ config: {
+ message: 'm2',
+ },
+ });
+
+ const n3 = await w2.createNode({
+ type: 'end',
+ config: {
+ endStatus: JOB_STATUS.RESOLVED,
+ },
+ upstreamId: n2.id,
+ });
+
+ await n2.setDownstream(n3);
+
+ const res1 = await userAgents[0].resource('posts').create({
+ values: { title: 't1' },
+ triggerWorkflows: [w2.key, w1.key].join(),
+ });
+
+ expect(res1.status).toBe(200);
+ expect(res1.body.data).toBeUndefined();
+ expect(res1.body.messages).toEqual([{ message: 'm2' }]);
+
+ const post = await PostRepo.findOne();
+ expect(post).toBeNull();
+
+ const e1s = await w1.getExecutions();
+ expect(e1s.length).toBe(0);
+ const [e2] = await w2.getExecutions();
+ expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await e2.getJobs();
+ expect(jobs.length).toBe(2);
+ });
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/index.ts b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/index.ts
new file mode 100644
index 0000000000..b0c269d075
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-response-message/src/server/index.ts
@@ -0,0 +1,19 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This program is offered under a commercial license.
+ * For more information, see
+ */
+
+export { default } from './Plugin';
diff --git a/packages/plugins/@nocobase/plugin-workflow-sql/package.json b/packages/plugins/@nocobase/plugin-workflow-sql/package.json
index cbae9075bd..2fb3bbba7f 100644
--- a/packages/plugins/@nocobase/plugin-workflow-sql/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-sql/package.json
@@ -4,7 +4,7 @@
"displayName.zh-CN": "工作流:SQL 节点",
"description": "Execute SQL statements in workflow.",
"description.zh-CN": "可用于在工作流中对数据库执行任意 SQL 语句。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow-sql",
diff --git a/packages/plugins/@nocobase/plugin-workflow-test/package.json b/packages/plugins/@nocobase/plugin-workflow-test/package.json
index ad39019b0b..a2e2f95942 100644
--- a/packages/plugins/@nocobase/plugin-workflow-test/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow-test/package.json
@@ -2,7 +2,7 @@
"name": "@nocobase/plugin-workflow-test",
"displayName": "Workflow: test kit",
"displayName.zh-CN": "工作流:测试工具包",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "dist/server/index.js",
"types": "./dist/server/index.d.ts",
diff --git a/packages/plugins/@nocobase/plugin-workflow/package.json b/packages/plugins/@nocobase/plugin-workflow/package.json
index e2f563137d..401cbe5e6b 100644
--- a/packages/plugins/@nocobase/plugin-workflow/package.json
+++ b/packages/plugins/@nocobase/plugin-workflow/package.json
@@ -4,13 +4,13 @@
"displayName.zh-CN": "工作流",
"description": "A powerful BPM tool that provides foundational support for business automation, with the capability to extend unlimited triggers and nodes.",
"description.zh-CN": "一个强大的 BPM 工具,为业务自动化提供基础支持,并且可任意扩展更多的触发器和节点。",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/workflow",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/workflow",
"dependencies": {
- "@nocobase/plugin-workflow-test": "1.7.0-beta.16"
+ "@nocobase/plugin-workflow-test": "1.7.0-beta.18"
},
"devDependencies": {
"@ant-design/icons": "5.x",
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/Plugin.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/Plugin.ts
index 658054b29f..2fe93695aa 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/server/Plugin.ts
+++ b/packages/plugins/@nocobase/plugin-workflow/src/server/Plugin.ts
@@ -115,11 +115,13 @@ export default class PluginWorkflowServer extends Plugin {
private onAfterCreate = async (model: WorkflowModel, { transaction }) => {
const WorkflowStatsModel = this.db.getModel('workflowStats');
- const [stats, created] = await WorkflowStatsModel.findOrCreate({
+ let stats = await WorkflowStatsModel.findOne({
where: { key: model.key },
- defaults: { key: model.key },
transaction,
});
+ if (!stats) {
+ stats = await model.createStats({ executed: 0 }, { transaction });
+ }
model.stats = stats;
model.versionStats = await model.createVersionStats({ id: model.id }, { transaction });
if (model.enabled) {
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/migrations/20250320223415-stats.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/migrations/20250320223415-stats.ts
index 0e0b787d09..45bbd3f81d 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/server/migrations/20250320223415-stats.ts
+++ b/packages/plugins/@nocobase/plugin-workflow/src/server/migrations/20250320223415-stats.ts
@@ -26,16 +26,21 @@ export default class extends Migration {
const groupCounts: { [key: string]: { key: string; executed: number } } = {};
for (const workflow of workflows) {
- await WorkflowVersionStatsModel.findOrCreate({
+ const versionStats = await WorkflowVersionStatsModel.findOne({
where: {
id: workflow.id,
},
- defaults: {
- id: workflow.id,
- executed: workflow.get('executed'),
- },
transaction,
});
+ if (!versionStats) {
+ await WorkflowVersionStatsModel.create(
+ {
+ id: workflow.id,
+ executed: workflow.get('executed'),
+ },
+ { transaction },
+ );
+ }
const key = workflow.get('key');
groupCounts[key] = {
@@ -44,13 +49,15 @@ export default class extends Migration {
};
}
for (const values of Object.values(groupCounts)) {
- await WorkflowStatsModel.findOrCreate({
+ const stats = await WorkflowStatsModel.findOne({
where: {
key: values.key,
},
- defaults: values,
transaction,
});
+ if (!stats) {
+ await WorkflowStatsModel.create(values, { transaction });
+ }
}
});
}
diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json
index e5bc84504b..9c5cfc3a73 100644
--- a/packages/presets/nocobase/package.json
+++ b/packages/presets/nocobase/package.json
@@ -1,82 +1,85 @@
{
"name": "@nocobase/preset-nocobase",
- "version": "1.7.0-beta.16",
+ "version": "1.7.0-beta.18",
"license": "AGPL-3.0",
"main": "./lib/server/index.js",
"dependencies": {
"@formily/json-schema": "2.x",
- "@nocobase/plugin-acl": "1.7.0-beta.16",
- "@nocobase/plugin-action-bulk-edit": "1.7.0-beta.16",
- "@nocobase/plugin-action-bulk-update": "1.7.0-beta.16",
- "@nocobase/plugin-action-custom-request": "1.7.0-beta.16",
- "@nocobase/plugin-action-duplicate": "1.7.0-beta.16",
- "@nocobase/plugin-action-export": "1.7.0-beta.16",
- "@nocobase/plugin-action-import": "1.7.0-beta.16",
- "@nocobase/plugin-action-print": "1.7.0-beta.16",
- "@nocobase/plugin-ai": "1.7.0-beta.16",
- "@nocobase/plugin-api-doc": "1.7.0-beta.16",
- "@nocobase/plugin-api-keys": "1.7.0-beta.16",
- "@nocobase/plugin-async-task-manager": "1.7.0-beta.16",
- "@nocobase/plugin-audit-logs": "1.7.0-beta.16",
- "@nocobase/plugin-auth": "1.7.0-beta.16",
- "@nocobase/plugin-auth-sms": "1.7.0-beta.16",
- "@nocobase/plugin-backup-restore": "1.7.0-beta.16",
- "@nocobase/plugin-block-iframe": "1.7.0-beta.16",
- "@nocobase/plugin-block-template": "1.7.0-beta.16",
- "@nocobase/plugin-block-workbench": "1.7.0-beta.16",
- "@nocobase/plugin-calendar": "1.7.0-beta.16",
- "@nocobase/plugin-charts": "1.7.0-beta.16",
- "@nocobase/plugin-client": "1.7.0-beta.16",
- "@nocobase/plugin-collection-sql": "1.7.0-beta.16",
- "@nocobase/plugin-collection-tree": "1.7.0-beta.16",
- "@nocobase/plugin-data-source-main": "1.7.0-beta.16",
- "@nocobase/plugin-data-source-manager": "1.7.0-beta.16",
- "@nocobase/plugin-data-visualization": "1.7.0-beta.16",
- "@nocobase/plugin-environment-variables": "1.7.0-beta.16",
- "@nocobase/plugin-error-handler": "1.7.0-beta.16",
- "@nocobase/plugin-field-china-region": "1.7.0-beta.16",
- "@nocobase/plugin-field-formula": "1.7.0-beta.16",
- "@nocobase/plugin-field-m2m-array": "1.7.0-beta.16",
- "@nocobase/plugin-field-markdown-vditor": "1.7.0-beta.16",
- "@nocobase/plugin-field-sequence": "1.7.0-beta.16",
- "@nocobase/plugin-field-sort": "1.7.0-beta.16",
- "@nocobase/plugin-file-manager": "1.7.0-beta.16",
- "@nocobase/plugin-gantt": "1.7.0-beta.16",
- "@nocobase/plugin-graph-collection-manager": "1.7.0-beta.16",
- "@nocobase/plugin-kanban": "1.7.0-beta.16",
- "@nocobase/plugin-locale-tester": "1.7.0-beta.16",
- "@nocobase/plugin-localization": "1.7.0-beta.16",
- "@nocobase/plugin-logger": "1.7.0-beta.16",
- "@nocobase/plugin-map": "1.7.0-beta.16",
- "@nocobase/plugin-mobile": "1.7.0-beta.16",
- "@nocobase/plugin-mobile-client": "1.7.0-beta.16",
- "@nocobase/plugin-mock-collections": "1.7.0-beta.16",
- "@nocobase/plugin-multi-app-manager": "1.7.0-beta.16",
- "@nocobase/plugin-multi-app-share-collection": "1.7.0-beta.16",
- "@nocobase/plugin-notification-email": "1.7.0-beta.16",
- "@nocobase/plugin-notification-in-app-message": "1.7.0-beta.16",
- "@nocobase/plugin-notification-manager": "1.7.0-beta.16",
- "@nocobase/plugin-public-forms": "1.7.0-beta.16",
- "@nocobase/plugin-snapshot-field": "1.7.0-beta.16",
- "@nocobase/plugin-system-settings": "1.7.0-beta.16",
- "@nocobase/plugin-theme-editor": "1.7.0-beta.16",
- "@nocobase/plugin-ui-schema-storage": "1.7.0-beta.16",
- "@nocobase/plugin-user-data-sync": "1.7.0-beta.16",
- "@nocobase/plugin-users": "1.7.0-beta.16",
- "@nocobase/plugin-verification": "1.7.0-beta.16",
- "@nocobase/plugin-workflow": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-action-trigger": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-aggregate": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-delay": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-dynamic-calculation": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-loop": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-mailer": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-manual": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-notification": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-parallel": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-request": "1.7.0-beta.16",
- "@nocobase/plugin-workflow-sql": "1.7.0-beta.16",
- "@nocobase/server": "1.7.0-beta.16",
+ "@nocobase/plugin-acl": "1.7.0-beta.18",
+ "@nocobase/plugin-action-bulk-edit": "1.7.0-beta.18",
+ "@nocobase/plugin-action-bulk-update": "1.7.0-beta.18",
+ "@nocobase/plugin-action-custom-request": "1.7.0-beta.18",
+ "@nocobase/plugin-action-duplicate": "1.7.0-beta.18",
+ "@nocobase/plugin-action-export": "1.7.0-beta.18",
+ "@nocobase/plugin-action-import": "1.7.0-beta.18",
+ "@nocobase/plugin-action-print": "1.7.0-beta.18",
+ "@nocobase/plugin-ai": "1.7.0-beta.18",
+ "@nocobase/plugin-api-doc": "1.7.0-beta.18",
+ "@nocobase/plugin-api-keys": "1.7.0-beta.18",
+ "@nocobase/plugin-async-task-manager": "1.7.0-beta.18",
+ "@nocobase/plugin-audit-logs": "1.7.0-beta.18",
+ "@nocobase/plugin-auth": "1.7.0-beta.18",
+ "@nocobase/plugin-auth-sms": "1.7.0-beta.18",
+ "@nocobase/plugin-backup-restore": "1.7.0-beta.18",
+ "@nocobase/plugin-block-iframe": "1.7.0-beta.18",
+ "@nocobase/plugin-block-template": "1.7.0-beta.18",
+ "@nocobase/plugin-block-workbench": "1.7.0-beta.18",
+ "@nocobase/plugin-calendar": "1.7.0-beta.18",
+ "@nocobase/plugin-charts": "1.7.0-beta.18",
+ "@nocobase/plugin-client": "1.7.0-beta.18",
+ "@nocobase/plugin-collection-sql": "1.7.0-beta.18",
+ "@nocobase/plugin-collection-tree": "1.7.0-beta.18",
+ "@nocobase/plugin-data-source-main": "1.7.0-beta.18",
+ "@nocobase/plugin-data-source-manager": "1.7.0-beta.18",
+ "@nocobase/plugin-data-visualization": "1.7.0-beta.18",
+ "@nocobase/plugin-departments": "1.7.0-beta.18",
+ "@nocobase/plugin-environment-variables": "1.7.0-beta.18",
+ "@nocobase/plugin-error-handler": "1.7.0-beta.18",
+ "@nocobase/plugin-field-attachment-url": "1.7.0-beta.18",
+ "@nocobase/plugin-field-china-region": "1.7.0-beta.18",
+ "@nocobase/plugin-field-formula": "1.7.0-beta.18",
+ "@nocobase/plugin-field-m2m-array": "1.7.0-beta.18",
+ "@nocobase/plugin-field-markdown-vditor": "1.7.0-beta.18",
+ "@nocobase/plugin-field-sequence": "1.7.0-beta.18",
+ "@nocobase/plugin-field-sort": "1.7.0-beta.18",
+ "@nocobase/plugin-file-manager": "1.7.0-beta.18",
+ "@nocobase/plugin-gantt": "1.7.0-beta.18",
+ "@nocobase/plugin-graph-collection-manager": "1.7.0-beta.18",
+ "@nocobase/plugin-kanban": "1.7.0-beta.18",
+ "@nocobase/plugin-locale-tester": "1.7.0-beta.18",
+ "@nocobase/plugin-localization": "1.7.0-beta.18",
+ "@nocobase/plugin-logger": "1.7.0-beta.18",
+ "@nocobase/plugin-map": "1.7.0-beta.18",
+ "@nocobase/plugin-mobile": "1.7.0-beta.18",
+ "@nocobase/plugin-mobile-client": "1.7.0-beta.18",
+ "@nocobase/plugin-mock-collections": "1.7.0-beta.18",
+ "@nocobase/plugin-multi-app-manager": "1.7.0-beta.18",
+ "@nocobase/plugin-multi-app-share-collection": "1.7.0-beta.18",
+ "@nocobase/plugin-notification-email": "1.7.0-beta.18",
+ "@nocobase/plugin-notification-in-app-message": "1.7.0-beta.18",
+ "@nocobase/plugin-notification-manager": "1.7.0-beta.18",
+ "@nocobase/plugin-public-forms": "1.7.0-beta.18",
+ "@nocobase/plugin-snapshot-field": "1.7.0-beta.18",
+ "@nocobase/plugin-system-settings": "1.7.0-beta.18",
+ "@nocobase/plugin-theme-editor": "1.7.0-beta.18",
+ "@nocobase/plugin-ui-schema-storage": "1.7.0-beta.18",
+ "@nocobase/plugin-user-data-sync": "1.7.0-beta.18",
+ "@nocobase/plugin-users": "1.7.0-beta.18",
+ "@nocobase/plugin-verification": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-action-trigger": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-aggregate": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-delay": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-dynamic-calculation": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-loop": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-mailer": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-manual": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-notification": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-parallel": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-request": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-response-message": "1.7.0-beta.18",
+ "@nocobase/plugin-workflow-sql": "1.7.0-beta.18",
+ "@nocobase/server": "1.7.0-beta.18",
"cronstrue": "^2.11.0",
"fs-extra": "^11.1.1"
},
diff --git a/yarn.lock b/yarn.lock
index 76cd3788b0..a03e37b526 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3194,7 +3194,7 @@
resolved "https://registry.npmmirror.com/@cfworker/json-schema/-/json-schema-4.1.1.tgz#4a2a3947ee9fa7b7c24be981422831b8674c3be6"
integrity sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==
-"@codemirror/autocomplete@^6.0.0":
+"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.3.2":
version "6.18.6"
resolved "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz#de26e864a1ec8192a1b241eb86addbb612964ddb"
integrity sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==
@@ -3214,7 +3214,25 @@
"@codemirror/view" "^6.27.0"
"@lezer/common" "^1.1.0"
-"@codemirror/lang-javascript@^6.2.2":
+"@codemirror/commands@^6.1.0":
+ version "6.8.1"
+ resolved "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.8.1.tgz#639f5559d2f33f2582a2429c58cb0c1b925c7a30"
+ integrity sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==
+ dependencies:
+ "@codemirror/language" "^6.0.0"
+ "@codemirror/state" "^6.4.0"
+ "@codemirror/view" "^6.27.0"
+ "@lezer/common" "^1.1.0"
+
+"@codemirror/lang-java@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.npmmirror.com/@codemirror/lang-java/-/lang-java-6.0.1.tgz#03bd06334da7c8feb9dff6db01ac6d85bd2e48bb"
+ integrity sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==
+ dependencies:
+ "@codemirror/language" "^6.0.0"
+ "@lezer/java" "^1.0.0"
+
+"@codemirror/lang-javascript@^6.2.2", "@codemirror/lang-javascript@^6.2.3":
version "6.2.3"
resolved "https://registry.npmmirror.com/@codemirror/lang-javascript/-/lang-javascript-6.2.3.tgz#d705c359dc816afcd3bcdf120a559f83d31d4cda"
integrity sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==
@@ -3227,6 +3245,29 @@
"@lezer/common" "^1.0.0"
"@lezer/javascript" "^1.0.0"
+"@codemirror/lang-python@^6.1.7":
+ version "6.1.7"
+ resolved "https://registry.npmmirror.com/@codemirror/lang-python/-/lang-python-6.1.7.tgz#1906234d453517d760a57c69672f0023a8649e14"
+ integrity sha512-mZnFTsL4lW5p9ch8uKNKeRU3xGGxr1QpESLilfON2E3fQzOa/OygEMkaDvERvXDJWJA9U9oN/D4w0ZuUzNO4+g==
+ dependencies:
+ "@codemirror/autocomplete" "^6.3.2"
+ "@codemirror/language" "^6.8.0"
+ "@codemirror/state" "^6.0.0"
+ "@lezer/common" "^1.2.1"
+ "@lezer/python" "^1.1.4"
+
+"@codemirror/lang-sql@^6.8.0":
+ version "6.8.0"
+ resolved "https://registry.npmmirror.com/@codemirror/lang-sql/-/lang-sql-6.8.0.tgz#1ae68ad49f378605ff88a4cc428ba667ce056068"
+ integrity sha512-aGLmY4OwGqN3TdSx3h6QeA1NrvaYtF7kkoWR/+W7/JzB0gQtJ+VJxewlnE3+VImhA4WVlhmkJr109PefOOhjLg==
+ dependencies:
+ "@codemirror/autocomplete" "^6.0.0"
+ "@codemirror/language" "^6.0.0"
+ "@codemirror/state" "^6.0.0"
+ "@lezer/common" "^1.2.0"
+ "@lezer/highlight" "^1.0.0"
+ "@lezer/lr" "^1.0.0"
+
"@codemirror/language@^6.0.0", "@codemirror/language@^6.6.0":
version "6.10.8"
resolved "https://registry.npmmirror.com/@codemirror/language/-/language-6.10.8.tgz#3e3a346a2b0a8cf63ee1cfe03349eb1965dce5f9"
@@ -3239,6 +3280,25 @@
"@lezer/lr" "^1.0.0"
style-mod "^4.0.0"
+"@codemirror/language@^6.11.0", "@codemirror/language@^6.8.0":
+ version "6.11.0"
+ resolved "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.0.tgz#5ae90972601497f4575f30811519d720bf7232c9"
+ integrity sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==
+ dependencies:
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.23.0"
+ "@lezer/common" "^1.1.0"
+ "@lezer/highlight" "^1.0.0"
+ "@lezer/lr" "^1.0.0"
+ style-mod "^4.0.0"
+
+"@codemirror/legacy-modes@^6.5.0":
+ version "6.5.0"
+ resolved "https://registry.npmmirror.com/@codemirror/legacy-modes/-/legacy-modes-6.5.0.tgz#21c8cf818f9ea4d6eba9f22afdfef010d1d9f754"
+ integrity sha512-dNw5pwTqtR1giYjaJyEajunLqxGavZqV0XRtVZyMJnNOD2HmK9DMUmuCAr6RMFGRJ4l8OeQDjpI/us+R09mQsw==
+ dependencies:
+ "@codemirror/language" "^6.0.0"
+
"@codemirror/lint@^6.0.0", "@codemirror/lint@^6.8.2":
version "6.8.4"
resolved "https://registry.npmmirror.com/@codemirror/lint/-/lint-6.8.4.tgz#7d8aa5d1a6dec89ffcc23ad45ddca2e12e90982d"
@@ -3257,13 +3317,23 @@
"@codemirror/view" "^6.0.0"
crelt "^1.0.5"
-"@codemirror/state@^6.0.0", "@codemirror/state@^6.4.0", "@codemirror/state@^6.4.1", "@codemirror/state@^6.5.0":
+"@codemirror/state@^6.0.0", "@codemirror/state@^6.1.1", "@codemirror/state@^6.4.0", "@codemirror/state@^6.4.1", "@codemirror/state@^6.5.0":
version "6.5.2"
resolved "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.2.tgz#8eca3a64212a83367dc85475b7d78d5c9b7076c6"
integrity sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==
dependencies:
"@marijn/find-cluster-break" "^1.0.0"
+"@codemirror/theme-one-dark@^6.0.0":
+ version "6.1.2"
+ resolved "https://registry.npmmirror.com/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz#fcef9f9cfc17a07836cb7da17c9f6d7231064df8"
+ integrity sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==
+ dependencies:
+ "@codemirror/language" "^6.0.0"
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.0.0"
+ "@lezer/highlight" "^1.0.0"
+
"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.32.0", "@codemirror/view@^6.35.0":
version "6.36.4"
resolved "https://registry.npmmirror.com/@codemirror/view/-/view-6.36.4.tgz#d47d38b92a22cc40647bfb9cc97944e13d44942d"
@@ -6016,7 +6086,7 @@
npmlog "^4.1.2"
write-file-atomic "^3.0.3"
-"@lezer/common@^1.0.0", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0":
+"@lezer/common@^1.0.0", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0", "@lezer/common@^1.2.1":
version "1.2.3"
resolved "https://registry.npmmirror.com/@lezer/common/-/common-1.2.3.tgz#138fcddab157d83da557554851017c6c1e5667fd"
integrity sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==
@@ -6028,6 +6098,15 @@
dependencies:
"@lezer/common" "^1.0.0"
+"@lezer/java@^1.0.0":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@lezer/java/-/java-1.1.3.tgz#9efd6a29b4142d07f211076a6fb5e8061c85e147"
+ integrity sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==
+ dependencies:
+ "@lezer/common" "^1.2.0"
+ "@lezer/highlight" "^1.0.0"
+ "@lezer/lr" "^1.0.0"
+
"@lezer/javascript@^1.0.0":
version "1.4.21"
resolved "https://registry.npmmirror.com/@lezer/javascript/-/javascript-1.4.21.tgz#8ebf7d1f891c70e3d00864f5a03ac42c75d19492"
@@ -6044,6 +6123,15 @@
dependencies:
"@lezer/common" "^1.0.0"
+"@lezer/python@^1.1.4":
+ version "1.1.17"
+ resolved "https://registry.npmmirror.com/@lezer/python/-/python-1.1.17.tgz#de0529e14605430ca8935fe4b3df0252ad173318"
+ integrity sha512-Iz0doICPko9uv2chIfSsViNSugNa4PWhxs17jtFd0ZMt+OieDq3wxtFOdmj7wtst3FWDeJkB0CxWNot0BlYixw==
+ dependencies:
+ "@lezer/common" "^1.2.0"
+ "@lezer/highlight" "^1.0.0"
+ "@lezer/lr" "^1.0.0"
+
"@ljharb/resumer@~0.0.1":
version "0.0.1"
resolved "https://registry.npmmirror.com/@ljharb/resumer/-/resumer-0.0.1.tgz#8a940a9192dd31f6a1df17564bbd26dc6ad3e68d"
@@ -6105,33 +6193,48 @@
resolved "https://registry.npmmirror.com/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.40.0.tgz#65f51600ab45ace97d7b1368c47f9e0f835fddca"
integrity sha512-1fcPVrB/NkbNcGNfCy+Cgnvwxt6/sbIEEFgZHFBJ670zYLegENYJF8qMo7x3LqBjWX2/Eneq5BVVRCLTmlJN+g==
-"@module-federation/runtime-tools@0.5.1":
- version "0.5.1"
- resolved "https://registry.npmmirror.com/@module-federation/runtime-tools/-/runtime-tools-0.5.1.tgz#1b1f93837159a6bf0c0ba78730d589a5a8f74aa3"
- integrity sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg==
- dependencies:
- "@module-federation/runtime" "0.5.1"
- "@module-federation/webpack-bundler-runtime" "0.5.1"
+"@module-federation/error-codes@0.11.2":
+ version "0.11.2"
+ resolved "https://registry.npmmirror.com/@module-federation/error-codes/-/error-codes-0.11.2.tgz#880cbaf370bacb5d27e5149a93228aebe7ed084c"
+ integrity sha512-ik1Qnn0I+WyEdprTck9WGlH41vGsVdUg8cfO+ZM02qOb2cZm5Vu3SlxGAobj6g7uAj0g8yINnd7h7Dci40BxQA==
-"@module-federation/runtime@0.5.1":
- version "0.5.1"
- resolved "https://registry.npmmirror.com/@module-federation/runtime/-/runtime-0.5.1.tgz#b548a75e2068952ff66ad717cbf73fc921edd5d7"
- integrity sha512-xgiMUWwGLWDrvZc9JibuEbXIbhXg6z2oUkemogSvQ4LKvrl/n0kbqP1Blk669mXzyWbqtSp6PpvNdwaE1aN5xQ==
+"@module-federation/runtime-core@0.11.2":
+ version "0.11.2"
+ resolved "https://registry.npmmirror.com/@module-federation/runtime-core/-/runtime-core-0.11.2.tgz#677aced902d56afd3e44f4033e8d78d57c8aa029"
+ integrity sha512-dia5kKybi6MFU0s5PgglJwN27k7n9Sf69Cy5xZ4BWaP0qlaXTsxHKO0PECHNt2Pt8jDdyU29sQ4DwAQfxpnXJQ==
dependencies:
- "@module-federation/sdk" "0.5.1"
+ "@module-federation/error-codes" "0.11.2"
+ "@module-federation/sdk" "0.11.2"
-"@module-federation/sdk@0.5.1":
- version "0.5.1"
- resolved "https://registry.npmmirror.com/@module-federation/sdk/-/sdk-0.5.1.tgz#6c0a4053c23fa84db7aae7e4736496c541de7191"
- integrity sha512-exvchtjNURJJkpqjQ3/opdbfeT2wPKvrbnGnyRkrwW5o3FH1LaST1tkiNviT6OXTexGaVc2DahbdniQHVtQ7pA==
-
-"@module-federation/webpack-bundler-runtime@0.5.1":
- version "0.5.1"
- resolved "https://registry.npmmirror.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.5.1.tgz#ef626af0d57e3568c474d66d7d3797366e09cafd"
- integrity sha512-mMhRFH0k2VjwHt3Jol9JkUsmI/4XlrAoBG3E0o7HoyoPYv1UFOWyqAflfANcUPgbYpvqmyLzDcO+3IT36LXnrA==
+"@module-federation/runtime-tools@0.11.2":
+ version "0.11.2"
+ resolved "https://registry.npmmirror.com/@module-federation/runtime-tools/-/runtime-tools-0.11.2.tgz#d6a5c4f61b93b647de656b08ba465590631a1316"
+ integrity sha512-4MJTGAxVq6vxQRkTtTlH7Mm9AVqgn0X9kdu+7RsL7T/qU+jeYsbrntN2CWG3GVVA8r5JddXyTI1iJ0VXQZLV1w==
dependencies:
- "@module-federation/runtime" "0.5.1"
- "@module-federation/sdk" "0.5.1"
+ "@module-federation/runtime" "0.11.2"
+ "@module-federation/webpack-bundler-runtime" "0.11.2"
+
+"@module-federation/runtime@0.11.2":
+ version "0.11.2"
+ resolved "https://registry.npmmirror.com/@module-federation/runtime/-/runtime-0.11.2.tgz#e623136774599ce202bb4ea1e396f18fbaeee19a"
+ integrity sha512-Ya9u/L6z2LvhgpqxuKCB7LcigIIRf1BbaxAZIH7mzbq/A7rZtTP7v+73E433jvgiAlbAfPSZkeoYGele6hfRwA==
+ dependencies:
+ "@module-federation/error-codes" "0.11.2"
+ "@module-federation/runtime-core" "0.11.2"
+ "@module-federation/sdk" "0.11.2"
+
+"@module-federation/sdk@0.11.2":
+ version "0.11.2"
+ resolved "https://registry.npmmirror.com/@module-federation/sdk/-/sdk-0.11.2.tgz#965b0dcf8fb036dda9b1e6812d6ae0a394ea827d"
+ integrity sha512-SBFe5xOamluT900J4AGBx+2/kCH/JbfqXoUwPSAC6PRzb8Y7LB0posnOGzmqYsLZXT37vp3d6AmJDsVoajDqxw==
+
+"@module-federation/webpack-bundler-runtime@0.11.2":
+ version "0.11.2"
+ resolved "https://registry.npmmirror.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.11.2.tgz#ef4a21e0ff8aefce9c264a57aa882ee72ecfe6aa"
+ integrity sha512-WdwIE6QF+MKs/PdVu0cKPETF743JB9PZ62/qf7Uo3gU4fjsUMc37RnbJZ/qB60EaHHfjwp1v6NnhZw1r4eVsnw==
+ dependencies:
+ "@module-federation/runtime" "0.11.2"
+ "@module-federation/sdk" "0.11.2"
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
@@ -6367,36 +6470,6 @@
dependencies:
"@octokit/openapi-types" "^12.11.0"
-"@opencensus/core@0.0.9":
- version "0.0.9"
- resolved "https://registry.npmmirror.com/@opencensus/core/-/core-0.0.9.tgz#b16f775435ee309433e4126af194d37313fc93b3"
- integrity sha512-31Q4VWtbzXpVUd2m9JS6HEaPjlKvNMOiF7lWKNmXF84yUcgfAFL5re7/hjDmdyQbOp32oGc+RFV78jXIldVz6Q==
- dependencies:
- continuation-local-storage "^3.2.1"
- log-driver "^1.2.7"
- semver "^5.5.0"
- shimmer "^1.2.0"
- uuid "^3.2.1"
-
-"@opencensus/core@^0.0.8":
- version "0.0.8"
- resolved "https://registry.npmmirror.com/@opencensus/core/-/core-0.0.8.tgz#df01f200c2d2fbfe14dae129a1a86fb87286db92"
- integrity sha512-yUFT59SFhGMYQgX0PhoTR0LBff2BEhPrD9io1jWfF/VDbakRfs6Pq60rjv0Z7iaTav5gQlttJCX2+VPxFWCuoQ==
- dependencies:
- continuation-local-storage "^3.2.1"
- log-driver "^1.2.7"
- semver "^5.5.0"
- shimmer "^1.2.0"
- uuid "^3.2.1"
-
-"@opencensus/propagation-b3@0.0.8":
- version "0.0.8"
- resolved "https://registry.npmmirror.com/@opencensus/propagation-b3/-/propagation-b3-0.0.8.tgz#0751e6fd75f09400d9d3c419001e9e15a0df68e9"
- integrity sha512-PffXX2AL8Sh0VHQ52jJC4u3T0H6wDK6N/4bg7xh4ngMYOIi13aR1kzVvX1sVDBgfGwDOkMbl4c54Xm3tlPx/+A==
- dependencies:
- "@opencensus/core" "^0.0.8"
- uuid "^3.2.1"
-
"@opentelemetry/api@^1.7.0":
version "1.7.0"
resolved "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.7.0.tgz#b139c81999c23e3c8d3c0a7234480e945920fc40"
@@ -6563,32 +6636,29 @@
dependencies:
playwright "1.45.3"
-"@pm2/agent@~2.0.0":
- version "2.0.3"
- resolved "https://registry.npmmirror.com/@pm2/agent/-/agent-2.0.3.tgz#6b47fda837f185864767fe1e048f61d1de31fc45"
- integrity sha512-xkqqCoTf5VsciMqN0vb9jthW7olVAi4KRFNddCc7ZkeJZ3i8QwZANr4NSH2H5DvseRFHq7MiPspRY/EWAFWWTg==
+"@pm2/agent@~2.1.1":
+ version "2.1.1"
+ resolved "https://registry.npmmirror.com/@pm2/agent/-/agent-2.1.1.tgz#b74dc0cc97e59827307fd6b9a4ebb5aeb40473fb"
+ integrity sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==
dependencies:
async "~3.2.0"
chalk "~3.0.0"
dayjs "~1.8.24"
debug "~4.3.1"
eventemitter2 "~5.0.1"
- fast-json-patch "^3.0.0-1"
+ fast-json-patch "^3.1.0"
fclone "~1.0.11"
- nssocket "0.6.0"
pm2-axon "~4.0.1"
pm2-axon-rpc "~0.7.0"
- proxy-agent "~6.3.0"
+ proxy-agent "~6.4.0"
semver "~7.5.0"
- ws "~7.4.0"
+ ws "~7.5.10"
-"@pm2/io@~5.0.0":
- version "5.0.2"
- resolved "https://registry.npmmirror.com/@pm2/io/-/io-5.0.2.tgz#5e4177281280082d7c490bb776fad7f8448c6bca"
- integrity sha512-XAvrNoQPKOyO/jJyCu8jPhLzlyp35MEf7w/carHXmWKddPzeNOFSEpSEqMzPDawsvpxbE+i918cNN+MwgVsStA==
+"@pm2/io@~6.1.0":
+ version "6.1.0"
+ resolved "https://registry.npmmirror.com/@pm2/io/-/io-6.1.0.tgz#37ea7908e2a1dd7b88862f60d98d8b1f495ee16a"
+ integrity sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==
dependencies:
- "@opencensus/core" "0.0.9"
- "@opencensus/propagation-b3" "0.0.8"
async "~2.6.1"
debug "~4.3.1"
eventemitter2 "^6.3.1"
@@ -6598,15 +6668,15 @@
signal-exit "^3.0.3"
tslib "1.9.3"
-"@pm2/js-api@~0.6.7":
- version "0.6.7"
- resolved "https://registry.npmmirror.com/@pm2/js-api/-/js-api-0.6.7.tgz#ed28c3b7b6d26f03f826318754fdc5468afa589f"
- integrity sha512-jiJUhbdsK+5C4zhPZNnyA3wRI01dEc6a2GhcQ9qI38DyIk+S+C8iC3fGjcjUbt/viLYKPjlAaE+hcT2/JMQPXw==
+"@pm2/js-api@~0.8.0":
+ version "0.8.0"
+ resolved "https://registry.npmmirror.com/@pm2/js-api/-/js-api-0.8.0.tgz#d1b8aff562dd34befa3cb30fe28e08c9f9743abc"
+ integrity sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==
dependencies:
async "^2.6.3"
- axios "^0.21.0"
debug "~4.3.1"
eventemitter2 "^6.3.1"
+ extrareqp2 "^1.0.0"
ws "^7.0.0"
"@pm2/pm2-version-check@latest":
@@ -7087,75 +7157,76 @@
rslog "^1.2.3"
strip-ansi "^6.0.1"
-"@rspack/binding-darwin-arm64@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.1.1.tgz#e03df97bebab2ef6ccbbef940c8ef092b37c8336"
- integrity sha512-BnvGPWObGZ2ZVnxe4K3NKwAWxYubOJvfwporXWD3NgkzeV5xJqGBFWRDnr/nfsFpgCTI8goxK5db/wb7NVzLqg==
+"@rspack/binding-darwin-arm64@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.2.tgz#7d7d9db21489d4afb0c5f05d9af4b879f4f2751f"
+ integrity sha512-oeZvdHCY3XML8U6npof3b7uNVmNMTIRccPe2IDHlV1zk1MPfBzgrKOKmo1V8kqI43xAWET7CpAX9C+TjDDcy/g==
-"@rspack/binding-darwin-x64@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.1.1.tgz#ce3893eee19e4f43b27e56b0fd2737b97efd69c0"
- integrity sha512-aiwJRkPGAg99vCrG/C9I87Fh9TShOAkzpf2yeJEZL4gwTj9A8wrc/xlrCFn1BDkbPnGYz62oCR7z6JLIDgYLuA==
+"@rspack/binding-darwin-x64@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.3.2.tgz#e9b910b70911c6f5e16099b7ba7c0e65ee119497"
+ integrity sha512-V1IKH3I0uEf4vjou158amWgpAUz9MgGiFU09LgZS/hz1jYMTCi3Z791EEL4Gz6iqAixIZxtw6aYeotjRJ4Kyqg==
-"@rspack/binding-linux-arm64-gnu@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.1.tgz#b9ba4d0cfc39fec5c2db9d3f75327c3b8a383e96"
- integrity sha512-2Z8YxH4+V0MiNhVQ2IFELDIFtykIdKgmOmGr/PuRQMHMxSn8AKo5uqBD30sZqe0+gryplZwK3hyrBETHOmSltQ==
+"@rspack/binding-linux-arm64-gnu@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.2.tgz#b7e540c1b95cb7547aa2d346ef113db30bc75988"
+ integrity sha512-nJzY+Ur6FxWM0xc+G2tY1TQu3s6qgolxXb5K2VLIDHSPqDAjqRc35ypQc9Tz3rUPb8HVh+X7YLIZxA0hE4eQOg==
-"@rspack/binding-linux-arm64-musl@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.1.tgz#bb98d98d703c6a0c69489975821a3d3236fa91cf"
- integrity sha512-l+cJd3wAxBt523Min7qN+G5s3SU0rif9Yq2AFWWl+R6IvmnMlMq6sAAyiyogUidFmJ5XIKSJJBTBnvLF3g4ezg==
+"@rspack/binding-linux-arm64-musl@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.3.2.tgz#29f7bc0461a4ec91b3cd86b21393360c73894b19"
+ integrity sha512-sRi77ccO/oOfyBNq3FgW2pDtXcgMzslLokOby8NpD/kv/SxtOE4ORoLZKzdJyGNh2WDPbtSwIDWPes2x4MKASQ==
-"@rspack/binding-linux-x64-gnu@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.1.tgz#17d7ceac270ffc5980a16e31badef68136b31d51"
- integrity sha512-goaDDrXNulR7FcvUfj8AjhF3g7IXUttjQ4QsfY2xz7s20tDETlq5HpcM2A8GEI6lqkPAv/ITU0AynLK7bfyr4A==
+"@rspack/binding-linux-x64-gnu@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.2.tgz#8771bfa8d18198e8b776d7e0ee33b5d29f0ccabe"
+ integrity sha512-KnrFQUj6SKJFGXqJW9Kgdv+mRGcPCirQesuwXtW+9YejT6MzLRRdJ4NDQdfcmfLZK9+ap+l73bLXAyMiIBZiOw==
-"@rspack/binding-linux-x64-musl@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.1.tgz#bfc6363ae73ffd04e7af00f134d640ec0407a730"
- integrity sha512-T4RRn9ycxUHAfZJpfNRy+DdfevTXIZqox+NNg/N3d+Pqj5QS3zqpHBfPLC2mIIN1dw55BoshRIP2C1hUG0Fk6g==
+"@rspack/binding-linux-x64-musl@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.3.2.tgz#7ea645db3bef68f162a06c16cfa0563576bb51ef"
+ integrity sha512-ZcTl4LBgxp5Bfyu9x7NhYRAR4qWPwhhxzwXmiQ1ya7DsdqiYaiCr59dPQx7ZaExXckeHGly75B3aTn1II9Vexw==
-"@rspack/binding-win32-arm64-msvc@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.1.tgz#efbd8c90d0097907104da2f5d75416655cfb0f60"
- integrity sha512-FHIPpueFc/+vWdZeVWRYWW0Z0IsDIHy+WhWxITeLjOVGsUN4rhaztYOausD7WsOlOhmR0SddeOYtRs/BR35wig==
+"@rspack/binding-win32-arm64-msvc@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.3.2.tgz#2ac75b045117301ea1fc7fd44a8428fbcec6d805"
+ integrity sha512-8volxqn9vps8XKj0DTRk/4d5TXL+vkaBRWF7CzzdfZYm/smvrdz2Iw7VmcACA7XaS41xqeTtrdq6CmaxC/4CFg==
-"@rspack/binding-win32-ia32-msvc@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.1.1.tgz#73f7c78bb4398e009708e6523e20222967ec9568"
- integrity sha512-pgXE45ATK/Iil/oXlqaGoWZ0x3SoQk4dAjJGK7TzQuek6UEoJbLQL+W1ufe/iUxz67ICAmUvq5NH2ftOhEE2SA==
+"@rspack/binding-win32-ia32-msvc@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.3.2.tgz#5f7c1d6182c7d442bd39e0e3b831e92c662242aa"
+ integrity sha512-jTIiV4pt62xK3qNqI88F8rM+ynM36UmbZ8CRFqXRHdC+Cx/dUmk83IGQr9DNvjM7we7BxUm3Shmi1f0KyZrBKw==
-"@rspack/binding-win32-x64-msvc@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.1.tgz#5b65e210d9a0dc042059399469ca5beeea54b1ee"
- integrity sha512-z/kdbB+uhMi+H4podjTE7bfUpahACUuPOZPUtAAA6PMgRyiigBTK5UFYN35D30MONwZP4yNiLvPjurwiLw7EpA==
+"@rspack/binding-win32-x64-msvc@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.3.2.tgz#1e11d6233aa9098952b6a5893beb02fbf9fc26f1"
+ integrity sha512-DfQmL7LsqD7KEZv8/z0p6AkwQAGlv5fvl5X5z4bxyRc4JMvEPBxY8lW9iK5Hk66ECzERUI2HcQ0JbRD/e4oL8A==
-"@rspack/binding@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/binding/-/binding-1.1.1.tgz#e37e0c34e723655775d33a72ba663c84a1310c0f"
- integrity sha512-BRFliHbErqWrUo9X9bdik9WTRi6EgrJSQbbUiVeIYgW4gzYdfHUohgTkWo2Byu36LZolKrEjq/Uq2A8q/tc0YA==
+"@rspack/binding@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/binding/-/binding-1.3.2.tgz#75aea649ee0db73dca98b8d939bb0afd38bbd658"
+ integrity sha512-QK+nHPDQGv16mBpJa5vULDrqDilgiFZ/BbGCZoCZRX373R9s0Doe6DBbty+RfTJwCsalF3r8X6MdWfy7UPu6Hw==
optionalDependencies:
- "@rspack/binding-darwin-arm64" "1.1.1"
- "@rspack/binding-darwin-x64" "1.1.1"
- "@rspack/binding-linux-arm64-gnu" "1.1.1"
- "@rspack/binding-linux-arm64-musl" "1.1.1"
- "@rspack/binding-linux-x64-gnu" "1.1.1"
- "@rspack/binding-linux-x64-musl" "1.1.1"
- "@rspack/binding-win32-arm64-msvc" "1.1.1"
- "@rspack/binding-win32-ia32-msvc" "1.1.1"
- "@rspack/binding-win32-x64-msvc" "1.1.1"
+ "@rspack/binding-darwin-arm64" "1.3.2"
+ "@rspack/binding-darwin-x64" "1.3.2"
+ "@rspack/binding-linux-arm64-gnu" "1.3.2"
+ "@rspack/binding-linux-arm64-musl" "1.3.2"
+ "@rspack/binding-linux-x64-gnu" "1.3.2"
+ "@rspack/binding-linux-x64-musl" "1.3.2"
+ "@rspack/binding-win32-arm64-msvc" "1.3.2"
+ "@rspack/binding-win32-ia32-msvc" "1.3.2"
+ "@rspack/binding-win32-x64-msvc" "1.3.2"
-"@rspack/core@1.1.1":
- version "1.1.1"
- resolved "https://registry.npmmirror.com/@rspack/core/-/core-1.1.1.tgz#69f795225e31f51dff6b0ccfcebcc07accdac4c8"
- integrity sha512-khYNAho2evyc7N5mYk4K6B587ou/dN1CDCqWrSDeZZNFFQHtuEp5T3kL1ntsKY7agObQhI60osCYaxFUPs0yww==
+"@rspack/core@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.npmmirror.com/@rspack/core/-/core-1.3.2.tgz#622e04228b39e442b7c8178bb0b71219b3f14f28"
+ integrity sha512-QbEn1SkNW3b89KTlSkp6OHdvw3DhpL6tSdDhsOlldw3LoRBy4fx80Z9W9lmg+g+8DjTAs1Z1ysElEFtAN69AZg==
dependencies:
- "@module-federation/runtime-tools" "0.5.1"
- "@rspack/binding" "1.1.1"
+ "@module-federation/runtime-tools" "0.11.2"
+ "@rspack/binding" "1.3.2"
"@rspack/lite-tapable" "1.0.1"
- caniuse-lite "^1.0.30001616"
+ caniuse-lite "^1.0.30001707"
+ tinypool "^1.0.2"
"@rspack/lite-tapable@1.0.1":
version "1.0.1"
@@ -9631,6 +9702,31 @@
"@typescript-eslint/types" "6.21.0"
eslint-visitor-keys "^3.4.1"
+"@uiw/codemirror-extensions-basic-setup@4.23.10":
+ version "4.23.10"
+ resolved "https://registry.npmmirror.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.10.tgz#e5d901e860a039ac61d955af26a12866e9dc356c"
+ integrity sha512-zpbmSeNs3OU/f/Eyd6brFnjsBUYwv2mFjWxlAsIRSwTlW+skIT60rQHFBSfsj/5UVSxSLWVeUYczN7AyXvgTGQ==
+ dependencies:
+ "@codemirror/autocomplete" "^6.0.0"
+ "@codemirror/commands" "^6.0.0"
+ "@codemirror/language" "^6.0.0"
+ "@codemirror/lint" "^6.0.0"
+ "@codemirror/search" "^6.0.0"
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.0.0"
+
+"@uiw/react-codemirror@4.23.10":
+ version "4.23.10"
+ resolved "https://registry.npmmirror.com/@uiw/react-codemirror/-/react-codemirror-4.23.10.tgz#2e34aec4f65f901ed8e9b8a22e28f2177addce69"
+ integrity sha512-AbN4eVHOL4ckRuIXpZxkzEqL/1ChVA+BSdEnAKjIB68pLQvKsVoYbiFP8zkXkYc4+Fcgq5KbAjvYqdo4ewemKw==
+ dependencies:
+ "@babel/runtime" "^7.18.6"
+ "@codemirror/commands" "^6.1.0"
+ "@codemirror/state" "^6.1.1"
+ "@codemirror/theme-one-dark" "^6.0.0"
+ "@uiw/codemirror-extensions-basic-setup" "4.23.10"
+ codemirror "^6.0.0"
+
"@umijs/ast@4.0.89":
version "4.0.89"
resolved "https://registry.npmmirror.com/@umijs/ast/-/ast-4.0.89.tgz#af7591eb9447c7adfb6f9704fd177542bae7c0d3"
@@ -10112,10 +10208,10 @@
resolved "https://registry.npmmirror.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
-"@zeit/schemas@2.6.0":
- version "2.6.0"
- resolved "https://registry.npmmirror.com/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3"
- integrity sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==
+"@zeit/schemas@2.36.0":
+ version "2.36.0"
+ resolved "https://registry.npmmirror.com/@zeit/schemas/-/schemas-2.36.0.tgz#7a1b53f4091e18d0b404873ea3e3c83589c765f2"
+ integrity sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==
JSONStream@^1.0.4:
version "1.3.5"
@@ -10322,7 +10418,17 @@ ajv-keywords@^5.1.0:
dependencies:
fast-deep-equal "^3.1.3"
-ajv@6.12.6, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6:
+ajv@8.12.0:
+ version "8.12.0"
+ resolved "https://registry.npmmirror.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
+ integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js "^4.2.2"
+
+ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6:
version "6.12.6"
resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -10421,7 +10527,7 @@ ansi-align@^2.0.0:
dependencies:
string-width "^2.0.0"
-ansi-align@^3.0.0:
+ansi-align@^3.0.0, ansi-align@^3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
@@ -10696,7 +10802,7 @@ aproba@^1.0.3, aproba@^1.1.1:
resolved "https://registry.npmmirror.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
-arch@^2.1.1:
+arch@^2.1.1, arch@^2.2.0:
version "2.2.0"
resolved "https://registry.npmmirror.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
@@ -10800,21 +10906,16 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"
-arg@2.0.0:
- version "2.0.0"
- resolved "https://registry.npmmirror.com/arg/-/arg-2.0.0.tgz#c06e7ff69ab05b3a4a03ebe0407fac4cba657545"
- integrity sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==
+arg@5.0.2, arg@^5.0.0:
+ version "5.0.2"
+ resolved "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
+ integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.npmmirror.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
-arg@^5.0.0:
- version "5.0.2"
- resolved "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
- integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
-
argparse@^1.0.7, argparse@~1.0.9:
version "1.0.10"
resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -11139,14 +11240,6 @@ async-each@^1.0.1:
resolved "https://registry.npmmirror.com/async-each/-/async-each-1.0.6.tgz#52f1d9403818c179b7561e11a5d1b77eb2160e77"
integrity sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==
-async-listener@^0.6.0:
- version "0.6.10"
- resolved "https://registry.npmmirror.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc"
- integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==
- dependencies:
- semver "^5.3.0"
- shimmer "^1.1.0"
-
async-mutex@^0.3.2:
version "0.3.2"
resolved "https://registry.npmmirror.com/async-mutex/-/async-mutex-0.3.2.tgz#1485eda5bda1b0ec7c8df1ac2e815757ad1831df"
@@ -11185,6 +11278,11 @@ async@^3.2.0, async@^3.2.3, async@^3.2.4, async@~3.2.0:
resolved "https://registry.npmmirror.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
+async@~3.2.6:
+ version "3.2.6"
+ resolved "https://registry.npmmirror.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
+ integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
+
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -11288,13 +11386,6 @@ axios@^0.18.1:
follow-redirects "1.5.10"
is-buffer "^2.0.2"
-axios@^0.21.0:
- version "0.21.4"
- resolved "https://registry.npmmirror.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
- integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
- dependencies:
- follow-redirects "^1.14.0"
-
axios@^1.7.0:
version "1.7.7"
resolved "https://registry.npmmirror.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f"
@@ -11691,19 +11782,19 @@ bowser@^2.11.0:
resolved "https://registry.npmmirror.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f"
integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==
-boxen@5.1.2:
- version "5.1.2"
- resolved "https://registry.npmmirror.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
- integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==
+boxen@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmmirror.com/boxen/-/boxen-7.0.0.tgz#9e5f8c26e716793fc96edcf7cf754cdf5e3fbf32"
+ integrity sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==
dependencies:
- ansi-align "^3.0.0"
- camelcase "^6.2.0"
- chalk "^4.1.0"
- cli-boxes "^2.2.1"
- string-width "^4.2.2"
- type-fest "^0.20.2"
- widest-line "^3.1.0"
- wrap-ansi "^7.0.0"
+ ansi-align "^3.0.1"
+ camelcase "^7.0.0"
+ chalk "^5.0.1"
+ cli-boxes "^3.0.0"
+ string-width "^5.1.2"
+ type-fest "^2.13.0"
+ widest-line "^4.0.1"
+ wrap-ansi "^8.0.1"
boxen@^1.2.1:
version "1.3.0"
@@ -12289,6 +12380,11 @@ camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+camelcase@^7.0.0:
+ version "7.0.1"
+ resolved "https://registry.npmmirror.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048"
+ integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==
+
camelize@^1.0.0:
version "1.0.1"
resolved "https://registry.npmmirror.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3"
@@ -12299,16 +12395,21 @@ caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001565:
resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca"
integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==
-caniuse-lite@^1.0.30001616, caniuse-lite@^1.0.30001688:
- version "1.0.30001690"
- resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8"
- integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==
-
caniuse-lite@^1.0.30001646:
version "1.0.30001651"
resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138"
integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==
+caniuse-lite@^1.0.30001688:
+ version "1.0.30001690"
+ resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8"
+ integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==
+
+caniuse-lite@^1.0.30001707:
+ version "1.0.30001713"
+ resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz#6b33a8857e6c7dcb41a0caa2dd0f0489c823a52d"
+ integrity sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==
+
capture-stack-trace@^1.0.0:
version "1.0.2"
resolved "https://registry.npmmirror.com/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz#1c43f6b059d4249e7f3f8724f15f048b927d3a8a"
@@ -12372,14 +12473,12 @@ chainsaw@~0.1.0:
dependencies:
traverse ">=0.3.0 <0.4"
-chalk@2.4.1:
- version "2.4.1"
- resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
- integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==
+chalk-template@0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmmirror.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b"
+ integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==
dependencies:
- ansi-styles "^3.2.1"
- escape-string-regexp "^1.0.5"
- supports-color "^5.3.0"
+ chalk "^4.1.2"
chalk@2.4.2, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
@@ -12398,6 +12497,11 @@ chalk@3.0.0, chalk@^3.0.0, chalk@~3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chalk@5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmmirror.com/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6"
+ integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==
+
chalk@5.3.0, chalk@^5.3.0:
version "5.3.0"
resolved "https://registry.npmmirror.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
@@ -12422,6 +12526,11 @@ chalk@^4, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chalk@^5.0.1:
+ version "5.4.1"
+ resolved "https://registry.npmmirror.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8"
+ integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
+
character-entities-html4@^2.0.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b"
@@ -12630,11 +12739,16 @@ cli-boxes@^1.0.0:
resolved "https://registry.npmmirror.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
integrity sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg==
-cli-boxes@^2.2.0, cli-boxes@^2.2.1:
+cli-boxes@^2.2.0:
version "2.2.1"
resolved "https://registry.npmmirror.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
+cli-boxes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145"
+ integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==
+
cli-cursor@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
@@ -12722,6 +12836,15 @@ clipboardy@2.3.0:
execa "^1.0.0"
is-wsl "^2.1.1"
+clipboardy@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/clipboardy/-/clipboardy-3.0.0.tgz#f3876247404d334c9ed01b6f269c11d09a5e3092"
+ integrity sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==
+ dependencies:
+ arch "^2.2.0"
+ execa "^5.1.1"
+ is-wsl "^2.2.0"
+
cliui@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
@@ -12858,7 +12981,7 @@ code-point-at@^1.0.0:
resolved "https://registry.npmmirror.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==
-codemirror@^6.0.1:
+codemirror@^6.0.0, codemirror@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29"
integrity sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==
@@ -13117,26 +13240,39 @@ compress-commons@^5.0.1:
normalize-path "^3.0.0"
readable-stream "^3.6.0"
-compressible@~2.0.14:
+compressible@~2.0.16, compressible@~2.0.18:
version "2.0.18"
resolved "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
dependencies:
mime-db ">= 1.43.0 < 2"
-compression@1.7.3:
- version "1.7.3"
- resolved "https://registry.npmmirror.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db"
- integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==
+compression@1.7.4:
+ version "1.7.4"
+ resolved "https://registry.npmmirror.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
+ integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
dependencies:
accepts "~1.3.5"
bytes "3.0.0"
- compressible "~2.0.14"
+ compressible "~2.0.16"
debug "2.6.9"
- on-headers "~1.0.1"
+ on-headers "~1.0.2"
safe-buffer "5.1.2"
vary "~1.1.2"
+compression@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.npmmirror.com/compression/-/compression-1.8.0.tgz#09420efc96e11a0f44f3a558de59e321364180f7"
+ integrity sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==
+ dependencies:
+ bytes "3.1.2"
+ compressible "~2.0.18"
+ debug "2.6.9"
+ negotiator "~0.6.4"
+ on-headers "~1.0.2"
+ safe-buffer "5.2.1"
+ vary "~1.1.2"
+
compute-scroll-into-view@^3.0.2:
version "3.1.0"
resolved "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz#753f11d972596558d8fe7c6bcbc8497690ab4c87"
@@ -13292,14 +13428,6 @@ content-type@^1.0.2, content-type@^1.0.4, content-type@~1.0.4, content-type@~1.0
resolved "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
-continuation-local-storage@^3.2.1:
- version "3.2.1"
- resolved "https://registry.npmmirror.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb"
- integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==
- dependencies:
- async-listener "^0.6.0"
- emitter-listener "^1.1.1"
-
contour_plot@^0.0.1:
version "0.0.1"
resolved "https://registry.npmmirror.com/contour_plot/-/contour_plot-0.0.1.tgz#475870f032b8e338412aa5fc507880f0bf495c77"
@@ -14443,7 +14571,7 @@ dayjs-timezone-iana-plugin@=0.1.0:
resolved "https://registry.npmmirror.com/dayjs-timezone-iana-plugin/-/dayjs-timezone-iana-plugin-0.1.0.tgz#216613f6ec80106ab8be025cf5935018c901e997"
integrity sha512-xc8cIZmi4oKr2nfu41I/FDWZKa8n8YaRMxSz9MrpXTNo8c6ZsjZuIoy5RPNmLXPqntFuITWI8obB7lUA+CdzGQ==
-dayjs@1.11.13, dayjs@=1.11.11, dayjs@^1.11.10, dayjs@^1.11.11, dayjs@^1.11.7, dayjs@^1.11.8, dayjs@^1.11.9, dayjs@^1.8.34, dayjs@^1.9.1, dayjs@~1.11.5, dayjs@~1.8.24:
+dayjs@1.11.13, dayjs@=1.11.11, dayjs@^1.11.10, dayjs@^1.11.11, dayjs@^1.11.7, dayjs@^1.11.8, dayjs@^1.11.9, dayjs@^1.8.34, dayjs@^1.9.1, dayjs@~1.11.13, dayjs@~1.8.24:
version "1.11.13"
resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
@@ -14502,6 +14630,13 @@ debug@^3.1.0, debug@^3.2.6, debug@^3.2.7:
dependencies:
ms "^2.1.1"
+debug@^4.3.7:
+ version "4.4.0"
+ resolved "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
+ integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
+ dependencies:
+ ms "^2.1.3"
+
debug@~4.3.2, debug@~4.3.4:
version "4.3.7"
resolved "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
@@ -15423,13 +15558,6 @@ elliptic@^6.5.3, elliptic@^6.5.4:
minimalistic-assert "^1.0.1"
minimalistic-crypto-utils "^1.0.1"
-emitter-listener@^1.1.1:
- version "1.1.2"
- resolved "https://registry.npmmirror.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
- integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==
- dependencies:
- shimmer "^1.2.0"
-
emittery@^0.13.0:
version "0.13.1"
resolved "https://registry.npmmirror.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
@@ -16424,11 +16552,6 @@ eventemitter2@^6.3.1:
resolved "https://registry.npmmirror.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125"
integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==
-eventemitter2@~0.4.14:
- version "0.4.14"
- resolved "https://registry.npmmirror.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab"
- integrity sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==
-
eventemitter3@^2.0.0, eventemitter3@^2.0.3:
version "2.0.3"
resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
@@ -16697,6 +16820,13 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
+extrareqp2@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmmirror.com/extrareqp2/-/extrareqp2-1.0.0.tgz#aaf8ad1495d723f71276b0eab041c061aa21f035"
+ integrity sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==
+ dependencies:
+ follow-redirects "^1.14.0"
+
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@@ -16767,7 +16897,7 @@ fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1:
merge2 "^1.3.0"
micromatch "^4.0.4"
-fast-json-patch@^3.0.0-1:
+fast-json-patch@^3.1.0:
version "3.1.1"
resolved "https://registry.npmmirror.com/fast-json-patch/-/fast-json-patch-3.1.1.tgz#85064ea1b1ebf97a3f7ad01e23f9337e72c66947"
integrity sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==
@@ -16807,13 +16937,6 @@ fast-uri@^3.0.1:
resolved "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241"
integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==
-fast-url-parser@1.1.3:
- version "1.1.3"
- resolved "https://registry.npmmirror.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
- integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==
- dependencies:
- punycode "^1.3.2"
-
fast-xml-parser@4.2.5:
version "4.2.5"
resolved "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz#a6747a09296a6cb34f2ae634019bf1738f3b421f"
@@ -17414,6 +17537,15 @@ fs-extra@^11.1.1:
jsonfile "^6.0.1"
universalify "^2.0.0"
+fs-extra@^11.3.0:
+ version "11.3.0"
+ resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
+ integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
fs-extra@^3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291"
@@ -17994,7 +18126,7 @@ glob@^10.2.2, glob@^10.3.10:
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
-glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.3, glob@~7.2.3:
+glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.3, glob@~7.2.3:
version "7.2.3"
resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -18959,7 +19091,7 @@ http-proxy-agent@^4.0.1:
agent-base "6"
debug "4"
-http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.2:
+http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1, http-proxy-agent@^7.0.2:
version "7.0.2"
resolved "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e"
integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==
@@ -18997,7 +19129,7 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
-https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.1:
+https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.1, https-proxy-agent@^7.0.3:
version "7.0.6"
resolved "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
@@ -19883,6 +20015,11 @@ is-plain-object@^5.0.0:
resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
+is-port-reachable@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmmirror.com/is-port-reachable/-/is-port-reachable-4.0.0.tgz#dac044091ef15319c8ab2f34604d8794181f8c2d"
+ integrity sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==
+
is-potential-custom-element-name@^1.0.1:
version "1.0.1"
resolved "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
@@ -20377,7 +20514,7 @@ js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
-js-yaml@^4.1.0:
+js-yaml@^4.1.0, js-yaml@~4.1.0:
version "4.1.0"
resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
@@ -20940,11 +21077,6 @@ lazy-cache@^1.0.3, lazy-cache@^1.0.4:
resolved "https://registry.npmmirror.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
integrity sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==
-lazy@~1.0.11:
- version "1.0.11"
- resolved "https://registry.npmmirror.com/lazy/-/lazy-1.0.11.tgz#daa068206282542c088288e975c297c1ae77b690"
- integrity sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==
-
lazystream@^1.0.0:
version "1.0.1"
resolved "https://registry.npmmirror.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638"
@@ -21524,11 +21656,6 @@ lodash@4.17.21, lodash@4.x, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, l
resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-log-driver@^1.2.7:
- version "1.2.7"
- resolved "https://registry.npmmirror.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8"
- integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==
-
log-symbols@^2.1.0:
version "2.2.0"
resolved "https://registry.npmmirror.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
@@ -22692,13 +22819,6 @@ minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.npmmirror.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
-minimatch@3.0.4:
- version "3.0.4"
- resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
- integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
- dependencies:
- brace-expansion "^1.1.7"
-
minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -23171,6 +23291,11 @@ nanoid@^2.1.0:
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
+nanoid@^3.3.11:
+ version "3.3.11"
+ resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
+ integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
+
nanoid@^3.3.6, nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
@@ -23230,7 +23355,7 @@ negotiator@0.6.3, negotiator@^0.6.2:
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
-negotiator@^0.6.3:
+negotiator@^0.6.3, negotiator@~0.6.4:
version "0.6.4"
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
@@ -23708,14 +23833,6 @@ nprogress@^0.2.0:
resolved "https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1"
integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==
-nssocket@0.6.0:
- version "0.6.0"
- resolved "https://registry.npmmirror.com/nssocket/-/nssocket-0.6.0.tgz#59f96f6ff321566f33c70f7dbeeecdfdc07154fa"
- integrity sha512-a9GSOIql5IqgWJR3F/JXG4KpJTA3Z53Cj0MeMvGpglytB1nxE4PdFNC0jINe27CS7cGivoynwc054EzCcT3M3w==
- dependencies:
- eventemitter2 "~0.4.14"
- lazy "~1.0.11"
-
nth-check@^1.0.2:
version "1.0.2"
resolved "https://registry.npmmirror.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
@@ -23962,7 +24079,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
-on-headers@~1.0.1:
+on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.npmmirror.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
@@ -24771,10 +24888,10 @@ path-to-regexp@1.7.0:
dependencies:
isarray "0.0.1"
-path-to-regexp@2.2.1:
- version "2.2.1"
- resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
- integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
+path-to-regexp@3.3.0:
+ version "3.3.0"
+ resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b"
+ integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==
path-to-regexp@6.2.2:
version "6.2.2"
@@ -25168,27 +25285,28 @@ pm2-sysmonit@^1.2.8:
systeminformation "^5.7"
tx2 "~1.0.4"
-pm2@^5.2.0:
- version "5.3.0"
- resolved "https://registry.npmmirror.com/pm2/-/pm2-5.3.0.tgz#06850810f77cd98495ae1c66fbdd028a8fb5899e"
- integrity sha512-xscmQiAAf6ArVmKhjKTeeN8+Td7ZKnuZFFPw1DGkdFPR/0Iyx+m+1+OpCdf9+HQopX3VPc9/wqPQHqVOfHum9w==
+pm2@^6.0.5:
+ version "6.0.5"
+ resolved "https://registry.npmmirror.com/pm2/-/pm2-6.0.5.tgz#a6eeedeb7d3bf59c145b2c00fb57ce1fbf1fa7db"
+ integrity sha512-+O43WPaEiwYbm6/XSpAOO1Rtya/Uof0n7x8hJZGfwIuepesNTIVArpZh4KqFfze0cvvqZMr0maTW3ifhvmyeMQ==
dependencies:
- "@pm2/agent" "~2.0.0"
- "@pm2/io" "~5.0.0"
- "@pm2/js-api" "~0.6.7"
+ "@pm2/agent" "~2.1.1"
+ "@pm2/io" "~6.1.0"
+ "@pm2/js-api" "~0.8.0"
"@pm2/pm2-version-check" latest
- async "~3.2.0"
+ async "~3.2.6"
blessed "0.1.81"
chalk "3.0.0"
chokidar "^3.5.3"
cli-tableau "^2.0.0"
commander "2.15.1"
croner "~4.1.92"
- dayjs "~1.11.5"
- debug "^4.3.1"
+ dayjs "~1.11.13"
+ debug "^4.3.7"
enquirer "2.3.6"
eventemitter2 "5.0.1"
fclone "1.0.11"
+ js-yaml "~4.1.0"
mkdirp "1.0.4"
needle "2.4.0"
pidusage "~3.0"
@@ -25197,11 +25315,10 @@ pm2@^5.2.0:
pm2-deploy "~1.0.2"
pm2-multimeter "^0.1.2"
promptly "^2"
- semver "^7.2"
+ semver "^7.6.2"
source-map-support "0.5.21"
sprintf-js "1.1.2"
vizion "~2.2.1"
- yamljs "0.3.0"
optionalDependencies:
pm2-sysmonit "^1.2.8"
@@ -26121,15 +26238,15 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
-proxy-agent@~6.3.0:
- version "6.3.1"
- resolved "https://registry.npmmirror.com/proxy-agent/-/proxy-agent-6.3.1.tgz#40e7b230552cf44fd23ffaf7c59024b692612687"
- integrity sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==
+proxy-agent@~6.4.0:
+ version "6.4.0"
+ resolved "https://registry.npmmirror.com/proxy-agent/-/proxy-agent-6.4.0.tgz#b4e2dd51dee2b377748aef8d45604c2d7608652d"
+ integrity sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==
dependencies:
agent-base "^7.0.2"
debug "^4.3.4"
- http-proxy-agent "^7.0.0"
- https-proxy-agent "^7.0.2"
+ http-proxy-agent "^7.0.1"
+ https-proxy-agent "^7.0.3"
lru-cache "^7.14.1"
pac-proxy-agent "^7.0.1"
proxy-from-env "^1.1.0"
@@ -26205,7 +26322,7 @@ punycode.js@^2.3.1:
resolved "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7"
integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==
-punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1:
+punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
@@ -28435,13 +28552,18 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1:
resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.1.1, semver@^7.1.3, semver@^7.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4:
+semver@^7.1.1, semver@^7.1.3, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4:
version "7.6.0"
resolved "https://registry.npmmirror.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
dependencies:
lru-cache "^6.0.0"
+semver@^7.6.2, semver@^7.7.1:
+ version "7.7.1"
+ resolved "https://registry.npmmirror.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
+ integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
+
semver@^7.6.3:
version "7.6.3"
resolved "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
@@ -28527,32 +28649,17 @@ sequelize@^6.35.0:
validator "^13.9.0"
wkx "^0.5.0"
-serve-handler@6.1.3:
- version "6.1.3"
- resolved "https://registry.npmmirror.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8"
- integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==
+serve-handler@6.1.6, serve-handler@^6.1.6:
+ version "6.1.6"
+ resolved "https://registry.npmmirror.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1"
+ integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==
dependencies:
bytes "3.0.0"
content-disposition "0.5.2"
- fast-url-parser "1.1.3"
- mime-types "2.1.18"
- minimatch "3.0.4"
- path-is-inside "1.0.2"
- path-to-regexp "2.2.1"
- range-parser "1.2.0"
-
-serve-handler@^6.1.5:
- version "6.1.5"
- resolved "https://registry.npmmirror.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375"
- integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==
- dependencies:
- bytes "3.0.0"
- content-disposition "0.5.2"
- fast-url-parser "1.1.3"
mime-types "2.1.18"
minimatch "3.1.2"
path-is-inside "1.0.2"
- path-to-regexp "2.2.1"
+ path-to-regexp "3.3.0"
range-parser "1.2.0"
serve-static@1.16.2:
@@ -28565,20 +28672,22 @@ serve-static@1.16.2:
parseurl "~1.3.3"
send "0.19.0"
-serve@^13.0.2:
- version "13.0.4"
- resolved "https://registry.npmmirror.com/serve/-/serve-13.0.4.tgz#fc4466dc84b3e4a6cb622247c85ed8afe4b88820"
- integrity sha512-Lj8rhXmphJCRQVv5qwu0NQZ2h+0MrRyRJxDZu5y3qLH2i/XY6a0FPj/VmjMUdkJb672MBfE8hJ274PU6JzBd0Q==
+serve@^14.2.4:
+ version "14.2.4"
+ resolved "https://registry.npmmirror.com/serve/-/serve-14.2.4.tgz#ba4c425c3c965f496703762e808f34b913f42fb0"
+ integrity sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==
dependencies:
- "@zeit/schemas" "2.6.0"
- ajv "6.12.6"
- arg "2.0.0"
- boxen "5.1.2"
- chalk "2.4.1"
- clipboardy "2.3.0"
- compression "1.7.3"
- serve-handler "6.1.3"
- update-check "1.5.2"
+ "@zeit/schemas" "2.36.0"
+ ajv "8.12.0"
+ arg "5.0.2"
+ boxen "7.0.0"
+ chalk "5.0.1"
+ chalk-template "0.4.0"
+ clipboardy "3.0.0"
+ compression "1.7.4"
+ is-port-reachable "4.0.0"
+ serve-handler "6.1.6"
+ update-check "1.5.4"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
@@ -28700,7 +28809,7 @@ shell-quote@^1.7.3:
resolved "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
-shimmer@^1.1.0, shimmer@^1.2.0, shimmer@^1.2.1:
+shimmer@^1.2.0, shimmer@^1.2.1:
version "1.2.1"
resolved "https://registry.npmmirror.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
@@ -29479,7 +29588,7 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
-"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -30356,6 +30465,11 @@ tinypool@^0.8.3:
resolved "https://registry.npmmirror.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8"
integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==
+tinypool@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmmirror.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2"
+ integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==
+
tinyspy@^2.2.0:
version "2.2.0"
resolved "https://registry.npmmirror.com/tinyspy/-/tinyspy-2.2.0.tgz#9dc04b072746520b432f77ea2c2d17933de5d6ce"
@@ -30851,6 +30965,11 @@ type-fest@^1.0.2:
resolved "https://registry.npmmirror.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==
+type-fest@^2.13.0:
+ version "2.19.0"
+ resolved "https://registry.npmmirror.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
+ integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
+
type-fest@^3.0.0:
version "3.13.1"
resolved "https://registry.npmmirror.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706"
@@ -31402,10 +31521,10 @@ update-browserslist-db@^1.1.1:
escalade "^3.2.0"
picocolors "^1.1.0"
-update-check@1.5.2:
- version "1.5.2"
- resolved "https://registry.npmmirror.com/update-check/-/update-check-1.5.2.tgz#2fe09f725c543440b3d7dabe8971f2d5caaedc28"
- integrity sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==
+update-check@1.5.4:
+ version "1.5.4"
+ resolved "https://registry.npmmirror.com/update-check/-/update-check-1.5.4.tgz#5b508e259558f1ad7dbc8b4b0457d4c9d28c8743"
+ integrity sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==
dependencies:
registry-auth-token "3.3.2"
registry-url "3.1.0"
@@ -31630,7 +31749,7 @@ uuid@^10.0.0:
resolved "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
-uuid@^3.2.1, uuid@^3.3.2:
+uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
@@ -32185,12 +32304,12 @@ widest-line@^2.0.0:
dependencies:
string-width "^2.1.1"
-widest-line@^3.1.0:
- version "3.1.0"
- resolved "https://registry.npmmirror.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
- integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
+widest-line@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmmirror.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2"
+ integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==
dependencies:
- string-width "^4.0.0"
+ string-width "^5.0.1"
wildcard@^1.1.0:
version "1.1.2"
@@ -32386,7 +32505,7 @@ ws@^7.0.0:
resolved "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
-ws@^7.3.1:
+ws@^7.3.1, ws@~7.5.10:
version "7.5.10"
resolved "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
@@ -32396,11 +32515,6 @@ ws@^8.13.0, ws@^8.18.0:
resolved "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
-ws@~7.4.0:
- version "7.4.6"
- resolved "https://registry.npmmirror.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
- integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
-
ws@~8.17.1:
version "8.17.1"
resolved "https://registry.npmmirror.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
@@ -32568,14 +32682,6 @@ yaml@^2.2.2:
resolved "https://registry.npmmirror.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98"
integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==
-yamljs@0.3.0:
- version "0.3.0"
- resolved "https://registry.npmmirror.com/yamljs/-/yamljs-0.3.0.tgz#dc060bf267447b39f7304e9b2bfbe8b5a7ddb03b"
- integrity sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==
- dependencies:
- argparse "^1.0.7"
- glob "^7.0.5"
-
yargs-parser@13.1.2:
version "13.1.2"
resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"