move audit logs from Dashboard to /audit page
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s
This commit is contained in:
parent
13006ba390
commit
274a27a10a
@ -10,6 +10,7 @@ import PageLoading from './components/PageLoading'
|
|||||||
import AdminLayout from './layouts/AdminLayout'
|
import AdminLayout from './layouts/AdminLayout'
|
||||||
|
|
||||||
const Login = lazy(() => import('./pages/Login'))
|
const Login = lazy(() => import('./pages/Login'))
|
||||||
|
const AuditLogPage = lazy(() => import("./pages/AuditLog"))
|
||||||
const Dashboard = lazy(() => import('./pages/Dashboard'))
|
const Dashboard = lazy(() => import('./pages/Dashboard'))
|
||||||
const UserManagement = lazy(() => import('./pages/UserManagement'))
|
const UserManagement = lazy(() => import('./pages/UserManagement'))
|
||||||
const TaskAssistant = lazy(() => import('./pages/TaskAssistant'))
|
const TaskAssistant = lazy(() => import('./pages/TaskAssistant'))
|
||||||
@ -83,7 +84,7 @@ function App() {
|
|||||||
path="audit"
|
path="audit"
|
||||||
element={
|
element={
|
||||||
<PermissionGuard requiredRole="ADMIN">
|
<PermissionGuard requiredRole="ADMIN">
|
||||||
<Placeholder title="审计日志" />
|
<Suspense fallback={<PageLoading />}><AuditLogPage /></Suspense>
|
||||||
</PermissionGuard>
|
</PermissionGuard>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
24
src/pages/AuditLog.tsx
Normal file
24
src/pages/AuditLog.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import AuditLogTable from '@/components/AuditLogTable'
|
||||||
|
import { getAuditLogs } from '@/services/admin-api'
|
||||||
|
|
||||||
|
export default function AuditLogPage() {
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [pageSize, setPageSize] = useState(20)
|
||||||
|
|
||||||
|
const { data, isLoading } = useQuery({
|
||||||
|
queryKey: ['audit-logs', page, pageSize],
|
||||||
|
queryFn: () => getAuditLogs({ page, limit: pageSize }),
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuditLogTable
|
||||||
|
headerTitle="操作日志"
|
||||||
|
dataSource={data?.items || []}
|
||||||
|
loading={isLoading}
|
||||||
|
pagination={data ? { current: data.page, pageSize: data.limit, total: data.total } : undefined}
|
||||||
|
onPageChange={(p, ps) => { setPage(p); setPageSize(ps) }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { Row, Col, Typography } from 'antd'
|
import { Row, Col, Typography } from 'antd'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import ReactEChartsCore from 'echarts-for-react/esm/core'
|
import ReactEChartsCore from 'echarts-for-react/esm/core'
|
||||||
@ -6,14 +6,11 @@ import * as echarts from 'echarts/core'
|
|||||||
import { LineChart, BarChart } from 'echarts/charts'
|
import { LineChart, BarChart } from 'echarts/charts'
|
||||||
import { GridComponent, TooltipComponent, TitleComponent, LegendComponent } from 'echarts/components'
|
import { GridComponent, TooltipComponent, TitleComponent, LegendComponent } from 'echarts/components'
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
import {
|
import { UserOutlined, BookOutlined, CloudOutlined, FileOutlined } from '@ant-design/icons'
|
||||||
UserOutlined, BookOutlined, CloudOutlined, FileOutlined,
|
|
||||||
} from '@ant-design/icons'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import MetricCard from '@/components/MetricCard'
|
import MetricCard from '@/components/MetricCard'
|
||||||
import EChartsChartContainer from '@/components/EChartsChartContainer'
|
import EChartsChartContainer from '@/components/EChartsChartContainer'
|
||||||
import AuditLogTable from '@/components/AuditLogTable'
|
import { getDashboardStats } from '@/services/admin-api'
|
||||||
import { getDashboardStats, getAuditLogs } from '@/services/admin-api'
|
|
||||||
|
|
||||||
echarts.use([LineChart, BarChart, GridComponent, TooltipComponent, TitleComponent, LegendComponent, CanvasRenderer])
|
echarts.use([LineChart, BarChart, GridComponent, TooltipComponent, TitleComponent, LegendComponent, CanvasRenderer])
|
||||||
|
|
||||||
@ -24,105 +21,41 @@ function formatStorage(bytes: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [auditPage, setAuditPage] = useState(1)
|
|
||||||
const [auditPageSize, setAuditPageSize] = useState(10)
|
|
||||||
|
|
||||||
const { data: stats, isLoading: statsLoading } = useQuery({
|
const { data: stats, isLoading: statsLoading } = useQuery({
|
||||||
queryKey: ['dashboard', 'stats'],
|
queryKey: ['dashboard', 'stats'],
|
||||||
queryFn: getDashboardStats,
|
queryFn: getDashboardStats,
|
||||||
staleTime: 60_000,
|
staleTime: 60_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: auditData, isLoading: auditLoading } = useQuery({
|
|
||||||
queryKey: ['dashboard', 'audit-logs', auditPage, auditPageSize],
|
|
||||||
queryFn: () => getAuditLogs({ page: auditPage, limit: auditPageSize }),
|
|
||||||
staleTime: 30_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
const userTrendOption = useMemo(() => ({
|
const userTrendOption = useMemo(() => ({
|
||||||
grid: { top: 20, right: 20, bottom: 20, left: 40 },
|
grid: { top: 20, right: 20, bottom: 20, left: 40 },
|
||||||
tooltip: { trigger: 'axis' as const },
|
tooltip: { trigger: 'axis' as const },
|
||||||
xAxis: {
|
xAxis: { type: 'category' as const, data: stats?.userTrend.map(p => dayjs(p.date).format('MM-DD')) || [], axisLabel: { fontSize: 11 } },
|
||||||
type: 'category' as const,
|
|
||||||
data: stats?.userTrend.map((p) => dayjs(p.date).format('MM-DD')) || [],
|
|
||||||
axisLabel: { fontSize: 11 },
|
|
||||||
},
|
|
||||||
yAxis: { type: 'value' as const, axisLabel: { fontSize: 11 } },
|
yAxis: { type: 'value' as const, axisLabel: { fontSize: 11 } },
|
||||||
series: [{
|
series: [{ name: '日活用户', type: 'line', data: stats?.userTrend.map(p => p.value) || [], smooth: true, symbol: 'none', lineStyle: { color: '#1677ff', width: 2 }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(22,119,255,0.15)' }, { offset: 1, color: 'rgba(22,119,255,0)' }]) } }],
|
||||||
name: '日活用户', type: 'line',
|
|
||||||
data: stats?.userTrend.map((p) => p.value) || [],
|
|
||||||
smooth: true, symbol: 'none',
|
|
||||||
lineStyle: { color: '#1677ff', width: 2 },
|
|
||||||
areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
||||||
{ offset: 0, color: 'rgba(22,119,255,0.15)' },
|
|
||||||
{ offset: 1, color: 'rgba(22,119,255,0)' },
|
|
||||||
])},
|
|
||||||
}],
|
|
||||||
}), [stats])
|
}), [stats])
|
||||||
|
|
||||||
const aiCallTrendOption = useMemo(() => ({
|
const aiCallTrendOption = useMemo(() => ({
|
||||||
grid: { top: 20, right: 20, bottom: 20, left: 40 },
|
grid: { top: 20, right: 20, bottom: 20, left: 40 },
|
||||||
tooltip: { trigger: 'axis' as const },
|
tooltip: { trigger: 'axis' as const },
|
||||||
xAxis: {
|
xAxis: { type: 'category' as const, data: stats?.aiCallTrend.map(p => dayjs(p.date).format('MM-DD')) || [], axisLabel: { fontSize: 11 } },
|
||||||
type: 'category' as const,
|
|
||||||
data: stats?.aiCallTrend.map((p) => dayjs(p.date).format('MM-DD')) || [],
|
|
||||||
axisLabel: { fontSize: 11 },
|
|
||||||
},
|
|
||||||
yAxis: { type: 'value' as const, axisLabel: { fontSize: 11 } },
|
yAxis: { type: 'value' as const, axisLabel: { fontSize: 11 } },
|
||||||
series: [{
|
series: [{ name: 'AI 调用', type: 'bar', data: stats?.aiCallTrend.map(p => p.value) || [], itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#52c41a' }, { offset: 1, color: '#b7eb8f' }]), borderRadius: [4, 4, 0, 0] } }],
|
||||||
name: 'AI 调用', type: 'bar',
|
|
||||||
data: stats?.aiCallTrend.map((p) => p.value) || [],
|
|
||||||
itemStyle: {
|
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
||||||
{ offset: 0, color: '#52c41a' }, { offset: 1, color: '#b7eb8f' },
|
|
||||||
]),
|
|
||||||
borderRadius: [4, 4, 0, 0],
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
}), [stats])
|
}), [stats])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '0 0 24px' }}>
|
<div style={{ padding: '0 0 24px' }}>
|
||||||
<Typography.Title level={5} style={{ margin: '0 0 16px' }}>数据概览</Typography.Title>
|
<Typography.Title level={5} style={{ margin: '0 0 16px' }}>数据概览</Typography.Title>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col xs={12} sm={12} lg={6}>
|
<Col xs={12} sm={12} lg={6}><MetricCard title="总用户数" value={stats?.totalUsers} loading={statsLoading} prefix={<UserOutlined />} trend="up" trendValue={`+${stats?.newUsersToday ?? 0}`} trendLabel="今日新增" /></Col>
|
||||||
<MetricCard title="总用户数" value={stats?.totalUsers} loading={statsLoading}
|
<Col xs={12} sm={12} lg={6}><MetricCard title="知识库总数" value={stats?.totalKnowledgeBases} loading={statsLoading} prefix={<BookOutlined />} trend="up" trendValue={`+${stats?.newKbsToday ?? 0}`} trendLabel="今日新增" /></Col>
|
||||||
prefix={<UserOutlined />} trend="up" trendValue={`+${stats?.newUsersToday ?? 0}`} trendLabel="今日新增" />
|
<Col xs={12} sm={12} lg={6}><MetricCard title="今日 AI 调用" value={stats?.totalAiCallsToday} loading={statsLoading} prefix={<CloudOutlined />} /></Col>
|
||||||
</Col>
|
<Col xs={12} sm={12} lg={6}><MetricCard title="文件存储" value={stats ? formatStorage(stats.totalStorageBytes) : undefined} loading={statsLoading} prefix={<FileOutlined />} suffix={`${stats?.totalFiles ?? 0} 个文件`} /></Col>
|
||||||
<Col xs={12} sm={12} lg={6}>
|
|
||||||
<MetricCard title="知识库总数" value={stats?.totalKnowledgeBases} loading={statsLoading}
|
|
||||||
prefix={<BookOutlined />} trend="up" trendValue={`+${stats?.newKbsToday ?? 0}`} trendLabel="今日新增" />
|
|
||||||
</Col>
|
|
||||||
<Col xs={12} sm={12} lg={6}>
|
|
||||||
<MetricCard title="今日 AI 调用" value={stats?.totalAiCallsToday} loading={statsLoading}
|
|
||||||
prefix={<CloudOutlined />} />
|
|
||||||
</Col>
|
|
||||||
<Col xs={12} sm={12} lg={6}>
|
|
||||||
<MetricCard title="文件存储" value={stats ? formatStorage(stats.totalStorageBytes) : undefined}
|
|
||||||
loading={statsLoading} prefix={<FileOutlined />} suffix={`${stats?.totalFiles ?? 0} 个文件`} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
||||||
<Col xs={24} lg={12}>
|
<Col xs={24} lg={12}><EChartsChartContainer title="日活用户趋势(近 30 天)" loading={statsLoading} isEmpty={!stats?.userTrend?.length}><ReactEChartsCore echarts={echarts} option={userTrendOption} style={{ height: 300 }} notMerge lazyUpdate /></EChartsChartContainer></Col>
|
||||||
<EChartsChartContainer title="日活用户趋势(近 30 天)" loading={statsLoading} isEmpty={!stats?.userTrend?.length}>
|
<Col xs={24} lg={12}><EChartsChartContainer title="AI 调用趋势(近 30 天)" loading={statsLoading} isEmpty={!stats?.aiCallTrend?.length}><ReactEChartsCore echarts={echarts} option={aiCallTrendOption} style={{ height: 300 }} notMerge /></EChartsChartContainer></Col>
|
||||||
<ReactEChartsCore echarts={echarts} option={userTrendOption} style={{ height: 300 }} notMerge lazyUpdate />
|
|
||||||
</EChartsChartContainer>
|
|
||||||
</Col>
|
|
||||||
<Col xs={24} lg={12}>
|
|
||||||
<EChartsChartContainer title="AI 调用趋势(近 30 天)" loading={statsLoading} isEmpty={!stats?.aiCallTrend?.length}>
|
|
||||||
<ReactEChartsCore echarts={echarts} option={aiCallTrendOption} style={{ height: 300 }} notMerge />
|
|
||||||
</EChartsChartContainer>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
<div style={{ marginTop: 16 }}>
|
|
||||||
<AuditLogTable
|
|
||||||
headerTitle="最近操作日志"
|
|
||||||
dataSource={auditData?.items || []}
|
|
||||||
loading={auditLoading}
|
|
||||||
pagination={auditData ? { current: auditData.page, pageSize: auditData.limit, total: auditData.total } : undefined}
|
|
||||||
onPageChange={(page, pageSize) => { setAuditPage(page); setAuditPageSize(pageSize) }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user