Merge pull request 'merge_from_upstream' (#457) from merge_from_upstream into @hera/dev

Reviewed-on: daoyoucloud/tachycode#457
This commit is contained in:
sealday 2024-03-21 18:40:33 +08:00
commit d766eb481a
133 changed files with 1318 additions and 641 deletions

View File

@ -43,18 +43,11 @@ jobs:
ref: ${{ inputs.branch }}
path: packages/pro-plugins
ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }}
# - name: Set variables
# run: |
# target_directory="./packages/pro-plugins/@nocobase"
# subdirectories=$(find "$target_directory" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | tr '\n' ' ')
# trimmed_variable=$(echo "$subdirectories" | xargs)
# 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: Set variables
run: |
APPEND_PRESET_LOCAL_PLUGINS=$(find ./packages/pro-plugins/@nocobase -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sed 's/^plugin-//' | tr '\n' ',' | sed 's/,$//')
echo "var2=$APPEND_PRESET_LOCAL_PLUGINS" >> $GITHUB_OUTPUT
id: vars
- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v3
with:
@ -89,5 +82,5 @@ jobs:
DB_USER: nocobase
DB_PASSWORD: password
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

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).
## [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
### Merged

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
import { Field, Form } from '@formily/core';
import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react';
import { untracked } from '@formily/reactive';
import { isURL, parse } from '@nocobase/utils/client';
import { App, message } from 'antd';
import _ from 'lodash';
@ -23,6 +25,7 @@ import { useFilterBlock } from '../../filter-provider/FilterProvider';
import { mergeFilter, transformToFilter } from '../../filter-provider/utils';
import { useRecord } from '../../record-provider';
import { removeNullCondition, useActionContext, useCompile } from '../../schema-component';
import { isSubMode } from '../../schema-component/antd/association-field/util';
import { useCurrentUserContext } from '../../user';
import { useLocalVariables, useVariables } from '../../variables';
import { isVariable } from '../../variables/utils/isVariable';
@ -30,9 +33,6 @@ import { transformVariableValue } from '../../variables/utils/transformVariableV
import { useBlockRequestContext, useFilterByTk, useParamsFromRecord } from '../BlockProvider';
import { useDetailsBlockContext } from '../DetailsBlockProvider';
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 './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 = () => {
const { formBlockRef } = useFormBlockContext();

View File

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

View File

@ -28,9 +28,9 @@ export class NanoidFieldInterface extends CollectionFieldInterface {
type: 'string',
title: '{{t("Field name")}}',
required: true,
'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-disabled': '{{ !createOnly }}',
description:
"{{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',
title: '{{t("Field name")}}',
required: true,
'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-disabled': '{{ !createOnly }}',
description:
"{{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 inner": "Insert inner",
"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",
"Collection": "Collection",
"Collection selector": "Collection selector",

View File

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

View File

@ -52,6 +52,9 @@
"Insert right": "Insérer à droite",
"Insert inner": "Insérer à l'intérieur",
"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",
"Collection": "Collection",
"Collections & Fields": "Collections et champs",

View File

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

View File

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

View File

@ -32,6 +32,9 @@
"Insert right": "Inserir à direita",
"Insert inner": "Inserir interno",
"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",
"Collection": "Coleção",
"Collections & Fields": "Coleções e campos",

View File

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

View File

@ -52,6 +52,9 @@
"Insert right": "Sağa yerleştir",
"Insert inner": "İçine yerleştir",
"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",
"Collection": "Koleksiyonlar",
"Collections & Fields": "Koleksiyonlar & Alanlar",

View File

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

View File

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

View File

@ -60,6 +60,9 @@
"Insert right": "從右邊插入",
"Insert inner": "從裡面插入",
"Delete": "刪除",
"Disassociate": "解除關聯",
"Disassociate record": "解除關聯記錄",
"Are you sure you want to disassociate it?": "你確定要解除關聯嗎?",
"UI editor": "介面編輯",
"Collection": "資料表",
"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 { useTranslation } from 'react-i18next';
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 { useDataBlockProps } from '../../../../data-source';
import { createDesignable, useDesignable } from '../../../../schema-component';
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 = () => {
const { t } = useTranslation();
@ -65,326 +66,197 @@ export const Resizable = () => {
);
};
const commonOptions = {
insertPosition: 'beforeEnd',
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("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',
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,
},
],
};
/**
* @deprecated
*/
export const tableActionColumnInitializers_deprecated = new CompatibleSchemaInitializer({
name: 'TableActionColumnInitializers',
insertPosition: 'beforeEnd',
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,
},
],
...commonOptions,
});
export const tableActionColumnInitializers = new CompatibleSchemaInitializer(
{
name: 'table:configureItemActions',
insertPosition: 'beforeEnd',
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,
},
],
...commonOptions,
},
tableActionColumnInitializers_deprecated,
);

