mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-19 10:07:24 +00:00
添加dashboard (#83)
* 后台 * 添加主界面 * 添加主界面 * Update server.js * 修复bug * 修复bug
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
import { User, Pay } from '../schema.js';
|
import { User, Pay } from '../schema.js';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { auth } from './system.js';
|
import { auth } from './system.js';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
// 加密
|
||||||
|
const hashPassword = (psw) => {
|
||||||
|
return crypto.createHash('sha256').update(psw).digest('hex');
|
||||||
|
};
|
||||||
|
|
||||||
export const useUserRoute = (app) => {
|
export const useUserRoute = (app) => {
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
@@ -82,7 +88,14 @@ export const useUserRoute = (app) => {
|
|||||||
app.put('/users/:id', auth(), async (req, res) => {
|
app.put('/users/:id', auth(), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const _id = req.params.id;
|
const _id = req.params.id;
|
||||||
|
|
||||||
|
// Check if a new password is provided in the request body
|
||||||
|
if (req.body.password) {
|
||||||
|
// Hash the new password
|
||||||
|
const hashedPassword = hashPassword(req.body.password);
|
||||||
|
req.body.password = hashedPassword;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await User.updateOne({ _id: _id }, { $set: req.body });
|
const result = await User.updateOne({ _id: _id }, { $set: req.body });
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@@ -46,7 +46,7 @@ function App() {
|
|||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
fields={userFields}
|
fields={userFields}
|
||||||
action={{ create: true, detail: true, edit: true }}
|
action={{ detail: true, edit: true }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
159
admin/src/Dashboard.tsx
Normal file
159
admin/src/Dashboard.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Link,
|
||||||
|
Space,
|
||||||
|
Grid,
|
||||||
|
Divider,
|
||||||
|
Typography,
|
||||||
|
} from '@arco-design/web-react';
|
||||||
|
import { IconApps, IconUser, IconUserGroup } from 'tushan/icon';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const Dashboard: React.FC = React.memo(() => {
|
||||||
|
|
||||||
|
const [userCount, setUserCount] = useState(0); //用户数量
|
||||||
|
const [kbCount, setkbCount] = useState(0);
|
||||||
|
const [modelCount, setmodelCount] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCounts = async () => {
|
||||||
|
const userResponse = await fetch('http://localhost:3001/users', {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
const kbResponse = await fetch('http://localhost:3001/kbs', {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
const modelResponse = await fetch('http://localhost:3001/models', {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const userTotalCount = userResponse.headers.get('X-Total-Count');
|
||||||
|
const kbTotalCount = kbResponse.headers.get('X-Total-Count');
|
||||||
|
const modelTotalCount = modelResponse.headers.get('X-Total-Count');
|
||||||
|
|
||||||
|
if (userTotalCount) {
|
||||||
|
setUserCount(Number(userTotalCount));
|
||||||
|
}
|
||||||
|
if (kbTotalCount) {
|
||||||
|
setkbCount(Number(kbTotalCount));
|
||||||
|
}
|
||||||
|
if (modelTotalCount) {
|
||||||
|
setmodelCount(Number(modelTotalCount));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCounts();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
<Card bordered={false}>
|
||||||
|
<Typography.Title heading={5}>
|
||||||
|
{'你好,管理员'}
|
||||||
|
</Typography.Title>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Grid.Row justify="center">
|
||||||
|
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
|
||||||
|
{/* 把 userCount 传递给 DataItem 组件 */}
|
||||||
|
<DataItem
|
||||||
|
icon={<IconUser />}
|
||||||
|
title={'用户'}
|
||||||
|
count={userCount}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
|
<Divider type="vertical" style={{ height: 40 }} />
|
||||||
|
|
||||||
|
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
|
||||||
|
<DataItem
|
||||||
|
icon={<IconUserGroup />}
|
||||||
|
title={'知识库'}
|
||||||
|
count={kbCount}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
|
<Divider type="vertical" style={{ height: 40 }} />
|
||||||
|
|
||||||
|
<Grid.Col flex={1} style={{ paddingLeft: '1rem' }}>
|
||||||
|
<DataItem
|
||||||
|
icon={<IconApps />}
|
||||||
|
title={'AI模型'}
|
||||||
|
count={modelCount}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid.Row>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
|
||||||
|
</Card>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Dashboard.displayName = 'Dashboard';
|
||||||
|
|
||||||
|
const DashboardItem: React.FC<
|
||||||
|
React.PropsWithChildren<{
|
||||||
|
title: string;
|
||||||
|
href?: string;
|
||||||
|
}>
|
||||||
|
> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={props.title}
|
||||||
|
extra={
|
||||||
|
props.href && (
|
||||||
|
<Link target="_blank" href={props.href}>
|
||||||
|
{t('tushan.dashboard.more')}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
bordered={false}
|
||||||
|
style={{ overflow: 'hidden' }}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
DashboardItem.displayName = 'DashboardItem';
|
||||||
|
|
||||||
|
const DataItem: React.FC<{
|
||||||
|
icon: React.ReactElement;
|
||||||
|
title: string;
|
||||||
|
count: number;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
padding: '0.5rem',
|
||||||
|
borderRadius: '9999px',
|
||||||
|
border: '1px solid #ccc',
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.icon}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontWeight: 700 }}>{props.title}</div>
|
||||||
|
<div>{props.count}</div>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
DataItem.displayName = 'DataItem';
|
@@ -1,4 +1,7 @@
|
|||||||
import { createTextField, createNumberField } from 'tushan';
|
import {
|
||||||
|
createTextField,
|
||||||
|
createNumberField,
|
||||||
|
} from 'tushan';
|
||||||
|
|
||||||
export const userFields = [
|
export const userFields = [
|
||||||
createTextField('id', { label: 'ID' }),
|
createTextField('id', { label: 'ID' }),
|
||||||
|
Reference in New Issue
Block a user