feat: 展示项目最近发布状态
- store: 将真实 deploy-runs 回填到项目环境最近发布摘要 - ProjectsView: 展示最近 run 状态、ref、操作者和跳转入口 - RunsView: 支持通过 runId query 定位运行记录
This commit is contained in:
@@ -55,6 +55,7 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
const summary = await devopsApi.getDashboard({ includeAdminConfig: includeAdminData })
|
||||
projects.value = summary.projects
|
||||
runs.value = summary.runs
|
||||
applyProjectRunSummaries()
|
||||
integrations.value = summary.integrations
|
||||
agentCards.value = summary.agentCards
|
||||
settings.value = summary.settings
|
||||
@@ -200,6 +201,49 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
|
||||
function upsertRun(run: ReleaseRun) {
|
||||
runs.value = [run, ...runs.value.filter((item) => item.id !== run.id)]
|
||||
applyProjectRunSummaries()
|
||||
}
|
||||
|
||||
function applyProjectRunSummaries() {
|
||||
const latestRunByEnvironment = new Map<string, ReleaseRun>()
|
||||
|
||||
// 后端 /deploy-runs 按最近记录返回;首次出现的项目环境即当前展示摘要。
|
||||
runs.value.forEach((run) => {
|
||||
const key = projectEnvironmentKey(run.projectKey, run.environment)
|
||||
|
||||
if (!latestRunByEnvironment.has(key)) {
|
||||
latestRunByEnvironment.set(key, run)
|
||||
}
|
||||
})
|
||||
|
||||
projects.value = projects.value.map((project) => ({
|
||||
...project,
|
||||
environments: project.environments.map((environment) => {
|
||||
const run = latestRunByEnvironment.get(
|
||||
projectEnvironmentKey(project.key, environment.name),
|
||||
)
|
||||
|
||||
if (!run) {
|
||||
return {
|
||||
...environment,
|
||||
status: 'idle',
|
||||
version: '-',
|
||||
lastRunId: '-',
|
||||
lastReleasedAt: '-',
|
||||
lastActor: '-',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...environment,
|
||||
status: run.status,
|
||||
version: run.ref,
|
||||
lastRunId: run.id,
|
||||
lastReleasedAt: run.startedAt,
|
||||
lastActor: run.actor,
|
||||
}
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -263,3 +307,10 @@ function jenkinsDiagnosticMessage(
|
||||
|
||||
return 'Jenkins job 不可用'
|
||||
}
|
||||
|
||||
function projectEnvironmentKey(
|
||||
projectKey: string,
|
||||
environment: ReleaseRun['environment'],
|
||||
): string {
|
||||
return `${projectKey}:${environment}`
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ function lastBuildText(env: EnvironmentStatus): string {
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-header">
|
||||
<h2>项目清单</h2>
|
||||
<div class="panel-header">
|
||||
<h2>项目清单</h2>
|
||||
<ElButton
|
||||
size="small"
|
||||
plain
|
||||
@@ -94,7 +94,23 @@ function lastBuildText(env: EnvironmentStatus): string {
|
||||
<dt>通知通道</dt>
|
||||
<dd>{{ env.wecomRobot }}</dd>
|
||||
<dt>最近发布</dt>
|
||||
<dd>{{ env.lastRunId }} · {{ env.version }}</dd>
|
||||
<dd>
|
||||
<div v-if="env.lastRunId !== '-'" class="latest-release">
|
||||
<div class="latest-release-head">
|
||||
<StatusPill :state="env.status" />
|
||||
<RouterLink
|
||||
class="run-link mono"
|
||||
:to="{ name: 'runs', query: { runId: env.lastRunId } }"
|
||||
>
|
||||
{{ env.lastRunId }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
<small>
|
||||
{{ env.version }} · {{ env.lastActor }} · {{ env.lastReleasedAt }}
|
||||
</small>
|
||||
</div>
|
||||
<span v-else>暂无真实发布记录</span>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -178,6 +194,31 @@ dd {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.latest-release {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.latest-release-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.latest-release small {
|
||||
color: #7b8798;
|
||||
}
|
||||
|
||||
.run-link {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.run-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.env-config {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
+20
-1
@@ -4,7 +4,8 @@
|
||||
*/
|
||||
import { CircleClose, RefreshRight } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import BpmnStatusViewer from '../components/BpmnStatusViewer.vue'
|
||||
import RunExecutionDetails from '../components/RunExecutionDetails.vue'
|
||||
import StatusPill from '../components/StatusPill.vue'
|
||||
@@ -12,6 +13,7 @@ import { usePlatformStore } from '../stores/platform'
|
||||
import type { ReleaseRun } from '../types/devops'
|
||||
|
||||
const platform = usePlatformStore()
|
||||
const route = useRoute()
|
||||
const selectedRunId = ref(platform.runs[0]?.id || '')
|
||||
const syncingAll = ref(false)
|
||||
|
||||
@@ -29,6 +31,23 @@ const canRetrySelectedRun = computed(() => {
|
||||
|
||||
const canSyncAnyRun = computed(() => platform.runs.some((run) => canSyncRun(run)))
|
||||
|
||||
watch(
|
||||
() => [route.query.runId, platform.runs.map((run) => run.id).join('|')],
|
||||
() => {
|
||||
const queryRunId = typeof route.query.runId === 'string' ? route.query.runId : ''
|
||||
|
||||
if (queryRunId && platform.runs.some((run) => run.id === queryRunId)) {
|
||||
selectedRunId.value = queryRunId
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedRunId.value && platform.runs[0]) {
|
||||
selectedRunId.value = platform.runs[0].id
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function canSyncRun(run?: ReleaseRun): boolean {
|
||||
return (
|
||||
Boolean(run?.jenkinsQueueId || run?.jenkinsBuildNumber) &&
|
||||
|
||||
Reference in New Issue
Block a user