refactor: audit logs block (#1517)

* refactor: audit-log

* refactor: audit-log fix view action

* refactor: audit-log view fix

* refactor(audit-log): collection field fix

* refactor: audit-log view field fix

* refactor(audit-log): support fixedBlock

* refactor(audit-log): i18n fix

* refactor(audit-log): fixedTable and drag issue

* refactor(audit-log): remove template support

* refactor(audit-log): drag fix

* refactor(audit-log): disableTemplate & DndContext

---------

Co-authored-by: anuoua <anuoua@gmail.com>
This commit is contained in:
chenos 2023-03-03 00:16:26 +08:00 committed by GitHub
parent e913af7f32
commit 07ec115904
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 968 additions and 95 deletions

View File

@ -20,7 +20,7 @@ export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (pr
service, service,
interfaces: { ...defaultInterfaces, ...ctx.interfaces, ...interfaces }, interfaces: { ...defaultInterfaces, ...ctx.interfaces, ...interfaces },
templates: { ...defaultTemplates, ...templates }, templates: { ...defaultTemplates, ...templates },
collections, collections: [...ctx.collections, ...collections],
refreshCM, refreshCM,
}} }}
> >
@ -38,7 +38,7 @@ export const RemoteCollectionManagerProvider = (props: any) => {
action: 'list', action: 'list',
params: { params: {
paginate: false, paginate: false,
appends: ['fields', 'fields.uiSchema','category'], appends: ['fields', 'fields.uiSchema', 'category'],
filter: { filter: {
// inherit: false, // inherit: false,
}, },
@ -75,13 +75,13 @@ export const RemoteCollectionManagerProvider = (props: any) => {
export const CollectionCategroriesProvider = (props) => { export const CollectionCategroriesProvider = (props) => {
const api = useAPIClient(); const api = useAPIClient();
const options={ const options = {
url: 'collectionCategories:list', url: 'collectionCategories:list',
params: { params: {
paginate: false, paginate: false,
sort:['sort'] sort: ['sort'],
}, },
} };
const result = useRequest(options); const result = useRequest(options);
if (result.loading) { if (result.loading) {
return <Spin />; return <Spin />;
@ -91,11 +91,11 @@ export const CollectionCategroriesProvider = (props) => {
value={{ value={{
...result, ...result,
data: result?.data?.data, data: result?.data?.data,
refresh:async ()=>{ refresh: async () => {
const { data } = await api.request(options); const { data } = await api.request(options);
result.mutate(data); result.mutate(data);
return data?.data || []; return data?.data || [];
} },
}} }}
> >
{props.children} {props.children}

View File

@ -4,7 +4,7 @@ import cls from 'classnames';
import React from 'react'; import React from 'react';
import { useCollection } from '../../../collection-manager'; import { useCollection } from '../../../collection-manager';
import { useSchemaInitializer } from '../../../schema-initializer'; import { useSchemaInitializer } from '../../../schema-initializer';
import { SortableItem } from '../../common'; import { DndContext, SortableItem } from '../../common';
import { useDesigner } from '../../hooks'; import { useDesigner } from '../../hooks';
import { AssociationFilterInitializer } from './AssociationFilter.Initializer'; import { AssociationFilterInitializer } from './AssociationFilter.Initializer';
import { AssociationFilterItem } from './AssociationFilter.Item'; import { AssociationFilterItem } from './AssociationFilter.Item';
@ -17,59 +17,61 @@ export const AssociationFilter = (props) => {
const { exists, render } = useSchemaInitializer(filedSchema['x-initializer']); const { exists, render } = useSchemaInitializer(filedSchema['x-initializer']);
return ( return (
<SortableItem <DndContext>
className={cls( <SortableItem
'nb-block-item', className={cls(
props.className, 'nb-block-item',
css` props.className,
position: relative; css`
&:hover { position: relative;
> .general-schema-designer { &:hover {
display: block; > .general-schema-designer {
} display: block;
}
&.nb-form-item:hover {
> .general-schema-designer {
background: rgba(241, 139, 98, 0.06) !important;
border: 0 !important;
top: -5px !important;
bottom: -5px !important;
left: -5px !important;
right: -5px !important;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
border: 2px solid rgba(241, 139, 98, 0.3);
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
} }
} }
} &.nb-form-item:hover {
`, > .general-schema-designer {
)} background: rgba(241, 139, 98, 0.06) !important;
> border: 0 !important;
<Designer /> top: -5px !important;
{props.children} bottom: -5px !important;
{render()} left: -5px !important;
</SortableItem> right: -5px !important;
}
}
> .general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
border: 2px solid rgba(241, 139, 98, 0.3);
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
}
}
}
`,
)}
>
<Designer />
{props.children}
{render()}
</SortableItem>
</DndContext>
); );
}; };

View File

@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import { useDesignable } from '../../hooks'; import { useDesignable } from '../../hooks';
import { useRecord } from '../../../record-provider'; import { useRecord } from '../../../record-provider';
import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplate'; import { useBlockTemplateContext } from '../../../schema-templates/BlockTemplate';
import { uid } from '@formily/shared';
const FixedBlockContext = React.createContext({ const FixedBlockContext = React.createContext({
setFixedSchema: (schema: Schema) => {}, setFixedSchema: (schema: Schema) => {},

View File

@ -23,6 +23,7 @@ export const TableBlockDesigner = () => {
const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {}; const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || []; const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
const defaultResource = fieldSchema?.['x-decorator-props']?.resource; const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
const supportTemplate = !fieldSchema?.['x-decorator-props']?.disableTemplate;
const sort = defaultSort?.map((item: string) => { const sort = defaultSort?.map((item: string) => {
return item.startsWith('-') return item.startsWith('-')
? { ? {
@ -211,8 +212,10 @@ export const TableBlockDesigner = () => {
}); });
}} }}
/> />
<SchemaSettings.Divider /> {supportTemplate && <SchemaSettings.Divider />}
<SchemaSettings.Template componentName={'Table'} collectionName={name} resourceName={defaultResource} /> {supportTemplate && (
<SchemaSettings.Template componentName={'Table'} collectionName={name} resourceName={defaultResource} />
)}
<SchemaSettings.Divider /> <SchemaSettings.Divider />
<SchemaSettings.Remove <SchemaSettings.Remove
removeParentsIfNoChildren removeParentsIfNoChildren

View File

@ -10,7 +10,7 @@ import {
SchemaInitializerButtonProps, SchemaInitializerButtonProps,
SchemaInitializerItemComponent, SchemaInitializerItemComponent,
SchemaInitializerItemOptions, SchemaInitializerItemOptions,
SchemaInitializerItemProps SchemaInitializerItemProps,
} from './types'; } from './types';
const defaultWrap = (s: ISchema) => s; const defaultWrap = (s: ISchema) => s;

View File

@ -6,7 +6,7 @@ import { SchemaInitializer, SchemaSettings } from '../..';
import { useAPIClient } from '../../api-client'; import { useAPIClient } from '../../api-client';
import { createDesignable, useDesignable } from '../../schema-component'; import { createDesignable, useDesignable } from '../../schema-component';
const Resizable = (props) => { export const Resizable = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { dn } = useDesignable(); const { dn } = useDesignable();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();

View File

@ -2,5 +2,13 @@ export * from './SchemaInitializer';
export * from './SchemaInitializerProvider'; export * from './SchemaInitializerProvider';
export * from './types'; export * from './types';
export * from './items'; export * from './items';
export { gridRowColWrap, useRecordCollectionDataSourceItems } from './utils'; export {
gridRowColWrap,
useRecordCollectionDataSourceItems,
createTableBlockSchema,
useAssociatedTableColumnInitializerFields,
useInheritsTableColumnInitializerFields,
useTableColumnInitializerFields,
itemsMerge,
} from './utils';
export * from './buttons'; export * from './buttons';

View File

@ -815,10 +815,20 @@ export const createReadPrettyFormBlockSchema = (options) => {
}; };
export const createTableBlockSchema = (options) => { export const createTableBlockSchema = (options) => {
const { collection, resource, rowKey, ...others } = options; const {
collection,
resource,
rowKey,
tableActionInitializers,
tableColumnInitializers,
tableActionColumnInitializers,
tableBlockProvider,
disableTemplate,
...others
} = options;
const schema: ISchema = { const schema: ISchema = {
type: 'void', type: 'void',
'x-decorator': 'TableBlockProvider', 'x-decorator': tableBlockProvider ?? 'TableBlockProvider',
'x-acl-action': `${resource || collection}:list`, 'x-acl-action': `${resource || collection}:list`,
'x-decorator-props': { 'x-decorator-props': {
collection, collection,
@ -830,6 +840,7 @@ export const createTableBlockSchema = (options) => {
rowKey, rowKey,
showIndex: true, showIndex: true,
dragSort: false, dragSort: false,
disableTemplate: disableTemplate ?? false,
...others, ...others,
}, },
'x-designer': 'TableBlockDesigner', 'x-designer': 'TableBlockDesigner',
@ -837,7 +848,7 @@ export const createTableBlockSchema = (options) => {
properties: { properties: {
actions: { actions: {
type: 'void', type: 'void',
'x-initializer': 'TableActionInitializers', 'x-initializer': tableActionInitializers ?? 'TableActionInitializers',
'x-component': 'ActionBar', 'x-component': 'ActionBar',
'x-component-props': { 'x-component-props': {
style: { style: {
@ -848,7 +859,7 @@ export const createTableBlockSchema = (options) => {
}, },
[uid()]: { [uid()]: {
type: 'array', type: 'array',
'x-initializer': 'TableColumnInitializers', 'x-initializer': tableColumnInitializers ?? 'TableColumnInitializers',
'x-component': 'TableV2', 'x-component': 'TableV2',
'x-component-props': { 'x-component-props': {
rowKey: 'id', rowKey: 'id',
@ -865,7 +876,7 @@ export const createTableBlockSchema = (options) => {
'x-decorator': 'TableV2.Column.ActionBar', 'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column', 'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner', 'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'TableActionColumnInitializers', 'x-initializer': tableActionColumnInitializers ?? 'TableActionColumnInitializers',
properties: { properties: {
actions: { actions: {
type: 'void', type: 'void',

View File

@ -1,31 +1,29 @@
import { TableOutlined } from '@ant-design/icons'; import { TableOutlined } from '@ant-design/icons';
import { SchemaInitializer } from '@nocobase/client'; import { ISchema } from '@formily/react';
import { createTableBlockSchema, SchemaInitializer } from '@nocobase/client';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const AuditLogsBlockInitializer = (props) => { export const AuditLogsBlockInitializer = (props) => {
const { insert } = props; const { insert } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const schema = createTableBlockSchema({
collection: 'auditLogs',
rowKey: 'id',
tableActionInitializers: 'AuditLogsTableActionInitializers',
tableColumnInitializers: 'AuditLogsTableColumnInitializers',
tableActionColumnInitializers: 'AuditLogsTableActionColumnInitializers',
tableBlockProvider: 'AuditLogsBlockProvider',
disableTemplate: true,
});
return ( return (
<SchemaInitializer.Item <SchemaInitializer.Item
{...props} {...props}
icon={<TableOutlined />} icon={<TableOutlined />}
onClick={() => { onClick={() => {
insert({ insert(schema as ISchema);
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')} title={t('Audit Logs')}
/> />

View File

@ -0,0 +1,33 @@
import {
CollectionManagerProvider,
SchemaInitializerContext,
SchemaInitializerProvider,
TableBlockProvider,
} from '@nocobase/client';
import React, { useContext } from 'react';
import { useAuditChangesCollection, useAuditLogsCollection, useCollectionsCollection } from './collections';
import { AuditLogsTableActionColumnInitializers } from './initializers/AuditLogsTableActionColumnInitializers';
import { AuditLogsTableActionInitializers } from './initializers/AuditLogsTableActionInitializers';
import { AuditLogsTableColumnInitializers } from './initializers/AuditLogsTableColumnInitializers';
export const AuditLogsBlockProvider: React.FC = ({ children, ...restProps }) => {
const initializers = useContext(SchemaInitializerContext);
const auditChangesCollection = useAuditChangesCollection();
const auditLogsCollection = useAuditLogsCollection();
const collectionsCollection = useCollectionsCollection();
return (
<SchemaInitializerProvider
initializers={{
...initializers,
AuditLogsTableActionInitializers,
AuditLogsTableActionColumnInitializers,
AuditLogsTableColumnInitializers,
}}
>
<CollectionManagerProvider collections={[auditLogsCollection, auditChangesCollection, collectionsCollection]}>
<TableBlockProvider {...restProps}>{children}</TableBlockProvider>
</CollectionManagerProvider>
</SchemaInitializerProvider>
);
};

View File

@ -1,11 +1,26 @@
import { SchemaComponentOptions } from '@nocobase/client'; import { SchemaComponentOptions, SchemaInitializerContext, SchemaInitializerProvider } from '@nocobase/client';
import React from 'react'; import React from 'react';
import { AuditLogs } from './AuditLogs';
import { AuditLogsBlockInitializer } from './AuditLogsBlockInitializer'; import { AuditLogsBlockInitializer } from './AuditLogsBlockInitializer';
import { AuditLogsValue } from './components/AuditLogsValue';
import { AuditLogsField } from './components/AuditLogsField';
import { AuditLogsBlockProvider } from './AuditLogsBlockProvider';
import { AuditLogsTableActionColumnInitializer } from './initializers/AuditLogsTableActionColumnInitializer';
import { AuditLogs } from './deplicated/AuditLogs';
import { AuditLogsViewActionInitializer } from './components/AuditLogsViewActionInitializer';
export const AuditLogsProvider = (props: any) => { export const AuditLogsProvider = (props: any) => {
return ( return (
<SchemaComponentOptions components={{ AuditLogs, AuditLogsBlockInitializer }}> <SchemaComponentOptions
components={{
AuditLogs,
AuditLogsBlockProvider,
AuditLogsBlockInitializer,
AuditLogsValue,
AuditLogsField,
AuditLogsViewActionInitializer,
AuditLogsTableActionColumnInitializer,
}}
>
{props.children} {props.children}
</SchemaComponentOptions> </SchemaComponentOptions>
); );

View File

@ -0,0 +1,166 @@
import { useAuditLogsTranslation } from './locale';
export const useAuditLogsCollection = () => {
return {
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,
ellipsis: true,
},
'x-read-pretty': true,
},
},
{
name: 'type',
type: 'string',
interface: 'select',
uiSchema: {
type: 'string',
title: '{{t("Action type")}}',
'x-component': 'Select',
'x-component-props': { ellipsis: true },
'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' },
],
},
},
{
name: 'recordId',
type: 'string',
interface: 'input',
uiSchema: {
title: '{{t("Record ID")}}',
type: 'string',
'x-component': 'Input',
'x-component-props': { ellipsis: true },
},
},
{
collectionName: 'auditLogs',
name: 'collection',
type: 'belongsTo',
interface: 'm2o',
target: 'collections',
targetKey: 'name',
sourceKey: 'id',
foreignKey: 'collectionName',
uiSchema: {
type: 'object',
title: '{{t("Collection")}}',
'x-component': 'RecordPicker',
'x-component-props': { fieldNames: { value: 'name', label: 'title' }, ellipsis: true },
'x-read-pretty': true,
},
},
{
name: 'user',
collectionName: 'auditLogs',
type: 'belongsTo',
interface: 'createdBy',
targetKey: 'id',
foreignKey: 'createdById',
target: 'users',
uiSchema: {
type: 'object',
title: '{{t("User")}}',
'x-component': 'RecordPicker',
'x-component-props': { fieldNames: { value: 'id', label: 'nickname' }, ellipsis: true },
'x-read-pretty': true,
},
},
{
name: 'changes',
collectionName: 'auditLogs',
type: 'hasMany',
interface: 'subTable',
target: 'auditChanges',
foreignKey: 'auditLogId',
targetKey: 'id',
uiSchema: {
type: 'object',
title: '{{t("Details of changes", { ns: "audit-logs" })}}',
},
},
],
};
};
export const useAuditChangesCollection = () => {
return {
name: 'auditChanges',
title: '{{t("Audit Changes")}}',
fields: [
{
name: 'field',
type: 'json',
interface: 'input',
uiSchema: {
title: '{{t("Field")}}',
'x-component': 'AuditLogsField',
},
},
{
type: 'json',
name: 'before',
interface: 'input',
uiSchema: {
title: '{{t("Before change")}}',
'x-component': 'AuditLogsValue',
},
},
{
type: 'json',
name: 'after',
interface: 'input',
uiSchema: {
title: '{{t("After change")}}',
'x-component': 'AuditLogsValue',
},
},
],
};
};
export const useCollectionsCollection = () => {
return {
name: 'collections',
title: '{{t("Collections")}}',
fields: [
{
name: 'name',
type: 'string',
interface: 'input',
uiSchema: {
title: '{{t("Collection name")}}',
type: 'string',
'x-component': 'Input',
'x-component-props': { ellipsis: true },
},
},
{
name: 'title',
type: 'string',
interface: 'input',
uiSchema: {
title: '{{t("Collection display name")}}',
type: 'string',
'x-component': 'Input',
'x-component-props': { ellipsis: true },
},
},
],
};
};

View File

@ -0,0 +1,12 @@
import { useCompile } from '@nocobase/client';
import React from 'react';
import { observer, useField } from '@formily/react';
export const AuditLogsField = 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>;
});

View File

@ -0,0 +1,23 @@
import { FormProvider, SchemaComponent, useRecord } from '@nocobase/client';
import React from 'react';
import { observer, useField } from '@formily/react';
export const AuditLogsValue = observer(() => {
const field = useField<any>();
const record = 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>;
});

View File

@ -0,0 +1,340 @@
import { ActionInitializer } from '@nocobase/client';
import React from 'react';
export const AuditLogsViewActionInitializer = (props) => {
const schema = {
type: 'void',
title: '{{ t("View") }}',
'x-action': 'view',
'x-designer': 'Action.Designer',
'x-component': 'Action',
'x-component-props': {
openMode: 'drawer',
},
properties: {
drawer: {
type: 'void',
title: '{{ t("View record") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
properties: {
tab1: {
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
properties: {
'7ypqrxaqysp': {
type: 'void',
'x-component': 'Grid.Row',
properties: {
'1fc2l8dwe7m': {
type: 'void',
'x-component': 'Grid.Col',
properties: {
'5ley6xifrsb': {
type: 'void',
'x-acl-action': 'auditLogs:get',
'x-decorator': 'FormBlockProvider',
'x-decorator-props': {
resource: 'auditLogs',
collection: 'auditLogs',
readPretty: true,
action: 'get',
useParams: '{{ useParamsFromRecord }}',
useSourceId: '{{ useSourceIdFromParentRecord }}',
},
'x-component': 'CardItem',
properties: {
bv710pbf9w6: {
type: 'void',
'x-component': 'FormV2',
'x-read-pretty': true,
'x-component-props': {
useProps: '{{ useFormBlockProps }}',
},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
properties: {
g4c24abnbd9: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
fkt9dj5lu1k: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
createdAt: {
type: 'string',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'auditLogs.createdAt',
'x-component-props': {},
'x-read-pretty': true,
},
},
},
},
},
l267bkv423v: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
tehaepm5xqy: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
type: {
type: 'string',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'auditLogs.type',
'x-component-props': {},
'x-read-pretty': true,
},
},
},
},
},
zhmn2tkzdh2: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
yuwyej17i64: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
recordId: {
type: 'string',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'auditLogs.recordId',
'x-component-props': {},
},
},
},
},
},
'6q933ic06mj': {
type: 'void',
'x-component': 'Grid.Row',
properties: {
nwtbkqd99zl2: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
'collection.title': {
type: 'string',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'auditLogs.collection.title',
'x-component-props': {},
},
},
},
nwtbkqd99zl1: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
'collection.name': {
type: 'string',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'auditLogs.collection.name',
'x-component-props': {},
},
},
},
},
},
xd4kzulndqk: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
k3sd72n83zd: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
user: {
type: 'string',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'auditLogs.user',
'x-component-props': {},
'x-read-pretty': true,
properties: {
viewer: {
type: 'void',
title: '{{ t("View record") }}',
'x-component': 'RecordPicker.Viewer',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'RecordBlockInitializers',
},
},
},
},
},
},
},
},
},
},
},
},
},
'3lghgclbtcg': {
type: 'void',
'x-component': 'Grid.Row',
properties: {
'8l1v1auwlx0': {
type: 'void',
'x-component': 'Grid.Col',
properties: {
changes: {
type: 'void',
'x-component': 'TableField',
'x-decorator': 'FormItem',
'x-collection-field': 'auditLogs.changes',
'x-component-props': {},
properties: {
block: {
type: 'void',
'x-decorator': 'TableFieldProvider',
'x-acl-action': 'auditChanges:list',
'x-decorator-props': {
collection: 'auditChanges',
association: 'auditLogs.changes',
resource: 'auditLogs.changes',
action: 'list',
params: {
paginate: false,
},
showIndex: true,
dragSort: false,
},
properties: {
actions: {
type: 'void',
'x-component': 'TableField.ActionBar',
'x-component-props': {},
},
changes: {
type: 'array',
'x-component': 'TableV2',
'x-component-props': {
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableFieldProps }}',
},
properties: {
'5uvv96u9ict': {
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-component': 'TableV2.Column',
properties: {
field: {
'x-collection-field': 'auditChanges.field',
'x-component': 'CollectionField',
'x-read-pretty': true,
'x-component-props': {
ellipsis: true,
},
},
},
},
h7a4tgt11gd: {
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-component': 'TableV2.Column',
properties: {
before: {
'x-collection-field': 'auditChanges.before',
'x-component': 'CollectionField',
'x-read-pretty': true,
'x-component-props': {
ellipsis: true,
},
},
},
},
m275z8rglzx: {
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-component': 'TableV2.Column',
properties: {
after: {
'x-collection-field': 'auditChanges.after',
'x-component': 'CollectionField',
'x-read-pretty': true,
'x-component-props': {
ellipsis: true,
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
};
return <ActionInitializer {...props} schema={schema} />;
};

View File

@ -7,7 +7,8 @@ import {
SchemaComponent, SchemaComponent,
TableBlockProvider, TableBlockProvider,
useCollection, useCollection,
useCompile, useRecord useCompile,
useRecord,
} from '@nocobase/client'; } from '@nocobase/client';
import React, { createContext, useContext } from 'react'; import React, { createContext, useContext } from 'react';
import { AuditLogsDesigner } from './AuditLogsDesigner'; import { AuditLogsDesigner } from './AuditLogsDesigner';
@ -106,7 +107,7 @@ export const AuditLogs: any = () => {
'x-read-pretty': true, 'x-read-pretty': true,
}, },
}, },
} };
} }
return ( return (
<SchemaComponent <SchemaComponent
@ -454,10 +455,13 @@ AuditLogs.Decorator = observer((props: any) => {
const filterByTk = record?.[parent.filterTargetKey || 'id']; const filterByTk = record?.[parent.filterTargetKey || 'id'];
if (filter) { if (filter) {
filter = { filter = {
$and: [filter, { $and: [
collectionName: parent.name, filter,
recordId: `${filterByTk}`, {
}], collectionName: parent.name,
recordId: `${filterByTk}`,
},
],
}; };
} else { } else {
filter = { filter = {

View File

@ -1,5 +1,13 @@
import { ISchema, useField, useFieldSchema } from '@formily/react'; import { ISchema, useField, useFieldSchema } from '@formily/react';
import { GeneralSchemaDesigner, SchemaSettings, useCollection, useCollectionFilterOptions, useDesignable, useSortFields, useTableBlockContext } from '@nocobase/client'; import {
GeneralSchemaDesigner,
SchemaSettings,
useCollection,
useCollectionFilterOptions,
useDesignable,
useSortFields,
useTableBlockContext,
} from '@nocobase/client';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -0,0 +1,26 @@
import { InitializerWithSwitch } from '@nocobase/client';
import React from 'react';
export const AuditLogsTableActionColumnInitializer = (props) => {
const schema = {
type: 'void',
title: '{{ t("Actions") }}',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'AuditLogsTableActionColumnInitializers',
'x-action-column': 'actions',
properties: {
actions: {
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {},
},
},
};
return <InitializerWithSwitch {...props} schema={schema} type={'x-action-column'} />;
};

View File

@ -0,0 +1,63 @@
import { MenuOutlined } from '@ant-design/icons';
import { useFieldSchema } from '@formily/react';
import { createDesignable, Resizable, SchemaInitializer, useAPIClient, useDesignable } from '@nocobase/client';
import React from 'react';
import { useTranslation } from 'react-i18next';
export const AuditLogsTableActionColumnInitializers = (props: any) => {
const fieldSchema = useFieldSchema();
const api = useAPIClient();
const { refresh } = useDesignable();
const { t } = useTranslation();
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
insert={(schema) => {
const spaceSchema = fieldSchema.reduceProperties((buf, schema) => {
if (schema['x-component'] === 'Space') {
return schema;
}
return buf;
}, null);
if (!spaceSchema) {
return;
}
const dn = createDesignable({
t,
api,
refresh,
current: spaceSchema,
});
dn.loadAPIClientEvents();
dn.insertBeforeEnd(schema);
}}
items={[
{
type: 'itemGroup',
title: t('Enable actions'),
children: [
{
type: 'item',
title: t('View'),
component: 'AuditLogsViewActionInitializer',
schema: {
'x-component': 'Action.Link',
'x-action': 'view',
'x-decorator': 'ACLActionProvider',
},
},
],
},
{
type: 'divider',
},
{
type: 'item',
title: t('Column width'),
component: Resizable,
},
]}
component={<MenuOutlined style={{ cursor: 'pointer' }} />}
/>
);
};

View File

@ -0,0 +1,51 @@
import { Schema } from '@formily/react';
// 操作记录表格操作配置
export const AuditLogsTableActionInitializers = {
title: "{{t('Configure actions')}}",
icon: 'SettingOutlined',
style: {
marginLeft: 8,
},
items: [
{
type: 'itemGroup',
title: "{{t('Enable actions')}}",
children: [
{
type: 'item',
title: "{{t('Filter')}}",
component: 'FilterActionInitializer',
schema: {
'x-align': 'left',
},
},
{
type: 'item',
title: "{{t('Refresh')}}",
component: 'RefreshActionInitializer',
schema: {
'x-align': 'right',
},
},
],
},
{
type: 'divider',
},
{
type: 'item',
title: "{{t('Association fields filter')}}",
component: 'ActionBarAssociationFilterAction',
schema: {
'x-align': 'left',
},
find: (schema: Schema) => {
const resultSchema = Object.entries(schema.parent.properties).find(
([, value]) => value['x-component'] === 'AssociationFilter',
)?.[1];
return resultSchema;
},
},
],
};

View File

@ -0,0 +1,88 @@
import {
itemsMerge,
SchemaInitializer,
useAssociatedTableColumnInitializerFields,
useCompile,
useInheritsTableColumnInitializerFields,
useTableColumnInitializerFields,
} from '@nocobase/client';
import React from 'react';
import { useTranslation } from 'react-i18next';
// 表格列配置
export const AuditLogsTableColumnInitializers = (props: any) => {
const { items = [] } = props;
const { t } = useTranslation();
const associatedFields = useAssociatedTableColumnInitializerFields();
const inheritFields = useInheritsTableColumnInitializerFields();
const compile = useCompile();
const fieldItems: any[] = [
{
type: 'itemGroup',
title: t('Display fields'),
children: useTableColumnInitializerFields(),
},
];
if (inheritFields?.length > 0) {
inheritFields.forEach((inherit) => {
Object.values(inherit)[0].length &&
fieldItems.push(
{
type: 'divider',
},
{
type: 'itemGroup',
title: t(`Parent collection fields`) + '(' + compile(`${Object.keys(inherit)[0]}`) + ')',
children: Object.values(inherit)[0],
},
);
});
}
if (associatedFields?.length > 0) {
fieldItems.push(
{
type: 'divider',
},
{
type: 'itemGroup',
title: t('Display association fields'),
children: associatedFields,
},
);
}
fieldItems.push(
{
type: 'divider',
},
{
type: 'item',
title: t('Action column'),
component: 'AuditLogsTableActionColumnInitializer',
},
);
return (
<SchemaInitializer.Button
insertPosition={'beforeEnd'}
icon={'SettingOutlined'}
wrap={(s) => {
if (s['x-action-column']) {
return s;
}
return {
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-designer': 'TableV2.Column.Designer',
'x-component': 'TableV2.Column',
properties: {
[s.name]: {
...s,
},
},
};
}}
items={itemsMerge(fieldItems, items)}
>
{t('Configure columns')}
</SchemaInitializer.Button>
);
};

View File

@ -0,0 +1,3 @@
export default {
'Details of changes': 'Details of changes',
};

View File

@ -0,0 +1,12 @@
import { i18n } from '@nocobase/client';
import { useTranslation } from 'react-i18next';
export const NAMESPACE = 'audit-logs';
export function lang(key: string) {
return i18n.t(key, { ns: NAMESPACE });
}
export function useAuditLogsTranslation() {
return useTranslation(NAMESPACE);
}

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1,3 @@
export default {
'Details of changes': '变更详情',
};