diff --git a/.gitignore b/.gitignore
index d2f0fb6..cbc6202 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,4 +40,4 @@ dist-ssr
*.xml
# web
-/web/
\ No newline at end of file
+# /web/
\ No newline at end of file
diff --git a/README.md b/README.md
index 361cb9e..0aa04e8 100644
--- a/README.md
+++ b/README.md
@@ -166,7 +166,7 @@ LiteOps主要适用于以下场景:
### 前置要求
-在开始部署之前,请确保您的系统满足以下要求:
+在开始部署之前,请确保你的系统满足以下要求:
- **操作系统**:Linux (推荐 Ubuntu 20.04+、CentOS 7+)
- **Docker**:版本 20.0+
@@ -179,7 +179,7 @@ LiteOps主要适用于以下场景:
#### 1. 获取部署文件
-您需要获取以下部署文件:
+你需要获取以下部署文件:
- `start-containers.sh` - 一键部署脚本
- `liteops_init.sql` - 数据库初始化文件
@@ -226,7 +226,7 @@ chmod +x start-containers.sh
#### 5. 验证部署
-部署完成后,您可以通过以下方式验证:
+部署完成后,你可以通过以下方式验证:
```bash
# 检查容器状态
@@ -257,7 +257,7 @@ docker logs liteops-mysql
### 访问应用
-部署成功后,您可以通过以下地址访问:
+部署成功后,你可以通过以下地址访问:
- **前端界面**:http://localhost
- **后端API**:http://localhost:8900/api/
@@ -284,7 +284,7 @@ LiteOps目前处于未完善状态,虽然核心功能已经初步实现,但
## 📞 联系我
-如果您对LiteOps有任何建议、问题或需求,欢迎通过以下方式联系我:
+如果你对LiteOps有任何建议、问题或需求,欢迎通过以下方式联系我:
- **邮箱**:hukdoesn@163.com
- **GitHub Issues**:[提交问题或建议](https://github.com/hukdoesn/liteops/issues)
diff --git a/backend/apps/views/build.py b/backend/apps/views/build.py
index d05854d..5c6cb60 100644
--- a/backend/apps/views/build.py
+++ b/backend/apps/views/build.py
@@ -819,7 +819,7 @@ class BuildExecuteView(View):
history_id=generate_id(),
task=task,
build_number=build_number,
- branch=branch if branch else '', # 对于预发布和生产环境,分支可能为空
+ branch=branch if branch else '', # 对于预发布和生产环境,分支为空
commit_id=commit_id,
version=version if version else None, # 对于预发布和生产环境,使用传入的版本号
status='pending', # 初始状态为等待中
diff --git a/backend/apps/views/notification.py b/backend/apps/views/notification.py
index 46f54c6..0f848aa 100644
--- a/backend/apps/views/notification.py
+++ b/backend/apps/views/notification.py
@@ -319,7 +319,7 @@ class NotificationTestView(View):
# 准备测试消息
timestamp = str(int(time.time() * 1000))
- test_message = "这是一条测试消息,如果您收到了这条消息,说明机器人配置正确。"
+ test_message = "这是一条测试消息,如果你收到了这条消息,说明机器人配置正确。"
# 根据不同类型的机器人发送测试消息
try:
diff --git a/backend/backend/settings.py b/backend/backend/settings.py
index 86e49fb..69ac827 100644
--- a/backend/backend/settings.py
+++ b/backend/backend/settings.py
@@ -194,6 +194,6 @@ STATIC_URL = 'static/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# 构建相关配置
-#BUILD_ROOT = Path('/data/liteops/build') # 修改为指定目录
-BUILD_ROOT = Path('/data')
+BUILD_ROOT = Path('/Users/huk/Downloads/data') # 修改为指定目录
+# BUILD_ROOT = Path('/data')
BUILD_ROOT.mkdir(exist_ok=True, parents=True) # 确保目录存在,包括父目录
\ No newline at end of file
diff --git a/backend/conf/config.txt b/backend/conf/config.txt
index e8c4f77..41a1f92 100644
--- a/backend/conf/config.txt
+++ b/backend/conf/config.txt
@@ -1,7 +1,7 @@
[client]
-#host = 127.0.0.1
+host = 127.0.0.1
#host = mysql
-host = liteops-mysql
+#host = liteops-mysql
port = 3306
database = liteops
user = root
diff --git a/web/.env.development b/web/.env.development
new file mode 100644
index 0000000..de96daa
--- /dev/null
+++ b/web/.env.development
@@ -0,0 +1,2 @@
+# 在开发环境中使用完整路径
+VITE_API_URL=http://localhost:8900
\ No newline at end of file
diff --git a/web/.vscode/extensions.json b/web/.vscode/extensions.json
new file mode 100644
index 0000000..a7cea0b
--- /dev/null
+++ b/web/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["Vue.volar"]
+}
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..8388c4b
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + Vue
+
+
+
+
+
+
diff --git a/web/public/vite.svg b/web/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/web/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/src/App.vue b/web/src/App.vue
new file mode 100644
index 0000000..71897ae
--- /dev/null
+++ b/web/src/App.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/assets/css/global.css b/web/src/assets/css/global.css
new file mode 100644
index 0000000..fa36090
--- /dev/null
+++ b/web/src/assets/css/global.css
@@ -0,0 +1,125 @@
+/* 定义自定义PingFang字体 - 强制使用项目中的字体文件 */
+@font-face {
+ font-family: 'PingFangCustom';
+ src: url('../font/PingFang.ttc') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'PingFangCustom';
+ src: url('../font/PingFang.ttc') format('truetype');
+ font-weight: 300;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'PingFangCustom';
+ src: url('../font/PingFang.ttc') format('truetype');
+ font-weight: 500;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'PingFangCustom';
+ src: url('../font/PingFang.ttc') format('truetype');
+ font-weight: 600;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'PingFangCustom';
+ src: url('../font/PingFang.ttc') format('truetype');
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+* {
+ font-family: 'PingFangCustom', Arial, sans-serif !important;
+}
+
+body, html {
+ font-family: 'PingFangCustom', Arial, sans-serif !important;
+ margin: 0;
+ padding: 0;
+}
+
+:root {
+ font-family: 'PingFangCustom', Arial, sans-serif !important;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.ant-typography,
+.ant-btn,
+.ant-input,
+.ant-select,
+.ant-table,
+.ant-menu,
+.ant-dropdown,
+.ant-modal,
+.ant-message,
+.ant-notification,
+.ant-form,
+.ant-card,
+.ant-tabs,
+.ant-breadcrumb,
+.ant-pagination,
+.ant-steps,
+.ant-tree,
+.ant-list,
+.ant-drawer,
+.ant-popover,
+.ant-tooltip,
+.ant-alert,
+.ant-badge,
+.ant-tag,
+.ant-progress,
+.ant-spin,
+.ant-switch,
+.ant-radio,
+.ant-checkbox,
+.ant-rate,
+.ant-slider,
+.ant-upload,
+.ant-calendar,
+.ant-date-picker,
+.ant-time-picker,
+.ant-config-provider,
+.ant-app {
+ font-family: 'PingFangCustom', Arial, sans-serif !important;
+}
+
+[class*="ant-"] {
+ font-family: 'PingFangCustom', Arial, sans-serif !important;
+}
+
+input, textarea, select, button {
+ font-family: 'PingFangCustom', Arial, sans-serif !important;
+}
+
+.ant-input,
+.ant-input-affix-wrapper,
+.ant-input-number,
+.ant-select-selector,
+.ant-cascader-picker,
+.ant-picker,
+.ant-mentions,
+.ant-checkbox-wrapper,
+.ant-radio-wrapper {
+ font-family: 'PingFangCustom', Arial, sans-serif !important;
+}
\ No newline at end of file
diff --git a/web/src/assets/css/reset.css b/web/src/assets/css/reset.css
new file mode 100644
index 0000000..67d7f8e
--- /dev/null
+++ b/web/src/assets/css/reset.css
@@ -0,0 +1,43 @@
+/* global.css */
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/web/src/assets/font/PingFang.ttc b/web/src/assets/font/PingFang.ttc
new file mode 100644
index 0000000..d06aa64
Binary files /dev/null and b/web/src/assets/font/PingFang.ttc differ
diff --git a/web/src/assets/image/liteops-sidebar.png b/web/src/assets/image/liteops-sidebar.png
new file mode 100644
index 0000000..706ab53
Binary files /dev/null and b/web/src/assets/image/liteops-sidebar.png differ
diff --git a/web/src/assets/image/liteops.png b/web/src/assets/image/liteops.png
new file mode 100644
index 0000000..2d83118
Binary files /dev/null and b/web/src/assets/image/liteops.png differ
diff --git a/web/src/assets/image/liteopsv1.png b/web/src/assets/image/liteopsv1.png
new file mode 100644
index 0000000..6c2cd72
Binary files /dev/null and b/web/src/assets/image/liteopsv1.png differ
diff --git a/web/src/assets/vue.svg b/web/src/assets/vue.svg
new file mode 100644
index 0000000..770e9d3
--- /dev/null
+++ b/web/src/assets/vue.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/src/components/layout/Content.vue b/web/src/components/layout/Content.vue
new file mode 100644
index 0000000..07d1571
--- /dev/null
+++ b/web/src/components/layout/Content.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/components/layout/Footer.vue b/web/src/components/layout/Footer.vue
new file mode 100644
index 0000000..7aea788
--- /dev/null
+++ b/web/src/components/layout/Footer.vue
@@ -0,0 +1,14 @@
+
+
+ LiteOps ©2024 Created by 胡图图
+
+
+
+
\ No newline at end of file
diff --git a/web/src/components/layout/Header.vue b/web/src/components/layout/Header.vue
new file mode 100644
index 0000000..77371dd
--- /dev/null
+++ b/web/src/components/layout/Header.vue
@@ -0,0 +1,297 @@
+
+
+
+
+
+
+
+
+
+
+ 用户名
+ {{ userInfo.username }}
+
+
+ 姓名
+ {{ userInfo.name }}
+
+
+ 邮箱
+ {{ userInfo.email }}
+
+
+ 状态
+
+ {{ userInfo.status === 1 ? '正常' : '禁用' }}
+
+
+
+ 最后登录
+ {{ userInfo.login_time || '暂无记录' }}
+
+
+ 创建时间
+ {{ userInfo.create_time }}
+
+
+
+
+
+
+
+
角色
+
+
+
+ {{ role.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/components/layout/MainLayout.vue b/web/src/components/layout/MainLayout.vue
new file mode 100644
index 0000000..3987230
--- /dev/null
+++ b/web/src/components/layout/MainLayout.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/components/layout/Sidebar.vue b/web/src/components/layout/Sidebar.vue
new file mode 100644
index 0000000..e1dfe4a
--- /dev/null
+++ b/web/src/components/layout/Sidebar.vue
@@ -0,0 +1,241 @@
+
+
+
+

+

+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/main.js b/web/src/main.js
new file mode 100644
index 0000000..794024e
--- /dev/null
+++ b/web/src/main.js
@@ -0,0 +1,24 @@
+import { createApp } from 'vue'
+// import './style.css'
+import App from './App.vue'
+import Antd from 'ant-design-vue';
+import './assets/css/reset.css';
+import './assets/css/global.css';
+import axios from 'axios'
+import router from './router'
+// import permissionDirective from './directives/permission';
+
+// 配置 axios
+// 使用环境变量中的API URL
+axios.defaults.baseURL = import.meta.env.VITE_API_URL;
+
+const app = createApp(App);
+
+// 使用 Ant Design Vue 和 Vue Router
+// app.use(router).use(Antd).use(permissionDirective);
+app.use(router).use(Antd)
+
+// 将 axios 添加到 Vue 实例的全局属性中
+app.config.globalProperties.$axios = axios;
+
+app.mount('#app')
\ No newline at end of file
diff --git a/web/src/router/index.js b/web/src/router/index.js
new file mode 100644
index 0000000..262d1c6
--- /dev/null
+++ b/web/src/router/index.js
@@ -0,0 +1,246 @@
+import { createRouter, createWebHistory } from 'vue-router';
+import MainLayout from '../components/layout/MainLayout.vue';
+import axios from 'axios';
+import { permissionStore, initUserPermissions, hasMenuPermission, hasAnySubMenuPermission } from '../utils/permission';
+import { message } from 'ant-design-vue';
+
+// 添加axios响应拦截器
+axios.interceptors.response.use(
+ response => response,
+ error => {
+ if (error.response && error.response.status === 401) {
+ // 清除登录信息
+ localStorage.removeItem('token');
+ localStorage.removeItem('user_info');
+ // 跳转到登录页
+ router.push('/login');
+ }
+ return Promise.reject(error);
+ }
+);
+
+const routes = [
+ {
+ path: '/login',
+ name: 'login',
+ component: () => import('../views/login/LoginView.vue'),
+ meta: { title: '登录' }
+ },
+ {
+ path: '/',
+ component: MainLayout,
+ redirect: '/dashboard',
+ children: [
+ {
+ path: 'dashboard',
+ name: 'dashboard',
+ component: () => import('../views/dashboard/Dashboard.vue'),
+ meta: { title: '首页', permission: '/dashboard' }
+ },
+ // 项目管理
+ {
+ path: 'projects',
+ name: 'projects',
+ meta: { title: '项目管理', permission: '/projects' },
+ redirect: '/projects/list',
+ children: [
+ {
+ path: 'list',
+ name: 'project-list',
+ component: () => import('../views/projects/ProjectList.vue'),
+ meta: { title: '项目列表', permission: '/projects/list' }
+ },
+ {
+ path: 'detail',
+ name: 'project-detail',
+ component: () => import('../views/projects/ProjectDetail.vue'),
+ meta: { title: '项目详情', permission: '/projects/list' }
+ }
+ ]
+ },
+ // 构建与部署
+ {
+ path: 'build',
+ name: 'build',
+ meta: { title: '构建与部署', permission: '/build' },
+ redirect: '/build/tasks',
+ children: [
+ {
+ path: 'tasks',
+ name: 'build-tasks',
+ component: () => import('../views/build/BuildTasks.vue'),
+ meta: { title: '构建任务', permission: '/build/tasks' }
+ },
+ {
+ path: 'tasks/detail',
+ name: 'build-task-detail',
+ component: () => import('../views/build/BuildTaskDetail.vue'),
+ meta: { title: '任务详情', permission: '/build/tasks' }
+ },
+ {
+ path: 'tasks/create',
+ name: 'build-task-create',
+ component: () => import('../views/build/BuildTaskEdit.vue'),
+ meta: { title: '新建构建任务', permission: '/build/tasks' }
+ },
+ {
+ path: 'tasks/edit',
+ name: 'build-task-edit',
+ component: () => import('../views/build/BuildTaskEdit.vue'),
+ meta: { title: '编辑构建任务', permission: '/build/tasks' }
+ },
+ {
+ path: 'history',
+ name: 'build-history',
+ component: () => import('../views/build/BuildHistory.vue'),
+ meta: { title: '构建历史', permission: '/build/history' }
+ }
+ ]
+ },
+ // 日志与监控
+ {
+ path: 'logs',
+ name: 'logs',
+ meta: { title: '日志与监控', permission: '/logs' },
+ redirect: '/logs/login',
+ children: [
+ {
+ path: 'login',
+ name: 'login-logs',
+ component: () => import('../views/logs/LoginLogs.vue'),
+ meta: { title: '登陆日志', permission: '/logs/login' }
+ },
+ {
+ path: 'login/detail',
+ name: 'login-log-detail',
+ component: () => import('../views/logs/LoginLogDetail.vue'),
+ meta: { title: '登录日志详情', permission: '/logs/login' }
+ }
+ ]
+ },
+ // 用户与权限
+ {
+ path: 'user',
+ name: 'user',
+ meta: { title: '用户与权限', permission: '/user' },
+ redirect: '/user/list',
+ children: [
+ {
+ path: 'list',
+ name: 'user-list',
+ component: () => import('../views/user/UserList.vue'),
+ meta: { title: '用户管理', permission: '/user/list' }
+ },
+ {
+ path: 'role',
+ name: 'user-role',
+ component: () => import('../views/user/UserRole.vue'),
+ meta: { title: '角色管理', permission: '/user/role' }
+ }
+ ]
+ },
+ // 凭证管理
+ {
+ path: 'credentials',
+ name: 'credentials',
+ component: () => import('../views/credentials/CredentialsList.vue'),
+ meta: { title: '凭证管理', permission: '/credentials' }
+ },
+ // 环境配置
+ {
+ path: 'environments',
+ name: 'environments',
+ meta: { title: '环境配置', permission: '/environments' },
+ redirect: '/environments/list',
+ children: [
+ {
+ path: 'list',
+ name: 'environment-list',
+ component: () => import('../views/environments/EnvironmentList.vue'),
+ meta: { title: '环境列表', permission: '/environments/list' }
+ },
+ {
+ path: 'detail',
+ name: 'environment-detail',
+ component: () => import('../views/environments/EnvironmentDetail.vue'),
+ meta: { title: '环境详情', permission: '/environments/list' }
+ }
+ ]
+ },
+ // 系统配置
+ {
+ path: 'system',
+ name: 'system',
+ meta: { title: '系统配置', permission: '/system' },
+ redirect: '/system/basic',
+ children: [
+ {
+ path: 'basic',
+ name: 'system-basic',
+ component: () => import('../views/system/BasicSettings.vue'),
+ meta: { title: '基本设置', permission: '/system/basic' }
+ }
+ ]
+ },
+
+ ]
+ }
+];
+
+const router = createRouter({
+ history: createWebHistory(),
+ routes
+});
+
+// 权限初始化标志
+let permissionInitialized = false;
+
+// 路由守卫
+router.beforeEach(async (to, from, next) => {
+ // 如果是登录页,直接通过
+ if (to.path === '/login') {
+ return next();
+ }
+
+ // 检查是否已登录
+ const token = localStorage.getItem('token');
+ if (!token) {
+ return next('/login');
+ }
+
+ // 初始化权限
+ if (!permissionInitialized && !permissionStore.initialized) {
+ permissionInitialized = true;
+ const success = await initUserPermissions();
+
+ // 如果权限初始化失败,跳转到登录页
+ if (!success) {
+ console.error('权限初始化失败,重定向到登录页');
+ localStorage.removeItem('token');
+ localStorage.removeItem('user_info');
+ return next('/login');
+ }
+ }
+
+ // 检查菜单权限
+ if (to.meta && to.meta.permission) {
+ const permissionPath = to.meta.permission;
+ const pathParts = permissionPath.split('/');
+ const isSubMenu = pathParts.length > 2;
+
+ // 如果是子菜单,同时检查直接权限和父菜单的子权限
+ const hasPermission = isSubMenu ?
+ (hasMenuPermission(permissionPath) || hasAnySubMenuPermission(permissionPath)) :
+ hasMenuPermission(permissionPath);
+
+ if (!hasPermission) {
+ console.warn(`用户无权访问路由: ${to.path}, 所需权限: ${to.meta.permission}`);
+ message.error(`你没有访问${to.meta.title || '该页面'}的权限`);
+ return next('/dashboard');
+ }
+ }
+
+ next();
+});
+
+export default router;
\ No newline at end of file
diff --git a/web/src/style.css b/web/src/style.css
new file mode 100644
index 0000000..06458a9
--- /dev/null
+++ b/web/src/style.css
@@ -0,0 +1,75 @@
+:root {
+ font-family: 'PingFangCustom', Arial, sans-serif !important;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+ padding: 2em;
+}
+
+#app {
+ width: 100%;
+ height: 100vh;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/web/src/utils/permission.js b/web/src/utils/permission.js
new file mode 100644
index 0000000..5831e56
--- /dev/null
+++ b/web/src/utils/permission.js
@@ -0,0 +1,222 @@
+import axios from 'axios';
+import { ref, reactive } from 'vue';
+import { message } from 'ant-design-vue';
+
+// 权限数据存储
+export const permissionStore = reactive({
+ initialized: false,
+ menuPermissions: [],
+ functionPermissions: {},
+ dataPermissions: {
+ project_scope: 'all',
+ project_ids: [],
+ environment_scope: 'all',
+ environment_types: []
+ }
+});
+
+// 初始化用户权限
+export const initUserPermissions = async () => {
+ try {
+ // 从本地存储获取用户信息
+ const userInfo = JSON.parse(localStorage.getItem('user_info') || '{}');
+
+ if (!userInfo.user_id) {
+ console.error('用户信息不存在,权限初始化失败');
+ return false;
+ }
+
+ // 获取用户的角色权限
+ const token = localStorage.getItem('token');
+ const response = await axios.get('/api/user/permissions', {
+ headers: {
+ 'Authorization': token
+ }
+ });
+
+ if (response.data.code === 200) {
+ const permissions = response.data.data;
+
+ // 存储权限信息
+ permissionStore.menuPermissions = permissions.menu || [];
+ permissionStore.functionPermissions = permissions.function || {};
+ permissionStore.dataPermissions = permissions.data || {
+ project_scope: 'all',
+ project_ids: [],
+ environment_scope: 'all',
+ environment_types: []
+ };
+
+ permissionStore.initialized = true;
+
+ return true;
+ } else {
+ console.error('获取用户权限失败:', response.data.message);
+ return false;
+ }
+ } catch (error) {
+ console.error('初始化用户权限失败:', error);
+ return false;
+ }
+};
+
+// 检查菜单权限
+export const hasMenuPermission = (menuPath) => {
+ if (!permissionStore.initialized) {
+ console.warn('权限尚未初始化,拒绝所有菜单权限', menuPath);
+ return false;
+ }
+
+ const userInfo = JSON.parse(localStorage.getItem('user_info') || '{}');
+ if (userInfo.is_admin) {
+ console.log(`用户是管理员,自动拥有菜单权限: ${menuPath}`);
+ return true;
+ }
+
+ const hasPermission = permissionStore.menuPermissions.includes(menuPath);
+ return hasPermission;
+};
+
+// 检查是否有子菜单权限
+export const hasAnySubMenuPermission = (parentPath) => {
+ if (!permissionStore.initialized) {
+ return false;
+ }
+
+ // 管理员拥有所有权限
+ const userInfo = JSON.parse(localStorage.getItem('user_info') || '{}');
+ if (userInfo.is_admin) {
+ return true;
+ }
+
+ // 检查是否直接拥有父菜单权限
+ if (permissionStore.menuPermissions.includes(parentPath)) {
+ return true;
+ }
+
+ // 检查是否拥有任何以父菜单路径开头的子菜单权限
+ return permissionStore.menuPermissions.some(permission =>
+ permission !== parentPath && permission.startsWith(`${parentPath}/`)
+ );
+};
+
+// 检查功能权限
+export const hasFunctionPermission = (module, action) => {
+ if (!permissionStore.initialized) {
+ return false;
+ }
+
+ const modulePermissions = permissionStore.functionPermissions[module] || [];
+ return modulePermissions.includes(action);
+};
+
+// 检查项目数据权限
+export const hasProjectPermission = (projectId) => {
+ if (!permissionStore.initialized) {
+ return false;
+ }
+
+ // 如果有所有项目的权限
+ if (permissionStore.dataPermissions.project_scope === 'all') {
+ return true;
+ }
+
+ return permissionStore.dataPermissions.project_ids.includes(projectId);
+};
+
+// 检查环境数据权限
+export const hasEnvironmentPermission = (environmentType) => {
+ if (!permissionStore.initialized) {
+ return false;
+ }
+
+ // 如果有所有环境的权限
+ if (permissionStore.dataPermissions.environment_scope === 'all') {
+ return true;
+ }
+
+ return permissionStore.dataPermissions.environment_types.includes(environmentType);
+};
+
+export const getPermittedProjectIds = () => {
+ if (!permissionStore.initialized) {
+ return [];
+ }
+
+ if (permissionStore.dataPermissions.project_scope === 'all') {
+ return null;
+ }
+
+ return permissionStore.dataPermissions.project_ids || [];
+};
+
+// 获取有权限的环境类型
+export const getPermittedEnvironmentTypes = () => {
+ if (!permissionStore.initialized) {
+ return [];
+ }
+
+ if (permissionStore.dataPermissions.environment_scope === 'all') {
+ return null;
+ }
+
+ return permissionStore.dataPermissions.environment_types || [];
+};
+
+// 统一的权限错误提示
+export const showPermissionError = (module, action) => {
+ let errorMsg = '你没有权限执行此操作';
+
+ if (module && action) {
+ const actionText = {
+ 'view': '查看',
+ 'create': '创建',
+ 'edit': '编辑',
+ 'delete': '删除',
+ 'execute': '执行',
+ 'deploy': '部署',
+ 'rollback': '回滚',
+ 'approve': '审批',
+ 'test': '测试',
+ 'view_log': '查看日志',
+ 'disable': '禁用/启用',
+ }[action] || action;
+
+ const moduleText = {
+ 'project': '项目',
+ 'build': '构建任务',
+ 'build_task': '构建任务',
+ 'build_history': '构建历史',
+ 'environment': '环境',
+ 'credential': '凭证',
+ 'user': '用户',
+ 'role': '角色',
+ 'notification': '通知'
+ }[module] || module;
+
+ errorMsg = `你没有${moduleText}${actionText}权限`;
+ }
+
+ message.error(errorMsg);
+ return false;
+};
+
+// 检查功能权限和数据权限
+export const checkPermission = (module, action, entityId = null, entityType = 'project') => {
+ if (!hasFunctionPermission(module, action)) {
+ showPermissionError(module, action);
+ return false;
+ }
+
+ if (entityId) {
+ if (entityType === 'project' && !hasProjectPermission(entityId)) {
+ message.error('你没有该项目的访问权限');
+ return false;
+ } else if (entityType === 'environment' && !hasEnvironmentPermission(entityId)) {
+ message.error('你没有该环境的访问权限');
+ return false;
+ }
+ }
+
+ return true;
+};
\ No newline at end of file
diff --git a/web/src/views/build/BuildHistory.vue b/web/src/views/build/BuildHistory.vue
new file mode 100644
index 0000000..bb82c9b
--- /dev/null
+++ b/web/src/views/build/BuildHistory.vue
@@ -0,0 +1,682 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ record.task.name }}
+
+
+
+ {{ record.commit }}
+
+
+ {{ record.version }}
+
+
+ {{ record.environment }}
+
+
+ {{ record.operator }}
+
+
+ {{ record.requirement }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 开始时间: {{ stage.startTime }}
+
+
+
+ 耗时: {{ stage.duration }}
+
+
+ 查看日志
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确定要回滚 {{ selectedTaskName }}任务 到版本 {{ selectedVersion }} 吗?
+ 注意:回滚操作不可逆,请谨慎操作!
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/build/BuildTaskDetail.vue b/web/src/views/build/BuildTaskDetail.vue
new file mode 100644
index 0000000..f52de10
--- /dev/null
+++ b/web/src/views/build/BuildTaskDetail.vue
@@ -0,0 +1,300 @@
+
+
+
+
+
+
+
+ {{ taskDetail.task_id }}
+
+
+ {{ taskDetail.name }}
+
+
+ {{ getStatusText(taskDetail.status) }}
+
+
+ {{ taskDetail.project?.name }}
+
+
+ {{ taskDetail.environment?.name }}
+
+
+ {{ taskDetail.environment?.type }}
+
+
+ {{ taskDetail.project?.repository }}
+
+
+
+ {{ taskDetail.branch }}
+
+
+
+ 未设置(将使用仓库默认分支)
+
+
+
+ {{ taskDetail.git_token?.name }}
+
+
+ {{ taskDetail.version || '暂无' }}
+
+
+ {{ taskDetail.version || '暂无' }}
+
+
+ {{ taskDetail.requirement || '暂无' }}
+
+
+ {{ taskDetail.creator?.name }}
+
+
+ {{ taskDetail.create_time }}
+
+
+ {{ taskDetail.update_time }}
+
+
+ {{ taskDetail.description || '暂无描述' }}
+
+
+
+
+
+
+
通知设置
+
+
+
+
+
+
+
+
+
+ {{ getRobotNameById(robotId) }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/build/BuildTaskEdit.vue b/web/src/views/build/BuildTaskEdit.vue
new file mode 100644
index 0000000..c6b011d
--- /dev/null
+++ b/web/src/views/build/BuildTaskEdit.vue
@@ -0,0 +1,879 @@
+
+
+
+
+
+
+
+
+
+
+ 任务名称将作为 Jenkins Job 名称,不能包含特殊字符
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 设置构建时默认选中的分支,留空则默认选择仓库的默认分支。实际构建时将使用用户选择的分支进行构建。
+
+
+
+
+
+
+
+
+
+
+ 用于访问Git仓库的Token凭证,如果没有合适的凭证,请先在凭证管理中添加
+
+
+
+
+
+
+
+
+
+ 构建配置
+
+
+
+
+
+
+
+
+
+ 使用外部脚本库
+
+ 从Git仓库拉取构建和部署脚本,在Shell脚本中调用
+
+
+
+
+
+
+ {{ truncateUrl(formState.external_script_repo_url) }}
+
+
+
+ {{ formState.external_script_directory }}
+
+
+ {{ formState.external_script_branch }}
+
+
+
+ 修改配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/build/BuildTasks.vue b/web/src/views/build/BuildTasks.vue
new file mode 100644
index 0000000..0dd2c27
--- /dev/null
+++ b/web/src/views/build/BuildTasks.vue
@@ -0,0 +1,1701 @@
+
+
+
+
+
+
+
+
+
+
+ 全部项目
+
+ {{ project.name }}
+
+
+
+
+
+ 全部环境
+
+ {{ env.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+ {{ record.name }}
+
+
+
+
+
+
+
+ 构建中
+
+
+ {{ getTaskStatusText(record.status) }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ record.total_builds }}
+
+
+
+
+
+ {{ record.success_builds }}
+
+
+
+
+
+ {{ record.failure_builds }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formatBuildTime(record.last_build.time) }}
+
+
+
+ {{ record.last_build.duration }}
+
+
+
+
+ 暂无构建记录
+
+
+
+
+
+
+
+ 构建中...
+ 立即构建
+
+
+ 停止构建
+
+
+
+ 更多
+
+
+
+
+
+ 日志
+
+
+ 编辑
+
+
+
+
+ 启用
+
+
+ 禁用
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ branch.name }}
+
+ {{ branch.commit.author_name }}
+ {{ branch.commit.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.short_id }}
+ {{ item.title }}
+
+
+
+ {{ item.author_name }}
+
+
+ {{ item.created_at }}
+
+
+
+ {{ item.message }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
提示: 可以去 构建历史 页面查找测试环境最新的构建版本
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/build/components/BuildNotification.vue b/web/src/views/build/components/BuildNotification.vue
new file mode 100644
index 0000000..89c43a8
--- /dev/null
+++ b/web/src/views/build/components/BuildNotification.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+ 选择构建完成后的通知方式,无论构建成功或失败都会发送通知
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/build/components/CodeEditor.vue b/web/src/views/build/components/CodeEditor.vue
new file mode 100644
index 0000000..534f03a
--- /dev/null
+++ b/web/src/views/build/components/CodeEditor.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/build/components/FullscreenLogViewer.vue b/web/src/views/build/components/FullscreenLogViewer.vue
new file mode 100644
index 0000000..b4c7e87
--- /dev/null
+++ b/web/src/views/build/components/FullscreenLogViewer.vue
@@ -0,0 +1,311 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/build/components/SystemVariablesList.vue b/web/src/views/build/components/SystemVariablesList.vue
new file mode 100644
index 0000000..e05182c
--- /dev/null
+++ b/web/src/views/build/components/SystemVariablesList.vue
@@ -0,0 +1,226 @@
+
+
+
+
+ 查看可用系统变量
+
+
+
+
+
+
+
+
+
+
+ {{ text }}
+
+
+ {{ text }}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/credentials/CredentialsList.vue b/web/src/views/credentials/CredentialsList.vue
new file mode 100644
index 0000000..d33efe1
--- /dev/null
+++ b/web/src/views/credentials/CredentialsList.vue
@@ -0,0 +1,904 @@
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ record.deploy_status }}
+
+
+
+
+ 编辑
+
+ 部署
+
+
+ 取消部署
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ record.cluster_name }}
+
+
+ liteops-{{ record.context_name }}
+
+
+
+ {{ record.deploy_status }}
+
+
+
+
+ 编辑
+
+ 部署
+
+
+ 取消部署
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 上传Kubeconfig文件
+
+
+
+
+
+
+
+
• 上传或粘贴你的Kubeconfig文件内容
+
• 系统将自动解析并提取集群和上下文信息
+
• 部署后,上下文名称将添加 "liteops-" 前缀以避免冲突
+
+
+
+
+
+
+
+
+
+
+
+
+
+
部署后,该SSH密钥将被配置到CI/CD容器中,你可以在构建脚本中直接使用:
+
ssh user@your-server-ip
+
来连接到远程服务器进行部署操作。
+
+
+
+
+
+
+
+
+
+
+
+
+ 使用说明:
+ 密钥文件: {{ deployResult.data.key_file }}
+ 使用示例: {{ deployResult.data.usage_example }}
+ 在构建脚本中使用:
+ ssh root@192.168.1.100
+ssh user@your-server-ip
+
+
+
+ {{ deployResult.data.usage_info }}
+
+
+ 知道了
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/dashboard/Dashboard.vue b/web/src/views/dashboard/Dashboard.vue
new file mode 100644
index 0000000..abfa0f2
--- /dev/null
+++ b/web/src/views/dashboard/Dashboard.vue
@@ -0,0 +1,895 @@
+
+
+
+
+
+
+
+
+ 项目总数
+
+
+
+
{{ stats.project_count || 0 }}
+
总构建: {{ stats.total_builds_count || 0 }} | 任务: {{ stats.task_count || 0 }}
+
+
+
+
+
+
+
+ 用户总数
+
+
+
+
{{ stats.user_count || 0 }}
+
环境数量: {{ stats.env_count || 0 }}
+
+
+
+
+
+
+
+ 构建成功率
+
+
+
+
{{ stats.success_rate || 0 }}%
+
最近7天: {{ stats.total_recent_builds || 0 }} 次构建
+
+
+
+
+
+
+
+ 今日构建
+
+
+
+
{{ todayBuilds.length }}
+
成功: {{ successBuilds }} | 失败: {{ failedBuilds }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getStatusText(record.status) }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/environments/EnvironmentDetail.vue b/web/src/views/environments/EnvironmentDetail.vue
new file mode 100644
index 0000000..a3d82da
--- /dev/null
+++ b/web/src/views/environments/EnvironmentDetail.vue
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+ 环境名称:
+ {{ environment?.name }}
+
+
+ 环境ID:
+ {{ environment?.environment_id }}
+
+
+ 环境类型:
+ {{ getEnvironmentTypeText(environment?.type) }}
+
+
+
+ 创建者:
+ {{ environment?.creator?.name || '未知' }}
+
+
+ 创建时间:
+ {{ environment?.create_time }}
+
+
+ 更新时间:
+ {{ environment?.update_time }}
+
+
+ 环境描述:
+ {{ environment?.description || '暂无描述' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 开发环境
+ 测试环境
+ 预发布环境
+ 生产环境
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+ 保存
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/environments/EnvironmentList.vue b/web/src/views/environments/EnvironmentList.vue
new file mode 100644
index 0000000..bd3bf77
--- /dev/null
+++ b/web/src/views/environments/EnvironmentList.vue
@@ -0,0 +1,459 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 开发环境
+ 测试环境
+ 预发布环境
+ 生产环境
+
+
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+ {{ record.name }}
+
+
+ {{ getEnvironmentTypeText(record.type) }}
+
+
+
+ 查看
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 开发环境
+ 测试环境
+ 预发布环境
+ 生产环境
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+ {{ isEdit ? '保存' : '创建' }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/login/LoginView.vue b/web/src/views/login/LoginView.vue
new file mode 100644
index 0000000..f30f7b8
--- /dev/null
+++ b/web/src/views/login/LoginView.vue
@@ -0,0 +1,269 @@
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登 录
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/logs/LoginLogDetail.vue b/web/src/views/logs/LoginLogDetail.vue
new file mode 100644
index 0000000..a5c0b72
--- /dev/null
+++ b/web/src/views/logs/LoginLogDetail.vue
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+ 日志ID:
+ {{ logDetail.log_id }}
+
+
+ 用户名:
+ {{ logDetail.username || '未知用户' }}
+
+
+ 用户姓名:
+ {{ logDetail.user_name || '未知' }}
+
+
+ 登录状态:
+
+ {{ logDetail.status === 'success' ? '成功' : '失败' }}
+
+
+
+ 失败原因:
+ {{ logDetail.fail_reason }}
+
+
+ 登录IP:
+ {{ logDetail.ip_address }}
+
+
+ 登录时间:
+ {{ logDetail.login_time }}
+
+
+ 用户代理:
+ {{ logDetail.user_agent }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/logs/LoginLogs.vue b/web/src/views/logs/LoginLogs.vue
new file mode 100644
index 0000000..1048634
--- /dev/null
+++ b/web/src/views/logs/LoginLogs.vue
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+
+
+ {{ record.status === 'success' ? '成功' : '失败' }}
+
+
+
+ {{ record.login_time }}
+
+
+ 详情
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/projects/ProjectDetail.vue b/web/src/views/projects/ProjectDetail.vue
new file mode 100644
index 0000000..d42c311
--- /dev/null
+++ b/web/src/views/projects/ProjectDetail.vue
@@ -0,0 +1,312 @@
+
+
+
+
+
+
+
+
+ 项目名称:
+ {{ project?.name }}
+
+
+ 项目ID:
+ {{ project?.project_id }}
+
+
+ 服务类别:
+ {{ getCategoryText(project?.category) }}
+
+
+ GitLab仓库:
+ {{ project?.repository }}
+
+
+ 创建者:
+ {{ project?.creator?.name }}
+
+
+ 创建时间:
+ {{ project?.create_time }}
+
+
+ 更新时间:
+ {{ project?.update_time }}
+
+
+ 项目描述:
+ {{ project?.description || '暂无描述' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 前端服务
+ 后端服务
+ 移动端服务
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+ 保存
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/projects/ProjectEdit.vue b/web/src/views/projects/ProjectEdit.vue
new file mode 100644
index 0000000..c3881eb
--- /dev/null
+++ b/web/src/views/projects/ProjectEdit.vue
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存修改
+ 重置
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/projects/ProjectList.vue b/web/src/views/projects/ProjectList.vue
new file mode 100644
index 0000000..8831b35
--- /dev/null
+++ b/web/src/views/projects/ProjectList.vue
@@ -0,0 +1,428 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 前端服务
+ 后端服务
+ 移动端服务
+
+
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+ {{ record.name }}
+
+
+ {{ getCategoryText(record.category) }}
+
+
+ {{ record.creator.name }}
+
+
+
+ 查看
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 前端服务
+ 后端服务
+ 移动端服务
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+ 创建
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/system/BasicSettings.vue b/web/src/views/system/BasicSettings.vue
new file mode 100644
index 0000000..34c8667
--- /dev/null
+++ b/web/src/views/system/BasicSettings.vue
@@ -0,0 +1,670 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 包含大写字母
+ 包含小写字母
+ 包含数字
+ 包含特殊字符
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getRobotTypeText(record.type) }}
+
+
+ {{ getSecurityTypeText(record.security_type) }}
+
+
+
+
+ 编辑
+
+
+ 测试
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 钉钉机器人
+ 企业微信机器人
+ 飞书机器人
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 无
+ 加签密钥
+ 自定义关键词
+ IP地址(段)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 支持IP地址或IP地址段,例如: 192.168.1.1 或 192.168.1.1/24
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/system/Notification.vue b/web/src/views/system/Notification.vue
new file mode 100644
index 0000000..ded6a8e
--- /dev/null
+++ b/web/src/views/system/Notification.vue
@@ -0,0 +1,476 @@
+
+
+
+
+
+
+
+
+
+
+ 编辑
+
+
+ 测试
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+ 钉钉机器人
+ 企业微信机器人
+ 飞书机器人
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 无
+ 加签密钥
+ 自定义关键词
+ IP地址(段)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 支持IP地址或IP地址段,例如: 192.168.1.1 或 192.168.1.1/24
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/user/UserList.vue b/web/src/views/user/UserList.vue
new file mode 100644
index 0000000..7af3934
--- /dev/null
+++ b/web/src/views/user/UserList.vue
@@ -0,0 +1,501 @@
+
+
+
+
+
+
+
+
+
+ {{ record.status === 1 ? '正常' : '锁定' }}
+
+
+
+
+
+ {{ role.name }}
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+
+ {{ record.status === 1 ? '锁定' : '解锁' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 不修改请留空
+
+
+
+
+ {{ role.name }}
+
+
+
+
+
+ 正常
+ 锁定
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/user/UserRole.vue b/web/src/views/user/UserRole.vue
new file mode 100644
index 0000000..d76236c
--- /dev/null
+++ b/web/src/views/user/UserRole.vue
@@ -0,0 +1,788 @@
+
+
+
+
+
+
+
+
+
+ 编辑
+
+
+ 删除
+
+
+ 权限配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 所有项目
+ 自定义项目
+
+
+
+
+
+ 暂无项目可选
+
+
+
+
+
+
+
+
+ 所有环境
+ 自定义环境
+
+
+
+
+
+ 暂无环境类型可选
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/vite.config.js b/web/vite.config.js
new file mode 100644
index 0000000..15cf964
--- /dev/null
+++ b/web/vite.config.js
@@ -0,0 +1,16 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [vue()],
+ server: {
+ port: 8000,
+ host: true,
+ strictPort: true,
+ cors: true,
+ allowedHosts: [
+ '0.0.0.0',
+ ]
+ }
+})
\ No newline at end of file