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 })
|
const summary = await devopsApi.getDashboard({ includeAdminConfig: includeAdminData })
|
||||||
projects.value = summary.projects
|
projects.value = summary.projects
|
||||||
runs.value = summary.runs
|
runs.value = summary.runs
|
||||||
|
applyProjectRunSummaries()
|
||||||
integrations.value = summary.integrations
|
integrations.value = summary.integrations
|
||||||
agentCards.value = summary.agentCards
|
agentCards.value = summary.agentCards
|
||||||
settings.value = summary.settings
|
settings.value = summary.settings
|
||||||
@@ -200,6 +201,49 @@ export const usePlatformStore = defineStore('platform', () => {
|
|||||||
|
|
||||||
function upsertRun(run: ReleaseRun) {
|
function upsertRun(run: ReleaseRun) {
|
||||||
runs.value = [run, ...runs.value.filter((item) => item.id !== run.id)]
|
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 {
|
return {
|
||||||
@@ -263,3 +307,10 @@ function jenkinsDiagnosticMessage(
|
|||||||
|
|
||||||
return 'Jenkins job 不可用'
|
return 'Jenkins job 不可用'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function projectEnvironmentKey(
|
||||||
|
projectKey: string,
|
||||||
|
environment: ReleaseRun['environment'],
|
||||||
|
): string {
|
||||||
|
return `${projectKey}:${environment}`
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ function lastBuildText(env: EnvironmentStatus): string {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h2>项目清单</h2>
|
<h2>项目清单</h2>
|
||||||
<ElButton
|
<ElButton
|
||||||
size="small"
|
size="small"
|
||||||
plain
|
plain
|
||||||
@@ -94,7 +94,23 @@ function lastBuildText(env: EnvironmentStatus): string {
|
|||||||
<dt>通知通道</dt>
|
<dt>通知通道</dt>
|
||||||
<dd>{{ env.wecomRobot }}</dd>
|
<dd>{{ env.wecomRobot }}</dd>
|
||||||
<dt>最近发布</dt>
|
<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>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,6 +194,31 @@ dd {
|
|||||||
font-size: 12px;
|
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) {
|
@media (max-width: 760px) {
|
||||||
.env-config {
|
.env-config {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
+20
-1
@@ -4,7 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
import { CircleClose, RefreshRight } from '@element-plus/icons-vue'
|
import { CircleClose, RefreshRight } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
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 BpmnStatusViewer from '../components/BpmnStatusViewer.vue'
|
||||||
import RunExecutionDetails from '../components/RunExecutionDetails.vue'
|
import RunExecutionDetails from '../components/RunExecutionDetails.vue'
|
||||||
import StatusPill from '../components/StatusPill.vue'
|
import StatusPill from '../components/StatusPill.vue'
|
||||||
@@ -12,6 +13,7 @@ import { usePlatformStore } from '../stores/platform'
|
|||||||
import type { ReleaseRun } from '../types/devops'
|
import type { ReleaseRun } from '../types/devops'
|
||||||
|
|
||||||
const platform = usePlatformStore()
|
const platform = usePlatformStore()
|
||||||
|
const route = useRoute()
|
||||||
const selectedRunId = ref(platform.runs[0]?.id || '')
|
const selectedRunId = ref(platform.runs[0]?.id || '')
|
||||||
const syncingAll = ref(false)
|
const syncingAll = ref(false)
|
||||||
|
|
||||||
@@ -29,6 +31,23 @@ const canRetrySelectedRun = computed(() => {
|
|||||||
|
|
||||||
const canSyncAnyRun = computed(() => platform.runs.some((run) => canSyncRun(run)))
|
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 {
|
function canSyncRun(run?: ReleaseRun): boolean {
|
||||||
return (
|
return (
|
||||||
Boolean(run?.jenkinsQueueId || run?.jenkinsBuildNumber) &&
|
Boolean(run?.jenkinsQueueId || run?.jenkinsBuildNumber) &&
|
||||||
|
|||||||
Reference in New Issue
Block a user