View File

@ -12,6 +12,7 @@ import {
} from '../modules/actions/add-record/customizeCreateFormBlockInitializers';
import { BulkDestroyActionInitializer } from '../modules/actions/bulk-destroy/BulkDestroyActionInitializer';
import { DestroyActionInitializer } from '../modules/actions/delete/DestroyActionInitializer';
import { DisassociateActionInitializer } from '../modules/actions/disassociate/DisassociateActionInitializer';
import { ExpandableActionInitializer } from '../modules/actions/expand-collapse/ExpandableActionInitializer';
import { FilterActionInitializer } from '../modules/actions/filter/FilterActionInitializer';
import { RefreshActionInitializer } from '../modules/actions/refresh/RefreshActionInitializer';
@ -121,10 +122,10 @@ import * as items from './items';
export * from './buttons';
export * from './items';
export {
createDetailsBlockSchema,
createFilterFormBlockSchema,
createFormBlockSchema,
createReadPrettyFormBlockSchema,
createDetailsBlockSchema,
createTableBlockSchema,
gridRowColWrap,
itemsMerge,
@ -144,6 +145,7 @@ export class SchemaInitializerPlugin extends Plugin {
this.app.addComponents({
...initializerComponents,
...items,
DestroyActionInitializer,
CreateFormBlockInitializer,
FormBlockInitializer,
RecordFormBlockInitializer,
@ -171,7 +173,7 @@ export class SchemaInitializerPlugin extends Plugin {
UpdateSubmitActionInitializer,
BulkDestroyActionInitializer,
ExpandableActionInitializer,
DestroyActionInitializer,
DisassociateActionInitializer,
FilterActionInitializer,
RefreshActionInitializer,
} 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 { bulkDeleteActionSettings } from '../modules/actions/bulk-destroy/bulkDeleteActionSettings';
import { deleteActionSettings } from '../modules/actions/delete/deleteActionSettings';
import { disassociateActionSettings } from '../modules/actions/disassociate/disassociateActionSettings';
import { expendableActionSettings } from '../modules/actions/expand-collapse/expendableActionSettings';
import { filterActionSettings } from '../modules/actions/filter/filterActionSettings';
import { refreshActionSettings } from '../modules/actions/refresh/refreshActionSettings';
@ -69,6 +70,7 @@ export class SchemaSettingsPlugin extends Plugin {
this.schemaSettingsManager.add(viewActionSettings);
this.schemaSettingsManager.add(editActionSettings);
this.schemaSettingsManager.add(deleteActionSettings);
this.schemaSettingsManager.add(disassociateActionSettings);
this.schemaSettingsManager.add(bulkDeleteActionSettings);
this.schemaSettingsManager.add(customizeAddRecordActionSettings);
this.schemaSettingsManager.add(customizePopupActionSettings);

View File

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

View File

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

View File

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

View File

@ -13,19 +13,20 @@ describe('string field', () => {
await db.close();
});
it('define', async () => {
it('should create uuid field', async () => {
const Test = db.collection({
name: 'tests',
autoGenId: false,
fields: [
{
name: 'uuid',
type: 'uuid',
name: 'id',
primaryKey: true,
},
],
});
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) {
super(
{
defaultValue: DataTypes.UUIDV4,
defaultValue: new DataTypes.UUIDV4(),
...options,
},
context,

View File

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

View File

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

View File

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

View File

@ -49,13 +49,13 @@ class SystemLoggerTransport extends Transport {
}
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 { module, submodule, method, ...meta } = args?.[0] || {};
logger.log({
level,
stack,
message,
stack,
meta,
module: module || info['module'] || '',
submodule: submodule || info['submodule'] || '',
@ -63,11 +63,51 @@ class SystemLoggerTransport extends Transport {
app,
reqId,
});
if (cause) {
logger.log({
level,
message: cause.message,
stack: cause.stack,
app,
reqId,
});
}
callback(null, true);
}
}
export const createSystemLogger = (options: SystemLoggerOptions): SystemLogger =>
winston.createLogger({
function child(defaultRequestMetadata: any) {
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)],
});
// 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",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "Apache-2.0",
"dependencies": {
"@nocobase/utils": "0.20.0-alpha.14",
"@nocobase/utils": "0.20.0-alpha.15",
"deepmerge": "^4.2.2",
"koa-compose": "^4.1.0",
"lodash": "^4.17.21",

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/test",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"main": "lib/index.js",
"module": "./src/index.ts",
"types": "./lib/index.d.ts",
@ -40,7 +40,7 @@
},
"dependencies": {
"@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",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",

View File

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

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "权限控制",
"description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.",
"description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/acl",

View File

@ -1,6 +1,13 @@
import React, { useState } from 'react';
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 { RolesMenu } from './RolesMenu';
import { useACLTranslation } from './locale';
@ -58,8 +65,10 @@ export const RolesManagement: React.FC = () => {
children: item.Component ? React.createElement(item.Component, { active: activeKey === name }) : null,
}));
const [role, setRole] = useState(null);
const scCtx = useSchemaComponentContext();
return (
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
<RolesManagerContext.Provider value={{ role, setRole }}>
<Card>
<Row gutter={24} style={{ flexWrap: 'nowrap' }}>
@ -70,7 +79,6 @@ export const RolesManagement: React.FC = () => {
resource: 'roles',
action: 'list',
params: {
pagination: false,
filter: {
'name.$ne': 'root',
},
@ -106,5 +114,6 @@ export const RolesManagement: React.FC = () => {
</Row>
</Card>
</RolesManagerContext.Provider>
</SchemaComponentContext.Provider>
);
};

View File

@ -5,25 +5,74 @@ import {
useAPIClient,
useResourceActionContext,
} 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 React, { useContext, useEffect } from 'react';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useACLTranslation } from './locale';
import { Schema } from '@formily/react';
import { RolesManagerContext } from './RolesManagerProvider';
import { roleEditSchema } from './schemas/roles';
import { useLoadMoreObserver } from './hooks/load-more-observer';
export const RolesMenu: React.FC & {
Item: React.FC<{ item: any; onEdit: () => void }>;
} = () => {
const { t } = useACLTranslation();
const { data } = useResourceActionContext();
const [visible, setVisible] = React.useState(false);
const [record, setRecord] = React.useState(null);
const { data, run, loading } = useResourceActionContext();
const [visible, setVisible] = useState(false);
const [record, setRecord] = useState(null);
const { role, setRole } = useContext(RolesManagerContext);
const items = (data?.data || []).map((item: any) => ({
const [roles, setRoles] = useState([]);
const loadMore = useCallback(() => {
const meta = data?.meta;
if (!meta || meta.page >= meta.totalPage) {
return;
}
run({
page: meta.page + 1,
});
}, [data]);
const { lastItem, setLastItem } = useLoadMoreObserver({ loadMore });
const handleSelect = ({ key }) => {
setRole(roles.find((item: any) => item.name === key));
};
useEffect(() => {
if (!roles[0]) {
return;
}
setRole(roles[0]);
}, [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: (
label:
index === roles.length - 1 ? (
<div ref={lastItem}>
<RolesMenu.Item
item={item}
onEdit={() => {
setVisible(true);
setRecord(item);
}}
/>
</div>
) : (
<RolesMenu.Item
item={item}
onEdit={() => {
@ -32,28 +81,22 @@ export const RolesMenu: React.FC & {
}}
/>
),
}));
const handleSelect = ({ key }) => {
setRole((data?.data || []).find((item: any) => item.name === key));
};
useEffect(() => {
if (!data?.data.length) {
return;
}
setRole(data?.data[0]);
}, [data, setRole]);
})),
[roles, lastItem],
);
return (
<>
{items.length ? (
{roles.length ? (
<>
<Menu
style={{ border: 'none', maxHeight: '65vh', overflowY: 'auto' }}
items={items}
selectedKeys={[role?.name]}
onSelect={handleSelect}
/>
{loading && <Spin />}
</>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
@ -92,12 +135,15 @@ RolesMenu.Item = function DepartmentTreeItem({ item, onEdit }) {
deleteDepartment();
}
};
const title = Schema.compile(item.title, { t });
return (
<Row>
<Col flex={3} style={{ display: 'inline-flex', alignItems: 'center' }}>
<span style={{ whiteSpace: 'nowrap', width: '120px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<TagOutlined />
<span style={{ marginLeft: '10px' }}>{Schema.compile(item.title, { t })}</span>
<span style={{ marginLeft: '10px' }} title={title}>
{title}
</span>
</span>
</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
this.app.on('afterLoad', async () => {
this.app.on('afterStart', async () => {
await writeRolesToACL(this.app, {
withOutResources: true,
});
});
// this.app.on('afterInstall', writeRolesToACL);
this.app.on('afterInstallPlugin', async (plugin) => {

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-duplicate",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"main": "dist/server/index.js",
"homepage": "https://docs.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",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"main": "dist/server/index.js",
"homepage": "https://docs.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",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"displayName": "API documentation",
"displayName.zh-CN": "API 文档",
"description": "An OpenAPI documentation generator for NocoBase HTTP API.",

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-audit-logs",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"displayName": "Audit logs (deprecated)",
"displayName.zh-CN": "审计日志(废弃)",
"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",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth",

View File

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

View File

@ -85,6 +85,43 @@ describe('dumper', () => {
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', () => {
let allGroups;

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-calendar",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"displayName": "Calendar",
"displayName.zh-CN": "日历",
"description": "Provides callendar collection template and block for managing date data, typically for date/time related information such as events, appointments, tasks, and so on.",

View File

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

View File

@ -3,20 +3,25 @@ import { AppSupervisor } from '@nocobase/server';
import { CASAuth } from '../auth';
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') {
const appSupervisor = AppSupervisor.getInstance();
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;
if (prefix.endsWith('/')) {
prefix = prefix.slice(0, -1);
}
try {
const { token } = await auth.signIn();
ctx.redirect(`${prefix}${redirect}?authenticator=${authenticator}&token=${token}`);
ctx.redirect(`${prefix}${redirect || '/admin'}?authenticator=${authenticator}&token=${token}`);
} catch (error) {
ctx.redirect(`${prefix}/signin?authenticator=${authenticator}&error=${error.message}&redirect=${redirect}`);
}

View File

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

View File

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

View File

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

View File

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

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",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-custom-request",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request",

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-data-visualization",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"displayName": "Data visualization",
"displayName.zh-CN": "数据可视化",
"description": "Provides data visualization feature, including chart block and chart filter block, support line charts, area charts, bar charts and more than a dozen kinds of charts, you can also extend more chart types.",

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-file-manager",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"displayName": "File manager",
"displayName.zh-CN": "文件管理器",
"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 { CollectionOptions } from '@nocobase/database';
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 {
name = 'file';
title = `{{t("File collection", { ns: "${NAMESPACE}" })}}`;
@ -236,7 +84,7 @@ export class FileCollectionTemplate extends CollectionTemplate {
deletable: false,
uiSchema: {
type: 'string',
title: `{{t("Path")}}`,
title: `{{t("Path", { ns: "${NAMESPACE}" })}}`,
'x-component': 'Input',
'x-read-pretty': true,
},
@ -263,7 +111,7 @@ export class FileCollectionTemplate extends CollectionTemplate {
deletable: false,
uiSchema: {
type: 'string',
title: `{{t("Preview")}}`,
title: `{{t("Preview", { ns: "${NAMESPACE}" })}}`,
'x-component': 'Preview',
'x-read-pretty': true,
},
@ -275,6 +123,12 @@ export class FileCollectionTemplate extends CollectionTemplate {
target: 'storages',
foreignKey: 'storageId',
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": "数据表字段:公式",
"description": "Configure and store the results of calculations between multiple field values in the same record, supporting both Math.js and Excel formula functions.",
"description.zh-CN": "可以配置并存储同一条记录的多字段值之间的计算结果,支持 Math.js 和 Excel formula functions 两种引擎",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/field-formula",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
"name": "@nocobase/plugin-map",
"displayName": "Block: Map",
"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.zh-CN": "地图区块,支持高德地图和 Google 地图,你也可以扩展更多地图类型。",
"license": "AGPL-3.0",

View File

@ -4,7 +4,7 @@
"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.zh-CN": "一个功能强大的数学公式字段插件旨在为数据库或数据管理系统提供灵活的数学计算和公式计算功能。它可以与各种数据库系统如MySQL、PostgreSQL无缝集成并提供直观的用户界面来定义和执行数学公式字段。",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"devDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-mobile-client",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"main": "./dist/server/index.js",
"homepage": "https://docs.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",
"displayName": "mock-collections",
"description": "mock-collections",
"version": "0.20.0-alpha.14",
"version": "0.20.0-alpha.15",
"main": "./dist/server/index.js",
"license": "AGPL-3.0",
"peerDependencies": {

View File

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

View File

@ -1,6 +1,6 @@
import { Database } from '@nocobase/database';
import { AppSupervisor, Gateway } from '@nocobase/server';
import { MockServer, createMockServer } from '@nocobase/test';
import { createMockServer, MockServer } from '@nocobase/test';
import { uid } from '@nocobase/utils';
import { vi } from 'vitest';
import { PluginMultiAppManager } from '../server';
@ -22,6 +22,29 @@ describe('multiple apps', () => {
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 () => {
const fn = vi.fn();

View File

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

View File

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

View File

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

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