Merge remote-tracking branch 'origin/main' into merge_from_upstream

This commit is contained in:
sealday 2024-03-21 18:40:03 +08:00
commit de89814eba
133 changed files with 1318 additions and 641 deletions

View File

@ -43,18 +43,11 @@ jobs:
ref: ${{ inputs.branch }} ref: ${{ inputs.branch }}
path: packages/pro-plugins path: packages/pro-plugins
ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }} ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }}
# - name: Set variables - name: Set variables
# run: | run: |
# target_directory="./packages/pro-plugins/@nocobase" APPEND_PRESET_LOCAL_PLUGINS=$(find ./packages/pro-plugins/@nocobase -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sed 's/^plugin-//' | tr '\n' ',' | sed 's/,$//')
# subdirectories=$(find "$target_directory" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | tr '\n' ' ') echo "var2=$APPEND_PRESET_LOCAL_PLUGINS" >> $GITHUB_OUTPUT
# trimmed_variable=$(echo "$subdirectories" | xargs) id: vars
# packageNames="@nocobase/${trimmed_variable// / @nocobase/}"
# pluginNames="${trimmed_variable//plugin-/}"
# BEFORE_PACK_NOCOBASE="yarn add @nocobase/plugin-notifications @nocobase/plugin-disable-pm-add $packageNames -W"
# APPEND_PRESET_LOCAL_PLUGINS="notifications,disable-pm-add,${pluginNames// /,}"
# echo "var1=$BEFORE_PACK_NOCOBASE" >> $GITHUB_OUTPUT
# echo "var2=$APPEND_PRESET_LOCAL_PLUGINS" >> $GITHUB_OUTPUT
# id: vars
- name: Use Node.js ${{ matrix.node_version }} - name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
@ -89,5 +82,5 @@ jobs:
DB_USER: nocobase DB_USER: nocobase
DB_PASSWORD: password DB_PASSWORD: password
DB_DATABASE: nocobase DB_DATABASE: nocobase
APPEND_PRESET_BUILT_IN_PLUGINS: mock-collections,workflow-response-message,workflow-request-interceptor,workflow-json-query,workflow-approval,telemetry-prometheus,data-source-external-postgres,embed,data-source-external-mysql,workflow-variable,collection-fdw,demo-platform,departments,data-source-external-mariadb APPEND_PRESET_LOCAL_PLUGINS: ${{ steps.vars.outputs.var2 }}
timeout-minutes: 120 timeout-minutes: 120

View File

