feat: 改为真实后端接口模式

- devopsApi: 移除前端 mock 降级和本地发布单/refs 伪造逻辑
- views: 同步发布、运行记录、配置和顶部状态文案为真实接口模式
- docs: 更新前端维护规则,禁止新增 mock 数据降级
This commit is contained in:
湛兮
2026-06-11 21:27:30 +08:00
parent f74454f00a
commit af0649a302
16 changed files with 228 additions and 959 deletions
-1
View File
@@ -1,2 +1 @@
VITE_API_BASE_URL=http://localhost:4300
VITE_DEVOPS_USE_MOCK=false
+1 -1
View File
@@ -17,7 +17,7 @@
- 路由使用 Vue Router。
- 请求封装使用 Axios,并集中在 `src/services/**`
- BPMN 展示使用 `bpmn-js`,不手写替代渲染器。
- mock 数据集中在 `src/mocks/**`,后续以后端 OpenAPI/Swagger 替换
- 不新增前端 mock 数据或接口失败降级;项目、发布、refs、流程和 Agent 调用统一走后端 API
## API 与安全边界
+3 -3
View File
@@ -5,7 +5,7 @@ Vue 3 + Vite + TypeScript 实现的 DevOps 运维控制台第一阶段,用于
## 当前范围
- 登录、总览、项目配置、发布中心、运行记录、系统配置,以及全局右侧 Agent 抽屉。
- 优先调用 `devops-platform-api`端不可用时降级到 mock 数据展示 `access-manage``role-admin``role-user``my-resume``devops-platform-api``devops-platform-web`
- 所有项目、发布、refs、流程和集成状态均调用 `devops-platform-api`端不再提供本地 mock 数据降级
- 发布中心包含项目、环境、ref/tag、备注、生产二次确认、触发、取消、重试和 Jenkins 状态同步入口。
- 运行记录展示 Jenkins queue/build、Build URL、开始/结束时间、执行提示、失败摘要和日志摘要。
- BPMN Viewer 使用 `bpmn-js` 渲染后端流程 XML,并按 queued/running/success/failed/skipped/canceled 状态高亮节点。
@@ -29,7 +29,7 @@ pnpm install
pnpm dev
```
The local web app listens on `http://localhost:4301` by default. Use `VITE_API_BASE_URL=http://localhost:4300` when switching from mock services to the local API.
The local web app listens on `http://localhost:4301` by default. Set `VITE_API_BASE_URL=http://localhost:4300` to point the web app at the local API.
## 构建检查
@@ -39,7 +39,7 @@ pnpm build
## API 契约来源
接口入口为 `src/services/devopsApi.ts`默认优先调用后端统一 envelope 响应;后端不可用`VITE_DEVOPS_USE_MOCK=true` 时降级到 `src/mocks/devops.ts`。后续以 `/Users/mac033/Desktop/my-project/devops-platform-docs/devops-platform-implementation-tracker.md` 中的产品约束和后端 OpenAPI 为准继续收敛字段。
接口入口为 `src/services/devopsApi.ts`调用后端统一 envelope 响应;后端不可用时页面应暴露真实接口错误,不伪造项目、运行记录或 refs。后续以 `/Users/mac033/Desktop/my-project/devops-platform-docs/devops-platform-implementation-tracker.md` 中的产品约束和后端 OpenAPI 为准继续收敛字段。
浏览器端不得保存或直连以下密钥和服务:
+3 -4
View File
@@ -9,11 +9,11 @@
- `role-user`
- `my-resume`
第一阶段为本地 mock UI,后续接入 `devops-platform-api`
当前前端只调用 `devops-platform-api`,不再提供本地 mock 数据降级
## 页面结构
- `/login`mock 登录
- `/login`本地操作员会话入口,后续接后端认证
- `/overview`:总览,展示服务状态、最近发布和项目环境。
- `/projects`:项目配置,展示 Gitea 仓库、Jenkins Job、企微机器人和环境策略。
- `/releases`:发布中心,提供发布参数、二次确认、触发、取消、重试和 BPMN 流程状态。
@@ -23,7 +23,6 @@
## 数据约定
- mock 数据入口:`src/mocks/devops.ts`
- API 封装入口:`src/services/devopsApi.ts`
- Axios 实例:`src/services/http.ts`
- 平台状态:`src/stores/platform.ts`
@@ -31,7 +30,7 @@
## 后续接入点
- 用后端 OpenAPI 替换 `devopsApi` mock 返回
- `devopsApi` 所有项目、发布、refs、流程和 Agent 调用均走后端接口
- 发布中心触发按钮接入后端发布单创建接口。
- 运行记录接入后端 run/step 查询接口。
- BPMN XML 由后端流程定义接口返回。
+3 -3
View File
@@ -41,7 +41,7 @@ const jenkinsStatus = computed(() => {
})
const apiModeLabel = computed(() => {
return platform.dataSource === 'backend' ? '后端 API' : 'Mock 降级'
return '后端 API'
})
onMounted(() => {
@@ -83,7 +83,7 @@ function logout() {
<div class="env-strip">
<span>本地开发</span>
<StatusPill :state="jenkinsStatus" text="Jenkins" />
<ElTag :type="platform.dataSource === 'backend' ? 'success' : 'info'" round>
<ElTag type="success" round>
{{ apiModeLabel }}
</ElTag>
</div>
@@ -94,7 +94,7 @@ function logout() {
</ElButton>
<ElTag type="primary" effect="plain" round>
<ElIcon><Connection /></ElIcon>
{{ platform.dataSource === 'backend' ? '后端已联通' : '后端不可用时自动降级' }}
后端已联通
</ElTag>
<ElDropdown>
<ElButton>
-638
View File
@@ -1,638 +0,0 @@
/**
* 本地降级数据只模拟 DevOps 运维展示,不包含任何真实 token、webhook 或服务地址。
*/
import type {
AgentCard,
DashboardSummary,
IntegrationConfigStatus,
IntegrationStatus,
PipelineStep,
ProjectConfig,
ReleaseRun,
SystemSetting,
} from '../types/devops'
export const releaseProcessXml = `<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_DevOpsRelease" targetNamespace="http://devops-platform.local/bpmn">
<bpmn:process id="Process_Release" name="DevOps release pipeline" isExecutable="false">
<bpmn:startEvent id="start-release" name="提交发布">
<bpmn:outgoing>Flow_Start_Gitea</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:serviceTask id="gitea-check" name="Gitea ref 校验">
<bpmn:incoming>Flow_Start_Gitea</bpmn:incoming>
<bpmn:outgoing>Flow_Gitea_Jenkins</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:serviceTask id="jenkins-build" name="Jenkins 构建">
<bpmn:incoming>Flow_Gitea_Jenkins</bpmn:incoming>
<bpmn:outgoing>Flow_Jenkins_Health</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:serviceTask id="health-check" name="健康检查">
<bpmn:incoming>Flow_Jenkins_Health</bpmn:incoming>
<bpmn:outgoing>Flow_Health_Notify</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:serviceTask id="notify-wecom" name="通知平台">
<bpmn:incoming>Flow_Health_Notify</bpmn:incoming>
<bpmn:outgoing>Flow_Notify_End</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="finish-release" name="归档审计">
<bpmn:incoming>Flow_Notify_End</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_Start_Gitea" sourceRef="start-release" targetRef="gitea-check" />
<bpmn:sequenceFlow id="Flow_Gitea_Jenkins" sourceRef="gitea-check" targetRef="jenkins-build" />
<bpmn:sequenceFlow id="Flow_Jenkins_Health" sourceRef="jenkins-build" targetRef="health-check" />
<bpmn:sequenceFlow id="Flow_Health_Notify" sourceRef="health-check" targetRef="notify-wecom" />
<bpmn:sequenceFlow id="Flow_Notify_End" sourceRef="notify-wecom" targetRef="finish-release" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_Release">
<bpmndi:BPMNPlane id="BPMNPlane_Release" bpmnElement="Process_Release">
<bpmndi:BPMNShape id="Shape_Start" bpmnElement="start-release">
<dc:Bounds x="120" y="145" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape_Gitea" bpmnElement="gitea-check">
<dc:Bounds x="210" y="123" width="118" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape_Jenkins" bpmnElement="jenkins-build">
<dc:Bounds x="380" y="123" width="118" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape_Health" bpmnElement="health-check">
<dc:Bounds x="550" y="123" width="118" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape_Notify" bpmnElement="notify-wecom">
<dc:Bounds x="720" y="123" width="118" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Shape_End" bpmnElement="finish-release">
<dc:Bounds x="900" y="145" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Edge_Start_Gitea" bpmnElement="Flow_Start_Gitea">
<di:waypoint x="156" y="163" />
<di:waypoint x="210" y="163" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Edge_Gitea_Jenkins" bpmnElement="Flow_Gitea_Jenkins">
<di:waypoint x="328" y="163" />
<di:waypoint x="380" y="163" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Edge_Jenkins_Health" bpmnElement="Flow_Jenkins_Health">
<di:waypoint x="498" y="163" />
<di:waypoint x="550" y="163" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Edge_Health_Notify" bpmnElement="Flow_Health_Notify">
<di:waypoint x="668" y="163" />
<di:waypoint x="720" y="163" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Edge_Notify_End" bpmnElement="Flow_Notify_End">
<di:waypoint x="838" y="163" />
<di:waypoint x="900" y="163" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>`
export const integrations: IntegrationStatus[] = [
{
key: 'jenkins',
name: 'Jenkins',
state: 'healthy',
summary: 'folder jobs 已按 my-project 规划,等待后端真实接入',
checkedAt: '2026-06-11 17:40',
latencyMs: 86,
},
{
key: 'gitea',
name: 'Gitea',
state: 'healthy',
summary: '纳管仓库 mock 同步完成,生产发布按项目策略选择 ref/tag',
checkedAt: '2026-06-11 17:40',
latencyMs: 42,
},
{
key: 'notification',
name: '通知平台',
state: 'warning',
summary: '机器人未配置,当前保留企微/飞书/通用 webhook 方案',
checkedAt: '2026-06-11 17:38',
},
{
key: 'llm',
name: 'LLM Agent',
state: 'warning',
summary: 'baseURL/key/model 待后端提供,前端不保存密钥',
checkedAt: '2026-06-11 17:38',
},
]
export const integrationConfigStatus: IntegrationConfigStatus = {
checkedAt: '2026-06-11T17:40:00+08:00',
integrations: [
{
key: 'jenkins',
name: 'Jenkins',
status: 'partial',
required: ['JENKINS_BASE_URL', 'JENKINS_USERNAME', 'JENKINS_API_TOKEN'],
configured: ['JENKINS_BASE_URL', 'JENKINS_USERNAME'],
missing: ['JENKINS_API_TOKEN'],
optional: [],
note: '前端只展示变量名,Jenkins 凭据由后端读取。',
},
{
key: 'gitea',
name: 'Gitea',
status: 'configured',
required: ['GITEA_BASE_URL', 'GITEA_TOKEN'],
configured: ['GITEA_BASE_URL', 'GITEA_TOKEN'],
missing: [],
optional: ['GITEA_WEBHOOK_SECRET'],
note: '仓库访问统一走后端 API。',
},
{
key: 'notification',
name: '通知平台',
status: 'missing',
required: ['NOTIFICATION_PROVIDER'],
configured: [],
missing: ['NOTIFICATION_PROVIDER'],
optional: ['WECOM_WEBHOOK_URL', 'FEISHU_WEBHOOK_URL', 'NOTIFICATION_WEBHOOK_URL'],
note: '浏览器不保存 webhook;后端按 provider 投递。',
},
{
key: 'llm',
name: 'LLM Agent',
status: 'partial',
required: ['LLM_BASE_URL', 'LLM_API_KEY', 'LLM_MODEL'],
configured: ['LLM_MODEL'],
missing: ['LLM_BASE_URL', 'LLM_API_KEY'],
optional: [],
note: '模型供应商只由后端代理调用。',
},
{
key: 'database',
name: 'Database',
status: 'configured',
required: ['DATABASE_URL'],
configured: ['DATABASE_URL'],
missing: [],
optional: ['USE_DATABASE_READS', 'PRISMA_CONNECT_ON_BOOT'],
note: '数据库连接仅服务端可见。',
},
{
key: 'redis',
name: 'Redis',
status: 'configured',
required: ['REDIS_URL'],
configured: ['REDIS_URL'],
missing: [],
optional: [],
note: 'Redis 连接仅服务端可见。',
},
{
key: 'secrets',
name: 'Secrets',
status: 'missing',
required: ['SECRET_ENCRYPTION_KEY'],
configured: [],
missing: ['SECRET_ENCRYPTION_KEY'],
optional: [],
note: '应用密钥只在后端环境中读取。',
},
],
}
export const projects: ProjectConfig[] = [
{
key: 'access-manage',
name: 'access-manage',
repository: 'https://gitea.ops.mrzhan.top/my-project/access-manage.git',
owner: 'my-project',
repo: 'access-manage',
serviceType: '后端服务',
defaultBranch: 'develop',
tagPolicy: '生产发布必须选择 v* tag',
environments: [
{
name: 'test',
label: '测试环境',
branchPolicy: 'develop / feature/*',
status: 'success',
version: 'develop@8f31a2c',
lastRunId: 'run-1048',
lastReleasedAt: '2026-06-11 15:10',
lastActor: 'ops-admin',
jenkinsJob: 'my-project/access-manage-test',
wecomRobot: 'devops-test',
},
{
name: 'production',
label: '生产环境',
branchPolicy: 'v* tag',
status: 'running',
version: 'v2026.06.11-api.1',
lastRunId: 'run-1052',
lastReleasedAt: '2026-06-11 17:12',
lastActor: 'ops-admin',
jenkinsJob: 'my-project/access-manage-prod',
wecomRobot: 'devops-prod',
},
],
integrations: {
jenkins: 'healthy',
gitea: 'healthy',
wecom: 'warning',
llm: 'warning',
},
},
{
key: 'role-admin',
name: 'role-admin',
repository: 'https://gitea.ops.mrzhan.top/my-project/role-admin.git',
owner: 'my-project',
repo: 'role-admin',
serviceType: '管理后台',
defaultBranch: 'develop',
tagPolicy: '生产发布必须由 develop 合并后 tag',
environments: [
{
name: 'test',
label: '测试环境',
branchPolicy: 'develop',
status: 'success',
version: 'develop@b42e18d',
lastRunId: 'run-1043',
lastReleasedAt: '2026-06-11 14:35',
lastActor: 'frontend-worker',
jenkinsJob: 'my-project/role-admin-test',
wecomRobot: 'devops-test',
},
{
name: 'production',
label: '生产环境',
branchPolicy: 'v* tag',
status: 'idle',
version: 'v2026.06.05-envsplit.7',
lastRunId: 'run-1026',
lastReleasedAt: '2026-06-05 16:02',
lastActor: 'ops-admin',
jenkinsJob: 'my-project/role-admin-prod',
wecomRobot: 'devops-prod',
},
],
integrations: {
jenkins: 'healthy',
gitea: 'healthy',
wecom: 'healthy',
llm: 'warning',
},
},
{
key: 'role-user',
name: 'role-user',
repository: 'https://gitea.ops.mrzhan.top/my-project/role-user.git',
owner: 'my-project',
repo: 'role-user',
serviceType: 'C 端员工应用',
defaultBranch: 'develop',
tagPolicy: '生产发布仅允许 release tag',
environments: [
{
name: 'test',
label: '测试环境',
branchPolicy: 'develop / feature/*',
status: 'failed',
version: 'develop@4a09fd2',
lastRunId: 'run-1050',
lastReleasedAt: '2026-06-11 16:24',
lastActor: 'frontend-worker',
jenkinsJob: 'my-project/role-user-test',
wecomRobot: 'devops-test',
},
{
name: 'production',
label: '生产环境',
branchPolicy: 'v* tag',
status: 'idle',
version: 'v2026.06.05-envsplit.7',
lastRunId: 'run-1028',
lastReleasedAt: '2026-06-05 16:08',
lastActor: 'ops-admin',
jenkinsJob: 'my-project/role-user-prod',
wecomRobot: 'devops-prod',
},
],
integrations: {
jenkins: 'error',
gitea: 'healthy',
wecom: 'healthy',
llm: 'warning',
},
},
{
key: 'my-resume',
name: 'my-resume',
repository: 'https://gitea.ops.mrzhan.top/my-project/my-resume.git',
owner: 'my-project',
repo: 'my-resume',
serviceType: '个人站点',
defaultBranch: 'master',
tagPolicy: '生产发布允许 master 或 v* tag',
environments: [
{
name: 'test',
label: '测试环境',
branchPolicy: 'master / feature/*',
status: 'success',
version: 'master@6c92e81',
lastRunId: 'run-1039',
lastReleasedAt: '2026-06-11 12:18',
lastActor: 'frontend-worker',
jenkinsJob: 'my-project/my-resume-test',
wecomRobot: 'devops-test',
},
{
name: 'production',
label: '生产环境',
branchPolicy: 'master 或 v* tag',
status: 'success',
version: 'v2026.06.10-resume.2',
lastRunId: 'run-1033',
lastReleasedAt: '2026-06-10 21:42',
lastActor: 'ops-admin',
jenkinsJob: 'my-project/my-resume-prod',
wecomRobot: 'devops-prod',
},
],
integrations: {
jenkins: 'healthy',
gitea: 'healthy',
wecom: 'healthy',
llm: 'warning',
},
},
{
key: 'devops-platform-api',
name: 'devops-platform-api',
repository: 'https://gitea.ops.mrzhan.top/devops-platform/devops-platform-api.git',
owner: 'devops-platform',
repo: 'devops-platform-api',
serviceType: 'DevOps 后端',
defaultBranch: 'master',
tagPolicy: '合并到 master 即发布 devops.mrzhan.top API',
environments: [
{
name: 'production',
label: '生产环境',
branchPolicy: 'master',
status: 'idle',
version: 'master@local',
lastRunId: '-',
lastReleasedAt: '-',
lastActor: '-',
jenkinsJob: 'devops-platform/prod-devops-platform-api-master',
wecomRobot: '通知平台待配置',
},
],
integrations: {
jenkins: 'warning',
gitea: 'warning',
wecom: 'warning',
llm: 'warning',
},
},
{
key: 'devops-platform-web',
name: 'devops-platform-web',
repository: 'https://gitea.ops.mrzhan.top/devops-platform/devops-platform-web.git',
owner: 'devops-platform',
repo: 'devops-platform-web',
serviceType: 'DevOps 前端',
defaultBranch: 'master',
tagPolicy: '合并到 master 即发布 devops.mrzhan.top Web',
environments: [
{
name: 'production',
label: '生产环境',
branchPolicy: 'master',
status: 'idle',
version: 'master@local',
lastRunId: '-',
lastReleasedAt: '-',
lastActor: '-',
jenkinsJob: 'devops-platform/prod-devops-platform-web-master',
wecomRobot: '通知平台待配置',
},
],
integrations: {
jenkins: 'warning',
gitea: 'warning',
wecom: 'warning',
llm: 'warning',
},
},
]
const baseSteps: PipelineStep[] = [
{
id: 'step-submit',
bpmnNodeId: 'start-release',
name: '提交发布单',
owner: 'Platform',
status: 'success',
duration: '2s',
summary: '记录操作者、项目、环境、ref 与备注。',
},
{
id: 'step-gitea',
bpmnNodeId: 'gitea-check',
name: '校验 Gitea ref',
owner: 'Gitea',
status: 'success',
duration: '5s',
summary: '验证 ref 存在并读取最近 commit 摘要。',
},
{
id: 'step-jenkins',
bpmnNodeId: 'jenkins-build',
name: '触发 Jenkins 构建',
owner: 'Jenkins',
status: 'running',
duration: '2m 18s',
summary: '进入 queue 后解析 build number,等待构建完成。',
},
{
id: 'step-health',
bpmnNodeId: 'health-check',
name: '环境健康检查',
owner: 'HealthCheck',
status: 'pending',
duration: '-',
summary: '构建成功后检查服务可达性和关键接口。',
},
{
id: 'step-wecom',
bpmnNodeId: 'notify-wecom',
name: '发送通知',
owner: 'Notification',
status: 'pending',
duration: '-',
summary: '按开始、成功、失败、取消模板投递到已配置通知平台。',
},
{
id: 'step-archive',
bpmnNodeId: 'finish-release',
name: '归档审计',
owner: 'Platform',
status: 'pending',
duration: '-',
summary: '保存 run、step、审计日志和通知投递结果。',
},
]
const successSteps = baseSteps.map((step) => ({
...step,
status: 'success' as const,
duration: step.duration === '-' ? '3s' : step.duration,
}))
const failedSteps: PipelineStep[] = baseSteps.map((step) => {
if (step.bpmnNodeId === 'jenkins-build') {
return {
...step,
status: 'failed',
duration: '1m 44s',
summary: 'pnpm build 失败,mock 日志提示类型检查未通过。',
}
}
if (['health-check', 'notify-wecom', 'finish-release'].includes(step.bpmnNodeId)) {
return {
...step,
status: 'skipped',
summary: '上游构建失败,当前节点跳过。',
}
}
return {
...step,
status: 'success',
}
})
export const runs: ReleaseRun[] = [
{
id: 'run-1052',
projectKey: 'access-manage',
projectName: 'access-manage',
environment: 'production',
ref: 'v2026.06.11-api.1',
status: 'running',
actor: 'ops-admin',
startedAt: '2026-06-11 17:12',
duration: '2m 25s',
note: '生产 API 修复发布,等待健康检查。',
jenkinsBuildUrl: 'https://jenkins.ops.mrzhan.top/job/my-project/job/access-manage-prod/1052',
commitSummary: 'fix: stabilize access policy migration and health output',
steps: baseSteps,
},
{
id: 'run-1050',
projectKey: 'role-user',
projectName: 'role-user',
environment: 'test',
ref: 'develop@4a09fd2',
status: 'failed',
actor: 'frontend-worker',
startedAt: '2026-06-11 16:24',
duration: '1m 56s',
note: '测试环境自动部署失败,等待诊断。',
jenkinsBuildUrl: 'https://jenkins.ops.mrzhan.top/job/my-project/job/role-user-test/1050',
commitSummary: 'feat: add employee workbench filters',
steps: failedSteps,
},
{
id: 'run-1048',
projectKey: 'access-manage',
projectName: 'access-manage',
environment: 'test',
ref: 'develop@8f31a2c',
status: 'success',
actor: 'ops-admin',
startedAt: '2026-06-11 15:10',
duration: '4m 03s',
note: '测试环境部署成功。',
jenkinsBuildUrl: 'https://jenkins.ops.mrzhan.top/job/my-project/job/access-manage-test/1048',
commitSummary: 'chore: update deployment env schema',
steps: successSteps,
},
]
export const agentCards: AgentCard[] = [
{
mode: 'risk',
title: '发布风险摘要',
description: '基于 ref diff、项目配置、历史失败记录生成检查项。',
inputScope: ['项目', '环境', 'ref/tag', '最近失败 run'],
outputPreview: ['配置变更影响范围', '构建和迁移风险', '发布前人工确认项'],
},
{
mode: 'diagnosis',
title: '失败诊断',
description: '只读取 Jenkins 日志片段、run steps 和已知 runbook。',
inputScope: ['失败 run', '失败节点', '脱敏日志片段'],
outputPreview: ['失败原因摘要', '下一步排查命令', '建议责任边界'],
},
{
mode: 'runbook',
title: 'Runbook 问答',
description: '围绕平台文档、项目 README、历史发布记录回答运维问题。',
inputScope: ['白名单文档', '项目', '环境'],
outputPreview: ['操作步骤', '风险提醒', '需要升级给后端的事项'],
},
{
mode: 'releaseNotes',
title: '发布说明草稿',
description: '根据 commit、PR 和 tag 生成生产发布说明草稿。',
inputScope: ['commit 列表', 'PR 信息', 'tag'],
outputPreview: ['变更摘要', '验证范围', '回滚提示'],
},
]
export const settings: SystemSetting[] = [
{
key: 'api-contract',
name: 'API 契约来源',
status: 'warning',
owner: '后端 OpenAPI',
contract: '本阶段使用 mock/devops.ts,联调后切换到后端 Swagger/OpenAPI。',
boundary: '前端不写死真实 token,不直接访问 Jenkins/Gitea/LLM。',
},
{
key: 'bpmn',
name: 'BPMN 流程定义',
status: 'healthy',
owner: '平台流程配置',
contract: 'step.bpmnNodeId 必须和 BPMN XML 节点 id 一致。',
boundary: '前端只展示流程和状态,不推进状态机。',
},
{
key: 'agent',
name: 'Agent 边界',
status: 'warning',
owner: '后端 Agent proxy',
contract: '只支持发布风险、失败诊断、Runbook、发布说明四类用途。',
boundary: '无泛聊天入口;模型 key/baseURL/model 仅后端保存。',
},
{
key: 'notification',
name: '通知平台',
status: 'warning',
owner: '后端 notification adapter',
contract: '通知模板由后端返回,后续可接企微、飞书或通用 webhook。',
boundary: 'webhook 不进入前端,不在浏览器保存。',
},
]
export const dashboardSummary: DashboardSummary = {
integrations,
projects,
runs,
agentCards,
settings,
integrationConfig: integrationConfigStatus,
}
+193 -281
View File
@@ -1,21 +1,9 @@
/**
* 前端唯一 DevOps API 入口;优先联调后端,失败时降级到本地 mock 保持页面可用
* 前端唯一 DevOps API 入口;所有运维数据都来自后端真实接口
*/
import {
agentCards,
dashboardSummary,
integrationConfigStatus as mockIntegrationConfigStatus,
integrations,
projects as mockProjects,
releaseProcessXml,
runs as mockRuns,
settings,
} from '../mocks/devops'
import type {
AgentCard,
AgentInvocation,
AgentMode,
AgentPurpose,
CreateAgentInvocationPayload,
CreateDeployRunPayload,
DashboardSummary,
@@ -36,7 +24,7 @@ import type {
} from '../types/devops'
import { http } from './http'
type DataSource = 'backend' | 'mock'
type DataSource = 'backend'
type BackendHealthStatus = 'ok' | 'not_configured' | 'unavailable'
type BackendRunStatus = 'pending' | 'queued' | 'running' | 'success' | 'failed' | 'canceled'
@@ -57,6 +45,8 @@ interface BackendProject {
name: EnvironmentName
displayName: string
jenkinsJobPath: string
releasePolicy?: 'branch' | 'tag' | 'master'
refPattern?: string
}>
}
@@ -108,9 +98,7 @@ interface BackendProcessDefinition {
nodeIds: string[]
}
let lastDataSource: DataSource = 'mock'
const wait = async () => new Promise((resolve) => window.setTimeout(resolve, 180))
let lastDataSource: DataSource = 'backend'
const integrationConfigKeys: IntegrationConfigKey[] = [
'jenkins',
'gitea',
@@ -123,221 +111,195 @@ const integrationConfigKeys: IntegrationConfigKey[] = [
]
const redactedVariableName = '非变量名内容已隐藏'
const agentCards: AgentCard[] = [
{
mode: 'risk',
title: '发布风险摘要',
description: '基于 ref diff、项目配置、历史失败记录生成检查项。',
inputScope: ['项目', '环境', 'ref/tag', '最近失败 run'],
outputPreview: ['配置变更影响范围', '构建和迁移风险', '发布前人工确认项'],
},
{
mode: 'diagnosis',
title: '失败诊断',
description: '只读取 Jenkins 日志片段、run steps 和已知 runbook。',
inputScope: ['失败 run', '失败节点', '脱敏日志片段'],
outputPreview: ['失败原因摘要', '下一步排查命令', '建议责任边界'],
},
{
mode: 'runbook',
title: 'Runbook 问答',
description: '围绕平台文档、项目 README、历史发布记录回答运维问题。',
inputScope: ['白名单文档', '项目', '环境'],
outputPreview: ['操作步骤', '风险提醒', '需要升级给后端的事项'],
},
{
mode: 'releaseNotes',
title: '发布说明草稿',
description: '根据 commit、PR 和 tag 生成生产发布说明草稿。',
inputScope: ['commit 列表', 'PR 信息', 'tag'],
outputPreview: ['变更摘要', '验证范围', '回滚提示'],
},
]
const settings: SystemSetting[] = [
{
key: 'api-contract',
name: 'API 契约来源',
status: 'warning',
owner: '后端 OpenAPI',
contract: '所有项目、发布、refs、流程和集成状态均通过 devops-platform-api 获取。',
boundary: '前端不写死真实 token,不直接访问 Jenkins/Gitea/LLM。',
},
{
key: 'bpmn',
name: 'BPMN 流程定义',
status: 'healthy',
owner: '平台流程配置',
contract: 'step.bpmnNodeId 必须和 BPMN XML 节点 id 一致。',
boundary: '前端只展示流程和状态,不推进状态机。',
},
{
key: 'agent',
name: 'Agent 边界',
status: 'warning',
owner: '后端 Agent proxy',
contract: '只支持发布风险、失败诊断、Runbook、发布说明四类用途。',
boundary: '无泛聊天入口;模型 key/baseURL/model 仅后端保存。',
},
{
key: 'notification',
name: '通知平台',
status: 'warning',
owner: '后端 notification adapter',
contract: '通知模板由后端返回,后续可接企微、飞书或通用 webhook。',
boundary: 'webhook 不进入前端,不在浏览器保存。',
},
]
export const devopsApi = {
getLastDataSource(): DataSource {
return lastDataSource
},
async getDashboard(): Promise<DashboardSummary> {
return withBackendFallback(
async () => {
const [health, backendProjects, backendRuns, integrationConfig] = await Promise.all([
fetchHealth(),
fetchProjects(),
fetchRuns(),
fetchIntegrationConfigStatusWithFallback(),
])
const adaptedProjects = toProjectConfigs(backendProjects)
const [health, backendProjects, backendRuns, integrationConfig] = await Promise.all([
fetchHealth(),
fetchProjects(),
fetchRuns(),
fetchIntegrationConfigStatus(),
])
const adaptedProjects = toProjectConfigs(backendProjects)
lastDataSource = 'backend'
return {
integrations: toIntegrationStatuses(health),
projects: adaptedProjects,
runs: backendRuns.map((run) => toReleaseRun(run, adaptedProjects)),
agentCards,
settings: toSystemSettings(health, integrationConfig),
integrationConfig,
}
},
() => dashboardSummary,
)
return {
integrations: toIntegrationStatuses(health),
projects: adaptedProjects,
runs: backendRuns.map((run) => toReleaseRun(run, adaptedProjects)),
agentCards,
settings: toSystemSettings(health, integrationConfig),
integrationConfig,
}
},
async getIntegrations(): Promise<IntegrationStatus[]> {
return withBackendFallback(
async () => toIntegrationStatuses(await fetchHealth()),
() => integrations,
)
lastDataSource = 'backend'
return toIntegrationStatuses(await fetchHealth())
},
async getProjects(): Promise<ProjectConfig[]> {
return withBackendFallback(
async () => toProjectConfigs(await fetchProjects()),
() => mockProjects,
)
lastDataSource = 'backend'
return toProjectConfigs(await fetchProjects())
},
async getProjectRefs(projectKey: string, projectList = mockProjects): Promise<ProjectRefs> {
return withBackendFallback(
async () => {
const { data } = await http.get<ProjectRefs>(`/projects/${projectKey}/refs`)
return data
},
() => createFallbackRefs(projectKey, projectList),
)
async getProjectRefs(projectKey: string): Promise<ProjectRefs> {
const { data } = await http.get<ProjectRefs>(`/projects/${projectKey}/refs`)
lastDataSource = 'backend'
return data
},
async getRuns(projectList = mockProjects): Promise<ReleaseRun[]> {
return withBackendFallback(
async () => (await fetchRuns()).map((run) => toReleaseRun(run, projectList)),
() => mockRuns,
)
async getRuns(projectList: ProjectConfig[] = []): Promise<ReleaseRun[]> {
lastDataSource = 'backend'
return (await fetchRuns()).map((run) => toReleaseRun(run, projectList))
},
async createDeployRun(
payload: CreateDeployRunPayload,
projectList = mockProjects,
projectList: ProjectConfig[] = [],
): Promise<ReleaseRun> {
return withBackendFallback(
async () => {
const { data } = await http.post<BackendDeployRun>('/deploy-runs', payload)
return toReleaseRun(data, projectList)
},
() => createFallbackRun(payload, projectList, 'queued'),
)
const { data } = await http.post<BackendDeployRun>('/deploy-runs', payload)
lastDataSource = 'backend'
return toReleaseRun(data, projectList)
},
async cancelDeployRun(runId: string, source?: ReleaseRun): Promise<ReleaseRun> {
return withBackendFallback(
async () => {
const { data } = await http.post<BackendDeployRun>(`/deploy-runs/${runId}/cancel`)
return toReleaseRun(data, mockProjects)
},
() => ({
...(source ?? mockRuns[0]),
id: source?.id ?? runId,
status: 'canceled',
duration: source?.duration ?? '-',
steps: (source?.steps ?? mockRuns[0].steps).map((step) => ({
...step,
status: step.status === 'success' ? 'success' : 'canceled',
summary: step.status === 'success' ? step.summary : '人工取消,本节点未继续执行。',
})),
}),
)
async cancelDeployRun(
runId: string,
projectList: ProjectConfig[] = [],
): Promise<ReleaseRun> {
const { data } = await http.post<BackendDeployRun>(`/deploy-runs/${runId}/cancel`)
lastDataSource = 'backend'
return toReleaseRun(data, projectList)
},
async retryDeployRun(runId: string, source?: ReleaseRun): Promise<ReleaseRun> {
return withBackendFallback(
async () => {
const { data } = await http.post<BackendDeployRun>(`/deploy-runs/${runId}/retry`)
return toReleaseRun(data, mockProjects)
},
() =>
createFallbackRun(
{
projectKey: source?.projectKey ?? 'access-manage',
environment: source?.environment ?? 'test',
ref: source?.ref ?? 'develop',
operator: source?.actor ?? 'ops-admin',
remark: source ? `重试 ${source.id}` : `重试 ${runId}`,
},
mockProjects,
'queued',
),
)
async retryDeployRun(
runId: string,
projectList: ProjectConfig[] = [],
): Promise<ReleaseRun> {
const { data } = await http.post<BackendDeployRun>(`/deploy-runs/${runId}/retry`)
lastDataSource = 'backend'
return toReleaseRun(data, projectList)
},
async syncJenkinsRun(
runId: string,
source?: ReleaseRun,
projectList = mockProjects,
projectList: ProjectConfig[] = [],
): Promise<ReleaseRun> {
return withBackendFallback(
async () => {
const { data } = await http.post<BackendDeployRun>(`/deploy-runs/${runId}/sync-jenkins`)
return toReleaseRun(data, projectList)
},
() => source ?? mockRuns.find((run) => run.id === runId) ?? mockRuns[0],
)
const { data } = await http.post<BackendDeployRun>(`/deploy-runs/${runId}/sync-jenkins`)
lastDataSource = 'backend'
return toReleaseRun(data, projectList)
},
async syncJenkinsRuns(projectList = mockProjects): Promise<JenkinsSyncSummary> {
return withBackendFallback(
async () => {
const { data } = await http.post<BackendJenkinsSyncSummary>(
'/deploy-runs/sync-jenkins',
)
return toJenkinsSyncSummary(data, projectList)
},
() => ({
checkedAt: new Date().toISOString(),
totalCandidates: 0,
syncedRuns: [],
failedRuns: [],
}),
async syncJenkinsRuns(projectList: ProjectConfig[] = []): Promise<JenkinsSyncSummary> {
const { data } = await http.post<BackendJenkinsSyncSummary>(
'/deploy-runs/sync-jenkins',
)
lastDataSource = 'backend'
return toJenkinsSyncSummary(data, projectList)
},
async getAgentCards(): Promise<AgentCard[]> {
await wait()
return agentCards
},
async getIntegrationConfigStatus(): Promise<IntegrationConfigStatus> {
return withBackendFallback(
async () => fetchIntegrationConfigStatus(),
() => mockIntegrationConfigStatus,
)
lastDataSource = 'backend'
return fetchIntegrationConfigStatus()
},
async createAgentInvocation(
payload: CreateAgentInvocationPayload,
): Promise<AgentInvocation> {
return withBackendFallback(
async () => {
const { data } = await http.post<AgentInvocation>('/agent/invocations', payload)
return data
},
() => createFallbackAgentInvocation(payload),
)
const { data } = await http.post<AgentInvocation>('/agent/invocations', payload)
lastDataSource = 'backend'
return data
},
async getSettings(): Promise<SystemSetting[]> {
return withBackendFallback(
async () => {
const [health, integrationConfig] = await Promise.all([
fetchHealth(),
fetchIntegrationConfigStatusWithFallback(),
])
return toSystemSettings(health, integrationConfig)
},
() => settings,
)
const [health, integrationConfig] = await Promise.all([
fetchHealth(),
fetchIntegrationConfigStatus(),
])
lastDataSource = 'backend'
return toSystemSettings(health, integrationConfig)
},
async getReleaseProcessXml(): Promise<string> {
return withBackendFallback(
async () => {
const { data } = await http.get<BackendProcessDefinition>(
'/process-definitions/release',
)
return data.xml
},
() => releaseProcessXml,
const { data } = await http.get<BackendProcessDefinition>(
'/process-definitions/release',
)
},
}
async function withBackendFallback<T>(
request: () => Promise<T>,
fallback: () => T | Promise<T>,
): Promise<T> {
if (import.meta.env.VITE_DEVOPS_USE_MOCK === 'true') {
lastDataSource = 'mock'
await wait()
return fallback()
}
try {
const result = await request()
lastDataSource = 'backend'
return result
} catch {
lastDataSource = 'mock'
await wait()
return fallback()
}
return data.xml
},
}
async function fetchHealth(): Promise<BackendHealth> {
@@ -360,14 +322,6 @@ async function fetchIntegrationConfigStatus(): Promise<IntegrationConfigStatus>
return sanitizeIntegrationConfigStatus(data)
}
async function fetchIntegrationConfigStatusWithFallback(): Promise<IntegrationConfigStatus> {
try {
return await fetchIntegrationConfigStatus()
} catch {
return mockIntegrationConfigStatus
}
}
function toIntegrationStatuses(health: BackendHealth): IntegrationStatus[] {
return [
toIntegrationStatus('jenkins', 'Jenkins', health),
@@ -464,7 +418,6 @@ function sanitizeIntegrationNote(note?: string): string | undefined {
function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] {
return backendProjects.map((project) => {
const mockProject = mockProjects.find((item) => item.key === project.key)
const repo = parseRepository(project.repositoryUrl, project.key)
return {
@@ -473,30 +426,24 @@ function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] {
repository: project.repositoryUrl,
owner: repo.owner,
repo: repo.repo,
serviceType: mockProject?.serviceType ?? '服务',
serviceType: serviceTypeLabel(project.key),
defaultBranch: project.defaultBranch,
tagPolicy: mockProject?.tagPolicy ?? '生产发布优先选择 v* tag',
tagPolicy: tagPolicyLabel(project.environments),
environments: project.environments.map((environment) => {
const mockEnvironment = mockProject?.environments.find(
(item) => item.name === environment.name,
)
return {
name: environment.name,
label: environment.displayName,
branchPolicy:
mockEnvironment?.branchPolicy ??
(environment.name === 'production' ? 'v* tag' : project.defaultBranch),
status: mockEnvironment?.status ?? 'idle',
version: mockEnvironment?.version ?? '-',
lastRunId: mockEnvironment?.lastRunId ?? '-',
lastReleasedAt: mockEnvironment?.lastReleasedAt ?? '-',
lastActor: mockEnvironment?.lastActor ?? '-',
branchPolicy: releasePolicyLabel(environment.releasePolicy, project.defaultBranch),
status: 'idle' as const,
version: '-',
lastRunId: '-',
lastReleasedAt: '-',
lastActor: '-',
jenkinsJob: environment.jenkinsJobPath,
wecomRobot: mockEnvironment?.wecomRobot ?? '后端通知配置',
wecomRobot: '后端通知配置',
}
}),
integrations: mockProject?.integrations ?? {
integrations: {
jenkins: 'warning',
gitea: 'warning',
wecom: 'warning',
@@ -571,66 +518,6 @@ function toPipelineStep(step: BackendDeployRunStep): PipelineStep {
}
}
function createFallbackRun(
payload: CreateDeployRunPayload,
projectList: ProjectConfig[],
status: ReleaseStatus,
): ReleaseRun {
const project = projectList.find((item) => item.key === payload.projectKey)
const now = new Date().toISOString()
return {
id: payload.idempotencyKey || `local-${Date.now()}`,
projectKey: payload.projectKey,
projectName: project?.name ?? payload.projectKey,
environment: payload.environment,
ref: payload.ref,
status,
actor: payload.operator,
startedAt: formatDateTime(now),
duration: status === 'queued' ? '等待 Jenkins queue' : '-',
note: payload.remark || '本地降级发布单',
jenkinsSummary: '等待 Jenkins 返回',
jenkinsBuildUrl: '',
commitSummary: '本地降级:后端不可用时保留操作上下文',
steps: mockRuns[0].steps.map((step) => ({
...step,
status: step.bpmnNodeId === 'start-release' ? 'success' : 'pending',
})),
}
}
function createFallbackAgentInvocation(
payload: CreateAgentInvocationPayload,
): AgentInvocation {
return {
id: `local-agent-${Date.now()}`,
runId: payload.runId,
projectKey: payload.projectKey,
type: payload.type,
status: 'success',
promptSummary: payload.promptSummary,
resultMarkdown: fallbackAgentResult(payload.type),
createdAt: new Date().toISOString(),
}
}
function createFallbackRefs(projectKey: string, projectList: ProjectConfig[]): ProjectRefs {
const project = projectList.find((item) => item.key === projectKey)
const defaultBranch = project?.defaultBranch ?? 'develop'
return {
source: 'mock',
branches: Array.from(new Set([defaultBranch, 'develop', 'master']))
.filter(Boolean)
.map((name) => ({ name })),
tags: [
{ name: 'v2026.06.05-envsplit.7' },
{ name: 'v2026.06.11-api.1' },
],
}
}
function toHealthState(status: BackendHealthStatus): HealthState {
if (status === 'ok') {
return 'healthy'
@@ -760,6 +647,47 @@ function parseRepository(repositoryUrl: string, fallbackRepo: string) {
}
}
function serviceTypeLabel(projectKey: string): string {
if (projectKey.endsWith('-web') || projectKey === 'role-admin' || projectKey === 'role-user') {
return '前端应用'
}
if (projectKey === 'my-resume') {
return '静态站点'
}
return '后端服务'
}
function tagPolicyLabel(environments: BackendProject['environments']): string {
const production = environments.find((environment) => environment.name === 'production')
if (production?.releasePolicy === 'master') {
return '生产发布固定 master'
}
if (production?.releasePolicy === 'tag') {
return '生产发布优先选择 v* tag'
}
return '按环境发布策略执行'
}
function releasePolicyLabel(
policy: BackendProject['environments'][number]['releasePolicy'],
defaultBranch: string,
): string {
if (policy === 'tag') {
return 'v* tag'
}
if (policy === 'master') {
return 'master'
}
return defaultBranch
}
function formatDateTime(value?: string): string {
if (!value) {
return '-'
@@ -814,7 +742,7 @@ function formatDuration(
function toSystemSettings(
health: BackendHealth,
integrationConfig = mockIntegrationConfigStatus,
integrationConfig: IntegrationConfigStatus,
): SystemSetting[] {
const integrationByKey = new Map(
integrationConfig.integrations.map((item) => [item.key, item]),
@@ -825,7 +753,7 @@ function toSystemSettings(
return {
...item,
status: toHealthState(health.status),
contract: '已优先读取 devops-platform-api 的统一 envelope;后端不可用时降级到本地 mock。',
contract: '已通过 devops-platform-api 的统一 envelope 读取真实接口数据。',
}
}
@@ -860,19 +788,3 @@ function toHealthStateFromConfig(state?: IntegrationConfigState): HealthState {
return 'error'
}
function fallbackAgentResult(type: AgentPurpose): string {
const mode: Record<AgentPurpose, AgentMode> = {
'release-risk': 'risk',
'failure-diagnosis': 'diagnosis',
'runbook-qa': 'runbook',
'release-note': 'releaseNotes',
'incident-review': 'diagnosis',
}
const card = agentCards.find((item) => item.mode === mode[type])
return [
`${card?.title ?? 'Agent'} 本地降级结果`,
'后端或 LLM 未配置时,前端只保留 DevOps 场景输入,不保存任何模型密钥。',
].join('\n\n')
}
+6 -6
View File
@@ -14,11 +14,11 @@ import type {
} from '../types/devops'
/**
* 平台运行态聚合后端和 mock 降级数据,页面只消费统一后的展示模型。
* 平台运行态聚合后端真实接口数据,页面只消费统一后的展示模型。
*/
export const usePlatformStore = defineStore('platform', () => {
const loading = ref(false)
const dataSource = ref<'backend' | 'mock'>('mock')
const dataSource = ref<'backend'>('backend')
const projects = ref<ProjectConfig[]>([])
const runs = ref<ReleaseRun[]>([])
const integrations = ref<IntegrationStatus[]>([])
@@ -61,21 +61,21 @@ export const usePlatformStore = defineStore('platform', () => {
}
async function cancelDeployRun(run: ReleaseRun) {
const canceled = await devopsApi.cancelDeployRun(run.id, run)
const canceled = await devopsApi.cancelDeployRun(run.id, projects.value)
upsertRun(canceled)
dataSource.value = devopsApi.getLastDataSource()
return canceled
}
async function retryDeployRun(run: ReleaseRun) {
const retried = await devopsApi.retryDeployRun(run.id, run)
const retried = await devopsApi.retryDeployRun(run.id, projects.value)
upsertRun(retried)
dataSource.value = devopsApi.getLastDataSource()
return retried
}
async function syncJenkinsRun(run: ReleaseRun) {
const synced = await devopsApi.syncJenkinsRun(run.id, run, projects.value)
const synced = await devopsApi.syncJenkinsRun(run.id, projects.value)
upsertRun(synced)
dataSource.value = devopsApi.getLastDataSource()
return synced
@@ -89,7 +89,7 @@ export const usePlatformStore = defineStore('platform', () => {
}
async function loadProjectRefs(projectKey: string) {
const refs = await devopsApi.getProjectRefs(projectKey, projects.value)
const refs = await devopsApi.getProjectRefs(projectKey)
projectRefs.value = {
...projectRefs.value,
[projectKey]: refs,
+2 -2
View File
@@ -4,13 +4,13 @@ import { defineStore } from 'pinia'
export const useSessionStore = defineStore('session', () => {
const operator = ref('ops-admin')
const role = ref<'admin' | 'operator' | 'viewer'>('admin')
const token = ref('mock-session')
const token = ref('local-session')
const isAuthenticated = computed(() => Boolean(token.value))
function login(name: string) {
operator.value = name || 'ops-admin'
token.value = 'mock-session'
token.value = 'local-session'
}
function logout() {
+1 -1
View File
@@ -106,7 +106,7 @@ export interface ProjectConfig {
}
export interface ProjectRefs {
source: 'gitea' | 'fallback' | 'mock'
source: 'gitea' | 'fallback'
branches: Array<{
name: string
commitSha?: string
+2 -2
View File
@@ -10,7 +10,7 @@ const session = useSessionStore()
const form = reactive({
operator: 'ops-admin',
password: 'mock-password',
password: '',
})
function submit() {
@@ -26,7 +26,7 @@ function submit() {
<div class="login-copy">
<ElIcon><Monitor /></ElIcon>
<h1>DevOps 运维平台</h1>
<p>第一阶段使用 mock session仅用于本地控制台联调真实身份RBAC 和审计由后端接入后替换</p>
<p>当前使用本地操作员会话进入控制台真实身份RBAC 和审计由后端认证模块接入</p>
</div>
<ElForm class="login-form" label-position="top" @submit.prevent="submit">
<ElFormItem label="操作员">
+2 -2
View File
@@ -25,7 +25,7 @@ const runningCount = computed(() => platform.runningRuns.length)
<div class="status-grid">
<MetricTile label="纳管项目" :value="platform.projects.length" caption="含 DevOps 平台自管理项目" :icon="Cpu" />
<MetricTile label="运行中发布" :value="runningCount" caption="当前 Jenkins 或健康检查处理中" :icon="Finished" tone="warning" />
<MetricTile label="成功记录" :value="successCount" caption="最近 mock run 成功数" :icon="CircleCheck" tone="success" />
<MetricTile label="成功记录" :value="successCount" caption="最近发布成功数" :icon="CircleCheck" tone="success" />
<MetricTile label="失败提醒" :value="failedCount" caption="需要诊断或重试的 run" :icon="Failed" tone="danger" />
</div>
@@ -33,7 +33,7 @@ const runningCount = computed(() => platform.runningRuns.length)
<section class="panel">
<div class="panel-header">
<h2>集成状态</h2>
<span>{{ platform.dataSource === 'backend' ? '来自后端 health' : '后端不可用,当前使用 mock' }}</span>
<span>来自后端 health</span>
</div>
<div class="panel-body compact-list">
<div v-for="item in platform.integrations" :key="item.key" class="list-row">
+2 -2
View File
@@ -10,7 +10,7 @@ const platform = usePlatformStore()
<div class="page-header">
<div>
<h1>项目配置</h1>
<p>第一版展示项目环境Jenkins JobGitea 仓库和通知通道绑定关系</p>
<p>展示后端返回的项目环境Jenkins JobGitea 仓库和通知通道绑定关系</p>
</div>
<ElButton type="primary" plain disabled>新增项目</ElButton>
</div>
@@ -18,7 +18,7 @@ const platform = usePlatformStore()
<section class="panel">
<div class="panel-header">
<h2>项目清单</h2>
<span>{{ platform.dataSource === 'backend' ? '由后端 projects 接口返回' : '当前使用本地 mock 配置' }}</span>
<span>由后端 projects 接口返回</span>
</div>
<div class="panel-body">
<ElTable :data="platform.projects" row-key="key">
+8 -11
View File
@@ -33,10 +33,7 @@ const environmentOptions = computed(() => {
const environments = selectedProject.value?.environments ?? []
if (!environments.length) {
return [
{ label: '测试环境', value: 'test' },
{ label: '生产环境', value: 'production' },
]
return []
}
return environments.map((environment) => ({
@@ -57,10 +54,10 @@ const refOptions = computed(() => {
const refs = currentRefs.value
if (form.environment === 'production' && !useBranchRefs.value) {
return refs?.tags.map((tag) => tag.name) ?? ['v2026.06.05-envsplit.7']
return refs?.tags.map((tag) => tag.name) ?? []
}
return refs?.branches.map((branch) => branch.name) ?? [selectedProject.value?.defaultBranch ?? 'develop']
return refs?.branches.map((branch) => branch.name) ?? []
})
const refSourceText = computed(() => {
@@ -74,7 +71,7 @@ const refSourceText = computed(() => {
return '后端 fallback refs'
}
return '本地 mock refs'
return '等待后端 refs'
})
const canTrigger = computed(() => {
@@ -106,7 +103,7 @@ async function triggerRelease() {
})
activeRun.value = run
localStatus.value = run.status
ElMessage.success(platform.dataSource === 'backend' ? '发布单已提交到后端' : '后端不可用,已生成本地发布单')
ElMessage.success('发布单已提交到后端')
} finally {
submitting.value = false
}
@@ -138,7 +135,7 @@ async function retryRelease() {
const run = await platform.retryDeployRun(shownRun.value)
activeRun.value = run
localStatus.value = run.status
ElMessage.success(platform.dataSource === 'backend' ? '已提交重试发布单' : '后端不可用,已生成本地重试单')
ElMessage.success('已提交重试发布单')
} finally {
submitting.value = false
}
@@ -154,7 +151,7 @@ async function syncJenkinsRun() {
const run = await platform.syncJenkinsRun(shownRun.value)
activeRun.value = run
localStatus.value = run.status
ElMessage.success(platform.dataSource === 'backend' ? '已同步 Jenkins 状态' : '后端不可用,保留本地状态')
ElMessage.success('已同步 Jenkins 状态')
} finally {
submitting.value = false
}
@@ -225,7 +222,7 @@ watch(refOptions, (options) => {
<section class="panel">
<div class="panel-header">
<h2>发布参数</h2>
<span>{{ platform.dataSource === 'backend' ? '通过后端创建发布单' : '后端不可用时使用本地降级' }}</span>
<span>通过后端创建发布单</span>
</div>
<div class="panel-body">
<ElForm label-position="top">
+1 -1
View File
@@ -36,7 +36,7 @@ async function syncSelectedRun() {
const run = await platform.syncJenkinsRun(selectedRun.value)
selectedRunId.value = run.id
ElMessage.success(platform.dataSource === 'backend' ? '已同步 Jenkins 状态' : '后端不可用,保留本地状态')
ElMessage.success('已同步 Jenkins 状态')
}
async function syncAllJenkinsRuns() {
+1 -1
View File
@@ -169,7 +169,7 @@ function formatCheckedAt(value: string): string {
<strong class="mono">VITE_API_BASE_URL</strong>
<small>联调时指向后端网关未配置时默认 /api</small>
</span>
<ElTag type="info" round>{{ platform.dataSource === 'backend' ? '后端已联通' : '/api 或 mock 降级' }}</ElTag>
<ElTag type="info" round>后端接口</ElTag>
</div>
<div class="list-row">
<span>