feat(analytics): persona logging upgrade and Sentry system config
Add client-logging SDK, expanded event taxonomy, API observability, admin domain events UI, and move SENTRY_DSN to HQ system settings with @sentry/node bootstrap after config preload. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Card, Descriptions, Drawer, Select, Space, Table, Tag } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { request } from '../lib/api';
|
||||
import { fmtTime } from '../lib/constants';
|
||||
|
||||
type Row = {
|
||||
id: string;
|
||||
eventType: string;
|
||||
refType: string;
|
||||
refId: string;
|
||||
status?: string | null;
|
||||
param1?: string | null;
|
||||
param2?: string | null;
|
||||
param3?: string | null;
|
||||
remark?: string | null;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
const EVENT_OPTIONS = [
|
||||
{ value: '', label: '全部领域事件' },
|
||||
{ value: 'ORDER_STATUS', label: '订单状态' },
|
||||
{ value: 'BENEFIT_LEDGER', label: '权益流水' },
|
||||
{ value: 'STORE_AUDIT', label: '门店审核' },
|
||||
{ value: 'TICKET_COLLAB', label: '工单协作' },
|
||||
{ value: 'PROMO_TOUCH', label: '推广触达' },
|
||||
];
|
||||
|
||||
export default function DomainEventsPage() {
|
||||
const [params, setParams] = useSearchParams();
|
||||
const eventType = params.get('eventType') ?? '';
|
||||
const refType = params.get('refType') ?? '';
|
||||
const refId = params.get('refId') ?? '';
|
||||
const [data, setData] = useState<{ items: Row[]; total: number; page: number; pageSize: number } | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [detail, setDetail] = useState<Row | null>(null);
|
||||
|
||||
const load = useCallback(() => {
|
||||
setLoading(true);
|
||||
const q = new URLSearchParams();
|
||||
if (eventType) q.set('eventType', eventType);
|
||||
if (refType) q.set('refType', refType);
|
||||
if (refId) q.set('refId', refId);
|
||||
q.set('page', params.get('page') ?? '1');
|
||||
q.set('pageSize', '20');
|
||||
request<{ items: Row[]; total: number; page: number; pageSize: number }>(
|
||||
`/admin/logs/domain-events?${q.toString()}`,
|
||||
)
|
||||
.then(setData)
|
||||
.finally(() => setLoading(false));
|
||||
}, [eventType, refType, refId, params]);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
const columns: ColumnsType<Row> = [
|
||||
{ title: '时间', dataIndex: 'createdAt', width: 170, render: (v) => fmtTime(v) },
|
||||
{ title: '类型', dataIndex: 'eventType', width: 130, render: (v) => <Tag>{v}</Tag> },
|
||||
{ title: '关联', render: (_, r) => `${r.refType} #${r.refId}` },
|
||||
{ title: '状态', dataIndex: 'status', width: 100 },
|
||||
{ title: '摘要', render: (_, r) => r.param1 || r.remark || '—' },
|
||||
{
|
||||
title: '操作',
|
||||
width: 80,
|
||||
render: (_, r) => (
|
||||
<a
|
||||
onClick={async () => {
|
||||
setDetail(await request<Row>(`/admin/logs/domain-events/${r.id}`));
|
||||
}}
|
||||
>
|
||||
详情
|
||||
</a>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card title="领域事件">
|
||||
<Space wrap style={{ marginBottom: 16 }}>
|
||||
<Select
|
||||
style={{ width: 180 }}
|
||||
value={eventType}
|
||||
options={EVENT_OPTIONS}
|
||||
onChange={(v) => {
|
||||
const next = new URLSearchParams(params);
|
||||
if (v) next.set('eventType', v);
|
||||
else next.delete('eventType');
|
||||
next.set('page', '1');
|
||||
setParams(next);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={data?.items ?? []}
|
||||
pagination={{
|
||||
current: data?.page ?? 1,
|
||||
pageSize: data?.pageSize ?? 20,
|
||||
total: data?.total ?? 0,
|
||||
onChange: (p) => {
|
||||
const next = new URLSearchParams(params);
|
||||
next.set('page', String(p));
|
||||
setParams(next);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Drawer open={!!detail} title="事件详情" width={520} onClose={() => setDetail(null)}>
|
||||
{detail && (
|
||||
<Descriptions column={1} size="small">
|
||||
<Descriptions.Item label="ID">{detail.id}</Descriptions.Item>
|
||||
<Descriptions.Item label="类型">{detail.eventType}</Descriptions.Item>
|
||||
<Descriptions.Item label="关联">{`${detail.refType} #${detail.refId}`}</Descriptions.Item>
|
||||
<Descriptions.Item label="param1">{detail.param1}</Descriptions.Item>
|
||||
<Descriptions.Item label="param2">{detail.param2}</Descriptions.Item>
|
||||
<Descriptions.Item label="param3">{detail.param3}</Descriptions.Item>
|
||||
<Descriptions.Item label="备注">{detail.remark}</Descriptions.Item>
|
||||
<Descriptions.Item label="时间">{fmtTime(detail.createdAt)}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
)}
|
||||
</Drawer>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user