@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
## [v0.20.0-alpha.15](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.14...v0.20.0-alpha.15) - 2024-03-20
### Merged
- fix(auth): sso auth bug when deploying with subpath [`#3764`](https://github.com/nocobase/nocobase/pull/3764)
- chore: load roles after start [`#3763`](https://github.com/nocobase/nocobase/pull/3763)
- fix: uuid & nanoid should be disabled when editing [`#3762`](https://github.com/nocobase/nocobase/pull/3762)
- fix(logger): output error cause info [`#3760`](https://github.com/nocobase/nocobase/pull/3760)
- fix: uuid field [`#3736`](https://github.com/nocobase/nocobase/pull/3736)
- feat: disassociate action [`#3759`](https://github.com/nocobase/nocobase/pull/3759)
- chore: merge sub app database options [`#3640`](https://github.com/nocobase/nocobase/pull/3640)
- fix(acl): bug of user filtering when adding them to roles [`#3754`](https://github.com/nocobase/nocobase/pull/3754)
- fix: app stopped status [`#3723`](https://github.com/nocobase/nocobase/pull/3723)
- fix: fix the disappearing collections when searching [`#3750`](https://github.com/nocobase/nocobase/pull/3750)
- fix: configure openSize for table action is only immediately valid for one row [`#3752`](https://github.com/nocobase/nocobase/pull/3752)
### Commits
- test: add e2e for Disassociate [`8e322ae`](https://github.com/nocobase/nocobase/commit/8e322ae1517e0cea5eac289bb5641b85d4997002)
- chore(versions): 😊 publish v0.20.0-alpha.15 [`bef9c8a`](https://github.com/nocobase/nocobase/commit/bef9c8ab7b54b0400dfd25c9ec85a141448125a2)
- chore: update yarn.lock [`3da340c`](https://github.com/nocobase/nocobase/commit/3da340c88d3a689e22e20dae798a1b51ed0405f7)
## [v0.20.0-alpha.14](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.13...v0.20.0-alpha.14) - 2024-03-18 ## [v0.20.0-alpha.14](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.13...v0.20.0-alpha.14) - 2024-03-18
### Merged ### Merged

View File

@ -1,5 +1,5 @@
{ {
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"npmClientArgs": ["--ignore-engines"], "npmClientArgs": ["--ignore-engines"],

View File

@ -1,13 +1,13 @@
{ {
"name": "@nocobase/acl", "name": "@nocobase/acl",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/resourcer": "0.20.0-alpha.14", "@nocobase/resourcer": "0.20.0-alpha.15",
"@nocobase/utils": "0.20.0-alpha.14", "@nocobase/utils": "0.20.0-alpha.15",
"minimatch": "^5.1.1" "minimatch": "^5.1.1"
}, },
"repository": { "repository": {

View File

@ -1,14 +1,14 @@
{ {
"name": "@nocobase/actions", "name": "@nocobase/actions",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/cache": "0.20.0-alpha.14", "@nocobase/cache": "0.20.0-alpha.15",
"@nocobase/database": "0.20.0-alpha.14", "@nocobase/database": "0.20.0-alpha.15",
"@nocobase/resourcer": "0.20.0-alpha.14" "@nocobase/resourcer": "0.20.0-alpha.15"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,17 +1,17 @@
{ {
"name": "@nocobase/app", "name": "@nocobase/app",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/database": "0.20.0-alpha.14", "@nocobase/database": "0.20.0-alpha.15",
"@nocobase/preset-nocobase": "0.20.0-alpha.14", "@nocobase/preset-nocobase": "0.20.0-alpha.15",
"@nocobase/server": "0.20.0-alpha.14" "@nocobase/server": "0.20.0-alpha.15"
}, },
"devDependencies": { "devDependencies": {
"@nocobase/client": "0.20.0-alpha.14" "@nocobase/client": "0.20.0-alpha.15"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,16 +1,16 @@
{ {
"name": "@nocobase/auth", "name": "@nocobase/auth",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/actions": "0.20.0-alpha.14", "@nocobase/actions": "0.20.0-alpha.15",
"@nocobase/cache": "0.20.0-alpha.14", "@nocobase/cache": "0.20.0-alpha.15",
"@nocobase/database": "0.20.0-alpha.14", "@nocobase/database": "0.20.0-alpha.15",
"@nocobase/resourcer": "0.20.0-alpha.14", "@nocobase/resourcer": "0.20.0-alpha.15",
"@nocobase/utils": "0.20.0-alpha.14", "@nocobase/utils": "0.20.0-alpha.15",
"@types/jsonwebtoken": "^8.5.8", "@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1" "jsonwebtoken": "^8.5.1"
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/build", "name": "@nocobase/build",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "Library build tool based on rollup.", "description": "Library build tool based on rollup.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/cache", "name": "@nocobase/cache",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/cli", "name": "@nocobase/cli",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./src/index.js", "main": "./src/index.js",
@ -8,7 +8,7 @@
"nocobase": "./bin/index.js" "nocobase": "./bin/index.js"
}, },
"dependencies": { "dependencies": {
"@nocobase/app": "0.20.0-alpha.14", "@nocobase/app": "0.20.0-alpha.15",
"@types/fs-extra": "^11.0.1", "@types/fs-extra": "^11.0.1",
"@umijs/utils": "3.5.20", "@umijs/utils": "3.5.20",
"chalk": "^4.1.1", "chalk": "^4.1.1",
@ -25,7 +25,7 @@
"tsx": "^4.6.2" "tsx": "^4.6.2"
}, },
"devDependencies": { "devDependencies": {
"@nocobase/devtools": "0.20.0-alpha.14" "@nocobase/devtools": "0.20.0-alpha.15"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/client", "name": "@nocobase/client",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "lib/index.js", "main": "lib/index.js",
"module": "es/index.mjs", "module": "es/index.mjs",
@ -26,9 +26,9 @@
"@formily/reactive-react": "^2.2.27", "@formily/reactive-react": "^2.2.27",
"@formily/shared": "^2.2.27", "@formily/shared": "^2.2.27",
"@formily/validator": "^2.2.27", "@formily/validator": "^2.2.27",
"@nocobase/evaluators": "0.20.0-alpha.14", "@nocobase/evaluators": "0.20.0-alpha.15",
"@nocobase/sdk": "0.20.0-alpha.14", "@nocobase/sdk": "0.20.0-alpha.15",
"@nocobase/utils": "0.20.0-alpha.14", "@nocobase/utils": "0.20.0-alpha.15",
"ahooks": "^3.7.2", "ahooks": "^3.7.2",
"antd": "^5.12.8", "antd": "^5.12.8",
"antd-style": "3.4.5", "antd-style": "3.4.5",

View File

@ -349,7 +349,7 @@ export const useParamsFromRecord = () => {
if (record.__collection && collectionField && !['oho', 'm2o', 'obo'].includes(collectionField.interface)) { if (record.__collection && collectionField && !['oho', 'm2o', 'obo'].includes(collectionField.interface)) {
obj['targetCollection'] = record.__collection; obj['targetCollection'] = record.__collection;
} }
if (!filterByTk) { if (!filterByTk && Object.keys(filter).length > 0) {
obj['filter'] = filter; obj['filter'] = filter;
} }
return obj; return obj;

View File

@ -1,4 +1,6 @@
import { Field, Form } from '@formily/core';
import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react'; import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react';
import { untracked } from '@formily/reactive';
import { isURL, parse } from '@nocobase/utils/client'; import { isURL, parse } from '@nocobase/utils/client';
import { App, message } from 'antd'; import { App, message } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
@ -23,6 +25,7 @@ import { useFilterBlock } from '../../filter-provider/FilterProvider';
import { mergeFilter, transformToFilter } from '../../filter-provider/utils'; import { mergeFilter, transformToFilter } from '../../filter-provider/utils';
import { useRecord } from '../../record-provider'; import { useRecord } from '../../record-provider';
import { removeNullCondition, useActionContext, useCompile } from '../../schema-component'; import { removeNullCondition, useActionContext, useCompile } from '../../schema-component';
import { isSubMode } from '../../schema-component/antd/association-field/util';
import { useCurrentUserContext } from '../../user'; import { useCurrentUserContext } from '../../user';
import { useLocalVariables, useVariables } from '../../variables'; import { useLocalVariables, useVariables } from '../../variables';
import { isVariable } from '../../variables/utils/isVariable'; import { isVariable } from '../../variables/utils/isVariable';
@ -30,9 +33,6 @@ import { transformVariableValue } from '../../variables/utils/transformVariableV
import { useBlockRequestContext, useFilterByTk, useParamsFromRecord } from '../BlockProvider'; import { useBlockRequestContext, useFilterByTk, useParamsFromRecord } from '../BlockProvider';
import { useDetailsBlockContext } from '../DetailsBlockProvider'; import { useDetailsBlockContext } from '../DetailsBlockProvider';
import { TableFieldResource } from '../TableFieldProvider'; import { TableFieldResource } from '../TableFieldProvider';
import { Field, Form } from '@formily/core';
import { untracked } from '@formily/reactive';
import { isSubMode } from '../../schema-component/antd/association-field/util';
export * from './useFormActiveFields'; export * from './useFormActiveFields';
export * from './useParsedFilter'; export * from './useParsedFilter';
@ -965,6 +965,34 @@ export const useRemoveActionProps = (associationName) => {
}; };
}; };
export const useDisassociateActionProps = () => {
const filterByTk = useFilterByTk();
const { resource, service, block, __parent } = useBlockRequestContext();
const { setVisible } = useActionContext();
return {
async onClick() {
await resource.remove({
values: [filterByTk],
});
const { count = 0, page = 0, pageSize = 0 } = service?.data?.meta || {};
if (count % pageSize === 1 && page !== 1) {
service.run({
...service?.params?.[0],
page: page - 1,
});
} else {
service?.refresh?.();
}
if (block && block !== 'TableField') {
__parent?.service?.refresh?.();
setVisible?.(false);
}
},
};
};
export const useDetailPrintActionProps = () => { export const useDetailPrintActionProps = () => {
const { formBlockRef } = useFormBlockContext(); const { formBlockRef } = useFormBlockContext();

View File

@ -284,7 +284,7 @@ export const ForeignKey = observer(
}} }}
onChange={(value, option) => { onChange={(value, option) => {
props?.onChange?.(value); props?.onChange?.(value);
setInitialValue(option.label); setInitialValue(option.label || value);
}} }}
/> />
</div> </div>

View File

@ -28,9 +28,9 @@ export class NanoidFieldInterface extends CollectionFieldInterface {
type: 'string', type: 'string',
title: '{{t("Field name")}}', title: '{{t("Field name")}}',
required: true, required: true,
'x-disabled': true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
'x-disabled': '{{ !createOnly }}',
description: description:
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}", "{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
}, },

View File

@ -30,9 +30,9 @@ export class UUIDFieldInterface extends CollectionFieldInterface {
type: 'string', type: 'string',
title: '{{t("Field name")}}', title: '{{t("Field name")}}',
required: true, required: true,
'x-disabled': true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
'x-disabled': '{{ !createOnly }}',
description: description:
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}", "{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
}, },

View File

@ -53,6 +53,9 @@
"Insert right": "Insert right", "Insert right": "Insert right",
"Insert inner": "Insert inner", "Insert inner": "Insert inner",
"Delete": "Delete", "Delete": "Delete",
"Disassociate": "Disassociate",
"Disassociate record": "Disassociate record",
"Are you sure you want to disassociate it?": "Are you sure you want to disassociate it?",
"UI editor": "UI editor", "UI editor": "UI editor",
"Collection": "Collection", "Collection": "Collection",
"Collection selector": "Collection selector", "Collection selector": "Collection selector",

View File

@ -52,6 +52,9 @@
"Insert right": "Insertar a la derecha", "Insert right": "Insertar a la derecha",
"Insert inner": "Insertar al interior", "Insert inner": "Insertar al interior",
"Delete": "Borrar", "Delete": "Borrar",
"Disassociate": "Desasociar",
"Disassociate record": "Desasociar registro",
"Are you sure you want to disassociate it?": "¿Seguro que quieres desasociarlo?",
"UI editor": "IU editor", "UI editor": "IU editor",
"Collection": "Colección", "Collection": "Colección",
"Collections & Fields": "Colección & Campos", "Collections & Fields": "Colección & Campos",

View File

@ -52,6 +52,9 @@
"Insert right": "Insérer à droite", "Insert right": "Insérer à droite",
"Insert inner": "Insérer à l'intérieur", "Insert inner": "Insérer à l'intérieur",
"Delete": "Supprimer", "Delete": "Supprimer",
"Disassociate": "Dissocier",
"Disassociate record": "Dissocier l'enregistrement",
"Are you sure you want to disassociate it?": "Êtes-vous sûr de vouloir le dissocier ?",
"UI editor": "Éditeur d'interface utilisateur", "UI editor": "Éditeur d'interface utilisateur",
"Collection": "Collection", "Collection": "Collection",
"Collections & Fields": "Collections et champs", "Collections & Fields": "Collections et champs",

View File

@ -52,6 +52,9 @@
"Insert right": "右に挿入", "Insert right": "右に挿入",
"Insert inner": "中に挿入", "Insert inner": "中に挿入",
"Delete": "削除", "Delete": "削除",
"Disassociate": "関連付けを解除",
"Disassociate record": "レコードの関連付けを解除",
"Are you sure you want to disassociate it?": "本当に関連付けを解除しますか?",
"UI editor": "UI エディタ", "UI editor": "UI エディタ",
"Collection": "コレクション", "Collection": "コレクション",
"Enable child collections": "启用子表", "Enable child collections": "启用子表",

View File

@ -60,6 +60,9 @@
"Insert right": "오른쪽에 삽입", "Insert right": "오른쪽에 삽입",
"Insert inner": "내부에 삽입", "Insert inner": "내부에 삽입",
"Delete": "삭제", "Delete": "삭제",
"Disassociate": "연결 해제",
"Disassociate record": "레코드 연결 해제",
"Are you sure you want to disassociate it?": "정말로 연결을 해제하시겠습니까?",
"UI editor": "UI 편집기", "UI editor": "UI 편집기",
"Collection": "컬렉션", "Collection": "컬렉션",
"Collection selector": "컬렉션 선택기", "Collection selector": "컬렉션 선택기",

View File

@ -32,6 +32,9 @@
"Insert right": "Inserir à direita", "Insert right": "Inserir à direita",
"Insert inner": "Inserir interno", "Insert inner": "Inserir interno",
"Delete": "Excluir", "Delete": "Excluir",
"Disassociate": "Desassociar",
"Disassociate record": "Desassociar registro",
"Are you sure you want to disassociate it?": "Tem certeza de que deseja desassociá-lo?",
"UI editor": "Editor de UI", "UI editor": "Editor de UI",
"Collection": "Coleção", "Collection": "Coleção",
"Collections & Fields": "Coleções e campos", "Collections & Fields": "Coleções e campos",

View File

@ -52,6 +52,9 @@
"Insert right": "Вставить справа", "Insert right": "Вставить справа",
"Insert inner": "Вставить внутрь", "Insert inner": "Вставить внутрь",
"Delete": "Удалить", "Delete": "Удалить",
"Disassociate": "Разъединить",
"Disassociate record": "Разъединить запись",
"Are you sure you want to disassociate it?": "Вы уверены, что хотите разъединить это?",
"UI editor": "UI редактор", "UI editor": "UI редактор",
"Collection": "Коллекция", "Collection": "Коллекция",
"Collections & Fields": "Коллекции & Поля", "Collections & Fields": "Коллекции & Поля",

View File

@ -52,6 +52,9 @@
"Insert right": "Sağa yerleştir", "Insert right": "Sağa yerleştir",
"Insert inner": "İçine yerleştir", "Insert inner": "İçine yerleştir",
"Delete": "Sil", "Delete": "Sil",
"Disassociate": "Bağlantıyı kes",
"Disassociate record": "Kaydı bağlantıyı kes",
"Are you sure you want to disassociate it?": "Bağlantıyı kesmek istediğinizden emin misiniz?",
"UI editor": "UI editor", "UI editor": "UI editor",
"Collection": "Koleksiyonlar", "Collection": "Koleksiyonlar",
"Collections & Fields": "Koleksiyonlar & Alanlar", "Collections & Fields": "Koleksiyonlar & Alanlar",

View File

@ -52,6 +52,9 @@
"Insert right": "Вставити справа", "Insert right": "Вставити справа",
"Insert inner": "Вставити всередину", "Insert inner": "Вставити всередину",
"Delete": "Видалити", "Delete": "Видалити",
"Disassociate": "Роз'єднати",
"Disassociate record": "Роз'єднати запис",
"Are you sure you want to disassociate it?": "Ви впевнені, що хочете роз'єднати це?",
"UI editor": "Редактор UI", "UI editor": "Редактор UI",
"Collection": "Колекція", "Collection": "Колекція",
"Collections & Fields": "Колекції та поля", "Collections & Fields": "Колекції та поля",

View File

@ -60,6 +60,9 @@
"Insert right": "在右边插入", "Insert right": "在右边插入",
"Insert inner": "在里面插入", "Insert inner": "在里面插入",
"Delete": "删除", "Delete": "删除",
"Disassociate": "解除关联",
"Disassociate record": "解除关联记录",
"Are you sure you want to disassociate it?": "你确定要解除关联吗?",
"UI editor": "界面配置", "UI editor": "界面配置",
"Collection": "数据表", "Collection": "数据表",
"Collection selector": "数据表选择器", "Collection selector": "数据表选择器",

View File

@ -60,6 +60,9 @@
"Insert right": "從右邊插入", "Insert right": "從右邊插入",
"Insert inner": "從裡面插入", "Insert inner": "從裡面插入",
"Delete": "刪除", "Delete": "刪除",
"Disassociate": "解除關聯",
"Disassociate record": "解除關聯記錄",
"Are you sure you want to disassociate it?": "你確定要解除關聯嗎?",
"UI editor": "介面編輯", "UI editor": "介面編輯",
"Collection": "資料表", "Collection": "資料表",
"Collection selector": "資料表選擇器", "Collection selector": "資料表選擇器",

View File

@ -0,0 +1,25 @@
import React from 'react';
import { ActionInitializer } from '../../../schema-initializer/items/ActionInitializer';
export const DisassociateActionInitializer = (props) => {
const schema = {
title: '{{ t("Disassociate") }}',
'x-action': 'disassociate',
'x-component': 'Action',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:disassociate',
'x-component-props': {
icon: 'DeleteOutlined',
confirm: {
title: "{{t('Disassociate record')}}",
content: "{{t('Are you sure you want to disassociate it?')}}",
},
useProps: '{{ useDisassociateActionProps }}',
},
'x-action-settings': {
triggerWorkflows: [],
},
};
return <ActionInitializer {...props} schema={schema} />;
};

View File

@ -0,0 +1,53 @@
import { test, expect } from '@nocobase/test/e2e';
import { disassociatePage } from './templatesOfPage';
test('basic', async ({ page, mockPage, mockRecord }) => {
await mockPage(disassociatePage).goto();
const record = await mockRecord('collection1');
// 1. 打开弹窗,并创建一个 Table 关系区块
await page.getByLabel('action-Action.Link-Edit record-update-collection1-table-0').click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'table Table right' }).hover();
await page.getByRole('menuitem', { name: 'manyToMany -> collection2' }).click();
// 2. Table 中显示 Role UID 字段
await page
.getByTestId('drawer-Action.Container-collection1-Edit record')
.getByLabel('schema-initializer-TableV2-')
.hover();
await page.getByRole('menuitem', { name: 'singleLineText' }).click();
// 3. 显示 Disassociate 按钮
await page
.getByTestId('drawer-Action.Container-collection1-Edit record')
.getByRole('button', { name: 'Actions', exact: true })
.hover();
await page
.getByTestId('drawer-Action.Container-collection1-Edit record')
.getByLabel('designer-schema-settings-TableV2.Column-TableV2.ActionColumnDesigner-collection2')
.hover();
await page.getByRole('menuitem', { name: 'Disassociate' }).click();
// 4. 点击 Disassociate 按钮,解除关联
await expect(
page
.getByTestId('drawer-Action.Container-collection1-Edit record')
.getByLabel('block-item-CardItem-')
.getByText(record.manyToMany[0].singleLineText),
).toBeVisible();
await page.getByLabel('action-Action.Link-Disassociate-disassociate-collection2-table-0').click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(
page
.getByTestId('drawer-Action.Container-collection1-Edit record')
.getByLabel('block-item-CardItem-')
.getByText(record.manyToMany[0].singleLineText),
).toBeHidden();
// 5. 刷新页面后,页面中 collection2 的表格中的 singleLineText 字段不应该被删除
await page.reload();
await expect(
page.getByLabel('block-item-CardItem-collection2-table').getByText(record.manyToMany[0].singleLineText),
).toBeVisible();
});

View File

@ -0,0 +1,384 @@
import { PageConfig } from '@nocobase/test/e2e';
export const disassociatePage: PageConfig = {
collections: [
{
name: 'collection1',
fields: [
{
name: 'manyToMany',
interface: 'm2m',
target: 'collection2',
},
],
},
{
name: 'collection2',
fields: [
{
name: 'singleLineText',
interface: 'input',
},
],
},
],
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
'x-index': 1,
properties: {
wpr26fau9xr: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
'x-index': 1,
properties: {
en9rmxzezha: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
yxbglvb5hog: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
'8j54tok1ami': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'collection1:list',
'x-decorator-props': {
collection: 'collection1',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
disableTemplate: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-uid': 'b678n93qa6y',
'x-async': false,
'x-index': 1,
},
qikcey9adni: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableBlockProps }}',
},
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'table:configureItemActions',
properties: {
m7jxx6ybqin: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
xm4xv98j8rq: {
'x-uid': 'zzpqq51vya7',
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: 'Edit record',
'x-action': 'update',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:edit',
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
danger: false,
},
'x-decorator': 'ACLActionProvider',
'x-designer-props': {
linkageAction: true,
},
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Edit record") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Edit")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
'x-uid': '4ywsuzafbcb',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'i9vjup0exgk',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'v193kvjot2a',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'cm1v9u7tfsu',
'x-async': false,
'x-index': 1,
},
},
'x-async': false,
'x-index': 1,
},
},
'x-uid': '1wxlbm4mmvg',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '5sqdt97tixj',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kplsu47q2a6',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'pq9i71986i8',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'oce2w1t8coi',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'i7ti4qryd4o',
'x-async': false,
'x-index': 1,
},
gq928ipecvy: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
zue50ioabjl: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
dv7csive187: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'collection2:list',
'x-decorator-props': {
collection: 'collection2',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
disableTemplate: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-uid': 'oe1qrytmueq',
'x-async': false,
'x-index': 1,
},
gjd57a4bks3: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableBlockProps }}',
},
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'table:configureItemActions',
properties: {
'3ysynbg1lv6': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
'x-uid': 'aelf0u0qx6q',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '5iadqvuebvd',
'x-async': false,
'x-index': 1,
},
'10a10qkpiz7': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-toolbar': 'TableColumnSchemaToolbar',
'x-settings': 'fieldSettings:TableColumn',
'x-component': 'TableV2.Column',
properties: {
singleLineText: {
_isJSONSchemaObject: true,
version: '2.0',
'x-collection-field': 'collection2.singleLineText',
'x-component': 'CollectionField',
'x-component-props': {
ellipsis: true,
},
'x-read-pretty': true,
'x-decorator': null,
'x-decorator-props': {
labelStyle: {
display: 'none',
},
},
'x-uid': 'fixqxflxl3p',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '8huj8o6hk58',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'zyewv4tfzsm',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'c5o951ypirb',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'i50ffbtryxp',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'w6dx01br9dy',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'aro2fcypx34',
'x-async': false,
},
},
'x-uid': '8ykc66k5p12',
'x-async': true,
},
};

View File

@ -0,0 +1,39 @@
import { useSchemaToolbar } from '../../../application';
import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings';
import { useCollection_deprecated } from '../../../collection-manager';
import { ButtonEditor, SecondConFirm } from '../../../schema-component/antd/action/Action.Designer';
import { SchemaSettingsLinkageRules } from '../../../schema-settings';
export const disassociateActionSettings = new SchemaSettings({
name: 'actionSettings:disassociate',
items: [
{
name: 'editButton',
Component: ButtonEditor,
useComponentProps() {
const { buttonEditorProps } = useSchemaToolbar();
return buttonEditorProps;
},
},
{
name: 'linkageRules',
Component: SchemaSettingsLinkageRules,
useComponentProps() {
const { name } = useCollection_deprecated();
const { linkageRulesProps } = useSchemaToolbar();
return {
...linkageRulesProps,
collectionName: name,
};
},
},
{
name: 'secondConFirm',
Component: SecondConFirm,
},
{
name: 'delete',
type: 'remove',
},
],
});

View File

@ -4,13 +4,14 @@ import _ from 'lodash';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../../../../api-client'; import { useAPIClient } from '../../../../api-client';
import { CompatibleSchemaInitializer } from '../../../../application/schema-initializer/CompatibleSchemaInitializer';
import { SchemaInitializerActionModal } from '../../../../application/schema-initializer/components/SchemaInitializerActionModal';
import { SchemaInitializerItem } from '../../../../application/schema-initializer/components/SchemaInitializerItem';
import { useSchemaInitializer } from '../../../../application/schema-initializer/context';
import { useCollection_deprecated } from '../../../../collection-manager'; import { useCollection_deprecated } from '../../../../collection-manager';
import { useDataBlockProps } from '../../../../data-source';
import { createDesignable, useDesignable } from '../../../../schema-component'; import { createDesignable, useDesignable } from '../../../../schema-component';
import { useGetAriaLabelOfDesigner } from '../../../../schema-settings/hooks/useGetAriaLabelOfDesigner'; import { useGetAriaLabelOfDesigner } from '../../../../schema-settings/hooks/useGetAriaLabelOfDesigner';
import { SchemaInitializerActionModal } from '../../../../application/schema-initializer/components/SchemaInitializerActionModal';
import { useSchemaInitializer } from '../../../../application/schema-initializer/context';
import { SchemaInitializerItem } from '../../../../application/schema-initializer/components/SchemaInitializerItem';
import { CompatibleSchemaInitializer } from '../../../../application/schema-initializer/CompatibleSchemaInitializer';
export const Resizable = () => { export const Resizable = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -65,11 +66,7 @@ export const Resizable = () => {
); );
}; };
/** const commonOptions = {
* @deprecated
*/
export const tableActionColumnInitializers_deprecated = new CompatibleSchemaInitializer({
name: 'TableActionColumnInitializers',
insertPosition: 'beforeEnd', insertPosition: 'beforeEnd',
useInsert: function useInsert() { useInsert: function useInsert() {
const { refresh } = useDesignable(); const { refresh } = useDesignable();
@ -156,6 +153,27 @@ export const tableActionColumnInitializers_deprecated = new CompatibleSchemaInit
return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql'; return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
}, },
}, },
{
type: 'item',
title: '{{t("Disassociate")}}',
name: 'disassociate',
Component: 'DisassociateActionInitializer',
schema: {
'x-component': 'Action.Link',
'x-action': 'disassociate',
'x-acl-action': 'destroy',
'x-decorator': 'ACLActionProvider',
},
useVisible() {
const props = useDataBlockProps();
const collection = useCollection_deprecated();
return (
!!props?.association &&
(collection.template !== 'view' || collection?.writableView) &&
collection.template !== 'sql'
);
},
},
{ {
type: 'item', type: 'item',
title: '{{t("Add child")}}', title: '{{t("Add child")}}',
@ -225,166 +243,20 @@ export const tableActionColumnInitializers_deprecated = new CompatibleSchemaInit
Component: Resizable, Component: Resizable,
}, },
], ],
};
/**
* @deprecated
*/
export const tableActionColumnInitializers_deprecated = new CompatibleSchemaInitializer({
name: 'TableActionColumnInitializers',
...commonOptions,
}); });
export const tableActionColumnInitializers = new CompatibleSchemaInitializer( export const tableActionColumnInitializers = new CompatibleSchemaInitializer(
{ {
name: 'table:configureItemActions', name: 'table:configureItemActions',
insertPosition: 'beforeEnd', ...commonOptions,
useInsert: function useInsert() {
const { refresh } = useDesignable();
const fieldSchema = useFieldSchema();
const api = useAPIClient();
const { t } = useTranslation();
return function insert(schema) {
const spaceSchema = fieldSchema.reduceProperties((buf, schema) => {
if (schema['x-component'] === 'Space') {
return schema;
}
return buf;
}, null);
if (!spaceSchema) {
return;
}
_.set(schema, 'x-designer-props.linkageAction', true);
const dn = createDesignable({
t,
api,
refresh,
current: spaceSchema,
});
dn.loadAPIClientEvents();
dn.insertBeforeEnd(schema);
};
},
Component: (props: any) => {
const { getAriaLabel } = useGetAriaLabelOfDesigner();
return (
<MenuOutlined
{...props}
role="button"
aria-label={getAriaLabel('schema-settings')}
style={{ cursor: 'pointer' }}
/>
);
},
items: [
{
type: 'itemGroup',
name: 'actions',
title: '{{t("Enable actions")}}',
children: [
{
type: 'item',
title: '{{t("View")}}',
name: 'view',
Component: 'ViewActionInitializer',
schema: {
'x-component': 'Action.Link',
'x-action': 'view',
'x-decorator': 'ACLActionProvider',
},
},
{
type: 'item',
name: 'edit',
title: '{{t("Edit")}}',
Component: 'UpdateActionInitializer',
schema: {
'x-component': 'Action.Link',
'x-action': 'update',
'x-decorator': 'ACLActionProvider',
},
useVisible() {
const collection = useCollection_deprecated();
return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
},
},
{
type: 'item',
title: '{{t("Delete")}}',
name: 'delete',
Component: 'DestroyActionInitializer',
schema: {
'x-component': 'Action.Link',
'x-action': 'destroy',
'x-decorator': 'ACLActionProvider',
},
useVisible() {
const collection = useCollection_deprecated();
return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
},
},
{
type: 'item',
title: '{{t("Add child")}}',
name: 'addChildren',
Component: 'CreateChildInitializer',
schema: {
'x-component': 'Action.Link',
'x-action': 'create',
'x-decorator': 'ACLActionProvider',
},
useVisible() {
const fieldSchema = useFieldSchema();
const collection = useCollection_deprecated();
const { treeTable } = fieldSchema?.parent?.parent['x-decorator-props'] || {};
return collection.tree && treeTable !== false;
},
},
],
},
{
name: 'divider',
type: 'divider',
},
{
type: 'subMenu',
title: '{{t("Customize")}}',
name: 'customize',
children: [
{
type: 'item',
title: '{{t("Popup")}}',
name: 'popup',
Component: 'PopupActionInitializer',
},
{
type: 'item',
title: '{{t("Update record")}}',
name: 'updateRecord',
Component: 'UpdateRecordActionInitializer',
useVisible() {
const collection = useCollection_deprecated();
return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
},
},
{
name: 'customRequest',
title: '{{t("Custom request")}}',
Component: 'CustomRequestInitializer',
schema: {
'x-action': 'customize:table:request',
},
useVisible() {
const collection = useCollection_deprecated();
return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
},
},
],
},
{
name: 'divider2',
type: 'divider',
},
{
type: 'item',
name: 'columnWidth',
title: 't("Column width")',
Component: Resizable,
},
],
}, },
tableActionColumnInitializers_deprecated, tableActionColumnInitializers_deprecated,
); );

