feat: 展示项目 Jenkins 诊断状态
This commit is contained in:
@@ -29,6 +29,8 @@ import type {
|
||||
PipelineStep,
|
||||
PlatformMessageSummary,
|
||||
ProjectConfig,
|
||||
ProjectJenkinsDiagnosticsSummary,
|
||||
ProjectJenkinsJobDiagnostic,
|
||||
ProjectRefs,
|
||||
ReleaseRun,
|
||||
ReleaseStatus,
|
||||
@@ -349,6 +351,14 @@ export const devopsApi = {
|
||||
return toProjectConfigs(await fetchProjects())
|
||||
},
|
||||
|
||||
async getProjectJenkinsDiagnostics(): Promise<ProjectJenkinsDiagnosticsSummary> {
|
||||
const { data } = await http.get<ProjectJenkinsDiagnosticsSummary>(
|
||||
'/projects/jenkins-diagnostics',
|
||||
)
|
||||
lastDataSource = 'backend'
|
||||
return sanitizeProjectJenkinsDiagnostics(data)
|
||||
},
|
||||
|
||||
async getProjectRefs(projectKey: string): Promise<ProjectRefs> {
|
||||
const { data } = await http.get<ProjectRefs>(`/projects/${projectKey}/refs`)
|
||||
lastDataSource = 'backend'
|
||||
@@ -787,6 +797,8 @@ function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] {
|
||||
lastReleasedAt: '-',
|
||||
lastActor: '-',
|
||||
jenkinsJob: environment.jenkinsJobPath,
|
||||
jenkinsStatus: 'warning' as const,
|
||||
jenkinsMessage: '尚未执行 Jenkins 诊断',
|
||||
wecomRobot: '后端通知配置',
|
||||
}
|
||||
}),
|
||||
@@ -800,6 +812,59 @@ function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] {
|
||||
})
|
||||
}
|
||||
|
||||
function sanitizeProjectJenkinsDiagnostics(
|
||||
value: ProjectJenkinsDiagnosticsSummary,
|
||||
): ProjectJenkinsDiagnosticsSummary {
|
||||
return {
|
||||
checkedAt: safeText(value.checkedAt, new Date().toISOString()),
|
||||
jobs: Array.isArray(value.jobs)
|
||||
? value.jobs.map(sanitizeProjectJenkinsJobDiagnostic)
|
||||
: [],
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeProjectJenkinsJobDiagnostic(
|
||||
value: ProjectJenkinsJobDiagnostic,
|
||||
): ProjectJenkinsJobDiagnostic {
|
||||
return {
|
||||
projectKey: safeText(value.projectKey, 'unknown-project'),
|
||||
environment: toEnvironmentName(value.environment),
|
||||
jenkinsJobPath: safeText(value.jenkinsJobPath, ''),
|
||||
status: toProjectJenkinsDiagnosticStatus(value.status),
|
||||
message: optionalSafeText(value.message),
|
||||
buildable: typeof value.buildable === 'boolean' ? value.buildable : undefined,
|
||||
url: optionalSafeText(value.url),
|
||||
lastBuild: value.lastBuild
|
||||
? {
|
||||
number: Number.isFinite(value.lastBuild.number)
|
||||
? value.lastBuild.number
|
||||
: undefined,
|
||||
result: value.lastBuild.result,
|
||||
building:
|
||||
typeof value.lastBuild.building === 'boolean'
|
||||
? value.lastBuild.building
|
||||
: undefined,
|
||||
url: optionalSafeText(value.lastBuild.url),
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function toProjectJenkinsDiagnosticStatus(
|
||||
value: ProjectJenkinsJobDiagnostic['status'],
|
||||
): ProjectJenkinsJobDiagnostic['status'] {
|
||||
if (
|
||||
value === 'ok' ||
|
||||
value === 'missing' ||
|
||||
value === 'not_configured' ||
|
||||
value === 'unavailable'
|
||||
) {
|
||||
return value
|
||||
}
|
||||
|
||||
return 'unavailable'
|
||||
}
|
||||
|
||||
function toReleaseRun(run: BackendDeployRun, projectList: ProjectConfig[]): ReleaseRun {
|
||||
const projectKey = run.projectKey || 'unknown-project'
|
||||
const project = projectList.find((item) => item.key === projectKey)
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
IntegrationStatus,
|
||||
IntegrationConfigStatus,
|
||||
ProjectConfig,
|
||||
ProjectJenkinsDiagnosticsSummary,
|
||||
ProjectRefs,
|
||||
JenkinsSyncSummary,
|
||||
NotificationOutboxMessage,
|
||||
@@ -123,6 +124,45 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
return refs
|
||||
}
|
||||
|
||||
async function loadProjectJenkinsDiagnostics(): Promise<ProjectJenkinsDiagnosticsSummary> {
|
||||
const diagnostics = await devopsApi.getProjectJenkinsDiagnostics()
|
||||
const jobByEnvironment = new Map(
|
||||
diagnostics.jobs.map((job) => [`${job.projectKey}:${job.environment}`, job]),
|
||||
)
|
||||
|
||||
projects.value = projects.value.map((project) => {
|
||||
const environments = project.environments.map((environment) => {
|
||||
const diagnostic = jobByEnvironment.get(`${project.key}:${environment.name}`)
|
||||
|
||||
if (!diagnostic) {
|
||||
return environment
|
||||
}
|
||||
|
||||
return {
|
||||
...environment,
|
||||
jenkinsJob: diagnostic.jenkinsJobPath || environment.jenkinsJob,
|
||||
jenkinsStatus: jenkinsDiagnosticState(diagnostic.status),
|
||||
jenkinsMessage: diagnostic.message || jenkinsDiagnosticMessage(diagnostic.status),
|
||||
jenkinsUrl: diagnostic.url,
|
||||
jenkinsLastBuild: diagnostic.lastBuild,
|
||||
}
|
||||
})
|
||||
const hasError = environments.some((environment) => environment.jenkinsStatus === 'error')
|
||||
const hasWarning = environments.some((environment) => environment.jenkinsStatus === 'warning')
|
||||
|
||||
return {
|
||||
...project,
|
||||
environments,
|
||||
integrations: {
|
||||
...project.integrations,
|
||||
jenkins: hasError ? 'error' : hasWarning ? 'warning' : 'healthy',
|
||||
},
|
||||
}
|
||||
})
|
||||
dataSource.value = devopsApi.getLastDataSource()
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
async function loadAuditLogs() {
|
||||
auditLogs.value = await devopsApi.getAuditLogs()
|
||||
dataSource.value = devopsApi.getLastDataSource()
|
||||
@@ -171,8 +211,41 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
syncJenkinsRun,
|
||||
syncJenkinsRuns,
|
||||
loadProjectRefs,
|
||||
loadProjectJenkinsDiagnostics,
|
||||
loadAuditLogs,
|
||||
loadNotificationOutbox,
|
||||
retryNotificationOutboxMessage,
|
||||
}
|
||||
})
|
||||
|
||||
function jenkinsDiagnosticState(
|
||||
status: ProjectJenkinsDiagnosticsSummary['jobs'][number]['status'],
|
||||
) {
|
||||
if (status === 'ok') {
|
||||
return 'healthy' as const
|
||||
}
|
||||
|
||||
if (status === 'not_configured') {
|
||||
return 'warning' as const
|
||||
}
|
||||
|
||||
return 'error' as const
|
||||
}
|
||||
|
||||
function jenkinsDiagnosticMessage(
|
||||
status: ProjectJenkinsDiagnosticsSummary['jobs'][number]['status'],
|
||||
): string {
|
||||
if (status === 'ok') {
|
||||
return 'Jenkins job 可达'
|
||||
}
|
||||
|
||||
if (status === 'missing') {
|
||||
return 'Jenkins job 不存在'
|
||||
}
|
||||
|
||||
if (status === 'not_configured') {
|
||||
return 'Jenkins 集成未配置'
|
||||
}
|
||||
|
||||
return 'Jenkins job 不可用'
|
||||
}
|
||||
|
||||
@@ -187,6 +187,15 @@ export interface EnvironmentStatus {
|
||||
lastReleasedAt: string
|
||||
lastActor: string
|
||||
jenkinsJob: string
|
||||
jenkinsStatus: HealthState
|
||||
jenkinsMessage?: string
|
||||
jenkinsUrl?: string
|
||||
jenkinsLastBuild?: {
|
||||
number?: number
|
||||
result?: 'SUCCESS' | 'FAILURE' | 'ABORTED' | 'UNSTABLE'
|
||||
building?: boolean
|
||||
url?: string
|
||||
}
|
||||
wecomRobot: string
|
||||
}
|
||||
|
||||
@@ -214,6 +223,27 @@ export interface ProjectRefs {
|
||||
tags: ProjectRefItem[]
|
||||
}
|
||||
|
||||
export interface ProjectJenkinsJobDiagnostic {
|
||||
projectKey: string
|
||||
environment: EnvironmentName
|
||||
jenkinsJobPath: string
|
||||
status: 'ok' | 'missing' | 'not_configured' | 'unavailable'
|
||||
message?: string
|
||||
buildable?: boolean
|
||||
url?: string
|
||||
lastBuild?: {
|
||||
number?: number
|
||||
result?: 'SUCCESS' | 'FAILURE' | 'ABORTED' | 'UNSTABLE'
|
||||
building?: boolean
|
||||
url?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProjectJenkinsDiagnosticsSummary {
|
||||
checkedAt: string
|
||||
jobs: ProjectJenkinsJobDiagnostic[]
|
||||
}
|
||||
|
||||
export interface ProjectRefCommit {
|
||||
sha: string
|
||||
message: string
|
||||
|
||||
@@ -1,8 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 项目配置页展示项目仓库、发布环境以及真实 Jenkins job 诊断状态。
|
||||
*/
|
||||
import { onMounted, ref } from 'vue'
|
||||
import StatusPill from '../components/StatusPill.vue'
|
||||
import { usePlatformStore } from '../stores/platform'
|
||||
import type { EnvironmentStatus } from '../types/devops'
|
||||
|
||||
const platform = usePlatformStore()
|
||||
const diagnosticsLoading = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
void refreshJenkinsDiagnostics()
|
||||
})
|
||||
|
||||
async function refreshJenkinsDiagnostics() {
|
||||
diagnosticsLoading.value = true
|
||||
|
||||
try {
|
||||
await platform.loadProjectJenkinsDiagnostics()
|
||||
} finally {
|
||||
diagnosticsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function lastBuildText(env: EnvironmentStatus): string {
|
||||
if (!env.jenkinsLastBuild?.number) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
if (env.jenkinsLastBuild.building) {
|
||||
return `#${env.jenkinsLastBuild.number} 运行中`
|
||||
}
|
||||
|
||||
return `#${env.jenkinsLastBuild.number} ${env.jenkinsLastBuild.result || 'UNKNOWN'}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -18,7 +50,14 @@ const platform = usePlatformStore()
|
||||
<section class="panel">
|
||||
<div class="panel-header">
|
||||
<h2>项目清单</h2>
|
||||
<span>由后端 projects 接口返回</span>
|
||||
<ElButton
|
||||
size="small"
|
||||
plain
|
||||
:loading="diagnosticsLoading"
|
||||
@click="refreshJenkinsDiagnostics"
|
||||
>
|
||||
刷新 Jenkins 诊断
|
||||
</ElButton>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ElTable :data="platform.projects" row-key="key">
|
||||
@@ -35,6 +74,23 @@ const platform = usePlatformStore()
|
||||
<dd>{{ env.branchPolicy }}</dd>
|
||||
<dt>Jenkins Job</dt>
|
||||
<dd class="mono">{{ env.jenkinsJob }}</dd>
|
||||
<dt>Job 状态</dt>
|
||||
<dd>
|
||||
<StatusPill :state="env.jenkinsStatus" />
|
||||
<span class="diagnostic-message">{{ env.jenkinsMessage }}</span>
|
||||
</dd>
|
||||
<dt>最近构建</dt>
|
||||
<dd>
|
||||
<a
|
||||
v-if="env.jenkinsLastBuild?.url"
|
||||
:href="env.jenkinsLastBuild.url"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ lastBuildText(env) }}
|
||||
</a>
|
||||
<span v-else>{{ lastBuildText(env) }}</span>
|
||||
</dd>
|
||||
<dt>通知通道</dt>
|
||||
<dd>{{ env.wecomRobot }}</dd>
|
||||
<dt>最近发布</dt>
|
||||
@@ -116,6 +172,12 @@ dd {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.diagnostic-message {
|
||||
margin-left: 8px;
|
||||
color: #596578;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.env-config {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
Reference in New Issue
Block a user