mirror of
https://github.com/opsre/LiteOps.git
synced 2026-04-22 05:07:25 +08:00
✨ feat:
优化: 1. 环境列表删除提示 2. 部署文档微调 新增: 1. 构建任务不同环境下构建方式选择
This commit is contained in:
14
README.md
14
README.md
@@ -154,10 +154,10 @@ chmod +x start-containers.sh
|
|||||||
```
|
```
|
||||||
#### 5. 不使用一键部署方式,自定义数据库
|
#### 5. 不使用一键部署方式,自定义数据库
|
||||||
|
|
||||||
##### 方案A:配置文件挂载方式(推荐)
|
##### 方案A:配置文件挂载方式
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 先启动MySQL容器(可选)
|
# 1. 先启动MySQL容器(可自定义mysql)
|
||||||
docker run -d \
|
docker run -d \
|
||||||
--name liteops-mysql \
|
--name liteops-mysql \
|
||||||
-e MYSQL_ROOT_PASSWORD=your_password \
|
-e MYSQL_ROOT_PASSWORD=your_password \
|
||||||
@@ -246,14 +246,14 @@ docker logs liteops-mysql
|
|||||||
- **用户名**:admin
|
- **用户名**:admin
|
||||||
- **密码**:admin123 (初始密码,可自行修改)
|
- **密码**:admin123 (初始密码,可自行修改)
|
||||||
|
|
||||||
## 📋 手动部署(源码启动)
|
## 📋 源码部署
|
||||||
|
|
||||||
如果你想从源码运行 LiteOps,可以按照以下步骤操作:
|
如果你想从源码运行 LiteOps,可以按照以下步骤操作:
|
||||||
|
|
||||||
### 环境要求
|
### 环境要求
|
||||||
|
|
||||||
- **Python**:3.8+
|
- **Python**:3.9+
|
||||||
- **Node.js**:16+
|
- **Node.js**:18+
|
||||||
- **MySQL**:8.0+
|
- **MySQL**:8.0+
|
||||||
- **Git**:用于克隆源码
|
- **Git**:用于克隆源码
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ npm install
|
|||||||
# 开发模式启动
|
# 开发模式启动
|
||||||
npm run dev
|
npm run dev
|
||||||
|
|
||||||
# 生产环境构建
|
# 生产环境构建(dist静态文件)
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ npm run build
|
|||||||
|
|
||||||
- **生产模式**:
|
- **生产模式**:
|
||||||
- 配置 Nginx Web 服务器托管前端构建文件
|
- 配置 Nginx Web 服务器托管前端构建文件
|
||||||
- 后端继续使用 http://localhost:8900
|
- 后端接口 http://localhost:8900
|
||||||
|
|
||||||
### 注意事项
|
### 注意事项
|
||||||
|
|
||||||
|
|||||||
@@ -358,9 +358,15 @@ class Builder:
|
|||||||
# 获取环境类型
|
# 获取环境类型
|
||||||
environment_type = self.task.environment.type if self.task.environment else None
|
environment_type = self.task.environment.type if self.task.environment else None
|
||||||
|
|
||||||
# 根据环境类型决定是否需要克隆代码
|
# 根据是否有分支信息决定是否需要克隆代码
|
||||||
if environment_type in ['development', 'testing']:
|
should_clone_code = (
|
||||||
# 只在开发和测试环境克隆代码
|
environment_type in ['development', 'testing'] or
|
||||||
|
(environment_type in ['staging', 'production'] and self.history.branch)
|
||||||
|
)
|
||||||
|
|
||||||
|
if should_clone_code:
|
||||||
|
# 克隆代码
|
||||||
|
self.send_log(f"开始克隆代码,分支: {self.history.branch}", "Git Clone")
|
||||||
clone_start_time = time.time()
|
clone_start_time = time.time()
|
||||||
if not self.clone_repository():
|
if not self.clone_repository():
|
||||||
self._update_build_stats(False) # 更新失败统计
|
self._update_build_stats(False) # 更新失败统计
|
||||||
@@ -377,9 +383,8 @@ class Builder:
|
|||||||
'duration': str(int(time.time() - clone_start_time))
|
'duration': str(int(time.time() - clone_start_time))
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
pass
|
# 预发布/生产环境使用版本模式,不克隆代码
|
||||||
# self.send_log(f"预发布/生产环境构建,跳过代码克隆,直接使用版本: {self.version}", "Environment")
|
# self.send_log(f"预发布/生产环境版本模式,使用版本: {self.history.version}", "Environment")
|
||||||
|
|
||||||
# 创建构建目录
|
# 创建构建目录
|
||||||
os.makedirs(self.build_path, exist_ok=True)
|
os.makedirs(self.build_path, exist_ok=True)
|
||||||
|
|
||||||
|
|||||||
@@ -861,11 +861,7 @@ class BuildExecuteView(View):
|
|||||||
'message': 'Commit ID不能为空'
|
'message': 'Commit ID不能为空'
|
||||||
})
|
})
|
||||||
elif env_type in ['staging', 'production']:
|
elif env_type in ['staging', 'production']:
|
||||||
if not version:
|
if version:
|
||||||
return JsonResponse({
|
|
||||||
'code': 400,
|
|
||||||
'message': '版本号不能为空'
|
|
||||||
})
|
|
||||||
parts = version.split('_')
|
parts = version.split('_')
|
||||||
if len(parts) == 2 and len(parts[1]) >= 8:
|
if len(parts) == 2 and len(parts[1]) >= 8:
|
||||||
commit_id = parts[1]
|
commit_id = parts[1]
|
||||||
@@ -874,6 +870,13 @@ class BuildExecuteView(View):
|
|||||||
'code': 400,
|
'code': 400,
|
||||||
'message': '版本号格式不正确,应为:YYYYMMDDHHmmSS_commitId'
|
'message': '版本号格式不正确,应为:YYYYMMDDHHmmSS_commitId'
|
||||||
})
|
})
|
||||||
|
elif branch and commit_id:
|
||||||
|
pass # 参数验证通过,继续执行
|
||||||
|
else:
|
||||||
|
return JsonResponse({
|
||||||
|
'code': 400,
|
||||||
|
'message': '预发布和生产环境请选择构建方式:输入版本号或选择分支进行重新构建'
|
||||||
|
})
|
||||||
|
|
||||||
if not requirement:
|
if not requirement:
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
@@ -932,9 +935,9 @@ class BuildExecuteView(View):
|
|||||||
history_id=generate_id(),
|
history_id=generate_id(),
|
||||||
task=task,
|
task=task,
|
||||||
build_number=build_number,
|
build_number=build_number,
|
||||||
branch=branch if branch else '', # 对于预发布和生产环境,分支为空
|
branch=branch if branch else '',
|
||||||
commit_id=commit_id,
|
commit_id=commit_id,
|
||||||
version=version if version else None, # 对于预发布和生产环境,使用传入的版本号
|
version=version if version else None,
|
||||||
status='pending', # 初始状态为等待中
|
status='pending', # 初始状态为等待中
|
||||||
requirement=requirement,
|
requirement=requirement,
|
||||||
parameter_values=parameter_values,
|
parameter_values=parameter_values,
|
||||||
|
|||||||
@@ -338,12 +338,34 @@
|
|||||||
</a-spin>
|
</a-spin>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 预发布和生产环境的构建方式选择 -->
|
||||||
|
<a-form-item
|
||||||
|
label="构建方式"
|
||||||
|
required
|
||||||
|
v-if="isStagingOrProdEnv"
|
||||||
|
>
|
||||||
|
<a-radio-group v-model:value="buildForm.buildType" @change="handleBuildTypeChange">
|
||||||
|
<a-radio value="existing_version">现有版本</a-radio>
|
||||||
|
<a-radio value="rebuild">重新构建</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
<div class="build-type-description">
|
||||||
|
<div v-if="buildForm.buildType === 'existing_version'" class="description-text">
|
||||||
|
<InfoCircleOutlined style="margin-right: 4px; color: #1890ff;" />
|
||||||
|
使用已在其它环境验证通过的版本进行部署,适用于可通过环境变量切换接口的项目
|
||||||
|
</div>
|
||||||
|
<div v-else class="description-text">
|
||||||
|
<InfoCircleOutlined style="margin-right: 4px; color: #ff7875;" />
|
||||||
|
重新从代码构建,适用于构建时需要指定不同环境接口配置的项目(如前端项目需要env指定)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<!-- 预发布和生产环境的版本选择 -->
|
<!-- 预发布和生产环境的版本选择 -->
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="输入版本号"
|
label="输入版本号"
|
||||||
required
|
required
|
||||||
v-if="isStagingOrProdEnv"
|
v-if="isStagingOrProdEnv && buildForm.buildType === 'existing_version'"
|
||||||
help="请输入已在测试环境验证通过的版本号,可以在测试环境构建历史中查看"
|
help="请输入已在其它环境验证通过的版本号,可以在构建历史中查看"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="buildForm.version"
|
v-model:value="buildForm.version"
|
||||||
@@ -354,17 +376,77 @@
|
|||||||
<TagOutlined style="color: rgba(0, 0, 0, 0.25)" />
|
<TagOutlined style="color: rgba(0, 0, 0, 0.25)" />
|
||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<a-tooltip title="版本号格式为: 年月日时分秒_提交ID前8位,可以在测试环境的构建历史中找到">
|
<a-tooltip title="版本号格式为: 年月日时分秒_提交ID前8位,可以在构建历史中找到">
|
||||||
<QuestionCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
<QuestionCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<div class="version-tip">
|
<div class="version-tip">
|
||||||
<InfoCircleOutlined style="margin-right: 4px; color: #1890ff;" />
|
<InfoCircleOutlined style="margin-right: 4px; color: #1890ff;" />
|
||||||
<span>提示: 可以去 <a @click="goToBuildHistory">构建历史</a> 页面查找测试环境最新的构建版本</span>
|
<span>提示: 可以去 <a @click="goToBuildHistory">构建历史</a> 页面查找其它环境最新的构建版本</span>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 预发布和生产环境重新构建时的分支选择 -->
|
||||||
|
<a-form-item label="选择分支" required v-if="isStagingOrProdEnv && buildForm.buildType === 'rebuild'">
|
||||||
|
<a-select
|
||||||
|
showSearch
|
||||||
|
v-model:value="buildForm.branch"
|
||||||
|
placeholder="请选择分支"
|
||||||
|
:loading="branchLoading"
|
||||||
|
@change="handleBranchChange"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="branch in branchList"
|
||||||
|
:key="branch.name"
|
||||||
|
:value="branch.name"
|
||||||
|
>
|
||||||
|
<span>{{ branch.name }}</span>
|
||||||
|
<span class="branch-commit-info">
|
||||||
|
<a-tag size="small">{{ branch.commit.author_name }}</a-tag>
|
||||||
|
{{ branch.commit.title }}
|
||||||
|
</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 预发布和生产环境重新构建时的提交记录 -->
|
||||||
|
<a-form-item label="提交记录" required v-if="isStagingOrProdEnv && buildForm.buildType === 'rebuild'">
|
||||||
|
<a-spin :spinning="commitLoading">
|
||||||
|
<div class="commit-list-wrapper">
|
||||||
|
<a-list
|
||||||
|
class="commit-list"
|
||||||
|
:data-source="commitList"
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<template #renderItem="{ item }">
|
||||||
|
<a-list-item>
|
||||||
|
<div class="commit-item">
|
||||||
|
<div class="commit-title">
|
||||||
|
<span class="commit-id">{{ item.short_id }}</span>
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
|
<div class="commit-meta">
|
||||||
|
<span class="commit-author">
|
||||||
|
<UserOutlined /> {{ item.author_name }}
|
||||||
|
</span>
|
||||||
|
<span class="commit-time">
|
||||||
|
<ClockCircleOutlined /> {{ item.created_at }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="commit-message" v-if="item.message">
|
||||||
|
{{ item.message }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<!-- 构建参数选择 -->
|
<!-- 构建参数选择 -->
|
||||||
<div v-if="selectedTask?.parameters?.length > 0" class="build-parameters">
|
<div v-if="selectedTask?.parameters?.length > 0" class="build-parameters">
|
||||||
<a-divider>构建参数</a-divider>
|
<a-divider>构建参数</a-divider>
|
||||||
@@ -448,6 +530,7 @@ const buildForm = reactive({
|
|||||||
commit_id: '',
|
commit_id: '',
|
||||||
requirement: '',
|
requirement: '',
|
||||||
version: '',
|
version: '',
|
||||||
|
buildType: 'existing_version',
|
||||||
parameterValues: {},
|
parameterValues: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -726,6 +809,7 @@ const handleBuild = async (record) => {
|
|||||||
buildForm.commit_id = '';
|
buildForm.commit_id = '';
|
||||||
buildForm.requirement = '';
|
buildForm.requirement = '';
|
||||||
buildForm.version = '';
|
buildForm.version = '';
|
||||||
|
buildForm.buildType = 'existing_version';
|
||||||
buildForm.parameterValues = {};
|
buildForm.parameterValues = {};
|
||||||
|
|
||||||
// 初始化参数默认值
|
// 初始化参数默认值
|
||||||
@@ -1050,6 +1134,21 @@ const handleBranchChange = async (branch) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理构建方式变更
|
||||||
|
const handleBuildTypeChange = async () => {
|
||||||
|
if (buildForm.buildType === 'rebuild') {
|
||||||
|
buildForm.version = '';
|
||||||
|
if (isStagingOrProdEnv.value) {
|
||||||
|
await loadBranches();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buildForm.branch = '';
|
||||||
|
buildForm.commit_id = '';
|
||||||
|
branchList.value = [];
|
||||||
|
commitList.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 连接SSE
|
// 连接SSE
|
||||||
const connectSSE = (taskId, buildNumber, preserveLog = false) => {
|
const connectSSE = (taskId, buildNumber, preserveLog = false) => {
|
||||||
const protocol = window.location.protocol;
|
const protocol = window.location.protocol;
|
||||||
@@ -1218,8 +1317,10 @@ const confirmBuild = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预发布和生产环境需要输入版本号
|
// 预发布和生产环境的验证逻辑
|
||||||
if (isStagingOrProdEnv.value) {
|
if (isStagingOrProdEnv.value) {
|
||||||
|
if (buildForm.buildType === 'existing_version') {
|
||||||
|
// 使用已验证版本,需要输入版本号
|
||||||
if (!buildForm.version) {
|
if (!buildForm.version) {
|
||||||
message.warning('请输入版本号');
|
message.warning('请输入版本号');
|
||||||
return;
|
return;
|
||||||
@@ -1229,6 +1330,12 @@ const confirmBuild = async () => {
|
|||||||
message.warning('版本号格式不正确,请输入类似 "20250320112507_029e149e" 的格式(年月日时分秒_提交ID前8位)');
|
message.warning('版本号格式不正确,请输入类似 "20250320112507_029e149e" 的格式(年月日时分秒_提交ID前8位)');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (buildForm.buildType === 'rebuild') {
|
||||||
|
if (!buildForm.branch) {
|
||||||
|
message.warning('请选择分支');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!buildForm.requirement) {
|
if (!buildForm.requirement) {
|
||||||
@@ -1256,7 +1363,12 @@ const confirmBuild = async () => {
|
|||||||
requestData.branch = buildForm.branch;
|
requestData.branch = buildForm.branch;
|
||||||
requestData.commit_id = buildForm.commit_id;
|
requestData.commit_id = buildForm.commit_id;
|
||||||
} else if (isStagingOrProdEnv.value) {
|
} else if (isStagingOrProdEnv.value) {
|
||||||
|
if (buildForm.buildType === 'existing_version') {
|
||||||
requestData.version = buildForm.version;
|
requestData.version = buildForm.version;
|
||||||
|
} else if (buildForm.buildType === 'rebuild') {
|
||||||
|
requestData.branch = buildForm.branch;
|
||||||
|
requestData.commit_id = buildForm.commit_id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios.post('/api/build/tasks/build', requestData, {
|
const response = await axios.post('/api/build/tasks/build', requestData, {
|
||||||
@@ -1274,6 +1386,8 @@ const confirmBuild = async () => {
|
|||||||
status: 'running'
|
status: 'running'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
selectedHistoryId.value = response.data.data.history_id;
|
||||||
|
|
||||||
// 使用返回的build_number连接SSE
|
// 使用返回的build_number连接SSE
|
||||||
connectSSE(selectedTask.value.task_id, response.data.data.build_number);
|
connectSSE(selectedTask.value.task_id, response.data.data.build_number);
|
||||||
|
|
||||||
@@ -1759,4 +1873,16 @@ const handleViewBuildDetail = (record) => {
|
|||||||
color: rgba(0, 0, 0, 0.45);
|
color: rgba(0, 0, 0, 0.45);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.build-type-description {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -75,12 +75,7 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<a-button type="link" class="action-button" @click="handleEnvironmentDetail(record)">查看</a-button>
|
<a-button type="link" class="action-button" @click="handleEnvironmentDetail(record)">查看</a-button>
|
||||||
<!-- <a-button type="link" class="action-button" @click="handleEditEnvironment(record)">编辑</a-button> -->
|
<!-- <a-button type="link" class="action-button" @click="handleEditEnvironment(record)">编辑</a-button> -->
|
||||||
<a-popconfirm
|
<a-button type="link" danger @click="handleDeleteEnvironment(record)">删除</a-button>
|
||||||
title="确定要删除这个环境吗?"
|
|
||||||
@confirm="handleDeleteEnvironment(record)"
|
|
||||||
>
|
|
||||||
<a-button type="link" danger>删除</a-button>
|
|
||||||
</a-popconfirm>
|
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -142,7 +137,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted } from 'vue';
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { message } from 'ant-design-vue';
|
import { message, Modal } from 'ant-design-vue';
|
||||||
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { checkPermission, hasFunctionPermission } from '../../utils/permission';
|
import { checkPermission, hasFunctionPermission } from '../../utils/permission';
|
||||||
@@ -353,6 +348,17 @@ const handleDeleteEnvironment = async (record) => {
|
|||||||
if (!checkPermission('environment', 'delete')) {
|
if (!checkPermission('environment', 'delete')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示确认对话框,警告可能的影响
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除环境',
|
||||||
|
content: `确定要删除环境"${record.name}"吗?\n\n⚠️ 注意:删除环境将同时删除该环境下的所有关联构建任务,此操作不可恢复,请谨慎操作!`,
|
||||||
|
okText: '确认删除',
|
||||||
|
okType: 'primary',
|
||||||
|
cancelText: '取消',
|
||||||
|
width: 450,
|
||||||
|
style: { top: '20vh' },
|
||||||
|
async onOk() {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const response = await axios.delete('/api/environments/', {
|
const response = await axios.delete('/api/environments/', {
|
||||||
@@ -374,6 +380,8 @@ const handleDeleteEnvironment = async (record) => {
|
|||||||
message.error('删除环境失败');
|
message.error('删除环境失败');
|
||||||
console.error('Delete environment error:', error);
|
console.error('Delete environment error:', error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user