feat: audit logs (#494)
* feat: audit logs * feat: improve code * feat: improve code * feat: improve code * feat: improve code * feat: improve code * fix: field load
This commit is contained in:
parent
e5cb94803f
commit
3fa13d8465
@ -35,6 +35,7 @@ import {
|
||||
WorkflowRouteProvider,
|
||||
WorkflowShortcut
|
||||
} from '@nocobase/client';
|
||||
import { AuditLogsProvider } from '@nocobase/plugin-audit-logs/client';
|
||||
import { notification } from 'antd';
|
||||
import 'antd/dist/antd.css';
|
||||
import React from 'react';
|
||||
@ -104,6 +105,7 @@ const providers = [
|
||||
},
|
||||
},
|
||||
],
|
||||
AuditLogsProvider,
|
||||
BlockSchemaComponentProvider,
|
||||
AntdSchemaComponentProvider,
|
||||
ACLProvider,
|
||||
|
@ -11,6 +11,8 @@
|
||||
"baseUrl": "./",
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@nocobase/plugin-*/client": ["../../plugins/*/src/client"],
|
||||
"@nocobase/*": ["../../core/*/src/"],
|
||||
"@/*": ["src/*"],
|
||||
"@@/*": ["src/.umi/*"]
|
||||
},
|
||||
|
@ -12,4 +12,5 @@ export default [
|
||||
'@nocobase/plugin-china-region',
|
||||
'@nocobase/plugin-workflow',
|
||||
'@nocobase/plugin-client',
|
||||
'@nocobase/plugin-audit-logs',
|
||||
] as PluginsConfigurations;
|
||||
|
@ -1,58 +0,0 @@
|
||||
import { Field } from '@formily/core';
|
||||
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { useCollection } from '..';
|
||||
import { SchemaComponent } from '../schema-component';
|
||||
import { ActionLogDesigner } from './ActionLog.Designer';
|
||||
import { createSchema } from './utils';
|
||||
|
||||
export const ActionLog = () => {
|
||||
const schema = createSchema();
|
||||
return <SchemaComponent schema={schema} name={schema.name} />;
|
||||
};
|
||||
|
||||
ActionLog.Designer = ActionLogDesigner;
|
||||
|
||||
ActionLog.Field = observer((props: any) => {
|
||||
const { value } = props;
|
||||
return <div>{value?.uiSchema?.title || value?.name}</div>;
|
||||
});
|
||||
|
||||
ActionLog.FieldValue = observer((props: any) => {
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { getField } = useCollection();
|
||||
const collectionField = getField(fieldSchema.name);
|
||||
|
||||
if (!collectionField.uiSchema) {
|
||||
if (!field.value) {
|
||||
return <div></div>;
|
||||
}
|
||||
if (typeof field.value === 'boolean') {
|
||||
return <div>{field.value ? 'true' : 'false'}</div>;
|
||||
}
|
||||
if (typeof field.value === 'string' || typeof field.value === 'number') {
|
||||
return <div>{field.value}</div>;
|
||||
}
|
||||
return <pre>{JSON.stringify(field.value, null, 2)}</pre>;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecursionField
|
||||
name="actionLogFieldValue"
|
||||
schema={
|
||||
{
|
||||
type: 'void',
|
||||
properties: {
|
||||
[collectionField.name as string]: {
|
||||
...collectionField.uiSchema,
|
||||
default: field.value,
|
||||
'x-decorator': null,
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
import { TableOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaInitializer } from '../schema-initializer';
|
||||
|
||||
export const ActionLogBlockInitializer = (props) => {
|
||||
const { insert } = props;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
{...props}
|
||||
icon={<TableOutlined />}
|
||||
onClick={({ item }) => {
|
||||
insert({ type: 'void', 'x-component': 'ActionLog', 'x-component-props': {} });
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
type: 'item',
|
||||
name: 'ActionLog',
|
||||
title: t('Action Logs'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { SchemaInitializerContext, SchemaInitializerProvider } from '../schema-initializer';
|
||||
|
||||
const actionLogsItemGroup = {
|
||||
type: 'itemGroup',
|
||||
title: '{{t("Others")}}',
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Action logs")}}',
|
||||
component: 'ActionLogBlockInitializer',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const ActionLogProvider = (props: any) => {
|
||||
const initializes = useContext(SchemaInitializerContext);
|
||||
initializes.BlockInitializers.items.push({ ...actionLogsItemGroup });
|
||||
return <SchemaInitializerProvider initializers={initializes}>{props.children}</SchemaInitializerProvider>;
|
||||
};
|
@ -1,88 +0,0 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import {
|
||||
ActionLog,
|
||||
ActionLogProvider,
|
||||
AntdSchemaComponentProvider,
|
||||
APIClient,
|
||||
APIClientProvider,
|
||||
CollectionManagerProvider,
|
||||
SchemaComponent,
|
||||
SchemaComponentProvider,
|
||||
SchemaInitializerProvider,
|
||||
} from '@nocobase/client';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import React from 'react';
|
||||
import { mockData } from './mockData';
|
||||
|
||||
const apiClient = new APIClient();
|
||||
|
||||
const mock = new MockAdapter(apiClient.axios);
|
||||
|
||||
mock.onGet('/action_logs:list').reply(200, mockData);
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'ActionLog',
|
||||
'x-component': 'ActionLog',
|
||||
};
|
||||
|
||||
const collections = [
|
||||
{
|
||||
name: 'action_logs',
|
||||
fields: [
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'id',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
title: 'ID',
|
||||
type: 'number',
|
||||
'x-component': 'InputNumber',
|
||||
required: true,
|
||||
description: 'description1',
|
||||
} as ISchema,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'created_at',
|
||||
interface: 'createdAt',
|
||||
uiSchema: {
|
||||
title: 'Created At',
|
||||
type: 'number',
|
||||
'x-component': 'DatePicker',
|
||||
required: true,
|
||||
description: 'Date Picker',
|
||||
} as ISchema,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'collection_name',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
title: 'collection name',
|
||||
type: 'number',
|
||||
'x-component': 'Input',
|
||||
description: 'collection name',
|
||||
} as ISchema,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<APIClientProvider apiClient={apiClient}>
|
||||
<SchemaInitializerProvider>
|
||||
<SchemaComponentProvider components={{ ActionLog }}>
|
||||
<AntdSchemaComponentProvider>
|
||||
<CollectionManagerProvider collections={collections}>
|
||||
<ActionLogProvider>
|
||||
<SchemaComponent schema={schema} />
|
||||
</ActionLogProvider>
|
||||
</CollectionManagerProvider>
|
||||
</AntdSchemaComponentProvider>
|
||||
</SchemaComponentProvider>
|
||||
</SchemaInitializerProvider>
|
||||
</APIClientProvider>
|
||||
);
|
||||
};
|
@ -1,285 +0,0 @@
|
||||
export const mockData = {
|
||||
data: [
|
||||
{
|
||||
id: 41,
|
||||
created_at: '2022-03-04T09:14:45.173Z',
|
||||
collection_name: 'users',
|
||||
type: 'create',
|
||||
index: 7,
|
||||
user_id: 1,
|
||||
user: {
|
||||
id: 1,
|
||||
nickname: 'Super Admin',
|
||||
email: 'admin@nocobase.com',
|
||||
appLang: null,
|
||||
token: '6287df8c3f623ba2cb12',
|
||||
reset_token: null,
|
||||
sort: 1,
|
||||
created_at: '2022-02-17T05:31:09.172Z',
|
||||
updated_at: '2022-02-17T05:31:32.031Z',
|
||||
},
|
||||
collection: {
|
||||
name: 'users',
|
||||
logging: true,
|
||||
title: '{{t("Users")}}',
|
||||
privilege: 'undelete',
|
||||
sortable: 'sort',
|
||||
options: { createdBy: false, updatedBy: false },
|
||||
sort: 2,
|
||||
created_at: '2021-09-02T15:07:56.914Z',
|
||||
updated_at: '2021-09-15T00:59:20.676Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 40,
|
||||
created_at: '2022-03-04T09:14:25.133Z',
|
||||
collection_name: 'users',
|
||||
type: 'create',
|
||||
index: 5,
|
||||
user_id: 1,
|
||||
user: {
|
||||
id: 1,
|
||||
nickname: 'Super Admin',
|
||||
email: 'admin@nocobase.com',
|
||||
appLang: null,
|
||||
token: '6287df8c3f623ba2cb12',
|
||||
reset_token: null,
|
||||
sort: 1,
|
||||
created_at: '2022-02-17T05:31:09.172Z',
|
||||
updated_at: '2022-02-17T05:31:32.031Z',
|
||||
},
|
||||
collection: {
|
||||
name: 'users',
|
||||
logging: true,
|
||||
title: '{{t("Users")}}',
|
||||
privilege: 'undelete',
|
||||
sortable: 'sort',
|
||||
options: { createdBy: false, updatedBy: false },
|
||||
sort: 2,
|
||||
created_at: '2021-09-02T15:07:56.914Z',
|
||||
updated_at: '2021-09-15T00:59:20.676Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 39,
|
||||
created_at: '2022-03-04T09:13:36.399Z',
|
||||
collection_name: 'users',
|
||||
type: 'create',
|
||||
index: 2,
|
||||
user_id: 1,
|
||||
user: {
|
||||
id: 1,
|
||||
nickname: 'Super Admin',
|
||||
email: 'admin@nocobase.com',
|
||||
appLang: null,
|
||||
token: '6287df8c3f623ba2cb12',
|
||||
reset_token: null,
|
||||
sort: 1,
|
||||
created_at: '2022-02-17T05:31:09.172Z',
|
||||
updated_at: '2022-02-17T05:31:32.031Z',
|
||||
},
|
||||
collection: {
|
||||
name: 'users',
|
||||
logging: true,
|
||||
title: '{{t("Users")}}',
|
||||
privilege: 'undelete',
|
||||
sortable: 'sort',
|
||||
options: { createdBy: false, updatedBy: false },
|
||||
sort: 2,
|
||||
created_at: '2021-09-02T15:07:56.914Z',
|
||||
updated_at: '2021-09-15T00:59:20.676Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 38,
|
||||
created_at: '2022-03-02T09:16:07.590Z',
|
||||
collection_name: 't_2uhu4szs1kq',
|
||||
type: 'update',
|
||||
index: 3,
|
||||
user_id: 1,
|
||||
user: {
|
||||
id: 1,
|
||||
nickname: 'Super Admin',
|
||||
email: 'admin@nocobase.com',
|
||||
appLang: null,
|
||||
token: '6287df8c3f623ba2cb12',
|
||||
reset_token: null,
|
||||
sort: 1,
|
||||
created_at: '2022-02-17T05:31:09.172Z',
|
||||
updated_at: '2022-02-17T05:31:32.031Z',
|
||||
},
|
||||
collection: {
|
||||
name: 't_2uhu4szs1kq',
|
||||
logging: true,
|
||||
title: '任务',
|
||||
privilege: null,
|
||||
sortable: 'sort',
|
||||
options: {},
|
||||
sort: 3,
|
||||
created_at: '2021-09-03T08:17:30.495Z',
|
||||
updated_at: '2021-09-18T04:15:42.572Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 37,
|
||||
created_at: '2022-03-02T09:16:06.611Z',
|
||||
collection_name: 't_2uhu4szs1kq',
|
||||
type: 'update',
|
||||
index: 3,
|
||||
user_id: null,
|
||||
user: null,
|
||||
collection: {
|
||||
name: 't_2uhu4szs1kq',
|
||||
logging: true,
|
||||
title: '任务',
|
||||
privilege: null,
|
||||
sortable: 'sort',
|
||||
options: {},
|
||||
sort: 3,
|
||||
created_at: '2021-09-03T08:17:30.495Z',
|
||||
updated_at: '2021-09-18T04:15:42.572Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 36,
|
||||
created_at: '2022-03-02T09:16:06.526Z',
|
||||
collection_name: 't_2uhu4szs1kq',
|
||||
type: 'update',
|
||||
index: 3,
|
||||
user_id: 1,
|
||||
user: {
|
||||
id: 1,
|
||||
nickname: 'Super Admin',
|
||||
email: 'admin@nocobase.com',
|
||||
appLang: null,
|
||||
token: '6287df8c3f623ba2cb12',
|
||||
reset_token: null,
|
||||
sort: 1,
|
||||
created_at: '2022-02-17T05:31:09.172Z',
|
||||
updated_at: '2022-02-17T05:31:32.031Z',
|
||||
},
|
||||
collection: {
|
||||
name: 't_2uhu4szs1kq',
|
||||
logging: true,
|
||||
title: '任务',
|
||||
privilege: null,
|
||||
sortable: 'sort',
|
||||
options: {},
|
||||
sort: 3,
|
||||
created_at: '2021-09-03T08:17:30.495Z',
|
||||
updated_at: '2021-09-18T04:15:42.572Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 35,
|
||||
created_at: '2022-03-02T09:16:05.930Z',
|
||||
collection_name: 't_2uhu4szs1kq',
|
||||
type: 'update',
|
||||
index: 3,
|
||||
user_id: null,
|
||||
user: null,
|
||||
collection: {
|
||||
name: 't_2uhu4szs1kq',
|
||||
logging: true,
|
||||
title: '任务',
|
||||
privilege: null,
|
||||
sortable: 'sort',
|
||||
options: {},
|
||||
sort: 3,
|
||||
created_at: '2021-09-03T08:17:30.495Z',
|
||||
updated_at: '2021-09-18T04:15:42.572Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 34,
|
||||
created_at: '2022-03-02T09:16:05.842Z',
|
||||
collection_name: 't_2uhu4szs1kq',
|
||||
type: 'update',
|
||||
index: 3,
|
||||
user_id: 1,
|
||||
user: {
|
||||
id: 1,
|
||||
nickname: 'Super Admin',
|
||||
email: 'admin@nocobase.com',
|
||||
appLang: null,
|
||||
token: '6287df8c3f623ba2cb12',
|
||||
reset_token: null,
|
||||
sort: 1,
|
||||
created_at: '2022-02-17T05:31:09.172Z',
|
||||
updated_at: '2022-02-17T05:31:32.031Z',
|
||||
},
|
||||
collection: {
|
||||
name: 't_2uhu4szs1kq',
|
||||
logging: true,
|
||||
title: '任务',
|
||||
privilege: null,
|
||||
sortable: 'sort',
|
||||
options: {},
|
||||
sort: 3,
|
||||
created_at: '2021-09-03T08:17:30.495Z',
|
||||
updated_at: '2021-09-18T04:15:42.572Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 33,
|
||||
created_at: '2022-03-02T09:16:04.209Z',
|
||||
collection_name: 't_2uhu4szs1kq',
|
||||
type: 'update',
|
||||
index: 3,
|
||||
user_id: 1,
|
||||
user: {
|
||||
id: 1,
|
||||
nickname: 'Super Admin',
|
||||
email: 'admin@nocobase.com',
|
||||
appLang: null,
|
||||
token: '6287df8c3f623ba2cb12',
|
||||
reset_token: null,
|
||||
sort: 1,
|
||||
created_at: '2022-02-17T05:31:09.172Z',
|
||||
updated_at: '2022-02-17T05:31:32.031Z',
|
||||
},
|
||||
collection: {
|
||||
name: 't_2uhu4szs1kq',
|
||||
logging: true,
|
||||
title: '任务',
|
||||
privilege: null,
|
||||
sortable: 'sort',
|
||||
options: {},
|
||||
sort: 3,
|
||||
created_at: '2021-09-03T08:17:30.495Z',
|
||||
updated_at: '2021-09-18T04:15:42.572Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 32,
|
||||
created_at: '2022-03-02T09:16:02.289Z',
|
||||
collection_name: 't_2uhu4szs1kq',
|
||||
type: 'update',
|
||||
index: 5,
|
||||
user_id: null,
|
||||
user: null,
|
||||
collection: {
|
||||
name: 't_2uhu4szs1kq',
|
||||
logging: true,
|
||||
title: '任务',
|
||||
privilege: null,
|
||||
sortable: 'sort',
|
||||
options: {},
|
||||
sort: 3,
|
||||
created_at: '2021-09-03T08:17:30.495Z',
|
||||
updated_at: '2021-09-18T04:15:42.572Z',
|
||||
ui_schema_key: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: { count: 41, page: 1, per_page: 10 },
|
||||
};
|
@ -1,12 +0,0 @@
|
||||
---
|
||||
nav:
|
||||
path: /client
|
||||
group:
|
||||
path: /client
|
||||
---
|
||||
|
||||
# ActionLogs
|
||||
|
||||
## Examples
|
||||
|
||||
<code src="./demos/demo1.tsx"/>
|
@ -1,3 +0,0 @@
|
||||
export * from './ActionLog';
|
||||
export * from './ActionLogBlockInitializer';
|
||||
export * from './ActionLogProvider';
|
@ -1,12 +1,10 @@
|
||||
import { Field } from '@formily/core';
|
||||
import { useFieldSchema, useForm } from '@formily/react';
|
||||
import { useForm } from '@formily/react';
|
||||
import { action } from '@formily/reactive';
|
||||
import { uid } from '@formily/shared';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useRequest } from '../../api-client';
|
||||
import { useRecord } from '../../record-provider';
|
||||
import { SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
import { useCollection } from '../hooks';
|
||||
import { useCollectionManager } from '../hooks/useCollectionManager';
|
||||
import { DataSourceContext } from '../sub-table';
|
||||
import { AddSubFieldAction } from './AddSubFieldAction';
|
||||
@ -33,6 +31,7 @@ const useCollectionValues = (options) => {
|
||||
createdBy: true,
|
||||
updatedBy: true,
|
||||
sortable: true,
|
||||
logging: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'id',
|
||||
|
@ -1,3 +1,4 @@
|
||||
export { useCollectionFilterOptions, useSortFields } from './action-hooks';
|
||||
export * from './CollectionField';
|
||||
export * from './CollectionFieldProvider';
|
||||
export * from './CollectionManagerProvider';
|
||||
|
@ -1,7 +1,6 @@
|
||||
import './global.less';
|
||||
|
||||
export * from './acl';
|
||||
export * from './action-logs';
|
||||
export * from './antd-config-provider';
|
||||
export * from './api-client';
|
||||
export * from './application';
|
||||
|
@ -526,5 +526,10 @@ export default {
|
||||
"Invalid JSON format": "Invalid JSON format",
|
||||
"After successful request": "After successful request",
|
||||
"Add exported field": "Add exported field",
|
||||
"Exported table header name": "Exported table header name"
|
||||
"Exported table header name": "Exported table header name",
|
||||
"Audit logs": "Audit logs",
|
||||
"Record ID": "Record ID",
|
||||
"User": "User",
|
||||
"Field": "Field",
|
||||
"Field value changes": "Field value changes",
|
||||
}
|
||||
|
@ -576,5 +576,10 @@ export default {
|
||||
'Request body': '请求体(JSON格式)',
|
||||
'Request success': '请求成功',
|
||||
'Invalid JSON format': '非法JSON格式',
|
||||
'After successful request': '请求成功之后'
|
||||
'After successful request': '请求成功之后',
|
||||
"Audit logs": "操作记录",
|
||||
"Record ID": "数据 ID",
|
||||
"User": "用户",
|
||||
"Field": "字段",
|
||||
"Field value changes": "变更记录",
|
||||
}
|
||||
|
@ -164,6 +164,12 @@ export const BlockInitializers = {
|
||||
title: '{{t("Markdown")}}',
|
||||
component: 'MarkdownBlockInitializer',
|
||||
},
|
||||
{
|
||||
key: 'auditLogs',
|
||||
type: 'item',
|
||||
title: '{{t("Audit logs")}}',
|
||||
component: 'AuditLogsBlockInitializer',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -27,7 +27,7 @@ export const CreateFormBlockInitializers = (props: any) => {
|
||||
},
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: '{{t("Media")}}',
|
||||
title: '{{t("Other blocks")}}',
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
|
@ -54,7 +54,7 @@ export const RecordBlockInitializers = (props: any) => {
|
||||
},
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: '{{t("Media")}}',
|
||||
title: '{{t("Other blocks")}}',
|
||||
children: [
|
||||
{
|
||||
key: 'markdown',
|
||||
@ -62,6 +62,12 @@ export const RecordBlockInitializers = (props: any) => {
|
||||
title: '{{t("Markdown")}}',
|
||||
component: 'MarkdownBlockInitializer',
|
||||
},
|
||||
{
|
||||
key: 'auditLogs',
|
||||
type: 'item',
|
||||
title: '{{t("Audit logs")}}',
|
||||
component: 'AuditLogsBlockInitializer',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
|
@ -24,7 +24,7 @@ export const RecordFormBlockInitializers = (props: any) => {
|
||||
},
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: '{{t("Media")}}',
|
||||
title: '{{t("Other blocks")}}',
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
|
@ -25,7 +25,7 @@ export const TableSelectorInitializers = (props: any) => {
|
||||
},
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: t('Media'),
|
||||
title: t('Other blocks'),
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
|
@ -1,6 +1,7 @@
|
||||
const { existsSync } = require('fs');
|
||||
const { resolve } = require('path');
|
||||
const packageJson = require('./package.json');
|
||||
const fs = require('fs');
|
||||
|
||||
console.log('VERSION: ', packageJson.version);
|
||||
|
||||
@ -44,16 +45,21 @@ function getUmiConfig() {
|
||||
}
|
||||
|
||||
function resolveNocobasePackagesAlias(config) {
|
||||
const clientSrc = resolve(process.cwd(), './packages/core/client/src');
|
||||
const utilsSrc = resolve(process.cwd(), './packages/core/utils/src');
|
||||
const sdkSrc = resolve(process.cwd(), './packages/core/sdk/src');
|
||||
if (existsSync(clientSrc)) {
|
||||
config.module.rules.get('ts-in-node_modules').include.add(clientSrc);
|
||||
config.resolve.alias.set('@nocobase/client', clientSrc);
|
||||
config.module.rules.get('ts-in-node_modules').include.add(utilsSrc);
|
||||
config.resolve.alias.set('@nocobase/utils', utilsSrc);
|
||||
config.module.rules.get('ts-in-node_modules').include.add(sdkSrc);
|
||||
config.resolve.alias.set('@nocobase/sdk', sdkSrc);
|
||||
const cores = fs.readdirSync(resolve(process.cwd(), './packages/core'));
|
||||
for (const package of cores) {
|
||||
const packageSrc = resolve(process.cwd(), './packages/core/', package, 'src');
|
||||
if (existsSync(packageSrc)) {
|
||||
config.module.rules.get('ts-in-node_modules').include.add(packageSrc);
|
||||
config.resolve.alias.set(`@nocobase/${package}`, packageSrc);
|
||||
}
|
||||
}
|
||||
const plugins = fs.readdirSync(resolve(process.cwd(), './packages/plugins'));
|
||||
for (const package of plugins) {
|
||||
const packageSrc = resolve(process.cwd(), './packages/plugins/', package, 'src');
|
||||
if (existsSync(packageSrc)) {
|
||||
config.module.rules.get('ts-in-node_modules').include.add(packageSrc);
|
||||
config.resolve.alias.set(`@nocobase/plugin-${package}`, packageSrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
2
packages/plugins/audit-logs/client.d.ts
vendored
Executable file
2
packages/plugins/audit-logs/client.d.ts
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/client';
|
12
packages/plugins/audit-logs/client.js
Executable file
12
packages/plugins/audit-logs/client.js
Executable file
@ -0,0 +1,12 @@
|
||||
var _useExportClient = require("./lib/client");
|
||||
|
||||
Object.keys(_useExportClient).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (key in exports && exports[key] === _useExportClient[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _useExportClient[key];
|
||||
}
|
||||
});
|
||||
});
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-action-logs",
|
||||
"name": "@nocobase/plugin-audit-logs",
|
||||
"version": "0.7.0-alpha.83",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"main": "./lib/server/index.js",
|
||||
"types": "./lib/server/index.d.ts",
|
||||
"license": "Apache-2.0",
|
||||
"licenses": [
|
||||
{
|
||||
@ -10,6 +10,10 @@
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@nocobase/client": "0.7.0-alpha.83",
|
||||
"@nocobase/server": "0.7.0-alpha.83"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/test": "0.7.0-alpha.83"
|
||||
},
|
2
packages/plugins/audit-logs/server.d.ts
vendored
Executable file
2
packages/plugins/audit-logs/server.d.ts
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/server';
|
12
packages/plugins/audit-logs/server.js
Executable file
12
packages/plugins/audit-logs/server.js
Executable file
@ -0,0 +1,12 @@
|
||||
var _useExportServer = require("./lib/server");
|
||||
|
||||
Object.keys(_useExportServer).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (key in exports && exports[key] === _useExportServer[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _useExportServer[key];
|
||||
}
|
||||
});
|
||||
});
|
493
packages/plugins/audit-logs/src/client/AuditLogs.tsx
Normal file
493
packages/plugins/audit-logs/src/client/AuditLogs.tsx
Normal file
@ -0,0 +1,493 @@
|
||||
import { ArrayTable } from '@formily/antd';
|
||||
import { observer, useField } from '@formily/react';
|
||||
import {
|
||||
CollectionManagerContext,
|
||||
CollectionManagerProvider,
|
||||
FormProvider,
|
||||
SchemaComponent,
|
||||
TableBlockProvider,
|
||||
useCollection,
|
||||
useCompile, useRecord
|
||||
} from '@nocobase/client';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { AuditLogsDesigner } from './AuditLogsDesigner';
|
||||
|
||||
const collection = {
|
||||
name: 'auditLogs',
|
||||
title: '{{t("Audit logs")}}',
|
||||
fields: [
|
||||
{
|
||||
name: 'createdAt',
|
||||
type: 'date',
|
||||
interface: 'createdAt',
|
||||
uiSchema: {
|
||||
type: 'datetime',
|
||||
title: '{{t("Created at")}}',
|
||||
'x-component': 'DatePicker',
|
||||
'x-component-props': {
|
||||
showTime: true,
|
||||
},
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'string',
|
||||
interface: 'select',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
title: '{{t("Action type")}}',
|
||||
'x-component': 'Select',
|
||||
'x-read-pretty': true,
|
||||
enum: [
|
||||
{ label: '{{t("Create record")}}', value: 'create', color: 'lime' },
|
||||
{ label: '{{t("Update record")}}', value: 'update', color: 'gold' },
|
||||
{ label: '{{t("Delete record")}}', value: 'destroy', color: 'magenta' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const Username = observer(() => {
|
||||
const field = useField<any>();
|
||||
return <div>{field?.value?.nickname || field.value?.id}</div>;
|
||||
});
|
||||
|
||||
const Collection = observer(() => {
|
||||
const field = useField<any>();
|
||||
const { title, name } = field.value || {};
|
||||
const compile = useCompile();
|
||||
return <div>{title ? compile(title) : name}</div>;
|
||||
});
|
||||
|
||||
const Field = observer(() => {
|
||||
const field = useField<any>();
|
||||
const compile = useCompile();
|
||||
if (!field.value) {
|
||||
return null;
|
||||
}
|
||||
return <div>{field.value?.uiSchema?.title ? compile(field.value?.uiSchema?.title) : field.value.name}</div>;
|
||||
});
|
||||
|
||||
const Value = observer(() => {
|
||||
const field = useField<any>();
|
||||
const record = ArrayTable.useRecord();
|
||||
if (record.field?.uiSchema) {
|
||||
return (
|
||||
<FormProvider>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
name: record.field.name,
|
||||
...record.field?.uiSchema,
|
||||
default: field.value,
|
||||
'x-read-pretty': true,
|
||||
}}
|
||||
/>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
return <div>{field.value ? JSON.stringify(field.value) : null}</div>;
|
||||
});
|
||||
|
||||
const IsAssociationBlock = createContext(null);
|
||||
|
||||
export const AuditLogs: any = () => {
|
||||
const isAssoc = useContext(IsAssociationBlock);
|
||||
const ext = {};
|
||||
if (!isAssoc) {
|
||||
ext['column31'] = {
|
||||
type: 'void',
|
||||
'x-component': 'TableV2.Column',
|
||||
title: '{{t("Record ID")}}',
|
||||
properties: {
|
||||
recordId: {
|
||||
'x-component': 'Input',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return (
|
||||
<SchemaComponent
|
||||
memoized
|
||||
components={{ ArrayTable, Username, Collection, Field, Value }}
|
||||
schema={{
|
||||
type: 'void',
|
||||
name: 'lfm4trkw8j3',
|
||||
'x-component': 'div',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
filter: {
|
||||
type: 'void',
|
||||
title: '{{ t("Filter") }}',
|
||||
'x-action': 'filter',
|
||||
// 'x-designer': 'Filter.Action.Designer',
|
||||
'x-component': 'Filter.Action',
|
||||
'x-component-props': {
|
||||
icon: 'FilterOutlined',
|
||||
useProps: '{{ useFilterActionProps }}',
|
||||
},
|
||||
'x-align': 'left',
|
||||
},
|
||||
},
|
||||
},
|
||||
y84dlntcaup: {
|
||||
type: 'array',
|
||||
'x-component': 'TableV2',
|
||||
'x-component-props': {
|
||||
rowKey: 'id',
|
||||
rowSelection: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
useProps: '{{ useTableBlockProps }}',
|
||||
},
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
title: '{{ t("Actions") }}',
|
||||
'x-action-column': 'actions',
|
||||
'x-decorator': 'TableV2.Column.ActionBar',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-designer': 'TableV2.ActionColumnDesigner',
|
||||
'x-initializer': 'TableActionColumnInitializers',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-decorator': 'DndContext',
|
||||
'x-component': 'Space',
|
||||
'x-component-props': {
|
||||
split: '|',
|
||||
},
|
||||
properties: {
|
||||
o80rypwmeeg: {
|
||||
type: 'void',
|
||||
title: '{{ t("View") }}',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: '{{ t("View record") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
tdlav8o9o17: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
'7bsnaf47i6g': {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
'6s2qbihe3tu': {
|
||||
type: 'void',
|
||||
'x-acl-action': 'auditLogs:get',
|
||||
'x-decorator': 'FormBlockProvider',
|
||||
'x-decorator-props': {
|
||||
resource: 'auditLogs',
|
||||
collection: 'auditLogs',
|
||||
readPretty: true,
|
||||
action: 'get',
|
||||
params: {
|
||||
appends: ['collection', 'user', 'changes'],
|
||||
},
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
mevpuonrda0: {
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {
|
||||
useProps: '{{ useFormBlockProps }}',
|
||||
},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
row1: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col11: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
collection: {
|
||||
type: 'string',
|
||||
title: '{{t("Collection display name")}}',
|
||||
'x-component': 'Collection',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row3: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col31: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row4: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col41: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
user: {
|
||||
type: 'string',
|
||||
title: '{{t("User")}}',
|
||||
'x-component': 'Username',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row5: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col51: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
changes: {
|
||||
type: 'array',
|
||||
title: '{{t("Field value changes")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayTable',
|
||||
'x-component-props': {
|
||||
pagination: { pageSize: 10 },
|
||||
scroll: { x: '100%' },
|
||||
},
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
column3: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
'x-component-props': {
|
||||
width: 200,
|
||||
title: '{{t("Field")}}',
|
||||
},
|
||||
properties: {
|
||||
field: {
|
||||
type: 'string',
|
||||
//'x-decorator': 'Editable',
|
||||
'x-component': 'Field',
|
||||
},
|
||||
},
|
||||
},
|
||||
column4: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
'x-component-props': {
|
||||
width: 200,
|
||||
title: '{{t("Before change")}}',
|
||||
},
|
||||
properties: {
|
||||
before: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Value',
|
||||
},
|
||||
},
|
||||
},
|
||||
column5: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
'x-component-props': {
|
||||
width: 200,
|
||||
title: '{{t("After change")}}',
|
||||
},
|
||||
properties: {
|
||||
after: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Value',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
column1: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
properties: {
|
||||
createdAt: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
column2: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
properties: {
|
||||
type: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
column3: {
|
||||
type: 'void',
|
||||
'x-component': 'TableV2.Column',
|
||||
title: '{{t("Collection display name")}}',
|
||||
properties: {
|
||||
collection: {
|
||||
'x-component': 'Collection',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...ext,
|
||||
column4: {
|
||||
type: 'void',
|
||||
'x-component': 'TableV2.Column',
|
||||
title: '{{t("User")}}',
|
||||
properties: {
|
||||
user: {
|
||||
'x-component': 'Username',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
AuditLogs.Decorator = observer((props: any) => {
|
||||
const parent = useCollection();
|
||||
const record = useRecord();
|
||||
const { interfaces } = useContext(CollectionManagerContext);
|
||||
let filter = props?.params?.filter;
|
||||
if (parent.name) {
|
||||
const filterByTk = record?.[parent.filterTargetKey || 'id'];
|
||||
if (filter) {
|
||||
filter = {
|
||||
$and: [filter, {
|
||||
collectionName: parent.name,
|
||||
recordId: filterByTk,
|
||||
}],
|
||||
};
|
||||
} else {
|
||||
filter = {
|
||||
collectionName: parent.name,
|
||||
recordId: filterByTk,
|
||||
};
|
||||
}
|
||||
}
|
||||
const defaults = {
|
||||
collection: 'auditLogs',
|
||||
resource: 'auditLogs',
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 20,
|
||||
appends: ['collection', 'user'],
|
||||
...props.params,
|
||||
filter,
|
||||
},
|
||||
rowKey: 'id',
|
||||
showIndex: true,
|
||||
dragSort: false,
|
||||
};
|
||||
return (
|
||||
<IsAssociationBlock.Provider value={!!parent.name}>
|
||||
<CollectionManagerProvider collections={[collection]} interfaces={interfaces}>
|
||||
<TableBlockProvider {...defaults}>{props.children}</TableBlockProvider>
|
||||
</CollectionManagerProvider>
|
||||
</IsAssociationBlock.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
AuditLogs.Designer = AuditLogsDesigner;
|
@ -0,0 +1,33 @@
|
||||
import { TableOutlined } from '@ant-design/icons';
|
||||
import { SchemaInitializer } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const AuditLogsBlockInitializer = (props) => {
|
||||
const { insert } = props;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
{...props}
|
||||
icon={<TableOutlined />}
|
||||
onClick={() => {
|
||||
insert({
|
||||
type: 'void',
|
||||
'x-designer': 'AuditLogs.Designer',
|
||||
'x-decorator': 'AuditLogs.Decorator',
|
||||
'x-decorator-props': {
|
||||
params: {},
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
auditLogs: {
|
||||
type: 'void',
|
||||
'x-component': 'AuditLogs',
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
title={t('Audit Logs')}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,46 +1,44 @@
|
||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
||||
import { GeneralSchemaDesigner, SchemaSettings, useCollection, useCollectionFilterOptions, useDesignable, useSortFields, useTableBlockContext } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCollection, useResourceActionContext } from '../collection-manager';
|
||||
import { useCollectionFilterOptions } from '../collection-manager/action-hooks';
|
||||
import { useDesignable } from '../schema-component/hooks';
|
||||
import { GeneralSchemaDesigner, SchemaSettings } from '../schema-settings';
|
||||
|
||||
export const ActionLogDesigner = () => {
|
||||
const { name, title } = useCollection();
|
||||
export const AuditLogsDesigner = () => {
|
||||
const { name, title, sortable } = useCollection();
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const dataSource = useCollectionFilterOptions(name);
|
||||
const ctx = useResourceActionContext();
|
||||
const sortFields = useSortFields(name);
|
||||
const { service } = useTableBlockContext();
|
||||
const { t } = useTranslation();
|
||||
const { dn } = useDesignable();
|
||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.request?.params?.filter || {};
|
||||
|
||||
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
|
||||
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
|
||||
return (
|
||||
<GeneralSchemaDesigner title={title || name}>
|
||||
<SchemaSettings.ModalItem
|
||||
title={'设置数据范围'}
|
||||
title={t('Set the data scope')}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: '设置数据范围',
|
||||
title: t('Set the data scope'),
|
||||
properties: {
|
||||
filter: {
|
||||
default: defaultFilter,
|
||||
title: '数据范围',
|
||||
// title: '数据范围',
|
||||
enum: dataSource,
|
||||
'x-component': 'Filter',
|
||||
'x-component-props': {},
|
||||
'x-decorator-props': {},
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ filter }) => {
|
||||
const params = field.decoratorProps.request.params || {};
|
||||
const params = field.decoratorProps.params || {};
|
||||
params.filter = filter;
|
||||
field.decoratorProps.request.params = params;
|
||||
fieldSchema['x-decorator-props']['request']['params'] = params;
|
||||
ctx.run({ ...ctx.params?.[0], filter });
|
||||
field.decoratorProps.params = params;
|
||||
fieldSchema['x-decorator-props']['params'] = params;
|
||||
service.run({ ...service.params?.[0], filter, page: 1 });
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
@ -49,10 +47,9 @@ export const ActionLogDesigner = () => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<SchemaSettings.SelectItem
|
||||
title={'每页显示'}
|
||||
value={field.decoratorProps.request.params?.pageSize || 20}
|
||||
title={t('Records per page')}
|
||||
value={field.decoratorProps?.params?.pageSize || 20}
|
||||
options={[
|
||||
{ label: '10', value: 10 },
|
||||
{ label: '20', value: 20 },
|
||||
@ -61,11 +58,11 @@ export const ActionLogDesigner = () => {
|
||||
{ label: '200', value: 200 },
|
||||
]}
|
||||
onChange={(pageSize) => {
|
||||
const params = field.decoratorProps.request.params || {};
|
||||
const params = field.decoratorProps.params || {};
|
||||
params.pageSize = pageSize;
|
||||
field.decoratorProps.request.params = params;
|
||||
fieldSchema['x-decorator-props']['request']['params'] = params;
|
||||
ctx.run({ ...ctx.params?.[0], pageSize });
|
||||
field.decoratorProps.params = params;
|
||||
fieldSchema['x-decorator-props']['params'] = params;
|
||||
service.run({ ...service.params?.[0], pageSize, page: 1 });
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
12
packages/plugins/audit-logs/src/client/AuditLogsProvider.tsx
Normal file
12
packages/plugins/audit-logs/src/client/AuditLogsProvider.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { SchemaComponentOptions } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { AuditLogs } from './AuditLogs';
|
||||
import { AuditLogsBlockInitializer } from './AuditLogsBlockInitializer';
|
||||
|
||||
export const AuditLogsProvider = (props: any) => {
|
||||
return (
|
||||
<SchemaComponentOptions components={{ AuditLogs, AuditLogsBlockInitializer }}>
|
||||
{props.children}
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
};
|
3
packages/plugins/audit-logs/src/client/index.ts
Normal file
3
packages/plugins/audit-logs/src/client/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './AuditLogsBlockInitializer';
|
||||
export * from './AuditLogsProvider';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Database from '@nocobase/database';
|
||||
import { mockServer, MockServer } from '@nocobase/test';
|
||||
import logPlugin from '../server';
|
||||
import logPlugin from '../';
|
||||
|
||||
describe('hook', () => {
|
||||
let api: MockServer;
|
||||
@ -47,8 +47,8 @@ describe('hook', () => {
|
||||
const post = await Post.create({ title: 't1' });
|
||||
await post.update({ title: 't2' });
|
||||
await post.destroy();
|
||||
const ActionLog = db.getCollection('action_logs').model;
|
||||
const count = await ActionLog.count();
|
||||
const AuditLog = db.getCollection('auditLogs').model;
|
||||
const count = await AuditLog.count();
|
||||
expect(count).toBe(3);
|
||||
});
|
||||
|
||||
@ -56,7 +56,7 @@ describe('hook', () => {
|
||||
const Post = db.getCollection('posts');
|
||||
const User = db.getCollection('users').model;
|
||||
const user = await User.create({ nickname: 'a', token: 'token1' });
|
||||
await Post.repository.create({
|
||||
const post = await Post.repository.create({
|
||||
values: { title: 't1' },
|
||||
context: {
|
||||
state: {
|
||||
@ -64,14 +64,15 @@ describe('hook', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const ActionLog = db.getCollection('action_logs');
|
||||
const log = await ActionLog.repository.findOne({
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const log = await AuditLog.repository.findOne({
|
||||
appends: ['changes'],
|
||||
});
|
||||
expect(log.toJSON()).toMatchObject({
|
||||
collectionName: 'posts',
|
||||
type: 'create',
|
||||
userId: 1,
|
||||
recordId: `${post.get('id')}`,
|
||||
changes: [
|
||||
{
|
||||
field: {
|
||||
@ -85,21 +86,21 @@ describe('hook', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('resource', async () => {
|
||||
const agent = api.agent();
|
||||
agent.set('Authorization', `Bearer token1`);
|
||||
const response = await agent.resource('posts').create({
|
||||
values: { title: 't1' },
|
||||
});
|
||||
await agent.resource('posts').update({
|
||||
resourceIndex: response.body.data.id,
|
||||
values: { title: 't2' },
|
||||
});
|
||||
await agent.resource('posts').destroy({
|
||||
resourceIndex: response.body.data.id,
|
||||
});
|
||||
const ActionLog = db.getCollection('action_logs').model;
|
||||
const count = await ActionLog.count();
|
||||
expect(count).toBe(3);
|
||||
});
|
||||
// it.skip('resource', async () => {
|
||||
// const agent = api.agent();
|
||||
// agent.set('Authorization', `Bearer token1`);
|
||||
// const response = await agent.resource('posts').create({
|
||||
// values: { title: 't1' },
|
||||
// });
|
||||
// await agent.resource('posts').update({
|
||||
// resourceIndex: response.body.data.id,
|
||||
// values: { title: 't2' },
|
||||
// });
|
||||
// await agent.resource('posts').destroy({
|
||||
// resourceIndex: response.body.data.id,
|
||||
// });
|
||||
// const ActionLog = db.getCollection('action_logs').model;
|
||||
// const count = await ActionLog.count();
|
||||
// expect(count).toBe(3);
|
||||
// });
|
||||
});
|
@ -1,19 +1,13 @@
|
||||
import { CollectionOptions } from '@nocobase/database';
|
||||
import { defineCollection } from '@nocobase/database';
|
||||
|
||||
export default {
|
||||
name: 'action_changes',
|
||||
export default defineCollection({
|
||||
name: 'auditChanges',
|
||||
title: '变动值',
|
||||
createdBy: false,
|
||||
updatedBy: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'log',
|
||||
target: 'action_logs',
|
||||
foreignKey: 'actionLogId',
|
||||
},
|
||||
{
|
||||
type: 'json',
|
||||
name: 'field',
|
||||
@ -26,5 +20,11 @@ export default {
|
||||
type: 'json',
|
||||
name: 'after',
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'log',
|
||||
target: 'auditLogs',
|
||||
foreignKey: 'auditLogId',
|
||||
},
|
||||
],
|
||||
} as CollectionOptions;
|
||||
});
|
@ -1,8 +1,7 @@
|
||||
import { CollectionOptions } from '@nocobase/database';
|
||||
import { defineCollection } from '@nocobase/database';
|
||||
|
||||
export default {
|
||||
name: 'action_logs',
|
||||
title: '操作记录',
|
||||
export default defineCollection({
|
||||
name: 'auditLogs',
|
||||
createdBy: false,
|
||||
updatedBy: false,
|
||||
updatedAt: false,
|
||||
@ -12,9 +11,12 @@ export default {
|
||||
name: 'createdAt',
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'user',
|
||||
target: 'users',
|
||||
type: 'string',
|
||||
name: 'type',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'recordId',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
@ -25,22 +27,20 @@ export default {
|
||||
name: 'collection',
|
||||
target: 'collections',
|
||||
targetKey: 'name',
|
||||
sourceKey: 'collectionName',
|
||||
sourceKey: 'id',
|
||||
foreignKey: 'collectionName',
|
||||
constraints: false,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'type',
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'index',
|
||||
},
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'changes',
|
||||
target: 'action_changes',
|
||||
foreignKey: 'actionLogId',
|
||||
target: 'auditChanges',
|
||||
foreignKey: 'auditLogId',
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'user',
|
||||
target: 'users',
|
||||
},
|
||||
],
|
||||
} as CollectionOptions;
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
import Database, { updateAssociations } from '@nocobase/database';
|
||||
import Database from '@nocobase/database';
|
||||
import { LOG_TYPE_CREATE } from '../constants';
|
||||
|
||||
export async function afterCreate(model, options) {
|
||||
@ -8,22 +8,9 @@ export async function afterCreate(model, options) {
|
||||
return;
|
||||
}
|
||||
const transaction = options.transaction;
|
||||
const ActionLog = db.getCollection('action_logs');
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const currentUserId = options?.context?.state?.currentUser?.id;
|
||||
try {
|
||||
const log = await ActionLog.model.create(
|
||||
{
|
||||
type: LOG_TYPE_CREATE,
|
||||
collectionName: model.constructor.name,
|
||||
index: model.get(model.constructor.primaryKeyAttribute),
|
||||
createdAt: model.get('createdAt'),
|
||||
userId: currentUserId,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
hooks: false,
|
||||
},
|
||||
);
|
||||
const changes = [];
|
||||
const changed = model.changed();
|
||||
if (changed) {
|
||||
@ -31,21 +18,25 @@ export async function afterCreate(model, options) {
|
||||
const field = collection.findField((field) => {
|
||||
return field.name === key || field.options.field === key;
|
||||
});
|
||||
if (field && !field.options.hidden && field.options.type !== 'formula') {
|
||||
if (field && !field.options.hidden) {
|
||||
changes.push({
|
||||
field: field.options,
|
||||
after: model.get(key),
|
||||
});
|
||||
}
|
||||
});
|
||||
await updateAssociations(log, { changes }, { transaction });
|
||||
}
|
||||
// if (!options.transaction) {
|
||||
// await transaction.commit();
|
||||
// }
|
||||
} catch (error) {
|
||||
// if (!options.transaction) {
|
||||
// await transaction.rollback();
|
||||
// }
|
||||
}
|
||||
await AuditLog.repository.create({
|
||||
values: {
|
||||
type: LOG_TYPE_CREATE,
|
||||
collectionName: model.constructor.name,
|
||||
recordId: model.get(model.constructor.primaryKeyAttribute),
|
||||
createdAt: model.get('createdAt'),
|
||||
userId: currentUserId,
|
||||
changes,
|
||||
},
|
||||
transaction,
|
||||
hooks: false,
|
||||
});
|
||||
} catch (error) {}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import Database, { updateAssociations } from '@nocobase/database';
|
||||
import Database from '@nocobase/database';
|
||||
import { LOG_TYPE_DESTROY } from '../constants';
|
||||
|
||||
export async function afterDestroy(model, options) {
|
||||
@ -8,21 +8,9 @@ export async function afterDestroy(model, options) {
|
||||
return;
|
||||
}
|
||||
const transaction = options.transaction;
|
||||
const ActionLog = db.getCollection('action_logs');
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const currentUserId = options?.context?.state?.currentUser?.id;
|
||||
try {
|
||||
const log = await ActionLog.model.create(
|
||||
{
|
||||
type: LOG_TYPE_DESTROY,
|
||||
collectionName: model.constructor.name,
|
||||
index: model.get(model.constructor.primaryKeyAttribute),
|
||||
userId: currentUserId,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
hooks: false,
|
||||
},
|
||||
);
|
||||
const changes = [];
|
||||
Object.keys(model.get()).forEach((key: string) => {
|
||||
const field = collection.findField((field) => {
|
||||
@ -35,7 +23,17 @@ export async function afterDestroy(model, options) {
|
||||
});
|
||||
}
|
||||
});
|
||||
await updateAssociations(log, { changes }, { transaction });
|
||||
await AuditLog.repository.create({
|
||||
values: {
|
||||
type: LOG_TYPE_DESTROY,
|
||||
collectionName: model.constructor.name,
|
||||
recordId: model.get(model.constructor.primaryKeyAttribute),
|
||||
userId: currentUserId,
|
||||
changes,
|
||||
},
|
||||
transaction,
|
||||
hooks: false,
|
||||
});
|
||||
// if (!options.transaction) {
|
||||
// await transaction.commit();
|
||||
// }
|
@ -1,4 +1,4 @@
|
||||
import Database, { updateAssociations } from '@nocobase/database';
|
||||
import Database from '@nocobase/database';
|
||||
import { LOG_TYPE_UPDATE } from '../constants';
|
||||
|
||||
export async function afterUpdate(model, options) {
|
||||
@ -12,14 +12,14 @@ export async function afterUpdate(model, options) {
|
||||
return;
|
||||
}
|
||||
const transaction = options.transaction;
|
||||
const ActionLog = db.getCollection('action_logs');
|
||||
const AuditLog = db.getCollection('auditLogs');
|
||||
const currentUserId = options?.context?.state?.currentUser?.id;
|
||||
const changes = [];
|
||||
changed.forEach((key: string) => {
|
||||
const field = collection.findField((field) => {
|
||||
return field.name === key || field.options.field === key;
|
||||
});
|
||||
if (field && !field.options.hidden && field.options.type !== 'formula') {
|
||||
if (field && !field.options.hidden) {
|
||||
changes.push({
|
||||
field: field.options,
|
||||
after: model.get(key),
|
||||
@ -31,20 +31,18 @@ export async function afterUpdate(model, options) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const log = await ActionLog.model.create(
|
||||
{
|
||||
await AuditLog.repository.create({
|
||||
values: {
|
||||
type: LOG_TYPE_UPDATE,
|
||||
collectionName: model.constructor.name,
|
||||
index: model.get(model.constructor.primaryKeyAttribute),
|
||||
recordId: model.get(model.constructor.primaryKeyAttribute),
|
||||
createdAt: model.get('updatedAt'),
|
||||
userId: currentUserId,
|
||||
changes,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
hooks: false,
|
||||
},
|
||||
);
|
||||
await updateAssociations(log, { changes }, { transaction });
|
||||
transaction,
|
||||
hooks: false,
|
||||
});
|
||||
// if (!options.transaction) {
|
||||
// await transaction.commit();
|
||||
// }
|
@ -1,6 +1,6 @@
|
||||
import path from 'path';
|
||||
import { afterCreate, afterUpdate, afterDestroy } from './hooks';
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import path from 'path';
|
||||
import { afterCreate, afterDestroy, afterUpdate } from './hooks';
|
||||
|
||||
export default class PluginActionLogs extends Plugin {
|
||||
async beforeLoad() {
|
@ -22,7 +22,16 @@ export class FieldModel extends MagicAttributeModel {
|
||||
if (skipExist && collection.hasField(name)) {
|
||||
return collection.getField(name);
|
||||
}
|
||||
return collection.setField(name, this.get());
|
||||
const options = this.get();
|
||||
if (options.uiSchemaUid) {
|
||||
const UISchema = this.db.getModel('uiSchemas');
|
||||
const uiSchema = await UISchema.findByPk(options.uiSchemaUid, {
|
||||
transaction: loadOptions.transaction,
|
||||
});
|
||||
return collection.setField(name, { ...options, uiSchema });
|
||||
} else {
|
||||
return collection.setField(name, options);
|
||||
}
|
||||
}
|
||||
|
||||
async migrate(options?: SyncOptions & Transactionable) {
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
afterCreateForReverseField,
|
||||
beforeCreateForChildrenCollection,
|
||||
beforeCreateForReverseField,
|
||||
beforeInitOptions,
|
||||
beforeInitOptions
|
||||
} from './hooks';
|
||||
import { CollectionModel, FieldModel } from './models';
|
||||
|
||||
@ -86,6 +86,12 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
}
|
||||
});
|
||||
|
||||
this.app.db.on('fields.afterCreateWithAssociations', async (model, { context, transaction }) => {
|
||||
if (context) {
|
||||
await model.load({ transaction });
|
||||
}
|
||||
});
|
||||
|
||||
this.app.db.on('fields.afterCreateWithAssociations', async (model, { context, transaction }) => {
|
||||
if (!context) {
|
||||
return;
|
||||
|
@ -7,6 +7,7 @@ export default {
|
||||
model: 'UserModel',
|
||||
createdBy: true,
|
||||
updatedBy: true,
|
||||
logging: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'id',
|
||||
|
Loading…
Reference in New Issue
Block a user