Commit 0a0febd2 by archer

perf: admin

parent 391332c8
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
"react-admin": "^4.11.0", "react-admin": "^4.11.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^12.3.1", "react-i18next": "^12.3.1",
"tushan": "^0.2.22" "tushan": "^0.2.23"
}, },
"devDependencies": { "devDependencies": {
"@types/jsonexport": "^3.0.2", "@types/jsonexport": "^3.0.2",
......
...@@ -15,6 +15,15 @@ useAppRoute(app); ...@@ -15,6 +15,15 @@ useAppRoute(app);
useKbRoute(app); useKbRoute(app);
useSystemRoute(app); useSystemRoute(app);
app.get('/*', (req, res) => {
res.sendFile(new URL('dist/index.html', import.meta.url).pathname);
});
app.use((err, req, res, next) => {
res.sendFile(new URL('dist/index.html', import.meta.url).pathname);
});
const PORT = process.env.PORT || 3001; const PORT = process.env.PORT || 3001;
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`); console.log(`Server is running on port ${PORT}`);
......
...@@ -110,8 +110,7 @@ export const auth = () => { ...@@ -110,8 +110,7 @@ export const auth = () => {
try { try {
const authorization = req.headers.authorization; const authorization = req.headers.authorization;
if (!authorization) { if (!authorization) {
res.status(401).end('not found authorization in headers'); return next(new Error("unAuthorization"))
return;
} }
const token = authorization.slice('Bearer '.length); const token = authorization.slice('Bearer '.length);
......
...@@ -12,8 +12,12 @@ export const useUserRoute = (app) => { ...@@ -12,8 +12,12 @@ export const useUserRoute = (app) => {
// 统计近 30 天注册用户数量 // 统计近 30 天注册用户数量
app.get('/users/data', auth(), async (req, res) => { app.get('/users/data', auth(), async (req, res) => {
try { try {
const day = 60;
let startCount = await User.countDocuments({
createTime: { $lt: new Date(Date.now() - day * 24 * 60 * 60 * 1000) }
});
const usersRaw = await User.aggregate([ const usersRaw = await User.aggregate([
{ $match: { createTime: { $gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) } } }, { $match: { createTime: { $gte: new Date(Date.now() - day * 24 * 60 * 60 * 1000) } } },
{ {
$group: { $group: {
_id: { _id: {
...@@ -34,7 +38,18 @@ export const useUserRoute = (app) => { ...@@ -34,7 +38,18 @@ export const useUserRoute = (app) => {
{ $sort: { date: 1 } } { $sort: { date: 1 } }
]); ]);
res.json(usersRaw); const countResult = usersRaw.map((item) => {
const increaseRate = `${((item.count / startCount) * 100).toFixed(2)}%`;
startCount += item.count;
return {
date: item.date,
count: startCount,
increase: item.count,
increaseRate
};
});
res.json(countResult);
} catch (err) { } catch (err) {
console.log(`Error fetching users: ${err}`); console.log(`Error fetching users: ${err}`);
res.status(500).json({ error: 'Error fetching users' }); res.status(500).json({ error: 'Error fetching users' });
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
import { authProvider } from './auth'; import { authProvider } from './auth';
import { userFields, payFields, kbFields, ModelFields, SystemFields } from './fields'; import { userFields, payFields, kbFields, ModelFields, SystemFields } from './fields';
import { Dashboard } from './Dashboard'; import { Dashboard } from './Dashboard';
import { IconUser, IconApps, IconBook, IconStamp } from 'tushan/icon';
const authStorageKey = 'tushan:auth'; const authStorageKey = 'tushan:auth';
...@@ -40,6 +41,7 @@ function App() { ...@@ -40,6 +41,7 @@ function App() {
<Resource <Resource
name="users" name="users"
label="用户信息" label="用户信息"
icon={<IconUser />}
list={ list={
<ListTable <ListTable
filter={[ filter={[
...@@ -56,6 +58,7 @@ function App() { ...@@ -56,6 +58,7 @@ function App() {
<Resource <Resource
name="pays" name="pays"
label="支付记录" label="支付记录"
icon={<IconStamp />}
list={ list={
<ListTable <ListTable
filter={[ filter={[
...@@ -71,6 +74,7 @@ function App() { ...@@ -71,6 +74,7 @@ function App() {
<Resource <Resource
name="kbs" name="kbs"
label="知识库" label="知识库"
icon={<IconBook />}
list={ list={
<ListTable <ListTable
filter={[ filter={[
...@@ -88,6 +92,7 @@ function App() { ...@@ -88,6 +92,7 @@ function App() {
/> />
<Resource <Resource
name="models" name="models"
icon={<IconApps />}
label="应用" label="应用"
list={<ListTable fields={ModelFields} action={{ detail: true }} />} list={<ListTable fields={ModelFields} action={{ detail: true }} />}
/> />
......
...@@ -15,13 +15,13 @@ import dayjs from 'dayjs'; ...@@ -15,13 +15,13 @@ import dayjs from 'dayjs';
const authStorageKey = 'tushan:auth'; const authStorageKey = 'tushan:auth';
type UsersChartDataType = { count: number; date: string }[]; type UsersChartDataType = { count: number; date: string; increase: number; increaseRate: 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>([]); const [usersData, setUsersData] = useState<UsersChartDataType[]>([]);
useEffect(() => { useEffect(() => {
const baseUrl = import.meta.env.VITE_PUBLIC_SERVER_URL; const baseUrl = import.meta.env.VITE_PUBLIC_SERVER_URL;
...@@ -57,7 +57,7 @@ export const Dashboard: React.FC = React.memo(() => { ...@@ -57,7 +57,7 @@ export const Dashboard: React.FC = React.memo(() => {
} }
}; };
const fetchUserData = async () => { const fetchUserData = async () => {
const userResponse: UsersChartDataType = await fetch(`${baseUrl}/users/data`, { const userResponse: UsersChartDataType[] = await fetch(`${baseUrl}/users/data`, {
headers headers
}).then((res) => res.json()); }).then((res) => res.json());
setUsersData( setUsersData(
...@@ -96,7 +96,7 @@ export const Dashboard: React.FC = React.memo(() => { ...@@ -96,7 +96,7 @@ export const Dashboard: React.FC = React.memo(() => {
<Divider type="vertical" style={{ height: 40 }} /> <Divider type="vertical" style={{ height: 40 }} />
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}> <Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
<DataItem icon={<IconApps />} title={'AI模型'} count={modelCount} /> <DataItem icon={<IconApps />} title={'应用'} count={modelCount} />
</Grid.Col> </Grid.Col>
</Grid.Row> </Grid.Row>
...@@ -110,38 +110,31 @@ export const Dashboard: React.FC = React.memo(() => { ...@@ -110,38 +110,31 @@ export const Dashboard: React.FC = React.memo(() => {
}); });
Dashboard.displayName = 'Dashboard'; Dashboard.displayName = 'Dashboard';
const DashboardItem: React.FC< const DashboardItem = React.memo(
React.PropsWithChildren<{ (props: { title: string; href?: string; children: React.ReactNode }) => {
title: string; const { t } = useTranslation();
href?: string;
}> return (
> = React.memo((props) => { <Card
const { t } = useTranslation(); title={props.title}
extra={
return ( props.href && (
<Card <Link target="_blank" href={props.href}>
title={props.title} {t('tushan.dashboard.more')}
extra={ </Link>
props.href && ( )
<Link target="_blank" href={props.href}> }
{t('tushan.dashboard.more')} bordered={false}
</Link> style={{ overflow: 'hidden' }}
) >
} {props.children}
bordered={false} </Card>
style={{ overflow: 'hidden' }} );
> }
{props.children} );
</Card>
);
});
DashboardItem.displayName = 'DashboardItem'; DashboardItem.displayName = 'DashboardItem';
const DataItem: React.FC<{ const DataItem = React.memo((props: { icon: React.ReactElement; title: string; count: number }) => {
icon: React.ReactElement;
title: string;
count: number;
}> = React.memo((props) => {
return ( return (
<Space> <Space>
<div <div
...@@ -168,7 +161,34 @@ const DataItem: React.FC<{ ...@@ -168,7 +161,34 @@ const DataItem: React.FC<{
}); });
DataItem.displayName = 'DataItem'; DataItem.displayName = 'DataItem';
const UserChart = ({ data }: { data: UsersChartDataType }) => { const CustomTooltip = ({ active, payload }: any) => {
const data = payload?.[0]?.payload as UsersChartDataType;
if (active && data) {
return (
<div
style={{
background: 'white',
padding: '5px 8px',
borderRadius: '8px',
boxShadow: '2px 2px 5px rgba(0,0,0,0.2)'
}}
>
<p className="label">
count: <strong>{data.count}</strong>
</p>
<p className="label">
increase: <strong>{data.increase}</strong>
</p>
<p className="label">
increaseRate: <strong>{data.increaseRate}</strong>
</p>
</div>
);
}
return null;
};
const UserChart = ({ data }: { data: UsersChartDataType[] }) => {
return ( return (
<ResponsiveContainer width="100%" height={320}> <ResponsiveContainer width="100%" height={320}>
<AreaChart <AreaChart
...@@ -190,7 +210,7 @@ const UserChart = ({ data }: { data: UsersChartDataType }) => { ...@@ -190,7 +210,7 @@ const UserChart = ({ data }: { data: UsersChartDataType }) => {
<XAxis dataKey="date" /> <XAxis dataKey="date" />
<YAxis /> <YAxis />
<CartesianGrid strokeDasharray="3 3" /> <CartesianGrid strokeDasharray="3 3" />
<Tooltip /> <Tooltip content={<CustomTooltip />} />
<Area <Area
type="monotone" type="monotone"
dataKey="count" dataKey="count"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment