feat(logs): Add various log pages for tracking user activities and system events

This commit is contained in:
web
2025-08-26 11:29:12 -07:00
parent 4f7cc807af
commit d85af491aa
28 changed files with 876 additions and 252 deletions
@@ -0,0 +1,49 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { ProTable } from '@/components/pro-table';
import { filterBalanceLog } from '@/services/admin/log';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function BalanceLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.BalanceLog, { search?: string }>
header={{ title: t('title.balance') }}
columns={[
{
accessorKey: 'user',
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'amount', header: t('column.amount') },
{ accessorKey: 'order_id', header: t('column.orderId') },
{ accessorKey: 'balance', header: t('column.balance') },
{ accessorKey: 'type', header: t('column.type') },
{
accessorKey: 'timestamp',
header: t('column.time'),
cell: ({ row }) => formatDate(row.original.timestamp),
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
request={async (pagination, filter) => {
const { data } = await filterBalanceLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
const list = (data?.data?.list || []) as any[];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,48 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { ProTable } from '@/components/pro-table';
import { filterCommissionLog } from '@/services/admin/log';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function CommissionLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.CommissionLog, { search?: string }>
header={{ title: t('title.commission') }}
columns={[
{
accessorKey: 'user',
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'amount', header: t('column.amount') },
{ accessorKey: 'order_no', header: t('column.orderNo') },
{ accessorKey: 'type', header: t('column.type') },
{
accessorKey: 'created_at',
header: t('column.createdAt'),
cell: ({ row }) => formatDate(row.original.created_at),
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
request={async (pagination, filter) => {
const { data } = await filterCommissionLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
const list = (data?.data?.list || []) as any[];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,53 @@
'use client';
import { ProTable } from '@/components/pro-table';
import { filterEmailLog } from '@/services/admin/log';
import { Badge } from '@workspace/ui/components/badge';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function EmailLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.MessageLog, { search?: string }>
header={{ title: t('title.email') }}
columns={[
{
accessorKey: 'id',
header: t('column.id'),
cell: ({ row }) => <Badge>{row.getValue('id')}</Badge>,
},
{ accessorKey: 'platform', header: t('column.platform') },
{ accessorKey: 'to', header: t('column.to') },
{ accessorKey: 'subject', header: t('column.subject') },
{
accessorKey: 'content',
header: t('column.content'),
cell: ({ row }) => (
<pre className='max-w-[480px] overflow-auto whitespace-pre-wrap break-words text-xs'>
{JSON.stringify(row.original.content || {}, null, 2)}
</pre>
),
},
{ accessorKey: 'status', header: t('column.status') },
{
accessorKey: 'created_at',
header: t('column.createdAt'),
cell: ({ row }) => formatDate(row.original.created_at),
},
]}
params={[{ key: 'search' }, { key: 'date', type: 'date' }]}
request={async (pagination, filter) => {
const { data } = await filterEmailLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
});
const list = ((data?.data?.list || []) as API.MessageLog[]) || [];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,50 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { ProTable } from '@/components/pro-table';
import { filterGiftLog } from '@/services/admin/log';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function GiftLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.GiftLog, { search?: string }>
header={{ title: t('title.gift') }}
columns={[
{
accessorKey: 'user',
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'subscribe_id', header: t('column.subscribeId') },
{ accessorKey: 'order_no', header: t('column.orderNo') },
{ accessorKey: 'amount', header: t('column.amount') },
{ accessorKey: 'balance', header: t('column.balance') },
{ accessorKey: 'remark', header: t('column.remark') },
{
accessorKey: 'created_at',
header: t('column.createdAt'),
cell: ({ row }) => formatDate(row.original.created_at),
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
request={async (pagination, filter) => {
const { data } = await filterGiftLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
const list = (data?.data?.list || []) as any[];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,63 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { IpLink } from '@/components/ip-link';
import { ProTable } from '@/components/pro-table';
import { filterLoginLog } from '@/services/admin/log';
import { Badge } from '@workspace/ui/components/badge';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function LoginLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.LoginLog, { search?: string }>
header={{ title: t('title.login') }}
columns={[
{
accessorKey: 'user',
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'method', header: t('column.method') },
{
accessorKey: 'login_ip',
header: t('column.ip'),
cell: ({ row }) => <IpLink ip={String((row.original as any).login_ip || '')} />,
},
{ accessorKey: 'user_agent', header: t('column.userAgent') },
{
accessorKey: 'success',
header: t('column.success'),
cell: ({ row }) => (
<Badge variant={row.original.success ? 'default' : 'destructive'}>
{row.original.success ? t('success') : t('failed')}
</Badge>
),
},
{
accessorKey: 'login_time',
header: t('column.time'),
cell: ({ row }) => formatDate(row.original.login_time),
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
request={async (pagination, filter) => {
const { data } = await filterLoginLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
const list = ((data?.data?.list || []) as API.LoginLog[]) || [];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,52 @@
'use client';
import { ProTable } from '@/components/pro-table';
import { filterMobileLog } from '@/services/admin/log';
import { Badge } from '@workspace/ui/components/badge';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function MobileLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.MessageLog, { search?: string }>
header={{ title: t('title.mobile') }}
columns={[
{
accessorKey: 'id',
header: t('column.id'),
cell: ({ row }) => <Badge>{row.getValue('id')}</Badge>,
},
{ accessorKey: 'platform', header: t('column.platform') },
{ accessorKey: 'to', header: t('column.to') },
{ accessorKey: 'subject', header: t('column.subject') },
{
accessorKey: 'content',
header: t('column.content'),
cell: ({ row }) => (
<pre className='max-w-[480px] overflow-auto whitespace-pre-wrap break-words text-xs'>
{JSON.stringify(row.original.content || {}, null, 2)}
</pre>
),
},
{ accessorKey: 'status', header: t('column.status') },
{
accessorKey: 'created_at',
header: t('column.createdAt'),
cell: ({ row }) => formatDate(row.original.created_at),
},
]}
params={[{ key: 'search' }, { key: 'date', type: 'date' }]}
request={async (pagination, filter) => {
const { data } = await filterMobileLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
});
const list = ((data?.data?.list || []) as API.MessageLog[]) || [];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,54 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { IpLink } from '@/components/ip-link';
import { ProTable } from '@/components/pro-table';
import { filterRegisterLog } from '@/services/admin/log';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function RegisterLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.RegisterLog, { search?: string }>
header={{ title: t('title.register') }}
columns={[
{
accessorKey: 'user',
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'auth_method', header: t('column.method') },
{ accessorKey: 'identifier', header: t('column.identifier') },
{
accessorKey: 'register_ip',
header: t('column.ip'),
cell: ({ row }) => <IpLink ip={String((row.original as any).register_ip || '')} />,
},
{ accessorKey: 'user_agent', header: t('column.userAgent') },
{
accessorKey: 'register_time',
header: t('column.time'),
cell: ({ row }) => formatDate(row.original.register_time),
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
request={async (pagination, filter) => {
const { data } = await filterRegisterLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
const list = (data?.data?.list || []) as any[];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,48 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { ProTable } from '@/components/pro-table';
import { filterResetSubscribeLog } from '@/services/admin/log';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function ResetSubscribeLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.ResetSubscribeLog, { search?: string }>
header={{ title: t('title.resetSubscribe') }}
columns={[
{
accessorKey: 'user',
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'user_subscribe_id', header: t('column.subscribeId') },
{ accessorKey: 'type', header: t('column.type') },
{ accessorKey: 'order_no', header: t('column.orderNo') },
{
accessorKey: 'reset_at',
header: t('column.resetAt'),
cell: ({ row }) => formatDate(row.original.reset_at),
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_subscribe_id', placeholder: t('column.subscribeId') },
]}
request={async (pagination, filter) => {
const { data } = await filterResetSubscribeLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_subscribe_id: (filter as any)?.user_subscribe_id,
});
const list = (data?.data?.list || []) as any[];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,51 @@
'use client';
import { ProTable } from '@/components/pro-table';
import { filterServerTrafficLog } from '@/services/admin/log';
import { formatBytes } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function ServerTrafficLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.ServerTrafficLog, { search?: string }>
header={{ title: t('title.serverTraffic') }}
columns={[
{ accessorKey: 'server_id', header: t('column.serverId') },
{
accessorKey: 'upload',
header: t('column.upload'),
cell: ({ row }) => formatBytes(row.original.upload),
},
{
accessorKey: 'download',
header: t('column.download'),
cell: ({ row }) => formatBytes(row.original.download),
},
{
accessorKey: 'total',
header: t('column.total'),
cell: ({ row }) => formatBytes(row.original.total),
},
{ accessorKey: 'date', header: t('column.date') },
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'server_id', placeholder: t('column.serverId') },
]}
request={async (pagination, filter) => {
const { data } = await filterServerTrafficLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
server_id: (filter as any)?.server_id,
});
const list = (data?.data?.list || []) as any[];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,63 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { ProTable } from '@/components/pro-table';
import { filterUserSubscribeTrafficLog } from '@/services/admin/log';
import { formatBytes, formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function SubscribeTrafficLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.UserSubscribeTrafficLog, { search?: string }>
header={{ title: t('title.subscribeTraffic') }}
columns={[
{
accessorKey: 'user',
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'subscribe_id', header: t('column.subscribeId') },
{
accessorKey: 'upload',
header: t('column.upload'),
cell: ({ row }) => formatBytes(row.original.upload),
},
{
accessorKey: 'download',
header: t('column.download'),
cell: ({ row }) => formatBytes(row.original.download),
},
{
accessorKey: 'total',
header: t('column.total'),
cell: ({ row }) => formatBytes(row.original.total),
},
{
accessorKey: 'date',
header: t('column.date'),
cell: ({ row }) => formatDate(new Date(row.original.date)),
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
{ key: 'user_subscribe_id', placeholder: t('column.subscribeId') },
]}
request={async (pagination, filter) => {
const { data } = await filterUserSubscribeTrafficLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
user_subscribe_id: (filter as any)?.user_subscribe_id,
});
const list = ((data?.data?.list || []) as API.UserSubscribeTrafficLog[]) || [];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,54 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { IpLink } from '@/components/ip-link';
import { ProTable } from '@/components/pro-table';
import { filterSubscribeLog } from '@/services/admin/log';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function SubscribeLogPage() {
const t = useTranslations('log');
return (
<ProTable<API.SubscribeLog, { search?: string }>
header={{ title: t('title.subscribe') }}
columns={[
{
accessorKey: 'user',
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'user_subscribe_id', header: t('column.subscribeId') },
{ accessorKey: 'token', header: t('column.token') },
{
accessorKey: 'client_ip',
header: t('column.ip'),
cell: ({ row }) => <IpLink ip={String((row.original as any).client_ip || '')} />,
},
{ accessorKey: 'user_agent', header: t('column.userAgent') },
{
accessorKey: 'subscribed_at',
header: t('column.time'),
cell: ({ row }) => formatDate(row.original.subscribed_at),
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
request={async (pagination, filter) => {
const { data } = await filterSubscribeLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
const list = (data?.data?.list || []) as any[];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}
@@ -0,0 +1,56 @@
'use client';
import { ProTable } from '@/components/pro-table';
import { filterTrafficLogDetails } from '@/services/admin/log';
import { formatBytes, formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
export default function TrafficDetailsPage() {
const t = useTranslations('log');
return (
<ProTable<any, { search?: string }>
header={{ title: t('title.trafficDetails') }}
columns={[
{ accessorKey: 'server_id', header: t('column.serverId') },
{ accessorKey: 'user_id', header: t('column.userId') },
{ accessorKey: 'subscribe_id', header: t('column.subscribeId') },
{
accessorKey: 'upload',
header: t('column.upload'),
cell: ({ row }) => formatBytes(row.original.upload),
},
{
accessorKey: 'download',
header: t('column.download'),
cell: ({ row }) => formatBytes(row.original.download),
},
{
accessorKey: 'timestamp',
header: t('column.time'),
cell: ({ row }) => formatDate(row.original.timestamp),
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'server_id', placeholder: t('column.serverId') },
{ key: 'user_id', placeholder: t('column.userId') },
{ key: 'subscribe_id', placeholder: t('column.subscribeId') },
]}
request={async (pagination, filter) => {
const { data } = await filterTrafficLogDetails({
page: pagination.page,
size: pagination.size,
search: (filter as any)?.search,
date: (filter as any)?.date,
server_id: (filter as any)?.server_id,
user_id: (filter as any)?.user_id,
subscribe_id: (filter as any)?.subscribe_id,
});
const list = (data?.data?.list || []) as any[];
const total = Number(data?.data?.total || list.length);
return { list, total };
}}
/>
);
}