Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions client/src/mock/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export interface DeploymentPlan {
id: string
service: string
version: string
status: 'InDeployment' | 'rollbacked' | 'completed'
status: 'Schedule' | 'InDeployment' | 'Finished'
scheduleTime?: string
finishTime?: string
isPaused?: boolean
Expand Down Expand Up @@ -240,7 +240,8 @@ export const mockDeploymentPlans: Record<string, DeploymentPlansResponse> = {
id: "1003",
service: "s3",
version: "v1.0.3",
status: "completed",
status: "Finished",
scheduleTime: "2024-01-14T09:00:00Z",
finishTime: "2024-01-14T18:00:00Z"
}
]
Expand All @@ -267,14 +268,7 @@ export const mockDeploymentPlans: Record<string, DeploymentPlansResponse> = {
id: "2003",
service: "stg",
version: "v1.0.3",
status: "completed",
finishTime: "2024-01-03T05:00:00Z"
},
{
id: "2004",
service: "stg",
version: "v1.0.3",
status: "rollbacked",
status: "Finished",
finishTime: "2024-01-03T05:00:00Z"
}
]
Expand All @@ -292,7 +286,7 @@ export const mockDeploymentPlans: Record<string, DeploymentPlansResponse> = {
id: "3002",
service: "meta",
version: "v1.0.5",
status: "completed",
status: "Finished",
finishTime: "2024-01-16T15:30:00Z"
}
]
Expand All @@ -310,7 +304,7 @@ export const mockDeploymentPlans: Record<string, DeploymentPlansResponse> = {
id: "4002",
service: "mq",
version: "v1.0.3",
status: "rollbacked",
status: "Finished",
finishTime: "2024-01-17T20:00:00Z"
}
]
Expand All @@ -328,7 +322,7 @@ export const mockDeploymentPlans: Record<string, DeploymentPlansResponse> = {
id: "5002",
service: "worker",
version: "v1.0.3",
status: "completed",
status: "Finished",
finishTime: "2024-01-18T16:00:00Z"
},
{
Expand All @@ -353,7 +347,7 @@ export const mockDeploymentPlans: Record<string, DeploymentPlansResponse> = {
id: "6002",
service: "mongodb",
version: "v1.0.3",
status: "completed",
status: "Finished",
finishTime: "2024-01-19T11:00:00Z"
}
]
Expand Down
204 changes: 167 additions & 37 deletions client/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,24 @@
</el-tag>
</template>
</el-table-column>
<el-table-column width="120">
<template #default="{ row }">
<div class="table-actions">
<span
:class="['action-link', row.isPaused ? 'continue' : 'pause']"
@click="togglePauseResumeForVersion(row)"
>
{{ row.isPaused ? '继续' : '暂停' }}
</span>
<span
class="action-link rollback"
@click="rollbackVersion(row)"
>
回滚
</span>
</div>
</template>
</el-table-column>
<template #empty>
<div class="no-data">暂无数据</div>
</template>
Expand Down Expand Up @@ -224,38 +242,27 @@
<div>{{ plan.time }}</div>
<div>状态:
<el-tag
:type="plan.originalStatus === 'completed' ? 'success' :
plan.originalStatus === 'rollbacked' ? 'danger' : 'warning'"
:type="plan.originalStatus === 'Finished' ? 'success' :
plan.originalStatus === 'Schedule' ? 'info' : 'warning'"
size="small"
>
{{ plan.status }}
</el-tag>
</div>
</div>
</div>
<!-- 未开始的发布计划:编辑、取消 -->
<div class="plan-actions" v-if="plan.originalStatus !== 'completed' && plan.originalStatus !== 'rollbacked' && (!plan.scheduleTime || plan.scheduleTime === '')">
<div class="action-column">
<el-button size="small" @click="editRelease(plan)">编辑</el-button>
<el-button size="small" type="danger" @click="confirmCancel(plan)">取消</el-button>
</div>
</div>

<!-- 部署中的发布计划:暂停/继续、回滚、取消 -->
<div class="plan-actions" v-else-if="plan.originalStatus === 'InDeployment' && !plan.isPaused">
<!-- 待发布和发布中的计划:编辑、取消 -->
<div class="plan-actions" v-if="plan.originalStatus === 'Schedule' || plan.originalStatus === 'InDeployment'">
<div class="action-column">
<el-button size="small" type="warning" @click="togglePauseResume(plan)">暂停</el-button>
<el-button size="small" type="info" @click="rollbackRelease(plan)">回滚</el-button>
<el-button size="small" type="danger" @click="confirmCancel(plan)">取消</el-button>
<span class="action-link edit" @click="editRelease(plan)">编辑</span>
<span class="action-link cancel" @click="confirmCancel(plan)">取消</span>
</div>
</div>

