update
This commit is contained in:
60
README.md
60
README.md
@@ -68,14 +68,54 @@ YG_FT/
|
||||
|
||||
> 说明:`audit`、`retention` 模块当前未拆为独立 REST 路由——审计接口挂在 `system` 下、留存策略嵌套于租户资源;用户「页面权限」精细控制 UI 仍为占位,待后续版本补齐。数据层当前为内存/本地存储实现,生产环境按 `docs/postgres-schema.sql` 迁移 PostgreSQL。
|
||||
|
||||
## 首次环境准备(新人 / 新机器)
|
||||
|
||||
> 后端与算力服务都运行在 **WSL2** 中(Windows 原生 Python / anaconda 依赖不兼容)。第一次拉代码、机器上没有任何环境时,按下面顺序一次性准备好;日常启动见下方「后端启动」「算力服务启动」。
|
||||
|
||||
### 1. 基础运行时
|
||||
- **WSL2**:Windows 终端执行 `wsl --install`(默认 Ubuntu),重启生效。
|
||||
- **Python ≥ 3.9(推荐 3.12)**,在 WSL 内确认:
|
||||
```bash
|
||||
python3 --version
|
||||
```
|
||||
版本过低或未安装:`sudo apt update && sudo apt install -y python3.12 python3.12-venv python3-pip`。
|
||||
- **Node.js 18+**:前端在 **Windows 终端**安装并运行(不要放进 WSL,否则热更新跨 `/mnt` 文件系统很慢)。
|
||||
|
||||
### 2. 数据库 PostgreSQL(首次需自建)
|
||||
后端默认连接 `postgresql+psycopg://yg_ft:change_me@localhost:15432/yg_ft`。首次没有数据库时,推荐用 Docker 一键起:
|
||||
```bash
|
||||
docker run -d --name yg_ft_pg -p 15432:5432 \
|
||||
-e POSTGRES_USER=yg_ft -e POSTGRES_PASSWORD=change_me -e POSTGRES_DB=yg_ft \
|
||||
postgres:16
|
||||
```
|
||||
无 Docker 时,可在 WSL 内 `sudo apt install -y postgresql` 后手动建库与用户。后端首次启动会自动建表并写入内置管理员账号(详见「后端启动」)。
|
||||
|
||||
### 3. 后端 / 算力 venv 首次创建
|
||||
后端与算力各有独立 venv,首次需在 WSL 中创建并安装依赖(完整命令见下方启动小节):
|
||||
- 后端:`cd /mnt/e/yg_ft/backend && python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt`
|
||||
- 算力:`cd /mnt/e/yg_ft && source compute/.venv/bin/activate && pip install -r compute/requirements.txt`(compute 使用绝对导入 `compute.*`,须在仓库根目录操作)
|
||||
|
||||
### 4. 前端首次安装
|
||||
在 Windows 终端:
|
||||
```powershell
|
||||
cd e:\yg_ft\frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 后端启动
|
||||
|
||||
> 后端需在 **WSL 终端** 中启动:Windows 的 anaconda 环境依赖不兼容,且项目 venv 为 Linux 版(使用 `bin/activate` 而非 `Scripts`)。若 `.wslconfig` 使用 `networkingMode=mirrored`,uvicorn 必须绑定 `--host 0.0.0.0` 才能被 Windows 侧 `localhost` 访问。
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate
|
||||
# 在 WSL 终端中执行
|
||||
cd /mnt/e/yg_ft/backend
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --reload --port 17861
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 17861 --reload
|
||||
```
|
||||
|
||||
默认接口前缀为 `/modelTF`,例如:
|
||||
@@ -118,14 +158,14 @@ npm run dev
|
||||
|
||||
## 算力服务启动
|
||||
|
||||
算力服务位于 `compute/` 目录,拥有**独立虚拟环境(不复用后端 venv)**。
|
||||
代码使用绝对导入 `compute.*`,因此必须从**仓库根目录(`yg_ft/`)**启动,
|
||||
不能先 `cd compute` 再启动(否则报 `No module named 'compute'`)。
|
||||
> 算力服务同样需在 **WSL 终端** 中启动,且拥有**独立虚拟环境(不复用后端 venv)**。代码使用绝对导入 `compute.*`,因此必须从**仓库根目录(`/mnt/e/yg_ft`)**执行,不能先 `cd compute` 再启动(否则报 `No module named 'compute'`)。若 `.wslconfig` 使用 `networkingMode=mirrored`,uvicorn 需绑定 `--host 0.0.0.0` 才能被 Windows 侧 `localhost` 访问。
|
||||
|
||||
```bash
|
||||
# 在仓库根目录 yg_ft/ 下执行
|
||||
source compute/.venv/bin/activate # 激活独立 venv(Windows 用 compute\.venv\Scripts\activate)
|
||||
pip install -r compute/requirements.txt # 首次安装依赖
|
||||
# 在 WSL 终端中执行(必须位于仓库根目录 yg_ft/)
|
||||
cd /mnt/e/yg_ft
|
||||
python3 -m venv compute/.venv
|
||||
source compute/.venv/bin/activate
|
||||
pip install -r compute/requirements.txt
|
||||
uvicorn compute.api.main:app --host 0.0.0.0 --port 19100 --reload
|
||||
```
|
||||
|
||||
|
||||
52
UI测试手册.md
52
UI测试手册.md
@@ -49,7 +49,29 @@ npm run dev
|
||||
|
||||
---
|
||||
|
||||
## 1.5 用户设置 → 重置密码(系统设置)
|
||||
## 1.4 用户设置 → 创建用户(系统设置)
|
||||
|
||||
> 入口:左侧菜单「系统设置 → 用户设置」页右上「创建用户」按钮(或 `/user-settings/create`),对应后端 `POST /modelTF/users`。新建的账号用于后续 1.5 各项验证(重置密码 / 启停 / 权限)。
|
||||
|
||||
### 1.4.1 创建普通账号
|
||||
1. 进入「用户设置」,点「**创建用户**」
|
||||
2. 填:
|
||||
- 账号:`test-user-manual`
|
||||
- 显示名称:`测试用户`
|
||||
- 初始密码:留默认 `platform123`(或自定义)
|
||||
- 角色:选「操作员」(非 admin,便于后续测启停/权限)
|
||||
- 状态:保持「启用」
|
||||
- 页面权限:默认已勾 `dashboard`,按需增删
|
||||
3. 点「**保存**」
|
||||
4. 预期:提示「用户已创建」,跳回列表,列表出现 `test-user-manual`
|
||||
5. 验证:退出后该账号 + `platform123` 应能登录成功;登录后左侧菜单按角色/权限显示(操作员无「用户设置」)
|
||||
|
||||
### 1.4.2 创建时即禁用(可选)
|
||||
- 新建时「状态」选「禁用」再保存,预期列表该账号状态为「禁用」,且用该账号登录会失败(后端 `login` 校验 `status != 'active'` 拒绝)。
|
||||
|
||||
---
|
||||
|
||||
## 1.5 用户设置 → 账号操作(系统设置)
|
||||
|
||||
> 入口:左侧菜单「系统设置 → 用户设置」。该页是账号列表,每行「操作」列有「重置密码」按钮,对应后端 `POST /modelTF/users/{id}/reset-password`。
|
||||
|
||||
@@ -69,6 +91,32 @@ npm run dev
|
||||
- `admin` 是内置保护账号(protected),不可被重置。点 `admin` 行「重置密码」并提交,后端会返回错误提示「protected user cannot be reset」,前端弹「重置失败」。
|
||||
- 验证:点 `admin` 行「重置密码」→ 确定,预期出现错误提示,且 `admin` 密码不变(仍可用 `admin123` 登录)。
|
||||
|
||||
### 1.5.4 启用 / 禁用账号
|
||||
> 操作列对 `admin` 保护账号**隐藏**启停按钮(避免锁死管理员自身);普通账号可见「禁用 / 启用」按钮,对应后端 `PUT /modelTF/users/{id}`(status 字段)。
|
||||
|
||||
1. 在任一**非 admin** 普通账号行,点「**禁用**」
|
||||
2. 预期:状态标签由「启用」变「禁用」,操作按钮变「启用」
|
||||
3. 验证:用该账号尝试登录,预期**登录失败**(后端 `login` 校验 `status != 'active'` 直接拒绝)
|
||||
4. 再点「**启用**」,预期状态恢复「启用」,该账号可重新登录
|
||||
|
||||
### 1.5.5 页面权限精细控制
|
||||
> 操作列「权限」按钮进入「权限设置」页(`/user-settings/:id/permission`),勾选可访问页面码后保存,对应后端 `PUT /modelTF/users/{id}`(permissions 字段,直接覆盖该用户权限清单)。
|
||||
|
||||
1. 在目标账号行点「**权限**」,进入「权限设置」页
|
||||
2. 页内列出 12 个页面权限码(仪表盘 / 模型微调 / … / 用户设置),当前已勾选项为该用户现有权限
|
||||
3. 取消某几项(如去掉 `user-settings`),点「**保存**」
|
||||
4. 预期:提示「权限已保存」,跳回用户列表
|
||||
5. 验证:用该账号登录后,其权限菜单应**不再包含**被取消的页面(前端 `hasPermission` 基于 `users.permissions` 实时判断)
|
||||
|
||||
### 1.5.6 删除账号
|
||||
> 操作列「删除」按钮仅对**非保护账号、且非当前登录自己**显示,点击触发二次确认,对应后端 `DELETE /modelTF/users/{id}`(带 `current_username` 防删自己)。
|
||||
|
||||
1. 在 1.4.1 创建的 `test-user-manual`(或任一非 admin 普通账号)行,点「**删除**」
|
||||
2. 弹出二次确认「确定删除该账号?此操作不可恢复」,点「**删除**」
|
||||
3. 预期:提示「用户已删除」,列表不再显示该账号
|
||||
4. 验证:用该账号 + `platform123` 登录,预期失败(账号已不存在)
|
||||
5. 边界:`admin` 保护账号行**无**「删除」按钮;当前登录账号自身也**无**「删除」按钮(防误删自己)
|
||||
|
||||
---
|
||||
|
||||
## 2. 租户管理(第 2 周)
|
||||
@@ -237,7 +285,7 @@ npm run dev
|
||||
- [ ] 4. 项目成员:添加 / 改角色 / 移除 均成功
|
||||
- [ ] 5. 资源 ACL:添加授权项 + 勾选权限 + 点「保存」成功,重开仍在
|
||||
- [ ] 6. 归档:点后状态变 `archived`
|
||||
- [ ] 7. 用户设置 → 重置密码:自定义密码生效 / 留空回退 `platform123` / `admin` 保护账号被拒
|
||||
- [ ] 7. 用户设置:创建用户(账号/显示名/初始密码/角色/状态/页面权限均可设,列表出现新账号)、重置密码(自定义生效 / 留空回退 `platform123` / `admin` 被拒)、启用禁用账号(禁用后登录失败、启用后恢复)、页面权限精细控制(取消权限后登录即生效)、删除账号(二次确认后列表移除、登录失败)
|
||||
|
||||
**第 3 周(审计)**
|
||||
- [ ] 8. 审计日志:按 动作/操作人/项目 过滤均能返回正确结果
|
||||
|
||||
@@ -1,12 +1,134 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getUsers, updateUserAccess } from '@/api/modules/system'
|
||||
import type { PermissionCode, SystemUser } from '@/types'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const userId = route.params.id as string
|
||||
const target = ref<SystemUser | null>(null)
|
||||
const checked = ref<PermissionCode[]>([])
|
||||
|
||||
const options: PermissionCode[] = [
|
||||
'dashboard',
|
||||
'fine-tune',
|
||||
'model-eval',
|
||||
'model-inference',
|
||||
'model-manage',
|
||||
'dataset',
|
||||
'data-process',
|
||||
'data-convert',
|
||||
'compute',
|
||||
'hardware',
|
||||
'logs',
|
||||
'user-settings',
|
||||
]
|
||||
|
||||
const labels: Record<PermissionCode, string> = {
|
||||
dashboard: '仪表盘',
|
||||
'fine-tune': '模型微调',
|
||||
'model-eval': '模型评估',
|
||||
'model-inference': '模型推理',
|
||||
'model-manage': '模型管理',
|
||||
dataset: '数据集',
|
||||
'data-process': '数据处理',
|
||||
'data-convert': '数据转换',
|
||||
compute: '算力',
|
||||
hardware: '平台性能',
|
||||
logs: '日志',
|
||||
'user-settings': '用户设置',
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const users = await getUsers()
|
||||
const u = users.find((x) => x.id === userId)
|
||||
if (!u) {
|
||||
ElMessage.error('用户不存在')
|
||||
return
|
||||
}
|
||||
target.value = u
|
||||
checked.value = [...u.permissions]
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
await updateUserAccess(userId, { permissions: checked.value })
|
||||
ElMessage.success('权限已保存')
|
||||
router.push('/user-settings')
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="simple-page">
|
||||
<h1>权限设置</h1>
|
||||
<p>第一版已支持账号页面权限读取与保存,精细化项目/模型/数据集权限将在后续版本补齐。</p>
|
||||
<section class="permission-page" v-loading="loading">
|
||||
<header class="page-header">
|
||||
<div>
|
||||
<h1>权限设置</h1>
|
||||
<p v-if="target">
|
||||
为账号 <b>{{ target.username }}</b>({{ target.display_name }})配置可访问的页面权限。
|
||||
</p>
|
||||
</div>
|
||||
<el-button @click="$router.push('/user-settings')">返回</el-button>
|
||||
</header>
|
||||
|
||||
<el-form label-width="90px" class="perm-form">
|
||||
<el-form-item label="账号">{{ target?.username }}</el-form-item>
|
||||
<el-form-item label="角色">{{ target?.role }}</el-form-item>
|
||||
<el-form-item label="页面权限">
|
||||
<el-checkbox-group v-model="checked">
|
||||
<el-checkbox v-for="opt in options" :key="opt" :value="opt">
|
||||
{{ labels[opt] }}({{ opt }})
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="$router.push('/user-settings')">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.simple-page {
|
||||
.permission-page {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 8px 0 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.perm-form {
|
||||
max-width: 760px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getUsers, resetUserPassword } from '@/api/modules/system'
|
||||
import type { SystemUser } from '@/types'
|
||||
import { getUsers, resetUserPassword, updateUserAccess, deleteUser } from '@/api/modules/system'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import type { SystemUser, UserStatus } from '@/types'
|
||||
|
||||
const auth = useAuthStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const users = ref<SystemUser[]>([])
|
||||
@@ -41,6 +44,27 @@ async function confirmReset() {
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleStatus(row: SystemUser) {
|
||||
const next: UserStatus = row.status === 'active' ? 'disabled' : 'active'
|
||||
try {
|
||||
await updateUserAccess(row.id, { status: next })
|
||||
ElMessage.success(next === 'active' ? '已启用' : '已禁用')
|
||||
await loadUsers()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function removeUser(row: SystemUser) {
|
||||
try {
|
||||
await deleteUser(row.id, auth.username)
|
||||
ElMessage.success('用户已删除')
|
||||
await loadUsers()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.message || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadUsers)
|
||||
</script>
|
||||
|
||||
@@ -58,14 +82,39 @@ onMounted(loadUsers)
|
||||
<el-table-column prop="username" label="账号" min-width="140" />
|
||||
<el-table-column prop="display_name" label="显示名称" min-width="160" />
|
||||
<el-table-column prop="role" label="角色" width="120" />
|
||||
<el-table-column prop="status" label="状态" width="120" />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'active' ? 'success' : 'info'">
|
||||
{{ row.status === 'active' ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="权限数" width="120">
|
||||
<template #default="{ row }">{{ row.permissions?.length || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<el-table-column label="操作" width="300" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openReset(row)">重置密码</el-button>
|
||||
<el-button link type="primary" size="small" @click="$router.push(`/user-settings/${row.id}/permission`)">权限</el-button>
|
||||
<el-button
|
||||
v-if="!row.protected"
|
||||
link
|
||||
:type="row.status === 'active' ? 'danger' : 'success'"
|
||||
size="small"
|
||||
@click="toggleStatus(row)"
|
||||
>{{ row.status === 'active' ? '禁用' : '启用' }}</el-button>
|
||||
<el-popconfirm
|
||||
v-if="!row.protected && row.username !== auth.username"
|
||||
title="确定删除该账号?此操作不可恢复"
|
||||
confirm-button-text="删除"
|
||||
cancel-button-text="取消"
|
||||
@confirm="removeUser(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button link type="danger" size="small">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
Reference in New Issue
Block a user