diff --git a/admin/package.json b/admin/package.json
index 300b0c1ef..5c5f9cb08 100644
--- a/admin/package.json
+++ b/admin/package.json
@@ -11,8 +11,10 @@
"start:api": "nodemon server.js"
},
"dependencies": {
+ "@arco-design/web-react": "^2.49.1",
"concurrently": "^8.1.0",
"cors": "^2.8.5",
+ "crypto": "^1.0.1",
"dayjs": "^1.11.8",
"dotenv": "^16.1.4",
"express": "^4.18.2",
@@ -21,6 +23,7 @@
"react": "^18.2.0",
"react-admin": "^4.11.0",
"react-dom": "^18.2.0",
+ "react-i18next": "^12.3.1",
"tushan": "^0.2.22"
},
"devDependencies": {
diff --git a/admin/pnpm-lock.yaml b/admin/pnpm-lock.yaml
index 7722f744c..5daa85811 100644
--- a/admin/pnpm-lock.yaml
+++ b/admin/pnpm-lock.yaml
@@ -5,12 +5,18 @@ settings:
excludeLinksFromLockfile: false
dependencies:
+ '@arco-design/web-react':
+ specifier: ^2.49.1
+ version: registry.npmmirror.com/@arco-design/web-react@2.49.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
concurrently:
specifier: ^8.1.0
version: registry.npmmirror.com/concurrently@8.1.0
cors:
specifier: ^2.8.5
version: registry.npmmirror.com/cors@2.8.5
+ crypto:
+ specifier: ^1.0.1
+ version: registry.npmmirror.com/crypto@1.0.1
dayjs:
specifier: ^1.11.8
version: registry.npmmirror.com/dayjs@1.11.8
@@ -35,6 +41,9 @@ dependencies:
react-dom:
specifier: ^18.2.0
version: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0)
+ react-i18next:
+ specifier: ^12.3.1
+ version: registry.npmmirror.com/react-i18next@12.3.1(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0)
tushan:
specifier: ^0.2.22
version: registry.npmmirror.com/tushan@0.2.22(history@5.3.0)(prop-types@15.8.1)(react-hook-form@7.44.3)
@@ -1896,6 +1905,13 @@ packages:
- encoding
dev: false
+ registry.npmmirror.com/crypto@1.0.1:
+ resolution: {integrity: sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/crypto/-/crypto-1.0.1.tgz}
+ name: crypto
+ version: 1.0.1
+ deprecated: This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.
+ dev: false
+
registry.npmmirror.com/css-color-keywords@1.0.0:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz}
name: css-color-keywords
diff --git a/admin/service/route/app.js b/admin/service/route/app.js
index efdd4789c..a89755805 100644
--- a/admin/service/route/app.js
+++ b/admin/service/route/app.js
@@ -1,8 +1,9 @@
import { User, Model, Kb } from '../schema.js';
+import { auth } from './system.js';
export const useAppRoute = (app) => {
// 获取AI助手列表
- app.get('/models', async (req, res) => {
+ app.get('/models', auth(), async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
diff --git a/admin/service/route/kb.js b/admin/service/route/kb.js
index 6a98c2332..3702e6cc5 100644
--- a/admin/service/route/kb.js
+++ b/admin/service/route/kb.js
@@ -1,8 +1,9 @@
import { Kb } from '../schema.js';
+import { auth } from './system.js';
export const useKbRoute = (app) => {
// 获取用户知识库列表
- app.get('/kbs', async (req, res) => {
+ app.get('/kbs', auth(), async (req, res) => {
try {
const start = parseInt(req.query._start) || 0;
const end = parseInt(req.query._end) || 20;
diff --git a/admin/service/route/user.js b/admin/service/route/user.js
index ab0210b81..f5e031e1b 100644
--- a/admin/service/route/user.js
+++ b/admin/service/route/user.js
@@ -31,7 +31,8 @@ export const useUserRoute = (app) => {
return {
...obj,
id: obj._id,
- createTime: dayjs(obj.createTime).format('YYYY/MM/DD HH:mm')
+ createTime: dayjs(obj.createTime).format('YYYY/MM/DD HH:mm'),
+ password: ''
};
});
@@ -49,14 +50,7 @@ export const useUserRoute = (app) => {
// 创建用户
app.post('/users', auth(), async (req, res) => {
try {
- const {
- username,
- password,
- balance,
- promotion,
- openaiKey = '',
- avatar = '/icon/human.png'
- } = req.body;
+ const { username, password, balance } = req.body;
if (!username || !password || !balance) {
return res.status(400).json({ error: 'Invalid user information' });
}
@@ -64,19 +58,12 @@ export const useUserRoute = (app) => {
if (existingUser) {
return res.status(400).json({ error: 'Username already exists' });
}
- const user = new User({
- _id: new mongoose.Types.ObjectId(),
+
+ const result = await User.create({
username,
password,
- balance,
- promotion: {
- rate: promotion?.rate || 0
- },
- openaiKey,
- avatar,
- createTime: new Date()
+ balance
});
- const result = await user.save();
res.json(result);
} catch (err) {
console.log(`Error creating user: ${err}`);
@@ -88,15 +75,13 @@ export const useUserRoute = (app) => {
app.put('/users/:id', auth(), async (req, res) => {
try {
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 });
+
+ let { password, balance = 0 } = req.body;
+
+ const result = await User.findByIdAndUpdate(_id, {
+ ...(password && { password: hashPassword(hashPassword(password)) }),
+ ...(balance && { balance })
+ });
res.json(result);
} catch (err) {
console.log(`Error updating user: ${err}`);
diff --git a/admin/src/App.tsx b/admin/src/App.tsx
index c391cf1c9..2781d9e0c 100644
--- a/admin/src/App.tsx
+++ b/admin/src/App.tsx
@@ -8,6 +8,7 @@ import {
} from 'tushan';
import { authProvider } from './auth';
import { userFields, payFields, kbFields, ModelFields } from './fields';
+import { Dashboard } from './Dashboard';
const authStorageKey = 'tushan:auth';
@@ -34,6 +35,7 @@ function App() {
header={'FastGpt-Admin'}
dataProvider={dataProvider}
authProvider={authProvider}
+ dashboard={}
>
{
-
- 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 (
-
-
-
-
-
- {'你好,管理员'}
-
-
-
-
-
-
- {/* 把 userCount 传递给 DataItem 组件 */}
- }
- title={'用户'}
- count={userCount}
- />
-
-
-
-
-
- }
- title={'知识库'}
- count={kbCount}
- />
-
-
-
-
-
- }
- title={'AI模型'}
- count={modelCount}
- />
-
-
-
-
-
-
-
-
-
-
- );
-});
-Dashboard.displayName = 'Dashboard';
-
-const DashboardItem: React.FC<
- React.PropsWithChildren<{
- title: string;
- href?: string;
- }>
-> = React.memo((props) => {
- const { t } = useTranslation();
-
- return (
-
- {t('tushan.dashboard.more')}
-
- )
- }
- bordered={false}
- style={{ overflow: 'hidden' }}
- >
- {props.children}
-
- );
-});
-DashboardItem.displayName = 'DashboardItem';
-
-const DataItem: React.FC<{
- icon: React.ReactElement;
- title: string;
- count: number;
-}> = React.memo((props) => {
- return (
-
-
- {props.icon}
-
-
-
{props.title}
-
{props.count}
-
-
- );
-});
-DataItem.displayName = 'DataItem';
+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';
+
+const authStorageKey = 'tushan:auth';
+
+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 baseUrl = import.meta.env.VITE_PUBLIC_SERVER_URL;
+ const { token } = JSON.parse(window.localStorage.getItem(authStorageKey) ?? '{}');
+
+ const headers = {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`
+ };
+ const userResponse = await fetch(`${baseUrl}/users?_end=1`, {
+ headers
+ });
+ const kbResponse = await fetch(`${baseUrl}/kbs?_end=1`, {
+ headers
+ });
+ const modelResponse = await fetch(`${baseUrl}/models?_end=1`, {
+ headers
+ });
+
+ const userTotalCount = userResponse.headers.get('X-Total-Count');
+ const kbTotalCount = kbResponse.headers.get('X-Total-Count');
+ const modelTotalCount = modelResponse.headers.get('X-Total-Count');
+ console.log(userTotalCount);
+
+ if (userTotalCount) {
+ setUserCount(Number(userTotalCount));
+ }
+ if (kbTotalCount) {
+ setkbCount(Number(kbTotalCount));
+ }
+ if (modelTotalCount) {
+ setmodelCount(Number(modelTotalCount));
+ }
+ };
+
+ fetchCounts();
+ }, []);
+
+ return (
+
+
+
+
+ FastGpt Admin
+
+
+
+
+
+ {/* 把 userCount 传递给 DataItem 组件 */}
+ } title={'用户'} count={userCount} />
+
+
+
+
+
+ } title={'知识库'} count={kbCount} />
+
+
+
+
+
+ } title={'AI模型'} count={modelCount} />
+
+
+
+
+
+
+
+
+ );
+});
+Dashboard.displayName = 'Dashboard';
+
+const DashboardItem: React.FC<
+ React.PropsWithChildren<{
+ title: string;
+ href?: string;
+ }>
+> = React.memo((props) => {
+ const { t } = useTranslation();
+
+ return (
+
+ {t('tushan.dashboard.more')}
+
+ )
+ }
+ bordered={false}
+ style={{ overflow: 'hidden' }}
+ >
+ {props.children}
+
+ );
+});
+DashboardItem.displayName = 'DashboardItem';
+
+const DataItem: React.FC<{
+ icon: React.ReactElement;
+ title: string;
+ count: number;
+}> = React.memo((props) => {
+ return (
+
+
+ {props.icon}
+
+
+
{props.title}
+
{props.count}
+
+
+ );
+});
+DataItem.displayName = 'DataItem';
diff --git a/admin/src/auth.ts b/admin/src/auth.ts
index 92bc39078..504fbaf6c 100644
--- a/admin/src/auth.ts
+++ b/admin/src/auth.ts
@@ -1,5 +1,5 @@
import { createAuthProvider, type AuthProvider } from 'tushan';
export const authProvider: AuthProvider = createAuthProvider({
- loginUrl: `${import.meta.env.VITE_PUBLIC_SERVER_URL}api/login`
+ loginUrl: `${import.meta.env.VITE_PUBLIC_SERVER_URL}/api/login`
});
diff --git a/admin/src/fields.ts b/admin/src/fields.ts
index 08ec91afc..42d4d437f 100644
--- a/admin/src/fields.ts
+++ b/admin/src/fields.ts
@@ -1,13 +1,11 @@
-import {
- createTextField,
- createNumberField,
-} from 'tushan';
+import { createTextField, createNumberField } from 'tushan';
export const userFields = [
createTextField('id', { label: 'ID' }),
createTextField('username', { label: '用户名' }),
createNumberField('balance', { label: '余额', list: { sort: true } }),
- createTextField('createTime', { label: 'Create Time', list: { sort: true } })
+ createTextField('createTime', { label: 'Create Time', list: { sort: true } }),
+ createTextField('password', { label: '密码', list: { hidden: true } })
];
export const payFields = [