feat: 展示项目 Gitea 仓库诊断
- devopsApi: 接入项目 Gitea 诊断接口 - platform store: 回填仓库可达性和默认分支 commit - ProjectsView: 展示 Gitea 状态、分支 tag 数量和 commit 摘要
This commit is contained in:
@@ -29,6 +29,8 @@ import type {
|
||||
PipelineStep,
|
||||
PlatformMessageSummary,
|
||||
ProjectConfig,
|
||||
ProjectGiteaDiagnosticsSummary,
|
||||
ProjectGiteaRepositoryDiagnostic,
|
||||
ProjectJenkinsDiagnosticsSummary,
|
||||
ProjectJenkinsJobDiagnostic,
|
||||
ProjectRefs,
|
||||
@@ -359,6 +361,14 @@ export const devopsApi = {
|
||||
return sanitizeProjectJenkinsDiagnostics(data)
|
||||
},
|
||||
|
||||
async getProjectGiteaDiagnostics(): Promise<ProjectGiteaDiagnosticsSummary> {
|
||||
const { data } = await http.get<ProjectGiteaDiagnosticsSummary>(
|
||||
'/projects/gitea-diagnostics',
|
||||
)
|
||||
lastDataSource = 'backend'
|
||||
return sanitizeProjectGiteaDiagnostics(data)
|
||||
},
|
||||
|
||||
async getProjectRefs(projectKey: string): Promise<ProjectRefs> {
|
||||
const { data } = await http.get<ProjectRefs>(`/projects/${projectKey}/refs`)
|
||||
lastDataSource = 'backend'
|
||||
@@ -786,6 +796,8 @@ function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] {
|
||||
serviceType: serviceTypeLabel(project.key),
|
||||
defaultBranch: project.defaultBranch,
|
||||
tagPolicy: tagPolicyLabel(project.environments),
|
||||
giteaStatus: 'warning',
|
||||
giteaMessage: '尚未执行 Gitea 诊断',
|
||||
environments: project.environments.map((environment) => {
|
||||
return {
|
||||
name: environment.name,
|
||||
@@ -812,6 +824,62 @@ function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] {
|
||||
})
|
||||
}
|
||||
|
||||
function sanitizeProjectGiteaDiagnostics(
|
||||
value: ProjectGiteaDiagnosticsSummary,
|
||||
): ProjectGiteaDiagnosticsSummary {
|
||||
return {
|
||||
checkedAt: safeText(value.checkedAt, new Date().toISOString()),
|
||||
repositories: Array.isArray(value.repositories)
|
||||
? value.repositories.map(sanitizeProjectGiteaRepositoryDiagnostic)
|
||||
: [],
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeProjectGiteaRepositoryDiagnostic(
|
||||
value: ProjectGiteaRepositoryDiagnostic,
|
||||
): ProjectGiteaRepositoryDiagnostic {
|
||||
return {
|
||||
projectKey: safeText(value.projectKey, 'unknown-project'),
|
||||
repositoryUrl: safeText(value.repositoryUrl, ''),
|
||||
owner: optionalSafeText(value.owner),
|
||||
repo: optionalSafeText(value.repo),
|
||||
status: toProjectGiteaDiagnosticStatus(value.status),
|
||||
message: optionalSafeText(value.message),
|
||||
defaultBranch: optionalSafeText(value.defaultBranch),
|
||||
webUrl: optionalSafeText(value.webUrl),
|
||||
cloneUrl: optionalSafeText(value.cloneUrl),
|
||||
archived: typeof value.archived === 'boolean' ? value.archived : undefined,
|
||||
private: typeof value.private === 'boolean' ? value.private : undefined,
|
||||
empty: typeof value.empty === 'boolean' ? value.empty : undefined,
|
||||
branchCount: Number.isFinite(value.branchCount) ? value.branchCount : undefined,
|
||||
tagCount: Number.isFinite(value.tagCount) ? value.tagCount : undefined,
|
||||
latestCommit: value.latestCommit
|
||||
? {
|
||||
sha: safeText(value.latestCommit.sha, ''),
|
||||
message: safeText(value.latestCommit.message, ''),
|
||||
authorName: optionalSafeText(value.latestCommit.authorName),
|
||||
authoredAt: optionalSafeText(value.latestCommit.authoredAt),
|
||||
}
|
||||
: undefined,
|
||||
updatedAt: optionalSafeText(value.updatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
function toProjectGiteaDiagnosticStatus(
|
||||
value: ProjectGiteaRepositoryDiagnostic['status'],
|
||||
): ProjectGiteaRepositoryDiagnostic['status'] {
|
||||
if (
|
||||
value === 'ok' ||
|
||||
value === 'missing' ||
|
||||
value === 'not_configured' ||
|
||||
value === 'unavailable'
|
||||
) {
|
||||
return value
|
||||
}
|
||||
|
||||
return 'unavailable'
|
||||
}
|
||||
|
||||
function sanitizeProjectJenkinsDiagnostics(
|
||||
value: ProjectJenkinsDiagnosticsSummary,
|
||||
): ProjectJenkinsDiagnosticsSummary {
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
IntegrationStatus,
|
||||
IntegrationConfigStatus,
|
||||
ProjectConfig,
|
||||
ProjectGiteaDiagnosticsSummary,
|
||||
ProjectJenkinsDiagnosticsSummary,
|
||||
ProjectRefs,
|
||||
JenkinsSyncSummary,
|
||||
@@ -34,6 +35,7 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
const auditLogs = ref<AuditLogEntry[]>([])
|
||||
const notificationOutbox = ref<NotificationOutboxMessage[]>([])
|
||||
const projectJenkinsDiagnostics = ref<ProjectJenkinsDiagnosticsSummary | null>(null)
|
||||
const projectGiteaDiagnostics = ref<ProjectGiteaDiagnosticsSummary | null>(null)
|
||||
const integrationConfig = ref<IntegrationConfigStatus>({
|
||||
checkedAt: '',
|
||||
integrations: [],
|
||||
@@ -75,6 +77,7 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
releaseProcessXml.value = processXml
|
||||
auditLogs.value = recentAuditLogs
|
||||
notificationOutbox.value = recentNotificationOutbox
|
||||
applyProjectGiteaDiagnostics()
|
||||
applyProjectJenkinsDiagnostics()
|
||||
dataSource.value = devopsApi.getLastDataSource()
|
||||
} finally {
|
||||
@@ -135,6 +138,14 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
async function loadProjectGiteaDiagnostics(): Promise<ProjectGiteaDiagnosticsSummary> {
|
||||
const diagnostics = await devopsApi.getProjectGiteaDiagnostics()
|
||||
projectGiteaDiagnostics.value = diagnostics
|
||||
applyProjectGiteaDiagnostics()
|
||||
dataSource.value = devopsApi.getLastDataSource()
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
function applyProjectJenkinsDiagnostics() {
|
||||
const diagnostics = projectJenkinsDiagnostics.value
|
||||
|
||||
@@ -177,6 +188,43 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
})
|
||||
}
|
||||
|
||||
function applyProjectGiteaDiagnostics() {
|
||||
const diagnostics = projectGiteaDiagnostics.value
|
||||
|
||||
if (!diagnostics) {
|
||||
return
|
||||
}
|
||||
|
||||
const repositoryByProject = new Map(
|
||||
diagnostics.repositories.map((repository) => [repository.projectKey, repository]),
|
||||
)
|
||||
|
||||
projects.value = projects.value.map((project) => {
|
||||
const diagnostic = repositoryByProject.get(project.key)
|
||||
|
||||
if (!diagnostic) {
|
||||
return project
|
||||
}
|
||||
|
||||
const giteaStatus = giteaDiagnosticState(diagnostic.status)
|
||||
|
||||
return {
|
||||
...project,
|
||||
giteaStatus,
|
||||
giteaMessage: diagnostic.message || giteaDiagnosticMessage(diagnostic.status),
|
||||
giteaWebUrl: diagnostic.webUrl,
|
||||
giteaDefaultBranch: diagnostic.defaultBranch,
|
||||
giteaBranchCount: diagnostic.branchCount,
|
||||
giteaTagCount: diagnostic.tagCount,
|
||||
giteaLatestCommit: diagnostic.latestCommit,
|
||||
integrations: {
|
||||
...project.integrations,
|
||||
gitea: giteaStatus,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function loadAuditLogs() {
|
||||
auditLogs.value = await devopsApi.getAuditLogs()
|
||||
dataSource.value = devopsApi.getLastDataSource()
|
||||
@@ -257,6 +305,7 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
auditLogs,
|
||||
notificationOutbox,
|
||||
projectJenkinsDiagnostics,
|
||||
projectGiteaDiagnostics,
|
||||
integrationConfig,
|
||||
releaseProcessXml,
|
||||
projectRefs,
|
||||
@@ -270,6 +319,7 @@ export const usePlatformStore = defineStore('platform', () => {
|
||||
syncJenkinsRuns,
|
||||
loadProjectRefs,
|
||||
loadProjectJenkinsDiagnostics,
|
||||
loadProjectGiteaDiagnostics,
|
||||
loadAuditLogs,
|
||||
loadNotificationOutbox,
|
||||
retryNotificationOutboxMessage,
|
||||
@@ -308,6 +358,38 @@ function jenkinsDiagnosticMessage(
|
||||
return 'Jenkins job 不可用'
|
||||
}
|
||||
|
||||
function giteaDiagnosticState(
|
||||
status: ProjectGiteaDiagnosticsSummary['repositories'][number]['status'],
|
||||
) {
|
||||
if (status === 'ok') {
|
||||
return 'healthy' as const
|
||||
}
|
||||
|
||||
if (status === 'not_configured') {
|
||||
return 'warning' as const
|
||||
}
|
||||
|
||||
return 'error' as const
|
||||
}
|
||||
|
||||
function giteaDiagnosticMessage(
|
||||
status: ProjectGiteaDiagnosticsSummary['repositories'][number]['status'],
|
||||
): string {
|
||||
if (status === 'ok') {
|
||||
return 'Gitea 仓库可达'
|
||||
}
|
||||
|
||||
if (status === 'missing') {
|
||||
return 'Gitea 仓库不存在或无权访问'
|
||||
}
|
||||
|
||||
if (status === 'not_configured') {
|
||||
return 'Gitea 集成未配置'
|
||||
}
|
||||
|
||||
return 'Gitea 仓库不可用'
|
||||
}
|
||||
|
||||
function projectEnvironmentKey(
|
||||
projectKey: string,
|
||||
environment: ReleaseRun['environment'],
|
||||
|
||||
@@ -208,6 +208,13 @@ export interface ProjectConfig {
|
||||
serviceType: string
|
||||
defaultBranch: string
|
||||
tagPolicy: string
|
||||
giteaStatus: HealthState
|
||||
giteaMessage?: string
|
||||
giteaWebUrl?: string
|
||||
giteaDefaultBranch?: string
|
||||
giteaBranchCount?: number
|
||||
giteaTagCount?: number
|
||||
giteaLatestCommit?: ProjectRefCommit
|
||||
environments: EnvironmentStatus[]
|
||||
integrations: {
|
||||
jenkins: HealthState
|
||||
@@ -251,6 +258,30 @@ export interface ProjectRefCommit {
|
||||
authoredAt?: string
|
||||
}
|
||||
|
||||
export interface ProjectGiteaRepositoryDiagnostic {
|
||||
projectKey: string
|
||||
repositoryUrl: string
|
||||
owner?: string
|
||||
repo?: string
|
||||
status: 'ok' | 'missing' | 'not_configured' | 'unavailable'
|
||||
message?: string
|
||||
defaultBranch?: string
|
||||
webUrl?: string
|
||||
cloneUrl?: string
|
||||
archived?: boolean
|
||||
private?: boolean
|
||||
empty?: boolean
|
||||
branchCount?: number
|
||||
tagCount?: number
|
||||
latestCommit?: ProjectRefCommit
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
export interface ProjectGiteaDiagnosticsSummary {
|
||||
checkedAt: string
|
||||
repositories: ProjectGiteaRepositoryDiagnostic[]
|
||||
}
|
||||
|
||||
export interface ProjectRefPullRequest {
|
||||
number: number
|
||||
title: string
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 项目配置页展示项目仓库、发布环境以及真实 Jenkins job 诊断状态。
|
||||
* 项目配置页展示项目仓库、发布环境、Gitea 仓库诊断和真实 Jenkins job 诊断状态。
|
||||
*/
|
||||
import { onMounted, ref } from 'vue'
|
||||
import StatusPill from '../components/StatusPill.vue'
|
||||
import { usePlatformStore } from '../stores/platform'
|
||||
import type { EnvironmentStatus } from '../types/devops'
|
||||
import type { EnvironmentStatus, ProjectConfig } from '../types/devops'
|
||||
|
||||
const platform = usePlatformStore()
|
||||
const diagnosticsLoading = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
void refreshJenkinsDiagnostics()
|
||||
void refreshProjectDiagnostics()
|
||||
})
|
||||
|
||||
async function refreshJenkinsDiagnostics() {
|
||||
async function refreshProjectDiagnostics() {
|
||||
diagnosticsLoading.value = true
|
||||
|
||||
try {
|
||||
await platform.loadProjectJenkinsDiagnostics()
|
||||
await Promise.all([
|
||||
platform.loadProjectJenkinsDiagnostics(),
|
||||
platform.loadProjectGiteaDiagnostics(),
|
||||
])
|
||||
} finally {
|
||||
diagnosticsLoading.value = false
|
||||
}
|
||||
@@ -35,6 +38,28 @@ function lastBuildText(env: EnvironmentStatus): string {
|
||||
|
||||
return `#${env.jenkinsLastBuild.number} ${env.jenkinsLastBuild.result || 'UNKNOWN'}`
|
||||
}
|
||||
|
||||
function commitText(project: ProjectConfig): string {
|
||||
const commit = project.giteaLatestCommit
|
||||
|
||||
if (!commit?.sha) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const title = commit.message.split('\n')[0] || '无提交说明'
|
||||
return `${commit.sha.slice(0, 8)} · ${title}`
|
||||
}
|
||||
|
||||
function branchTagText(project: ProjectConfig): string {
|
||||
if (project.giteaBranchCount === undefined && project.giteaTagCount === undefined) {
|
||||
return '尚未诊断'
|
||||
}
|
||||
|
||||
const branchCount = project.giteaBranchCount ?? 0
|
||||
const tagCount = project.giteaTagCount ?? 0
|
||||
|
||||
return `${branchCount} branches · ${tagCount} tags`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -54,9 +79,9 @@ function lastBuildText(env: EnvironmentStatus): string {
|
||||
size="small"
|
||||
plain
|
||||
:loading="diagnosticsLoading"
|
||||
@click="refreshJenkinsDiagnostics"
|
||||
@click="refreshProjectDiagnostics"
|
||||
>
|
||||
刷新 Jenkins 诊断
|
||||
刷新项目诊断
|
||||
</ElButton>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
@@ -64,6 +89,37 @@ function lastBuildText(env: EnvironmentStatus): string {
|
||||
<ElTableColumn type="expand">
|
||||
<template #default="{ row }">
|
||||
<div class="env-config">
|
||||
<div class="project-diagnostic-card">
|
||||
<div class="env-config-head">
|
||||
<strong>Gitea 仓库诊断</strong>
|
||||
<StatusPill :state="row.giteaStatus" />
|
||||
</div>
|
||||
<dl>
|
||||
<dt>仓库状态</dt>
|
||||
<dd>
|
||||
<StatusPill :state="row.giteaStatus" />
|
||||
<span class="diagnostic-message">{{ row.giteaMessage }}</span>
|
||||
</dd>
|
||||
<dt>公开地址</dt>
|
||||
<dd>
|
||||
<a
|
||||
v-if="row.giteaWebUrl"
|
||||
:href="row.giteaWebUrl"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{{ row.giteaWebUrl }}
|
||||
</a>
|
||||
<span v-else class="mono">{{ row.repository }}</span>
|
||||
</dd>
|
||||
<dt>默认分支</dt>
|
||||
<dd>{{ row.giteaDefaultBranch || row.defaultBranch }}</dd>
|
||||
<dt>分支 / Tag</dt>
|
||||
<dd>{{ branchTagText(row) }}</dd>
|
||||
<dt>默认分支 commit</dt>
|
||||
<dd class="mono">{{ commitText(row) }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div v-for="env in row.environments" :key="env.name" class="env-config-card">
|
||||
<div class="env-config-head">
|
||||
<strong>{{ env.label }}</strong>
|
||||
@@ -157,6 +213,14 @@ function lastBuildText(env: EnvironmentStatus): string {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-diagnostic-card {
|
||||
grid-column: 1 / -1;
|
||||
padding: 14px;
|
||||
border: 1px solid #d7e5ff;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.env-config-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user