View File

@ -12,6 +12,7 @@ import {
} from '../modules/actions/add-record/customizeCreateFormBlockInitializers'; } from '../modules/actions/add-record/customizeCreateFormBlockInitializers';
import { BulkDestroyActionInitializer } from '../modules/actions/bulk-destroy/BulkDestroyActionInitializer'; import { BulkDestroyActionInitializer } from '../modules/actions/bulk-destroy/BulkDestroyActionInitializer';
import { DestroyActionInitializer } from '../modules/actions/delete/DestroyActionInitializer'; import { DestroyActionInitializer } from '../modules/actions/delete/DestroyActionInitializer';
import { DisassociateActionInitializer } from '../modules/actions/disassociate/DisassociateActionInitializer';
import { ExpandableActionInitializer } from '../modules/actions/expand-collapse/ExpandableActionInitializer'; import { ExpandableActionInitializer } from '../modules/actions/expand-collapse/ExpandableActionInitializer';
import { FilterActionInitializer } from '../modules/actions/filter/FilterActionInitializer'; import { FilterActionInitializer } from '../modules/actions/filter/FilterActionInitializer';
import { RefreshActionInitializer } from '../modules/actions/refresh/RefreshActionInitializer'; import { RefreshActionInitializer } from '../modules/actions/refresh/RefreshActionInitializer';
@ -121,10 +122,10 @@ import * as items from './items';
export * from './buttons'; export * from './buttons';
export * from './items'; export * from './items';
export { export {
createDetailsBlockSchema,
createFilterFormBlockSchema, createFilterFormBlockSchema,
createFormBlockSchema, createFormBlockSchema,
createReadPrettyFormBlockSchema, createReadPrettyFormBlockSchema,
createDetailsBlockSchema,
createTableBlockSchema, createTableBlockSchema,
gridRowColWrap, gridRowColWrap,
itemsMerge, itemsMerge,
@ -144,6 +145,7 @@ export class SchemaInitializerPlugin extends Plugin {
this.app.addComponents({ this.app.addComponents({
...initializerComponents, ...initializerComponents,
...items, ...items,
DestroyActionInitializer,
CreateFormBlockInitializer, CreateFormBlockInitializer,
FormBlockInitializer, FormBlockInitializer,
RecordFormBlockInitializer, RecordFormBlockInitializer,
@ -171,7 +173,7 @@ export class SchemaInitializerPlugin extends Plugin {
UpdateSubmitActionInitializer, UpdateSubmitActionInitializer,
BulkDestroyActionInitializer, BulkDestroyActionInitializer,
ExpandableActionInitializer, ExpandableActionInitializer,
DestroyActionInitializer, DisassociateActionInitializer,
FilterActionInitializer, FilterActionInitializer,
RefreshActionInitializer, RefreshActionInitializer,
} as any); } as any);

View File

@ -4,6 +4,7 @@ import { addNewActionSettings } from '../modules/actions/add-new/addNewActionSet
import { customizeAddRecordActionSettings } from '../modules/actions/add-record/customizeAddRecordActionSettings'; import { customizeAddRecordActionSettings } from '../modules/actions/add-record/customizeAddRecordActionSettings';
import { bulkDeleteActionSettings } from '../modules/actions/bulk-destroy/bulkDeleteActionSettings'; import { bulkDeleteActionSettings } from '../modules/actions/bulk-destroy/bulkDeleteActionSettings';
import { deleteActionSettings } from '../modules/actions/delete/deleteActionSettings'; import { deleteActionSettings } from '../modules/actions/delete/deleteActionSettings';
import { disassociateActionSettings } from '../modules/actions/disassociate/disassociateActionSettings';
import { expendableActionSettings } from '../modules/actions/expand-collapse/expendableActionSettings'; import { expendableActionSettings } from '../modules/actions/expand-collapse/expendableActionSettings';
import { filterActionSettings } from '../modules/actions/filter/filterActionSettings'; import { filterActionSettings } from '../modules/actions/filter/filterActionSettings';
import { refreshActionSettings } from '../modules/actions/refresh/refreshActionSettings'; import { refreshActionSettings } from '../modules/actions/refresh/refreshActionSettings';
@ -69,6 +70,7 @@ export class SchemaSettingsPlugin extends Plugin {
this.schemaSettingsManager.add(viewActionSettings); this.schemaSettingsManager.add(viewActionSettings);
this.schemaSettingsManager.add(editActionSettings); this.schemaSettingsManager.add(editActionSettings);
this.schemaSettingsManager.add(deleteActionSettings); this.schemaSettingsManager.add(deleteActionSettings);
this.schemaSettingsManager.add(disassociateActionSettings);
this.schemaSettingsManager.add(bulkDeleteActionSettings); this.schemaSettingsManager.add(bulkDeleteActionSettings);
this.schemaSettingsManager.add(customizeAddRecordActionSettings); this.schemaSettingsManager.add(customizeAddRecordActionSettings);
this.schemaSettingsManager.add(customizePopupActionSettings); this.schemaSettingsManager.add(customizePopupActionSettings);

View File

@ -1,6 +1,6 @@
{ {
"name": "create-nocobase-app", "name": "create-nocobase-app",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "src/index.js", "main": "src/index.js",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {

View File

@ -1,16 +1,16 @@
{ {
"name": "@nocobase/data-source-manager", "name": "@nocobase/data-source-manager",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/actions": "0.20.0-alpha.14", "@nocobase/actions": "0.20.0-alpha.15",
"@nocobase/cache": "0.20.0-alpha.14", "@nocobase/cache": "0.20.0-alpha.15",
"@nocobase/database": "0.20.0-alpha.14", "@nocobase/database": "0.20.0-alpha.15",
"@nocobase/resourcer": "0.20.0-alpha.14", "@nocobase/resourcer": "0.20.0-alpha.15",
"@nocobase/utils": "0.20.0-alpha.14", "@nocobase/utils": "0.20.0-alpha.15",
"@types/jsonwebtoken": "^8.5.8", "@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1" "jsonwebtoken": "^8.5.1"
}, },

View File

@ -1,13 +1,13 @@
{ {
"name": "@nocobase/database", "name": "@nocobase/database",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@nocobase/logger": "0.20.0-alpha.14", "@nocobase/logger": "0.20.0-alpha.15",
"@nocobase/utils": "0.20.0-alpha.14", "@nocobase/utils": "0.20.0-alpha.15",
"async-mutex": "^0.3.2", "async-mutex": "^0.3.2",
"chalk": "^4.1.1", "chalk": "^4.1.1",
"cron-parser": "4.4.0", "cron-parser": "4.4.0",

View File

@ -13,19 +13,20 @@ describe('string field', () => {
await db.close(); await db.close();
}); });
it('define', async () => { it('should create uuid field', async () => {
const Test = db.collection({ const Test = db.collection({
name: 'tests', name: 'tests',
autoGenId: false,
fields: [ fields: [
{ {
name: 'uuid',
type: 'uuid', type: 'uuid',
name: 'id',
primaryKey: true,
}, },
], ],
}); });
await Test.sync(); await Test.sync();
await Test.model.create(); const item = await Test.model.create();
expect(item['uuid']).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
}); });
}); });

View File

@ -5,7 +5,7 @@ export class UuidField extends Field {
constructor(options?: any, context?: FieldContext) { constructor(options?: any, context?: FieldContext) {
super( super(
{ {
defaultValue: DataTypes.UUIDV4, defaultValue: new DataTypes.UUIDV4(),
...options, ...options,
}, },
context, context,

View File

@ -1,13 +1,13 @@
{ {
"name": "@nocobase/devtools", "name": "@nocobase/devtools",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./src/index.js", "main": "./src/index.js",
"dependencies": { "dependencies": {
"@nocobase/build": "0.20.0-alpha.14", "@nocobase/build": "0.20.0-alpha.15",
"@nocobase/client": "0.20.0-alpha.14", "@nocobase/client": "0.20.0-alpha.15",
"@nocobase/test": "0.20.0-alpha.14", "@nocobase/test": "0.20.0-alpha.15",
"@types/koa": "^2.13.4", "@types/koa": "^2.13.4",
"@types/koa-bodyparser": "^4.3.4", "@types/koa-bodyparser": "^4.3.4",
"@types/lodash": "^4.14.177", "@types/lodash": "^4.14.177",

View File

@ -1,13 +1,13 @@
{ {
"name": "@nocobase/evaluators", "name": "@nocobase/evaluators",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@formulajs/formulajs": "4.2.0", "@formulajs/formulajs": "4.2.0",
"@nocobase/utils": "0.20.0-alpha.14", "@nocobase/utils": "0.20.0-alpha.15",
"mathjs": "^10.6.0" "mathjs": "^10.6.0"
}, },
"repository": { "repository": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/logger", "name": "@nocobase/logger",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "nocobase logging library", "description": "nocobase logging library",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",

View File

@ -49,13 +49,13 @@ class SystemLoggerTransport extends Transport {
} }
log(info: any, callback: any) { log(info: any, callback: any) {
const { level, message, reqId, app, stack, [SPLAT]: args } = info; const { level, message, reqId, app, stack, cause, [SPLAT]: args } = info;
const logger = level === 'error' && this.errorLogger ? this.errorLogger : this.logger; const logger = level === 'error' && this.errorLogger ? this.errorLogger : this.logger;
const { module, submodule, method, ...meta } = args?.[0] || {}; const { module, submodule, method, ...meta } = args?.[0] || {};
logger.log({ logger.log({
level, level,
stack,
message, message,
stack,
meta, meta,
module: module || info['module'] || '', module: module || info['module'] || '',
submodule: submodule || info['submodule'] || '', submodule: submodule || info['submodule'] || '',
@ -63,11 +63,51 @@ class SystemLoggerTransport extends Transport {
app, app,
reqId, reqId,
}); });
if (cause) {
logger.log({
level,
message: cause.message,
stack: cause.stack,
app,
reqId,
});
}
callback(null, true); callback(null, true);
} }
} }
export const createSystemLogger = (options: SystemLoggerOptions): SystemLogger => function child(defaultRequestMetadata: any) {
winston.createLogger({ const logger = this;
return Object.create(logger, {
write: {
value: function (info: any) {
const infoClone = Object.assign({}, defaultRequestMetadata, info);
if (info instanceof Error) {
infoClone.stack = info.stack;
infoClone.message = info.message;
infoClone.cause = info.cause;
}
logger.write(infoClone);
},
},
});
}
export const createSystemLogger = (options: SystemLoggerOptions): SystemLogger => {
const logger = winston.createLogger({
transports: [new SystemLoggerTransport(options)], transports: [new SystemLoggerTransport(options)],
}); });
// Since error.cause is not supported by child logger of winston
// we have to use a proxy to rewrite child method
return new Proxy(logger, {
get(target, prop) {
if (prop === 'child') {
return child.bind(target);
}
return Reflect.get(target, prop);
},
});
};

View File

@ -1,12 +1,12 @@
{ {
"name": "@nocobase/resourcer", "name": "@nocobase/resourcer",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"main": "./lib/index.js", "main": "./lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@nocobase/utils": "0.20.0-alpha.14", "@nocobase/utils": "0.20.0-alpha.15",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"koa-compose": "^4.1.0", "koa-compose": "^4.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/sdk", "name": "@nocobase/sdk",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/server", "name": "@nocobase/server",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "lib/index.js", "main": "lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -10,18 +10,18 @@
"@koa/cors": "^3.1.0", "@koa/cors": "^3.1.0",
"@koa/multer": "^3.0.2", "@koa/multer": "^3.0.2",
"@koa/router": "^9.4.0", "@koa/router": "^9.4.0",
"@nocobase/acl": "0.20.0-alpha.14", "@nocobase/acl": "0.20.0-alpha.15",
"@nocobase/actions": "0.20.0-alpha.14", "@nocobase/actions": "0.20.0-alpha.15",
"@nocobase/auth": "0.20.0-alpha.14", "@nocobase/auth": "0.20.0-alpha.15",
"@nocobase/cache": "0.20.0-alpha.14", "@nocobase/cache": "0.20.0-alpha.15",
"@nocobase/data-source-manager": "0.20.0-alpha.14", "@nocobase/data-source-manager": "0.20.0-alpha.15",
"@nocobase/database": "0.20.0-alpha.14", "@nocobase/database": "0.20.0-alpha.15",
"@nocobase/evaluators": "0.20.0-alpha.14", "@nocobase/evaluators": "0.20.0-alpha.15",
"@nocobase/logger": "0.20.0-alpha.14", "@nocobase/logger": "0.20.0-alpha.15",
"@nocobase/resourcer": "0.20.0-alpha.14", "@nocobase/resourcer": "0.20.0-alpha.15",
"@nocobase/sdk": "0.20.0-alpha.14", "@nocobase/sdk": "0.20.0-alpha.15",
"@nocobase/telemetry": "0.20.0-alpha.14", "@nocobase/telemetry": "0.20.0-alpha.15",
"@nocobase/utils": "0.20.0-alpha.14", "@nocobase/utils": "0.20.0-alpha.15",
"@types/decompress": "4.2.4", "@types/decompress": "4.2.4",
"@types/ini": "^1.3.31", "@types/ini": "^1.3.31",
"@types/koa-send": "^4.1.3", "@types/koa-send": "^4.1.3",

View File

@ -34,6 +34,7 @@ const availableActions: {
}, },
destroy: { destroy: {
displayName: '{{t("Delete")}}', displayName: '{{t("Delete")}}',
aliases: ['destroy', 'remove'],
type: 'old-data', type: 'old-data',
}, },
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/telemetry", "name": "@nocobase/telemetry",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "nocobase telemetry library", "description": "nocobase telemetry library",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
@ -11,7 +11,7 @@
"directory": "packages/telemetry" "directory": "packages/telemetry"
}, },
"dependencies": { "dependencies": {
"@nocobase/utils": "0.20.0-alpha.14", "@nocobase/utils": "0.20.0-alpha.15",
"@opentelemetry/api": "^1.7.0", "@opentelemetry/api": "^1.7.0",
"@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/instrumentation": "^0.46.0",
"@opentelemetry/resources": "^1.19.0", "@opentelemetry/resources": "^1.19.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/test", "name": "@nocobase/test",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "lib/index.js", "main": "lib/index.js",
"module": "./src/index.ts", "module": "./src/index.ts",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
@ -40,7 +40,7 @@
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "8.1.0", "@faker-js/faker": "8.1.0",
"@nocobase/server": "0.20.0-alpha.14", "@nocobase/server": "0.20.0-alpha.15",
"@playwright/test": "^1.42.1", "@playwright/test": "^1.42.1",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/utils", "name": "@nocobase/utils",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "lib/index.js", "main": "lib/index.js",
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "权限控制", "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": "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": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。", "description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/acl", "homepage": "https://docs.nocobase.com/handbook/acl",

View File

@ -1,6 +1,13 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Card, Row, Col, Tabs, Divider } from 'antd'; import { Card, Row, Col, Tabs, Divider } from 'antd';
import { CollectionProvider, CollectionProvider_deprecated, ResourceActionProvider, usePlugin } from '@nocobase/client'; import {
CollectionProvider,
CollectionProvider_deprecated,
ResourceActionProvider,
SchemaComponentContext,
usePlugin,
useSchemaComponentContext,
} from '@nocobase/client';
import { ISchema, Schema } from '@formily/react'; import { ISchema, Schema } from '@formily/react';
import { RolesMenu } from './RolesMenu'; import { RolesMenu } from './RolesMenu';
import { useACLTranslation } from './locale'; import { useACLTranslation } from './locale';
@ -58,53 +65,55 @@ export const RolesManagement: React.FC = () => {
children: item.Component ? React.createElement(item.Component, { active: activeKey === name }) : null, children: item.Component ? React.createElement(item.Component, { active: activeKey === name }) : null,
})); }));
const [role, setRole] = useState(null); const [role, setRole] = useState(null);
const scCtx = useSchemaComponentContext();
return ( return (
<RolesManagerContext.Provider value={{ role, setRole }}> <SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
<Card> <RolesManagerContext.Provider value={{ role, setRole }}>
<Row gutter={24} style={{ flexWrap: 'nowrap' }}> <Card>
<Col flex="280px" style={{ borderRight: '1px solid #eee', minWidth: '250px' }}> <Row gutter={24} style={{ flexWrap: 'nowrap' }}>
<ResourceActionProvider <Col flex="280px" style={{ borderRight: '1px solid #eee', minWidth: '250px' }}>
collection={collection} <ResourceActionProvider
request={{ collection={collection}
resource: 'roles', request={{
action: 'list', resource: 'roles',
params: { action: 'list',
pagination: false, params: {
filter: { filter: {
'name.$ne': 'root', 'name.$ne': 'root',
},
showAnonymous: true,
sort: ['createdAt'],
appends: [],
}, },
showAnonymous: true, }}
sort: ['createdAt'], >
appends: [], <CollectionProvider_deprecated collection={collection}>
}, <Row>
}} <NewRole />
> </Row>
<CollectionProvider_deprecated collection={collection}> <Divider style={{ margin: '12px 0' }} />
<Row> <RolesMenu />
<NewRole /> </CollectionProvider_deprecated>
</Row> </ResourceActionProvider>
<Divider style={{ margin: '12px 0' }} /> </Col>
<RolesMenu /> <Col flex="auto" style={{ overflow: 'hidden' }}>
</CollectionProvider_deprecated> <Tabs
</ResourceActionProvider> activeKey={activeKey}
</Col> onChange={(key) => setActiveKey(key)}
<Col flex="auto" style={{ overflow: 'hidden' }}> items={[
<Tabs {
activeKey={activeKey} key: 'permissions',
onChange={(key) => setActiveKey(key)} label: t('Permissions'),
items={[ children: <Permissions active={activeKey === 'permissions'} />,
{ },
key: 'permissions', ...tabs,
label: t('Permissions'), ]}
children: <Permissions active={activeKey === 'permissions'} />, />
}, </Col>
...tabs, </Row>
]} </Card>
/> </RolesManagerContext.Provider>
</Col> </SchemaComponentContext.Provider>
</Row>
</Card>
</RolesManagerContext.Provider>
); );
}; };

View File

@ -5,55 +5,98 @@ import {
useAPIClient, useAPIClient,
useResourceActionContext, useResourceActionContext,
} from '@nocobase/client'; } from '@nocobase/client';
import { Menu, Empty, Dropdown, App, Tag, Row, Col } from 'antd'; import { Menu, Empty, Dropdown, App, Tag, Row, Col, Spin } from 'antd';
import { TagOutlined, MoreOutlined } from '@ant-design/icons'; import { TagOutlined, MoreOutlined } from '@ant-design/icons';
import React, { useContext, useEffect } from 'react'; import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useACLTranslation } from './locale'; import { useACLTranslation } from './locale';
import { Schema } from '@formily/react'; import { Schema } from '@formily/react';
import { RolesManagerContext } from './RolesManagerProvider'; import { RolesManagerContext } from './RolesManagerProvider';
import { roleEditSchema } from './schemas/roles'; import { roleEditSchema } from './schemas/roles';
import { useLoadMoreObserver } from './hooks/load-more-observer';
export const RolesMenu: React.FC & { export const RolesMenu: React.FC & {
Item: React.FC<{ item: any; onEdit: () => void }>; Item: React.FC<{ item: any; onEdit: () => void }>;
} = () => { } = () => {
const { t } = useACLTranslation(); const { t } = useACLTranslation();
const { data } = useResourceActionContext(); const { data, run, loading } = useResourceActionContext();
const [visible, setVisible] = React.useState(false); const [visible, setVisible] = useState(false);
const [record, setRecord] = React.useState(null); const [record, setRecord] = useState(null);
const { role, setRole } = useContext(RolesManagerContext); const { role, setRole } = useContext(RolesManagerContext);
const items = (data?.data || []).map((item: any) => ({ const [roles, setRoles] = useState([]);
key: item.name,
label: ( const loadMore = useCallback(() => {
<RolesMenu.Item const meta = data?.meta;
item={item} if (!meta || meta.page >= meta.totalPage) {
onEdit={() => { return;
setVisible(true); }
setRecord(item); run({
}} page: meta.page + 1,
/> });
), }, [data]);
})); const { lastItem, setLastItem } = useLoadMoreObserver({ loadMore });
const handleSelect = ({ key }) => { const handleSelect = ({ key }) => {
setRole((data?.data || []).find((item: any) => item.name === key)); setRole(roles.find((item: any) => item.name === key));
}; };
useEffect(() => { useEffect(() => {
if (!data?.data.length) { if (!roles[0]) {
return; return;
} }
setRole(data?.data[0]); setRole(roles[0]);
}, [data, setRole]); }, [roles, setRole]);
useEffect(() => {
if (!data?.data?.length) {
return;
}
const ref = React.createRef<any>();
setLastItem(ref);
setRoles((prev) => prev.concat(data.data));
}, [data, setLastItem]);
const items = useMemo(
() =>
roles.map((item: any, index: number) => ({
key: item.name,
label:
index === roles.length - 1 ? (
<div ref={lastItem}>
<RolesMenu.Item
item={item}
onEdit={() => {
setVisible(true);
setRecord(item);
}}
/>
</div>
) : (
<RolesMenu.Item
item={item}
onEdit={() => {
setVisible(true);
setRecord(item);
}}
/>
),
})),
[roles, lastItem],
);
return ( return (
<> <>
{items.length ? ( {roles.length ? (
<Menu <>
style={{ border: 'none', maxHeight: '65vh', overflowY: 'auto' }} <Menu
items={items} style={{ border: 'none', maxHeight: '65vh', overflowY: 'auto' }}
selectedKeys={[role?.name]} items={items}
onSelect={handleSelect} selectedKeys={[role?.name]}
/> onSelect={handleSelect}
/>
{loading && <Spin />}
</>
) : ( ) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)} )}
@ -92,12 +135,15 @@ RolesMenu.Item = function DepartmentTreeItem({ item, onEdit }) {
deleteDepartment(); deleteDepartment();
} }
}; };
const title = Schema.compile(item.title, { t });
return ( return (
<Row> <Row>
<Col flex={3} style={{ display: 'inline-flex', alignItems: 'center' }}> <Col flex={3} style={{ display: 'inline-flex', alignItems: 'center' }}>
<span style={{ whiteSpace: 'nowrap', width: '120px', overflow: 'hidden', textOverflow: 'ellipsis' }}> <span style={{ whiteSpace: 'nowrap', width: '120px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<TagOutlined /> <TagOutlined />
<span style={{ marginLeft: '10px' }}>{Schema.compile(item.title, { t })}</span> <span style={{ marginLeft: '10px' }} title={title}>
{title}
</span>
</span> </span>
</Col> </Col>
<Col> <Col>

View File

@ -0,0 +1,45 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
export const useLoadMoreObserver = ({
loadMore,
}: {
loadMore: () => void; // callback
}) => {
const [lastItem, setLastItem] = useState<React.RefObject<any>>(null);
const observer = useRef<IntersectionObserver | null>(null);
const observeExposure = useCallback(
(lastItem: React.RefObject<any>) => {
if (!lastItem) {
return;
}
observer.current && observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.target !== lastItem.current) {
return;
}
if (entry.isIntersecting) {
observer.current && observer.current.unobserve(lastItem.current);
loadMore();
}
});
});
lastItem.current && observer.current && observer.current.observe(lastItem.current);
},
[loadMore],
);
useEffect(() => {
observeExposure(lastItem);
return () => {
observer.current && observer.current.disconnect();
};
}, [lastItem, observeExposure]);
return {
lastItem,
setLastItem,
};
};

View File

@ -398,11 +398,12 @@ export class PluginACL extends Plugin {
}; };
// sync database role data to acl // sync database role data to acl
this.app.on('afterLoad', async () => { this.app.on('afterStart', async () => {
await writeRolesToACL(this.app, { await writeRolesToACL(this.app, {
withOutResources: true, withOutResources: true,
}); });
}); });
// this.app.on('afterInstall', writeRolesToACL); // this.app.on('afterInstall', writeRolesToACL);
this.app.on('afterInstallPlugin', async (plugin) => { this.app.on('afterInstallPlugin', async (plugin) => {

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-action-bulk-edit", "name": "@nocobase/plugin-action-bulk-edit",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-edit", "homepage": "https://docs.nocobase.com/handbook/action-bulk-edit",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-action-bulk-update", "name": "@nocobase/plugin-action-bulk-update",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-update", "homepage": "https://docs.nocobase.com/handbook/action-bulk-update",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-action-duplicate", "name": "@nocobase/plugin-action-duplicate",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-duplicate", "homepage": "https://docs.nocobase.com/handbook/action-duplicate",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-action-print", "name": "@nocobase/plugin-action-print",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-print", "homepage": "https://docs.nocobase.com/handbook/action-print",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-api-doc", "name": "@nocobase/plugin-api-doc",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"displayName": "API documentation", "displayName": "API documentation",
"displayName.zh-CN": "API 文档", "displayName.zh-CN": "API 文档",
"description": "An OpenAPI documentation generator for NocoBase HTTP API.", "description": "An OpenAPI documentation generator for NocoBase HTTP API.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "认证API 密钥", "displayName.zh-CN": "认证API 密钥",
"description": "Allows users to use API key to access application's HTTP API", "description": "Allows users to use API key to access application's HTTP API",
"description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API", "description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/api-keys", "homepage": "https://docs.nocobase.com/handbook/api-keys",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-audit-logs", "name": "@nocobase/plugin-audit-logs",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"displayName": "Audit logs (deprecated)", "displayName": "Audit logs (deprecated)",
"displayName.zh-CN": "审计日志(废弃)", "displayName.zh-CN": "审计日志(废弃)",
"description": "This plugin is deprecated. There will be a new audit log plugin in the future.", "description": "This plugin is deprecated. There will be a new audit log plugin in the future.",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-auth", "name": "@nocobase/plugin-auth",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth", "homepage": "https://docs.nocobase.com/handbook/auth",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "应用的备份与还原", "displayName.zh-CN": "应用的备份与还原",
"description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.", "description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.",
"description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。", "description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/backup-restore", "homepage": "https://docs.nocobase.com/handbook/backup-restore",

View File

@ -85,6 +85,43 @@ describe('dumper', () => {
expect(items.length).toBe(1); expect(items.length).toBe(1);
}); });
it('should dump and restore uuid field', async () => {
await db.getRepository('collections').create({
values: {
name: 'tests',
fields: [
{
type: 'uuid',
name: 'uuid_test',
},
],
},
context: {},
});
await db.getRepository('tests').create({
values: {},
});
const dumper = new Dumper(app);
const result = await dumper.dump({
groups: new Set(['required', 'custom']),
});
const restorer = new Restorer(app, {
backUpFilePath: result.filePath,
});
await restorer.restore({
groups: new Set(['required', 'custom']),
});
const testCollection = app.db.getCollection('tests');
const items = await testCollection.repository.find();
expect(items.length).toBe(1);
});
describe('id seq', () => { describe('id seq', () => {
let allGroups; let allGroups;

View File

@ -412,12 +412,18 @@ export class Dumper extends AppMigrator {
if (collectionField) { if (collectionField) {
// is a field // is a field
return { const fieldAttributes: any = {
field: attr.field, field: attr.field,
isCollectionField: true, isCollectionField: true,
type: collectionField.type, type: collectionField.type,
typeOptions: collectionField.options, typeOptions: collectionField.options,
}; };
if (fieldAttributes.typeOptions?.defaultValue?.constructor?.name === 'UUIDV4') {
delete fieldAttributes.typeOptions.defaultValue;
}
return fieldAttributes;
} }
return { return {

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-calendar", "name": "@nocobase/plugin-calendar",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"displayName": "Calendar", "displayName": "Calendar",
"displayName.zh-CN": "日历", "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.", "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.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "认证CAS", "displayName.zh-CN": "认证CAS",
"description": "CAS authentication.", "description": "CAS authentication.",
"description.zh-CN": "通过 CAS 协议认证身份。", "description.zh-CN": "通过 CAS 协议认证身份。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth-cas", "homepage": "https://docs.nocobase.com/handbook/auth-cas",

View File

@ -3,20 +3,25 @@ import { AppSupervisor } from '@nocobase/server';
import { CASAuth } from '../auth'; import { CASAuth } from '../auth';
export const service = async (ctx: Context, next: Next) => { export const service = async (ctx: Context, next: Next) => {
const { authenticator, __appName: appName, redirect = '/admin' } = ctx.action.params; const { authenticator, __appName: appName, redirect } = ctx.action.params;
let prefix = ''; let prefix = process.env.APP_PUBLIC_PATH || '';
if (appName && appName !== 'main') { if (appName && appName !== 'main') {
const appSupervisor = AppSupervisor.getInstance(); const appSupervisor = AppSupervisor.getInstance();
if (appSupervisor?.runningMode !== 'single') { if (appSupervisor?.runningMode !== 'single') {
prefix = process.env.APP_PUBLIC_PATH + `apps/${appName}`; prefix += `apps/${appName}`;
} }
} }
const auth = (await ctx.app.authManager.get(authenticator, ctx)) as CASAuth; const auth = (await ctx.app.authManager.get(authenticator, ctx)) as CASAuth;
if (prefix.endsWith('/')) {
prefix = prefix.slice(0, -1);
}
try { try {
const { token } = await auth.signIn(); const { token } = await auth.signIn();
ctx.redirect(`${prefix}${redirect}?authenticator=${authenticator}&token=${token}`); ctx.redirect(`${prefix}${redirect || '/admin'}?authenticator=${authenticator}&token=${token}`);
} catch (error) { } catch (error) {
ctx.redirect(`${prefix}/signin?authenticator=${authenticator}&error=${error.message}&redirect=${redirect}`); ctx.redirect(`${prefix}/signin?authenticator=${authenticator}&error=${error.message}&redirect=${redirect}`);
} }

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "图表(废弃)", "displayName.zh-CN": "图表(废弃)",
"description": "The plugin has been deprecated, please use the data visualization plugin instead.", "description": "The plugin has been deprecated, please use the data visualization plugin instead.",
"description.zh-CN": "已废弃插件,请使用数据可视化插件代替。", "description.zh-CN": "已废弃插件,请使用数据可视化插件代替。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"devDependencies": { "devDependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-china-region", "name": "@nocobase/plugin-china-region",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"displayName": "Administrative divisions of China", "displayName": "Administrative divisions of China",
"displayName.zh-CN": "中国行政区划", "displayName.zh-CN": "中国行政区划",
"description": "Provides data and field type for administrative divisions of China.", "description": "Provides data and field type for administrative divisions of China.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "WEB 客户端", "displayName.zh-CN": "WEB 客户端",
"description": "Provides a client interface for the NocoBase server", "description": "Provides a client interface for the NocoBase server",
"description.zh-CN": "为 NocoBase 服务端提供客户端界面", "description.zh-CN": "为 NocoBase 服务端提供客户端界面",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"devDependencies": { "devDependencies": {

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "数据源:主数据库", "displayName.zh-CN": "数据源:主数据库",
"description": "NocoBase main database, supports relational databases such as MySQL, PostgreSQL, SQLite and so on.", "description": "NocoBase main database, supports relational databases such as MySQL, PostgreSQL, SQLite and so on.",
"description.zh-CN": "NocoBase 主数据库,支持 MySQL、PostgreSQL、SQLite 等关系型数据库。", "description.zh-CN": "NocoBase 主数据库,支持 MySQL、PostgreSQL、SQLite 等关系型数据库。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/data-source-main", "homepage": "https://docs.nocobase.com/handbook/data-source-main",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-main", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-main",

View File

@ -0,0 +1,46 @@
import Database, { Collection as DBCollection } from '@nocobase/database';
import Application from '@nocobase/server';
import { createApp } from '..';
describe('uuid', () => {
let db: Database;
let app: Application;
let Collection: DBCollection;
let Field: DBCollection;
beforeEach(async () => {
app = await createApp();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
});
afterEach(async () => {
await app.destroy();
});
it('should create uuid field', async () => {
await Collection.repository.create({
values: {
name: 'tests',
},
context: {},
});
await Field.repository.create({
values: {
collectionName: 'tests',
type: 'uuid',
name: 'uuid',
},
context: {},
});
// @ts-ignore
const resp = await app.agent().resource('tests').create({});
expect(resp.status).toBe(200);
const data = resp.body.data;
expect(data['uuid']).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
});
});

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-custom-request", "name": "@nocobase/plugin-custom-request",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-custom-request", "homepage": "https://docs.nocobase.com/handbook/action-custom-request",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-data-source-manager", "name": "@nocobase/plugin-data-source-manager",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"displayName": "Data source manager", "displayName": "Data source manager",
"displayName.zh-CN": "数据源管理", "displayName.zh-CN": "数据源管理",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-data-visualization", "name": "@nocobase/plugin-data-visualization",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"displayName": "Data visualization", "displayName": "Data visualization",
"displayName.zh-CN": "数据可视化", "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.", "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.",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-disable-pm-add", "name": "@nocobase/plugin-disable-pm-add",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"peerDependencies": { "peerDependencies": {
"@nocobase/client": "0.x", "@nocobase/client": "0.x",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "错误处理器", "displayName.zh-CN": "错误处理器",
"description": "Handling application errors and exceptions.", "description": "Handling application errors and exceptions.",
"description.zh-CN": "处理应用程序中的错误和异常。", "description.zh-CN": "处理应用程序中的错误和异常。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"devDependencies": { "devDependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-excel-formula-field", "name": "@nocobase/plugin-excel-formula-field",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "", "description": "",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导出记录", "displayName.zh-CN": "操作:导出记录",
"description": "Export filtered records to excel, you can configure which fields to export.", "description": "Export filtered records to excel, you can configure which fields to export.",
"description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。", "description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-export", "homepage": "https://docs.nocobase.com/handbook/action-export",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-file-manager", "name": "@nocobase/plugin-file-manager",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"displayName": "File manager", "displayName": "File manager",
"displayName.zh-CN": "文件管理器", "displayName.zh-CN": "文件管理器",
"description": "Provides files storage services with files collection template and attachment field.", "description": "Provides files storage services with files collection template and attachment field.",

View File

@ -1,158 +1,6 @@
import { CollectionTemplate, getConfigurableProperties } from '@nocobase/client'; import { CollectionTemplate, getConfigurableProperties } from '@nocobase/client';
// import { CollectionOptions } from '@nocobase/database';
import { NAMESPACE } from '../locale'; import { NAMESPACE } from '../locale';
// export const fileCollectionTemplate = {
// name: 'file',
// title: `{{t("File collection", { ns: "${NAMESPACE}" })}}`,
// order: 3,
// color: 'blue',
// default: {
// createdBy: true,
// updatedBy: true,
// fields: [
// {
// interface: 'input',
// type: 'string',
// name: 'title',
// deletable: false,
// uiSchema: {
// type: 'string',
// title: `{{t("Title")}}`,
// 'x-component': 'Input',
// },
// },
// // '系统文件名(含扩展名)',
// {
// interface: 'input',
// type: 'string',
// name: 'filename',
// deletable: false,
// uiSchema: {
// type: 'string',
// title: `{{t("File name", { ns: "${NAMESPACE}" })}}`,
// 'x-component': 'Input',
// 'x-read-pretty': true,
// },
// },
// // '扩展名(含“.”)',
// {
// interface: 'input',
// type: 'string',
// name: 'extname',
// deletable: false,
// uiSchema: {
// type: 'string',
// title: `{{t("Extension name", { ns: "${NAMESPACE}" })}}`,
// 'x-component': 'Input',
// 'x-read-pretty': true,
// },
// },
// // '文件体积(字节)',
// {
// interface: 'integer',
// type: 'integer',
// name: 'size',
// deletable: false,
// uiSchema: {
// type: 'number',
// title: `{{t("Size", { ns: "${NAMESPACE}" })}}`,
// 'x-component': 'InputNumber',
// 'x-read-pretty': true,
// 'x-component-props': {
// stringMode: true,
// step: '0',
// },
// },
// },
// {
// interface: 'input',
// type: 'string',
// name: 'mimetype',
// deletable: false,
// uiSchema: {
// type: 'string',
// title: `{{t("MIME type", { ns: "${NAMESPACE}" })}}`,
// 'x-component': 'Input',
// 'x-read-pretty': true,
// },
// },
// // '相对路径(含“/”前缀)',
// {
// interface: 'input',
// type: 'string',
// name: 'path',
// deletable: false,
// uiSchema: {
// type: 'string',
// title: `{{t("Path")}}`,
// 'x-component': 'Input',
// 'x-read-pretty': true,
// },
// },
// // 文件的可访问地址
// {
// interface: 'input',
// type: 'string',
// name: 'url',
// deletable: false,
// uiSchema: {
// type: 'string',
// title: `{{t("URL")}}`,
// 'x-component': 'Input.URL',
// 'x-read-pretty': true,
// },
// },
// // 用于预览
// {
// interface: 'url',
// type: 'string',
// name: 'preview',
// field: 'url', // 直接引用 url 字段
// deletable: false,
// uiSchema: {
// type: 'string',
// title: `{{t("Preview")}}`,
// 'x-component': 'Preview',
// 'x-read-pretty': true,
// },
// },
// {
// comment: '存储引擎',
// type: 'belongsTo',
// name: 'storage',
// target: 'storages',
// foreignKey: 'storageId',
// deletable: false,
// },
// // '其他文件信息(如图片的宽高)',
// {
// type: 'jsonb',
// name: 'meta',
// deletable: false,
// defaultValue: {},
// },
// ],
// },
// configurableProperties: {
// ...getConfigurableProperties('title', 'name'),
// inherits: {
// ...getConfigurableProperties('inherits').inherits,
// 'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
// },
// ...getConfigurableProperties('category', 'description'),
// storage: {
// title: `{{t("File storage", { ns: "${NAMESPACE}" })}}`,
// type: 'hasOne',
// name: 'storage',
// required: true,
// 'x-decorator': 'FormItem',
// 'x-component': 'Select',
// 'x-reactions': ['{{useAsyncDataSource(loadStorages)}}'],
// },
// },
// } as CollectionOptions;
export class FileCollectionTemplate extends CollectionTemplate { export class FileCollectionTemplate extends CollectionTemplate {
name = 'file'; name = 'file';
title = `{{t("File collection", { ns: "${NAMESPACE}" })}}`; title = `{{t("File collection", { ns: "${NAMESPACE}" })}}`;
@ -236,7 +84,7 @@ export class FileCollectionTemplate extends CollectionTemplate {
deletable: false, deletable: false,
uiSchema: { uiSchema: {
type: 'string', type: 'string',
title: `{{t("Path")}}`, title: `{{t("Path", { ns: "${NAMESPACE}" })}}`,
'x-component': 'Input', 'x-component': 'Input',
'x-read-pretty': true, 'x-read-pretty': true,
}, },
@ -263,7 +111,7 @@ export class FileCollectionTemplate extends CollectionTemplate {
deletable: false, deletable: false,
uiSchema: { uiSchema: {
type: 'string', type: 'string',
title: `{{t("Preview")}}`, title: `{{t("Preview", { ns: "${NAMESPACE}" })}}`,
'x-component': 'Preview', 'x-component': 'Preview',
'x-read-pretty': true, 'x-read-pretty': true,
}, },
@ -275,6 +123,12 @@ export class FileCollectionTemplate extends CollectionTemplate {
target: 'storages', target: 'storages',
foreignKey: 'storageId', foreignKey: 'storageId',
deletable: false, deletable: false,
uiSchema: {
type: 'string',
title: `{{t("Storage", { ns: "${NAMESPACE}" })}}`,
'x-component': 'Input',
'x-read-pretty': true,
},
}, },
// '其他文件信息(如图片的宽高)', // '其他文件信息(如图片的宽高)',
{ {

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表字段:公式", "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": "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 两种引擎", "description.zh-CN": "可以配置并存储同一条记录的多字段值之间的计算结果,支持 Math.js 和 Excel formula functions 两种引擎",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-formula", "homepage": "https://docs.nocobase.com/handbook/field-formula",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-gantt", "name": "@nocobase/plugin-gantt",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"displayName": "Block: Gantt", "displayName": "Block: Gantt",
"displayName.zh-CN": "区块:甘特图", "displayName.zh-CN": "区块:甘特图",
"description": "Provides Gantt block.", "description": "Provides Gantt block.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "可视化数据表管理", "displayName.zh-CN": "可视化数据表管理",
"description": "An ER diagram-like tool. Currently only the Master database is supported.", "description": "An ER diagram-like tool. Currently only the Master database is supported.",
"description.zh-CN": "类似 ER 图的工具,目前只支持主数据库。", "description.zh-CN": "类似 ER 图的工具,目前只支持主数据库。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/graph-collection-manager", "homepage": "https://docs.nocobase.com/handbook/graph-collection-manager",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "区块iframe", "displayName.zh-CN": "区块iframe",
"description": "Create an iframe block on the page to embed and display external web pages or content.", "description": "Create an iframe block on the page to embed and display external web pages or content.",
"description.zh-CN": "在页面上创建和管理iframe用于嵌入和展示外部网页或内容。", "description.zh-CN": "在页面上创建和管理iframe用于嵌入和展示外部网页或内容。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-iframe", "homepage": "https://docs.nocobase.com/handbook/block-iframe",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导入记录", "displayName.zh-CN": "操作:导入记录",
"description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.", "description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.",
"description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。", "description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-import", "homepage": "https://docs.nocobase.com/handbook/action-import",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-kanban", "name": "@nocobase/plugin-kanban",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-kanban", "homepage": "https://docs.nocobase.com/handbook/block-kanban",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-kanban", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-kanban",

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-localization-management", "name": "@nocobase/plugin-localization-management",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/localization-management", "homepage": "https://docs.nocobase.com/handbook/localization-management",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/localization-management", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/localization-management",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "日志", "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": "Server-side logs, mainly including API request logs and system runtime logs, and allows to package and download log files.",
"description.zh-CN": "服务端日志,主要包括接口请求日志和系统运行日志,并支持打包和下载日志文件。", "description.zh-CN": "服务端日志,主要包括接口请求日志和系统运行日志,并支持打包和下载日志文件。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "dist/server/index.js", "main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/logger", "homepage": "https://docs.nocobase.com/handbook/logger",

View File

@ -2,7 +2,7 @@
"name": "@nocobase/plugin-map", "name": "@nocobase/plugin-map",
"displayName": "Block: Map", "displayName": "Block: Map",
"displayName.zh-CN": "区块:地图", "displayName.zh-CN": "区块:地图",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"description": "Map block, support Gaode map and Google map, you can also extend more map types.", "description": "Map block, support Gaode map and Google map, you can also extend more map types.",
"description.zh-CN": "地图区块,支持高德地图和 Google 地图,你也可以扩展更多地图类型。", "description.zh-CN": "地图区块,支持高德地图和 Google 地图,你也可以扩展更多地图类型。",
"license": "AGPL-3.0", "license": "AGPL-3.0",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "数学公式字段", "displayName.zh-CN": "数学公式字段",
"description": "A powerful mathematical formula field plugin designed to provide flexible mathematical and formula calculation capabilities for databases or data management systems. It seamlessly integrates with various database systems such as MySQL, PostgreSQL, and offers an intuitive user interface for defining and executing mathematical formula fields.", "description": "A powerful mathematical formula field plugin designed to provide flexible mathematical and formula calculation capabilities for databases or data management systems. It seamlessly integrates with various database systems such as MySQL, PostgreSQL, and offers an intuitive user interface for defining and executing mathematical formula fields.",
"description.zh-CN": "一个功能强大的数学公式字段插件旨在为数据库或数据管理系统提供灵活的数学计算和公式计算功能。它可以与各种数据库系统如MySQL、PostgreSQL无缝集成并提供直观的用户界面来定义和执行数学公式字段。", "description.zh-CN": "一个功能强大的数学公式字段插件旨在为数据库或数据管理系统提供灵活的数学计算和公式计算功能。它可以与各种数据库系统如MySQL、PostgreSQL无缝集成并提供直观的用户界面来定义和执行数学公式字段。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"devDependencies": { "devDependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "@nocobase/plugin-mobile-client", "name": "@nocobase/plugin-mobile-client",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/mobile-client", "homepage": "https://docs.nocobase.com/handbook/mobile-client",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/mobile-client", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/mobile-client",

View File

@ -2,7 +2,7 @@
"name": "@nocobase/plugin-mock-collections", "name": "@nocobase/plugin-mock-collections",
"displayName": "mock-collections", "displayName": "mock-collections",
"description": "mock-collections", "description": "mock-collections",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"peerDependencies": { "peerDependencies": {

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "多应用管理器", "displayName.zh-CN": "多应用管理器",
"description": "Dynamically create multiple apps without separate deployments.", "description": "Dynamically create multiple apps without separate deployments.",
"description.zh-CN": "无需单独部署即可动态创建多个应用。", "description.zh-CN": "无需单独部署即可动态创建多个应用。",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/multi-app-manager", "homepage": "https://docs.nocobase.com/handbook/multi-app-manager",

View File

@ -1,6 +1,6 @@
import { Database } from '@nocobase/database'; import { Database } from '@nocobase/database';
import { AppSupervisor, Gateway } from '@nocobase/server'; import { AppSupervisor, Gateway } from '@nocobase/server';
import { MockServer, createMockServer } from '@nocobase/test'; import { createMockServer, MockServer } from '@nocobase/test';
import { uid } from '@nocobase/utils'; import { uid } from '@nocobase/utils';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { PluginMultiAppManager } from '../server'; import { PluginMultiAppManager } from '../server';
@ -22,6 +22,29 @@ describe('multiple apps', () => {
await app.destroy(); await app.destroy();
}); });
it('should merge database options', async () => {
const name = `td_${uid()}`;
await db.getRepository('applications').create({
values: {
name,
options: {
plugins: [],
database: {
underscored: true,
},
},
},
context: {
waitSubAppInstall: true,
},
});
const subApp = await AppSupervisor.getInstance().getApp(name);
expect(subApp.db.options.underscored).toBeTruthy();
});
it('should register db creator', async () => { it('should register db creator', async () => {
const fn = vi.fn(); const fn = vi.fn();

View File

@ -1,6 +1,7 @@
import { Model, Transactionable } from '@nocobase/database'; import { Model, Transactionable } from '@nocobase/database';
import { Application } from '@nocobase/server'; import { Application } from '@nocobase/server';
import { AppOptionsFactory } from '../server'; import { AppOptionsFactory } from '../server';
import { merge } from '@nocobase/utils';
export interface registerAppOptions extends Transactionable { export interface registerAppOptions extends Transactionable {
skipInstall?: boolean; skipInstall?: boolean;
@ -10,11 +11,12 @@ export interface registerAppOptions extends Transactionable {
export class ApplicationModel extends Model { export class ApplicationModel extends Model {
registerToSupervisor(mainApp: Application, options: registerAppOptions) { registerToSupervisor(mainApp: Application, options: registerAppOptions) {
const appName = this.get('name') as string; const appName = this.get('name') as string;
const appOptions = (this.get('options') as any) || {}; const appModelOptions = (this.get('options') as any) || {};
const appOptions = options.appOptionsFactory(appName, mainApp);
const subAppOptions = { const subAppOptions = {
...options.appOptionsFactory(appName, mainApp), ...(merge(appOptions, appModelOptions) as object),
...appOptions,
name: appName, name: appName,
}; };

View File

@ -1,6 +1,5 @@
import { Database, IDatabaseOptions, Transactionable } from '@nocobase/database'; import { Database, IDatabaseOptions, Transactionable } from '@nocobase/database';
import Application, { AppSupervisor, Gateway, Plugin } from '@nocobase/server'; import Application, { AppSupervisor, Gateway, Plugin } from '@nocobase/server';
import { Mutex } from 'async-mutex';
import lodash from 'lodash'; import lodash from 'lodash';
import path from 'path'; import path from 'path';
import { ApplicationModel } from '../server'; import { ApplicationModel } from '../server';
@ -125,8 +124,6 @@ export class PluginMultiAppManager extends Plugin {
appOptionsFactory: AppOptionsFactory = defaultAppOptionsFactory; appOptionsFactory: AppOptionsFactory = defaultAppOptionsFactory;
subAppUpgradeHandler: SubAppUpgradeHandler = defaultSubAppUpgradeHandle; subAppUpgradeHandler: SubAppUpgradeHandler = defaultSubAppUpgradeHandle;
private beforeGetApplicationMutex = new Mutex();
static getDatabaseConfig(app: Application): IDatabaseOptions { static getDatabaseConfig(app: Application): IDatabaseOptions {
let oldConfig = let oldConfig =
app.options.database instanceof Database app.options.database instanceof Database
@ -136,6 +133,7 @@ export class PluginMultiAppManager extends Plugin {
if (!oldConfig && app.db) { if (!oldConfig && app.db) {
oldConfig = app.db.options; oldConfig = app.db.options;
} }
return lodash.cloneDeep(lodash.omit(oldConfig, ['migrator'])); return lodash.cloneDeep(lodash.omit(oldConfig, ['migrator']));
} }

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "多应用数据表共享", "displayName.zh-CN": "多应用数据表共享",
"description": "", "description": "",
"description.zh-CN": "", "description.zh-CN": "",
"version": "0.20.0-alpha.14", "version": "0.20.0-alpha.15",
"main": "./dist/server/index.js", "main": "./dist/server/index.js",
"devDependencies": { "devDependencies": {
"@formily/react": "2.x", "@formily/react": "2.x",

Some files were not shown because too many files have changed in this diff Show More