<!-- 暂停中的发布计划:继续、回滚、取消 -->
<div class="plan-actions" v-else-if="plan.originalStatus === 'InDeployment' && plan.isPaused">
<!-- 已完成的计划:只显示状态,无操作按钮 -->
<div class="plan-actions" v-else-if="plan.originalStatus === 'Finished'">
<div class="action-column">
<el-button size="small" type="success" @click="togglePauseResume(plan)">继续</el-button>
<el-button size="small" type="info" @click="rollbackRelease(plan)">回滚</el-button>
<el-button size="small" type="danger" @click="confirmCancel(plan)">取消</el-button>
<span class="completed-text">已完成</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -744,9 +751,9 @@ const deploymentPlansForDisplay = computed(() => {
return currentServiceDeploymentPlans.value.items.map(plan => {
// 状态映射
const statusMap = {
'Schedule': '待发布',
'InDeployment': '部署中',
'completed': '已完成',
'rollbacked': '已回滚'
'Finished': '已完成'
}

// 时间格式化
Expand All @@ -771,7 +778,7 @@ const deploymentPlansForDisplay = computed(() => {
} else {
return '已开始'
}
case 'completed':
case 'Finished':
// 已完成:显示开始时间 → 结束时间
if (plan.scheduleTime && plan.finishTime) {
return `开始时间: ${formatTime(plan.scheduleTime)} → 结束时间: ${formatTime(plan.finishTime)}`
Expand All @@ -780,15 +787,6 @@ const deploymentPlansForDisplay = computed(() => {
} else {
return '已完成'
}
case 'rollbacked':
// 已回滚:显示开始时间 → 回滚时间
if (plan.scheduleTime && plan.finishTime) {
return `开始时间: ${formatTime(plan.scheduleTime)} → 回滚时间: ${formatTime(plan.finishTime)}`
} else if (plan.finishTime) {
return `回滚时间: ${formatTime(plan.finishTime)}`
} else {
return '已回滚'
}
default:
return '未知状态'
}
Expand Down Expand Up @@ -967,7 +965,21 @@ const transformMetricsToTableData = (versions: any[], metricsResponse: ServiceMe
}

const getVersionTableData = (versions: any[]) => {
return transformMetricsToTableData(versions, currentServiceMetrics.value)
const tableData = transformMetricsToTableData(versions, currentServiceMetrics.value)

// 为每个版本添加部署状态信息(用于操作按钮)
return tableData.map(version => {
// 检查是否有正在进行的部署
const activeDeployment = currentServiceDeploymentPlans.value?.items?.find((plan: any) =>
plan.version === version.version && plan.status === 'InDeployment'
)

return {
...version,
isPaused: activeDeployment?.isPaused || false,
deployId: activeDeployment?.id || version.version // 如果没有deployId,使用version作为标识
}
})
}
Comment on lines 967 to 983
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

这个函数存在几个问题:

  1. find 回调中,plan 参数被显式地指定为 any 类型,而 DeploymentPlan 类型是可用的。这降低了类型安全性。
  2. deployId 的备用值 version.version 可能不是一个有效的部署ID。如果找不到 activeDeploymentdeployId 应该是 undefined,并且相关的操作(暂停/继续/回滚)应该被禁用或在处理函数中处理这种情况,以避免向后端发送无效请求。
const getVersionTableData = (versions: any[]) => {
  const tableData = transformMetricsToTableData(versions, currentServiceMetrics.value)
  
  // 为每个版本添加部署状态信息(用于操作按钮)
  return tableData.map(version => {
    // 检查是否有正在进行的部署
    const activeDeployment = currentServiceDeploymentPlans.value?.items?.find((plan: DeploymentPlan) => 
      plan.version === version.version && plan.status === 'InDeployment'
    )
    
    return {
      ...version,
      isPaused: activeDeployment?.isPaused || false,
      deployId: activeDeployment?.id // 如果没有部署,则为 undefined
    }
  })
}


const handleCloseDialog = () => {
Expand Down Expand Up @@ -1094,6 +1106,44 @@ const rollbackRelease = async (plan: any) => {
}
}

// 版本表格中的操作
const togglePauseResumeForVersion = async (version: any) => {
try {
if (version.isPaused) {
await mockApi.continueDeployment(version.deployId)
ElMessage.success('继续部署成功')
// 更新本地状态
version.isPaused = false
} else {
await mockApi.pauseDeployment(version.deployId)
ElMessage.success('暂停部署成功')
// 更新本地状态
version.isPaused = true
}
// 刷新服务详情数据
if (selectedNode.value) {
await loadServiceDetail(selectedNode.value.name)
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error('操作失败')
}
}
Comment on lines +1110 to +1131
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

此函数存在几个问题:

  1. version 参数的类型是 any,应该使用更具体的类型。
  2. 函数没有在使用 version.deployId 前检查其是否存在,这与上一个评论中 deployId 可能为 undefined 的情况相关。
  3. 函数在API调用成功后更新本地状态,但如果后续的数据刷新失败,这个状态不会被回滚,可能导致UI与后端状态不一致。推荐使用乐观更新模式:在API调用前更新UI,并在 catch 块中回滚状态以处理任何失败情况。
const togglePauseResumeForVersion = async (version: { deployId?: string; isPaused: boolean }) => {
  if (!version.deployId) {
    ElMessage.error('操作失败: 部署ID不存在');
    return;
  }
  const originalIsPaused = version.isPaused;
  // 乐观更新UI
  version.isPaused = !originalIsPaused;

  try {
    if (originalIsPaused) {
      await mockApi.continueDeployment(version.deployId);
      ElMessage.success('继续部署成功');
    } else {
      await mockApi.pauseDeployment(version.deployId);
      ElMessage.success('暂停部署成功');
    }
    // 刷新服务详情数据
    if (selectedNode.value) {
      await loadServiceDetail(selectedNode.value.name);
    }
  } catch (error) {
    console.error('操作失败:', error);
    ElMessage.error('操作失败');
    // 失败时回滚UI状态
    version.isPaused = originalIsPaused;
  }
}


const rollbackVersion = async (version: any) => {
try {
await mockApi.rollbackDeployment(version.deployId)
ElMessage.success('回滚成功')
// 刷新服务详情数据
if (selectedNode.value) {
await loadServiceDetail(selectedNode.value.name)
}
} catch (error) {
console.error('回滚失败:', error)
ElMessage.error('回滚失败')
}
}
Comment on lines +1133 to +1145
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

version 参数的类型是 any,应该使用更具体的类型。此外,函数没有在使用 version.deployId 之前检查其是否存在,这可能导致向API发送带有 undefined ID的无效请求。

const rollbackVersion = async (version: { deployId?: string }) => {
  if (!version.deployId) {
    ElMessage.error('回滚失败: 部署ID不存在');
    return;
  }
  try {
    await mockApi.rollbackDeployment(version.deployId)
    ElMessage.success('回滚成功')
    // 刷新服务详情数据
    if (selectedNode.value) {
      await loadServiceDetail(selectedNode.value.name)
    }
  } catch (error) {
    console.error('回滚失败:', error)
    ElMessage.error('回滚失败')
  }
}


// 初始化饼图
let pieChart: echarts.ECharts | null = null

Expand Down Expand Up @@ -1654,16 +1704,96 @@ const disposeMetricsCharts = () => {

.plan-actions {
display: flex;
justify-content: flex-end;
justify-content: flex-start;
align-items: flex-end;
min-width: 100px;
padding-left: 10px;
}

.action-column {
display: flex;
flex-direction: column;
gap: 4px;
align-items: flex-end;
flex-direction: row;
gap: 8px;
align-items: center;
}

/* 表格操作样式 */
.table-actions {
display: flex;
gap: 8px;
align-items: center;
justify-content: flex-end;
padding-right: 10px;
}

.action-link {
cursor: pointer;
font-size: 12px;
text-decoration: none;
padding: 2px 4px;
border-radius: 2px;
transition: all 0.2s;
}

.action-link:hover {
opacity: 0.8;
}

/* 暂停按钮 - 黄色 */
.action-link.pause {
color: #e6a23c;
}

.action-link.pause:hover {
background-color: #fdf6ec;
}

/* 继续按钮 - 绿色 */
.action-link.continue {
color: #67c23a;
}

.action-link.continue:hover {
background-color: #f0f9ff;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

“继续”操作链接的鼠标悬浮背景色 #f0f9ff (淡蓝色) 与其代表的“成功”状态 (绿色文字) 不匹配。根据Element Plus的设计规范,成功状态的背景色通常是淡绿色,例如 #f0f9eb

  background-color: #f0f9eb;

}

/* 编辑按钮 - 黑色 */
.action-link.edit {
color: #303133;
}

.action-link.edit:hover {
background-color: #f5f7fa;
}

/* 取消按钮 - 红色 */
.action-link.cancel {
color: #f56c6c;
}

.action-link.cancel:hover {
background-color: #fef0f0;
}

/* 回滚按钮 - 蓝色(保持原有颜色) */
.action-link.rollback {
color: #409eff;
}

.action-link.rollback:hover {
background-color: #ecf5ff;
}

.action-link:not(:last-child)::after {
content: '|';
margin-left: 8px;
color: #dcdfe6;
}

.completed-text {
color: #67c23a;
font-size: 12px;
font-weight: 500;
}

.no-plans {
Expand Down