mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-19 18:14:38 +00:00
perf: admin
This commit is contained in:
@@ -10,7 +10,21 @@ export const useKbRoute = (app) => {
|
|||||||
const order = req.query._order === 'DESC' ? -1 : 1;
|
const order = req.query._order === 'DESC' ? -1 : 1;
|
||||||
const sort = req.query._sort || '_id';
|
const sort = req.query._sort || '_id';
|
||||||
const tag = req.query.tag || '';
|
const tag = req.query.tag || '';
|
||||||
const where = { tags: { $elemMatch: { $regex: tag, $options: 'i' } } };
|
const name = req.query.name || '';
|
||||||
|
|
||||||
|
const where = {
|
||||||
|
...(name
|
||||||
|
? {
|
||||||
|
name: { $regex: name, $options: 'i' }
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(tag
|
||||||
|
? {
|
||||||
|
tags: { $elemMatch: { $regex: tag, $options: 'i' } }
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
};
|
||||||
|
console.log(where);
|
||||||
|
|
||||||
const kbsRaw = await Kb.find(where)
|
const kbsRaw = await Kb.find(where)
|
||||||
.skip(start)
|
.skip(start)
|
||||||
|
@@ -9,6 +9,37 @@ const hashPassword = (psw) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useUserRoute = (app) => {
|
export const useUserRoute = (app) => {
|
||||||
|
// 统计近 30 天注册用户数量
|
||||||
|
app.get('/users/data', auth(), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const usersRaw = await User.aggregate([
|
||||||
|
{ $match: { createTime: { $gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) } } },
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: {
|
||||||
|
year: { $year: '$createTime' },
|
||||||
|
month: { $month: '$createTime' },
|
||||||
|
day: { $dayOfMonth: '$createTime' }
|
||||||
|
},
|
||||||
|
count: { $sum: 1 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$project: {
|
||||||
|
_id: 0,
|
||||||
|
date: { $dateFromParts: { year: '$_id.year', month: '$_id.month', day: '$_id.day' } },
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ $sort: { date: 1 } }
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.json(usersRaw);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`Error fetching users: ${err}`);
|
||||||
|
res.status(500).json({ error: 'Error fetching users' });
|
||||||
|
}
|
||||||
|
});
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
app.get('/users', auth(), async (req, res) => {
|
app.get('/users', auth(), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
@@ -74,6 +74,9 @@ function App() {
|
|||||||
list={
|
list={
|
||||||
<ListTable
|
<ListTable
|
||||||
filter={[
|
filter={[
|
||||||
|
createTextField('name', {
|
||||||
|
label: 'name'
|
||||||
|
}),
|
||||||
createTextField('tag', {
|
createTextField('tag', {
|
||||||
label: 'tag'
|
label: 'tag'
|
||||||
})
|
})
|
||||||
|
@@ -2,22 +2,36 @@ import { Card, Link, Space, Grid, Divider, Typography } from '@arco-design/web-r
|
|||||||
import { IconApps, IconUser, IconUserGroup } from 'tushan/icon';
|
import { IconApps, IconUser, IconUserGroup } from 'tushan/icon';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
ResponsiveContainer,
|
||||||
|
AreaChart,
|
||||||
|
Area
|
||||||
|
} from 'tushan/chart';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const authStorageKey = 'tushan:auth';
|
const authStorageKey = 'tushan:auth';
|
||||||
|
|
||||||
|
type UsersChartDataType = { count: number; date: string }[];
|
||||||
|
|
||||||
export const Dashboard: React.FC = React.memo(() => {
|
export const Dashboard: React.FC = React.memo(() => {
|
||||||
const [userCount, setUserCount] = useState(0); //用户数量
|
const [userCount, setUserCount] = useState(0); //用户数量
|
||||||
const [kbCount, setkbCount] = useState(0);
|
const [kbCount, setkbCount] = useState(0);
|
||||||
const [modelCount, setmodelCount] = useState(0);
|
const [modelCount, setmodelCount] = useState(0);
|
||||||
|
const [usersData, setUsersData] = useState<UsersChartDataType>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCounts = async () => {
|
|
||||||
const baseUrl = import.meta.env.VITE_PUBLIC_SERVER_URL;
|
const baseUrl = import.meta.env.VITE_PUBLIC_SERVER_URL;
|
||||||
const { token } = JSON.parse(window.localStorage.getItem(authStorageKey) ?? '{}');
|
const { token } = JSON.parse(window.localStorage.getItem(authStorageKey) ?? '{}');
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchCounts = async () => {
|
||||||
const userResponse = await fetch(`${baseUrl}/users?_end=1`, {
|
const userResponse = await fetch(`${baseUrl}/users?_end=1`, {
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
@@ -31,7 +45,6 @@ export const Dashboard: React.FC = React.memo(() => {
|
|||||||
const userTotalCount = userResponse.headers.get('X-Total-Count');
|
const userTotalCount = userResponse.headers.get('X-Total-Count');
|
||||||
const kbTotalCount = kbResponse.headers.get('X-Total-Count');
|
const kbTotalCount = kbResponse.headers.get('X-Total-Count');
|
||||||
const modelTotalCount = modelResponse.headers.get('X-Total-Count');
|
const modelTotalCount = modelResponse.headers.get('X-Total-Count');
|
||||||
console.log(userTotalCount);
|
|
||||||
|
|
||||||
if (userTotalCount) {
|
if (userTotalCount) {
|
||||||
setUserCount(Number(userTotalCount));
|
setUserCount(Number(userTotalCount));
|
||||||
@@ -43,8 +56,20 @@ export const Dashboard: React.FC = React.memo(() => {
|
|||||||
setmodelCount(Number(modelTotalCount));
|
setmodelCount(Number(modelTotalCount));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const fetchUserData = async () => {
|
||||||
|
const userResponse: UsersChartDataType = await fetch(`${baseUrl}/users/data`, {
|
||||||
|
headers
|
||||||
|
}).then((res) => res.json());
|
||||||
|
setUsersData(
|
||||||
|
userResponse.map((item) => ({
|
||||||
|
...item,
|
||||||
|
date: dayjs(item.date).format('MM/DD')
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
fetchCounts();
|
fetchCounts();
|
||||||
|
fetchUserData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -76,6 +101,7 @@ export const Dashboard: React.FC = React.memo(() => {
|
|||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<UserChart data={usersData} />
|
||||||
</Card>
|
</Card>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,3 +167,38 @@ const DataItem: React.FC<{
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
DataItem.displayName = 'DataItem';
|
DataItem.displayName = 'DataItem';
|
||||||
|
|
||||||
|
const UserChart = ({ data }: { data: UsersChartDataType }) => {
|
||||||
|
return (
|
||||||
|
<ResponsiveContainer width="100%" height={320}>
|
||||||
|
<AreaChart
|
||||||
|
width={730}
|
||||||
|
height={250}
|
||||||
|
data={data}
|
||||||
|
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="5%" stopColor="#8884d8" stopOpacity={0.8} />
|
||||||
|
<stop offset="95%" stopColor="#8884d8" stopOpacity={0} />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="colorPv" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="5%" stopColor="#82ca9d" stopOpacity={0.8} />
|
||||||
|
<stop offset="95%" stopColor="#82ca9d" stopOpacity={0} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<XAxis dataKey="date" />
|
||||||
|
<YAxis />
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<Tooltip />
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="count"
|
||||||
|
stroke="#82ca9d"
|
||||||
|
fillOpacity={1}
|
||||||
|
fill="url(#colorPv)"
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user