diff --git a/README.md b/README.md
index 37904c30f..933349272 100644
--- a/README.md
+++ b/README.md
@@ -10,35 +10,28 @@
MaxKB 是一款基于 LLM 大语言模型的知识库问答系统。
-- **多模型**:支持对接主流的大模型,包括本地私有大模型(如 Llama 2)、Azure OpenAI 和百度千帆大模型等;
+- **多模型支持**:支持对接主流的大模型,包括本地私有大模型(如 Llama 2)、Azure OpenAI 和百度千帆大模型等;
- **开箱即用**:支持直接上传文档、自动爬取在线文档,支持文本自动拆分、向量化,智能问答交互体验好;
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统。
## 快速开始
```
-docker run -d --name=maxkb -p 8080:8080 1panel/maxkb
+docker run -d --name=maxkb -p 8080:8000 -v /opt/maxkb/data:/var/lib/postgresql/data 1panel/maxkb
```
-## 自定义持久化数据
-
-- 在主机系统上创建一个存储数据的目录,例如:/opt/maxkb/data
-- 在主机系统上创建一个存储配置文件的目录,例如:/opt/maxkb/conf
-```
-docker run --name=maxkb -p 8080:8000 -v /opt/maxkb/data:/var/lib/postgresql/data -v /opt/maxkb/conf:/opt/maxkb/conf -d 1panel/maxkb
-```
-也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB + Ollama(Llama 2),30 分钟内即可上线基于本地大模型的知识库问答系统。
-
+也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB + Ollama + Llama 2,30 分钟内即可上线基于本地大模型的知识库问答系统。
## UI 展示
+
@@ -46,13 +39,12 @@ docker run --name=maxkb -p 8080:8000 -v /opt/maxkb/data:/var/lib/postgresql/data
[论坛](https://bbs.fit2cloud.com/c/mk/11)
-
## 技术栈
- 前端:[Vue.js](https://cn.vuejs.org/)
-- 后端:[Django](https://www.djangoproject.com/)
+- 后端:[Python / Django](https://www.djangoproject.com/)
- Langchain:[Langchain](https://www.langchain.com/)
-- 向量数据库:[PostgreSQL](https://www.postgresql.org/)
+- 向量数据库:[PostgreSQL / pgvector](https://www.postgresql.org/)
- 大模型:Azure OpenAI、百度千帆大模型、[Ollama](https://github.com/ollama/ollama)
## License
diff --git a/apps/common/util/rsa_util.py b/apps/common/util/rsa_util.py
index ed950776a..a8e361e8c 100644
--- a/apps/common/util/rsa_util.py
+++ b/apps/common/util/rsa_util.py
@@ -11,7 +11,16 @@ import os
from Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher
from Crypto.PublicKey import RSA
+from django.db.models import QuerySet
+from django.core import cache
+from setting.models import SystemSetting, SettingType
+
+import threading
+
+lock = threading.Lock()
+rsa_cache = cache.caches['default']
+cache_key = "rsa_key"
# 对密钥加密的密码
secret_code = "mac_kb_password"
@@ -31,15 +40,40 @@ def generate():
def get_key_pair():
- if not os.path.exists("/opt/maxkb/conf/receiver.pem"):
+ rsa_value = rsa_cache.get(cache_key)
+ if rsa_value is None:
+ lock.acquire()
+ rsa_value = rsa_cache.get(cache_key)
+ if rsa_value is not None:
+ return rsa_value
+ try:
+ rsa_value = get_key_pair_by_sql()
+ rsa_cache.set(cache_key, rsa_value)
+ finally:
+ lock.release()
+ return rsa_value
+
+
+def get_key_pair_by_sql():
+ system_setting = QuerySet(SystemSetting).filter(type=SettingType.RSA.value).first()
+ if system_setting is None:
kv = generate()
- private_file_out = open("/opt/maxkb/conf/private.pem", "wb")
- private_file_out.write(kv.get('value'))
- private_file_out.close()
- receiver_file_out = open("/opt/maxkb/conf/receiver.pem", "wb")
- receiver_file_out.write(kv.get('key'))
- receiver_file_out.close()
- return {'key': open("/opt/maxkb/conf/receiver.pem").read(), 'value': open("/opt/maxkb/conf/private.pem").read()}
+ system_setting = SystemSetting(type=SettingType.RSA.value,
+ meta={'key': kv.get('key').decode(), 'value': kv.get('value').decode()})
+ system_setting.save()
+ return system_setting.meta
+
+
+# def get_key_pair():
+# if not os.path.exists("/opt/maxkb/conf/receiver.pem"):
+# kv = generate()
+# private_file_out = open("/opt/maxkb/conf/private.pem", "wb")
+# private_file_out.write(kv.get('value'))
+# private_file_out.close()
+# receiver_file_out = open("/opt/maxkb/conf/receiver.pem", "wb")
+# receiver_file_out.write(kv.get('key'))
+# receiver_file_out.close()
+# return {'key': open("/opt/maxkb/conf/receiver.pem").read(), 'value': open("/opt/maxkb/conf/private.pem").read()}
def encrypt(msg, public_key: str | None = None):
diff --git a/apps/setting/migrations/0002_systemsetting.py b/apps/setting/migrations/0002_systemsetting.py
new file mode 100644
index 000000000..1293ca5df
--- /dev/null
+++ b/apps/setting/migrations/0002_systemsetting.py
@@ -0,0 +1,25 @@
+# Generated by Django 4.1.10 on 2024-03-19 16:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('setting', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='SystemSetting',
+ fields=[
+ ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+ ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
+ ('type', models.IntegerField(choices=[('0', '邮箱')], default='0', max_length=5, primary_key=True, serialize=False, verbose_name='设置类型')),
+ ('meta', models.JSONField(default=dict, verbose_name='配置数据')),
+ ],
+ options={
+ 'db_table': 'system_setting',
+ },
+ ),
+ ]
diff --git a/apps/setting/models/__init__.py b/apps/setting/models/__init__.py
index 1de0764b2..155129ebb 100644
--- a/apps/setting/models/__init__.py
+++ b/apps/setting/models/__init__.py
@@ -8,3 +8,4 @@
"""
from .team_management import *
from .model_management import *
+from .system_management import *
diff --git a/apps/setting/models/system_management.py b/apps/setting/models/system_management.py
new file mode 100644
index 000000000..84060b792
--- /dev/null
+++ b/apps/setting/models/system_management.py
@@ -0,0 +1,33 @@
+# coding=utf-8
+"""
+ @project: maxkb
+ @Author:虎
+ @file: system_management.py
+ @date:2024/3/19 13:47
+ @desc: 邮箱管理
+"""
+import uuid
+
+from django.db import models
+
+from common.mixins.app_model_mixin import AppModelMixin
+
+
+class SettingType(models.IntegerChoices):
+ """系统设置类型"""
+ EMAIL = 0, '邮箱'
+
+ RSA = 1, "私钥秘钥"
+
+
+class SystemSetting(AppModelMixin):
+ """
+ 系统设置
+ """
+ type = models.IntegerField(primary_key=True, verbose_name='设置类型', max_length=5, choices=SettingType.choices,
+ default=SettingType.EMAIL)
+
+ meta = models.JSONField(verbose_name="配置数据", default=dict)
+
+ class Meta:
+ db_table = "system_setting"
diff --git a/apps/setting/serializers/system_setting.py b/apps/setting/serializers/system_setting.py
new file mode 100644
index 000000000..a66b15805
--- /dev/null
+++ b/apps/setting/serializers/system_setting.py
@@ -0,0 +1,67 @@
+# coding=utf-8
+"""
+ @project: maxkb
+ @Author:虎
+ @file: system_setting.py
+ @date:2024/3/19 16:29
+ @desc:
+"""
+from django.core.mail.backends.smtp import EmailBackend
+from django.db.models import QuerySet
+from rest_framework import serializers
+
+from common.exception.app_exception import AppApiException
+from common.util.field_message import ErrMessage
+from setting.models.system_management import SystemSetting, SettingType
+
+
+class SystemSettingSerializer(serializers.Serializer):
+ class EmailSerializer(serializers.Serializer):
+ @staticmethod
+ def one():
+ system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first()
+ if system_setting is None:
+ return {}
+ return system_setting.meta
+
+ class Create(serializers.Serializer):
+ email_host = serializers.CharField(required=True, error_messages=ErrMessage.char("SMTP 主机"))
+ email_port = serializers.IntegerField(required=True, error_messages=ErrMessage.char("SMTP 端口"))
+ email_host_user = serializers.CharField(required=True, error_messages=ErrMessage.char("发件人邮箱"))
+ email_host_password = serializers.CharField(required=True, error_messages=ErrMessage.char("密码"))
+ email_use_tls = serializers.BooleanField(required=True, error_messages=ErrMessage.char("是否开启TLS"))
+ email_use_ssl = serializers.BooleanField(required=True, error_messages=ErrMessage.char("是否开启SSL"))
+ from_email = serializers.EmailField(required=True, error_messages=ErrMessage.char("发送人邮箱"))
+
+ def is_valid(self, *, raise_exception=False):
+ super().is_valid(raise_exception=True)
+ try:
+ EmailBackend(self.data.get("email_host"),
+ self.data.get("email_port"),
+ self.data.get("email_host_user"),
+ self.data.get("email_host_password"),
+ self.data.get("email_use_tls"),
+ False,
+ self.data.get("email_use_ssl")
+ ).open()
+ except Exception as e:
+ raise AppApiException(1004, "邮箱校验失败")
+
+ def update_or_save(self):
+ self.is_valid(raise_exception=True)
+ system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first()
+ if system_setting is None:
+ system_setting = SystemSetting(type=SettingType.EMAIL.value)
+ system_setting.meta = self.to_email_meta()
+ system_setting.save()
+ return system_setting.meta
+
+ def to_email_meta(self):
+ return {'email_host': self.data.get('email_host'),
+ 'email_port': self.data.get('email_port'),
+ 'email_host_user': self.data.get('email_host_user'),
+ 'email_host_password': self.data.get('email_host_password'),
+ 'email_use_tls': self.data.get('email_use_tls'),
+ 'email_use_ssl': self.data.get('email_use_ssl'),
+ 'from_email': self.data.get('from_email')
+ }
diff --git a/apps/setting/swagger_api/system_setting.py b/apps/setting/swagger_api/system_setting.py
new file mode 100644
index 000000000..1246ff27d
--- /dev/null
+++ b/apps/setting/swagger_api/system_setting.py
@@ -0,0 +1,77 @@
+# coding=utf-8
+"""
+ @project: maxkb
+ @Author:虎
+ @file: system_setting.py
+ @date:2024/3/19 16:05
+ @desc:
+"""
+from drf_yasg import openapi
+
+from common.mixins.api_mixin import ApiMixin
+
+
+class SystemSettingEmailApi(ApiMixin):
+ @staticmethod
+ def get_request_body_api():
+ return openapi.Schema(type=openapi.TYPE_OBJECT,
+ title="邮箱相关参数",
+ description="邮箱相关参数",
+ required=['email_host', 'email_port', 'email_host_user', 'email_host_password',
+ 'email_use_tls', 'email_use_ssl', 'from_email'],
+ properties={
+ 'email_host': openapi.Schema(type=openapi.TYPE_STRING,
+ title="SMTP 主机",
+ description="SMTP 主机"),
+ 'email_port': openapi.Schema(type=openapi.TYPE_NUMBER,
+ title="SMTP 端口",
+ description="SMTP 端口"),
+ 'email_host_user': openapi.Schema(type=openapi.TYPE_STRING,
+ title="发件人邮箱",
+ description="发件人邮箱"),
+ 'email_host_password': openapi.Schema(type=openapi.TYPE_STRING,
+ title="密码",
+ description="密码"),
+ 'email_use_tls': openapi.Schema(type=openapi.TYPE_BOOLEAN,
+ title="是否开启TLS",
+ description="是否开启TLS"),
+ 'email_use_ssl': openapi.Schema(type=openapi.TYPE_BOOLEAN,
+ title="是否开启SSL",
+ description="是否开启SSL"),
+ 'from_email': openapi.Schema(type=openapi.TYPE_STRING,
+ title="发送人邮箱",
+ description="发送人邮箱")
+ }
+ )
+
+ @staticmethod
+ def get_response_body_api():
+ return openapi.Schema(type=openapi.TYPE_OBJECT,
+ title="邮箱相关参数",
+ description="邮箱相关参数",
+ required=['email_host', 'email_port', 'email_host_user', 'email_host_password',
+ 'email_use_tls', 'email_use_ssl', 'from_email'],
+ properties={
+ 'email_host': openapi.Schema(type=openapi.TYPE_STRING,
+ title="SMTP 主机",
+ description="SMTP 主机"),
+ 'email_port': openapi.Schema(type=openapi.TYPE_NUMBER,
+ title="SMTP 端口",
+ description="SMTP 端口"),
+ 'email_host_user': openapi.Schema(type=openapi.TYPE_STRING,
+ title="发件人邮箱",
+ description="发件人邮箱"),
+ 'email_host_password': openapi.Schema(type=openapi.TYPE_STRING,
+ title="密码",
+ description="密码"),
+ 'email_use_tls': openapi.Schema(type=openapi.TYPE_BOOLEAN,
+ title="是否开启TLS",
+ description="是否开启TLS"),
+ 'email_use_ssl': openapi.Schema(type=openapi.TYPE_BOOLEAN,
+ title="是否开启SSL",
+ description="是否开启SSL"),
+ 'from_email': openapi.Schema(type=openapi.TYPE_STRING,
+ title="发送人邮箱",
+ description="发送人邮箱")
+ }
+ )
diff --git a/apps/setting/urls.py b/apps/setting/urls.py
index f58679046..460facb34 100644
--- a/apps/setting/urls.py
+++ b/apps/setting/urls.py
@@ -15,6 +15,7 @@ urlpatterns = [
path('provider/model_form', views.Provide.ModelForm.as_view(),
name="provider/model_form"),
path('model', views.Model.as_view(), name='model'),
- path('model/', views.Model.Operate.as_view(), name='model/operate')
+ path('model/', views.Model.Operate.as_view(), name='model/operate'),
+ path('email_setting', views.SystemSetting.Email.as_view(), name='email_setting')
]
diff --git a/apps/setting/views/__init__.py b/apps/setting/views/__init__.py
index f959267f2..0885ef978 100644
--- a/apps/setting/views/__init__.py
+++ b/apps/setting/views/__init__.py
@@ -8,3 +8,4 @@
"""
from .Team import *
from .model import *
+from .system_setting import *
diff --git a/apps/setting/views/system_setting.py b/apps/setting/views/system_setting.py
new file mode 100644
index 000000000..fdf19d67d
--- /dev/null
+++ b/apps/setting/views/system_setting.py
@@ -0,0 +1,57 @@
+# coding=utf-8
+"""
+ @project: maxkb
+ @Author:虎
+ @file: system_setting.py
+ @date:2024/3/19 16:01
+ @desc:
+"""
+
+from drf_yasg.utils import swagger_auto_schema
+from rest_framework.decorators import action
+from rest_framework.request import Request
+from rest_framework.views import APIView
+
+from common.auth import TokenAuth, has_permissions
+from common.constants.permission_constants import PermissionConstants, RoleConstants
+from common.response import result
+from setting.serializers.system_setting import SystemSettingSerializer
+from setting.swagger_api.system_setting import SystemSettingEmailApi
+
+
+class SystemSetting(APIView):
+ class Email(APIView):
+ authentication_classes = [TokenAuth]
+
+ @action(methods=['PUT'], detail=False)
+ @swagger_auto_schema(operation_summary="创建或者修改邮箱设置",
+ operation_id="创建或者修改邮箱设置",
+ request_body=SystemSettingEmailApi.get_request_body_api(), tags=["邮箱设置"],
+ responses=result.get_api_response(SystemSettingEmailApi.get_response_body_api()))
+ @has_permissions(RoleConstants.ADMIN)
+ def put(self, request: Request):
+ return result.success(
+ SystemSettingSerializer.EmailSerializer.Create(
+ data=request.data).update_or_save())
+
+ @action(methods=['POST'], detail=False)
+ @swagger_auto_schema(operation_summary="测试邮箱设置",
+ operation_id="测试邮箱设置",
+ request_body=SystemSettingEmailApi.get_request_body_api(),
+ responses=result.get_default_response(),
+ tags=["邮箱设置"])
+ @has_permissions(RoleConstants.ADMIN)
+ def post(self, request: Request):
+ return result.success(
+ SystemSettingSerializer.EmailSerializer.Create(
+ data=request.data).is_valid())
+
+ @action(methods=['GET'], detail=False)
+ @swagger_auto_schema(operation_summary="获取邮箱设置",
+ operation_id="获取邮箱设置",
+ responses=result.get_api_response(SystemSettingEmailApi.get_response_body_api()),
+ tags=["邮箱设置"])
+ @has_permissions(RoleConstants.ADMIN)
+ def get(self, request: Request):
+ return result.success(
+ SystemSettingSerializer.EmailSerializer.one())
diff --git a/apps/users/serializers/user_serializers.py b/apps/users/serializers/user_serializers.py
index 0302e6b00..ba38bc182 100644
--- a/apps/users/serializers/user_serializers.py
+++ b/apps/users/serializers/user_serializers.py
@@ -14,6 +14,7 @@ import uuid
from django.core import validators, signing, cache
from django.core.mail import send_mail
+from django.core.mail.backends.smtp import EmailBackend
from django.db import transaction
from django.db.models import Q, QuerySet
from drf_yasg import openapi
@@ -28,9 +29,8 @@ from common.mixins.api_mixin import ApiMixin
from common.response.result import get_api_response
from common.util.field_message import ErrMessage
from common.util.lock import lock
-from setting.models import Team
+from setting.models import Team, SystemSetting, SettingType
from smartdoc.conf import PROJECT_DIR
-from smartdoc.settings import EMAIL_ADDRESS
from users.models.user import User, password_encrypt, get_user_dynamics_permission
user_cache = cache.caches['user_cache']
@@ -345,13 +345,25 @@ class SendEmailSerializer(ApiMixin, serializers.Serializer):
code_cache_key_lock = code_cache_key + "_lock"
# 设置缓存
user_cache.set(code_cache_key_lock, code, timeout=datetime.timedelta(minutes=1))
+ system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first()
+ if system_setting is None:
+ user_cache.delete(code_cache_key_lock)
+ raise AppApiException(1004, "邮箱未设置,请联系管理员设置")
try:
+ connection = EmailBackend(system_setting.meta.get("email_host"),
+ system_setting.meta.get('email_port'),
+ system_setting.meta.get('email_host_user'),
+ system_setting.meta.get('email_host_password'),
+ system_setting.meta.get('email_use_tls'),
+ False,
+ system_setting.meta.get('email_use_ssl')
+ )
# 发送邮件
send_mail(f'【MaxKB 智能知识库-{"用户注册" if state == "register" else "修改密码"}】',
'',
html_message=f'{content.replace("${code}", code)}',
- from_email=EMAIL_ADDRESS,
- recipient_list=[email], fail_silently=False)
+ from_email=system_setting.meta.get('from_email'),
+ recipient_list=[email], fail_silently=False, connection=connection)
except Exception as e:
user_cache.delete(code_cache_key_lock)
raise AppApiException(500, f"{str(e)}邮件发送失败")
diff --git a/ui/src/api/application-overview.ts b/ui/src/api/application-overview.ts
index 361990193..940088353 100644
--- a/ui/src/api/application-overview.ts
+++ b/ui/src/api/application-overview.ts
@@ -1,5 +1,5 @@
import { Result } from '@/request/Result'
-import { get, post, postStream, del, put } from '@/request/index'
+import { get, post, del, put } from '@/request/index'
import { type Ref } from 'vue'
diff --git a/ui/src/api/log.ts b/ui/src/api/log.ts
index 4c989b619..26f9245fe 100644
--- a/ui/src/api/log.ts
+++ b/ui/src/api/log.ts
@@ -1,5 +1,5 @@
import { Result } from '@/request/Result'
-import { get, post, postStream, del, put } from '@/request/index'
+import { get, post, del, put } from '@/request/index'
import type { pageRequest } from '@/api/type/common'
import { type Ref } from 'vue'
diff --git a/ui/src/api/type/user.ts b/ui/src/api/type/user.ts
index 6462354f8..04cbd4140 100644
--- a/ui/src/api/type/user.ts
+++ b/ui/src/api/type/user.ts
@@ -89,11 +89,11 @@ interface ResetPasswordRequest {
/**
* 邮箱
*/
- email: string
+ email?: string
/**
* 验证码
*/
- code: string
+ code?: string
/**
* 密码
*/
diff --git a/ui/src/api/user-manage.ts b/ui/src/api/user-manage.ts
new file mode 100644
index 000000000..ceed082b6
--- /dev/null
+++ b/ui/src/api/user-manage.ts
@@ -0,0 +1,77 @@
+import { Result } from '@/request/Result'
+import { get, post, del, put } from '@/request/index'
+import type { pageRequest } from '@/api/type/common'
+import { type Ref } from 'vue'
+
+const prefix = '/user_manage'
+/**
+ * 用户分页列表
+ * @param 参数
+ * page {
+ "current_page": "string",
+ "page_size": "string",
+ }
+ * @query 参数
+ email_or_username: string
+ */
+const getUserManage: (
+ page: pageRequest,
+ email_or_username: string,
+ loading?: Ref
+) => Promise> = (page, email_or_username, loading) => {
+ return get(
+ `${prefix}/${page.current_page}/${page.page_size}`,
+ email_or_username ? { email_or_username } : undefined,
+ loading
+ )
+}
+
+/**
+ * 删除用户
+ * @param 参数 user_id,
+ */
+const delUserManage: (user_id: string, loading?: Ref) => Promise> = (
+ user_id,
+ loading
+) => {
+ return del(`${prefix}/${user_id}`, undefined, {}, loading)
+}
+
+/**
+ * 创建用户
+ */
+const postUserManage: (data: any, loading?: Ref) => Promise> = (
+ data,
+ loading
+) => {
+ return post(`${prefix}`, data, undefined, loading)
+}
+
+/**
+ * 编辑用户
+ */
+const putUserManage: (
+ user_id: string,
+ data: any,
+ loading?: Ref
+) => Promise> = (user_id, data, loading) => {
+ return put(`${prefix}/${user_id}`, data, undefined, loading)
+}
+/**
+ * 修改用户密码
+ */
+const putUserManagePassword: (
+ user_id: string,
+ data: any,
+ loading?: Ref
+) => Promise> = (user_id, data, loading) => {
+ return put(`${prefix}/${user_id}/re_password`, data, undefined, loading)
+}
+
+export default {
+ getUserManage,
+ delUserManage,
+ postUserManage,
+ putUserManage,
+ putUserManagePassword
+}
diff --git a/ui/src/router/modules/setting.ts b/ui/src/router/modules/setting.ts
index 2aa5a92b8..f37dbd491 100644
--- a/ui/src/router/modules/setting.ts
+++ b/ui/src/router/modules/setting.ts
@@ -3,20 +3,34 @@ const settingRouter = {
path: '/setting',
name: 'setting',
meta: { icon: 'Setting', title: '系统设置', permission: 'SETTING:READ' },
- redirect: '/setting',
+ redirect: '/user',
component: Layout,
children: [
{
- path: '/setting',
- name: 'setting',
+ path: '/user',
+ name: 'user',
+ meta: {
+ icon: 'User',
+ iconActive: 'UserFilled',
+ title: '用户管理',
+ activeMenu: '/setting',
+ parentPath: '/setting',
+ parentName: 'setting'
+ },
+ component: () => import('@/views/user-manage/index.vue')
+ },
+ {
+ path: '/team',
+ name: 'team',
meta: {
icon: 'app-team',
iconActive: 'app-team-active',
title: '团队管理',
+ activeMenu: '/setting',
parentPath: '/setting',
parentName: 'setting'
},
- component: () => import('@/views/setting/index.vue')
+ component: () => import('@/views/team/index.vue')
},
{
path: '/template',
diff --git a/ui/src/views/setting/component/CreateMemberDialog.vue b/ui/src/views/team/component/CreateMemberDialog.vue
similarity index 100%
rename from ui/src/views/setting/component/CreateMemberDialog.vue
rename to ui/src/views/team/component/CreateMemberDialog.vue
diff --git a/ui/src/views/setting/component/PermissionSetting.vue b/ui/src/views/team/component/PermissionSetting.vue
similarity index 99%
rename from ui/src/views/setting/component/PermissionSetting.vue
rename to ui/src/views/team/component/PermissionSetting.vue
index a234318d6..19886dbe5 100644
--- a/ui/src/views/setting/component/PermissionSetting.vue
+++ b/ui/src/views/team/component/PermissionSetting.vue
@@ -144,3 +144,4 @@ onMounted(() => {
})
+../utils
\ No newline at end of file
diff --git a/ui/src/views/setting/index.vue b/ui/src/views/team/index.vue
similarity index 100%
rename from ui/src/views/setting/index.vue
rename to ui/src/views/team/index.vue
diff --git a/ui/src/views/setting/utils.ts b/ui/src/views/team/utils.ts
similarity index 100%
rename from ui/src/views/setting/utils.ts
rename to ui/src/views/team/utils.ts
diff --git a/ui/src/views/user-manage/component/UserDialog.vue b/ui/src/views/user-manage/component/UserDialog.vue
new file mode 100644
index 000000000..b39829fd7
--- /dev/null
+++ b/ui/src/views/user-manage/component/UserDialog.vue
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/views/user-manage/component/UserPwdDialog.vue b/ui/src/views/user-manage/component/UserPwdDialog.vue
new file mode 100644
index 000000000..3be7f5e61
--- /dev/null
+++ b/ui/src/views/user-manage/component/UserPwdDialog.vue
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/views/user-manage/index.vue b/ui/src/views/user-manage/index.vue
new file mode 100644
index 000000000..165d133e1
--- /dev/null
+++ b/ui/src/views/user-manage/index.vue
@@ -0,0 +1,155 @@
+
+
+
+
+ 创建用户
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ datetimeFormat(row.create_time) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+