feat: 展示 Gitea webhook relay 诊断

- types/api: 接入后端 webhook 诊断响应并安全降级
- store: 将 webhook 状态并入项目 Gitea 集成状态
- ProjectsView: 展示 relay 目标、hook 数量和匹配明细
This commit is contained in:
湛兮
2026-06-12 04:57:15 +08:00
parent cb8519d3f9
commit 0302660351
4 changed files with 191 additions and 1 deletions
+51
View File
@@ -50,6 +50,9 @@ type BackendRunStatus = 'pending' | 'queued' | 'running' | 'success' | 'failed'
type DashboardOptions = {
includeAdminConfig?: boolean
}
type ProjectGiteaWebhookStatus = NonNullable<
ProjectGiteaRepositoryDiagnostic['webhook']
>['status']
type BackendVariableHelpValue =
| string
| {
@@ -798,6 +801,9 @@ function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] {
tagPolicy: tagPolicyLabel(project.environments),
giteaStatus: 'warning',
giteaMessage: '尚未执行 Gitea 诊断',
giteaWebhookStatus: 'warning',
giteaWebhookMessage: '尚未执行 Webhook 诊断',
giteaWebhookHooks: [],
environments: project.environments.map((environment) => {
return {
name: environment.name,
@@ -861,10 +867,55 @@ function sanitizeProjectGiteaRepositoryDiagnostic(
authoredAt: optionalSafeText(value.latestCommit.authoredAt),
}
: undefined,
webhook: value.webhook
? {
status: toProjectGiteaWebhookDiagnosticStatus(value.webhook.status),
message: optionalSafeText(value.webhook.message),
hookCount: Number.isFinite(value.webhook.hookCount) ? value.webhook.hookCount : 0,
activePushHookCount: Number.isFinite(value.webhook.activePushHookCount)
? value.webhook.activePushHookCount
: 0,
expectedRelayConfigured: value.webhook.expectedRelayConfigured === true,
expectedRelayTarget: optionalSafeText(value.webhook.expectedRelayTarget),
matchedHookId: Number.isFinite(value.webhook.matchedHookId)
? value.webhook.matchedHookId
: undefined,
hooks: Array.isArray(value.webhook.hooks)
? value.webhook.hooks.map((hook) => ({
id: Number.isFinite(hook.id) ? hook.id : 0,
type: safeText(hook.type, 'unknown'),
active: hook.active === true,
events: Array.isArray(hook.events)
? hook.events.filter((event): event is string => typeof event === 'string')
: [],
branchFilter: optionalSafeText(hook.branchFilter),
target: optionalSafeText(hook.target),
matchesExpectedRelay: hook.matchesExpectedRelay === true,
createdAt: optionalSafeText(hook.createdAt),
updatedAt: optionalSafeText(hook.updatedAt),
}))
: [],
}
: undefined,
updatedAt: optionalSafeText(value.updatedAt),
}
}
function toProjectGiteaWebhookDiagnosticStatus(
value: ProjectGiteaWebhookStatus,
): ProjectGiteaWebhookStatus {
if (
value === 'ok' ||
value === 'missing' ||
value === 'not_configured' ||
value === 'unavailable'
) {
return value
}
return 'unavailable'
}
function toProjectGiteaDiagnosticStatus(
value: ProjectGiteaRepositoryDiagnostic['status'],
): ProjectGiteaRepositoryDiagnostic['status'] {
+52 -1
View File
@@ -21,6 +21,10 @@ import type {
SystemSetting,
} from '../types/devops'
type ProjectGiteaWebhookStatus = NonNullable<
ProjectGiteaDiagnosticsSummary['repositories'][number]['webhook']
>['status']
/**
* 平台运行态聚合后端真实接口数据,页面只消费统一后的展示模型。
*/
@@ -207,6 +211,8 @@ export const usePlatformStore = defineStore('platform', () => {
}
const giteaStatus = giteaDiagnosticState(diagnostic.status)
const webhook = diagnostic.webhook
const giteaWebhookStatus = webhook ? giteaDiagnosticState(webhook.status) : 'warning'
return {
...project,
@@ -217,9 +223,18 @@ export const usePlatformStore = defineStore('platform', () => {
giteaBranchCount: diagnostic.branchCount,
giteaTagCount: diagnostic.tagCount,
giteaLatestCommit: diagnostic.latestCommit,
giteaWebhookStatus,
giteaWebhookMessage: webhook
? webhook.message || giteaWebhookDiagnosticMessage(webhook.status)
: '尚未执行 Webhook 诊断',
giteaWebhookHookCount: webhook?.hookCount,
giteaWebhookActivePushHookCount: webhook?.activePushHookCount,
giteaWebhookExpectedRelayTarget: webhook?.expectedRelayTarget,
giteaWebhookMatchedHookId: webhook?.matchedHookId,
giteaWebhookHooks: webhook?.hooks ?? [],
integrations: {
...project.integrations,
gitea: giteaStatus,
gitea: aggregateGiteaState(giteaStatus, giteaWebhookStatus),
},
}
})
@@ -390,6 +405,42 @@ function giteaDiagnosticMessage(
return 'Gitea 仓库不可用'
}
function giteaWebhookDiagnosticMessage(
status: ProjectGiteaWebhookStatus,
): string {
if (status === 'ok') {
return 'Jenkins relay hook 已配置'
}
if (status === 'missing') {
return '缺少匹配的 Jenkins relay hook'
}
if (status === 'not_configured') {
return 'Webhook relay 诊断未配置'
}
return 'Webhook 诊断不可用'
}
function aggregateGiteaState(
...states: Array<ProjectConfig['giteaStatus']>
): ProjectConfig['giteaStatus'] {
if (states.includes('error')) {
return 'error'
}
if (states.includes('warning')) {
return 'warning'
}
if (states.includes('offline')) {
return 'offline'
}
return 'healthy'
}
function projectEnvironmentKey(
projectKey: string,
environment: ReleaseRun['environment'],
+31
View File
@@ -215,6 +215,13 @@ export interface ProjectConfig {
giteaBranchCount?: number
giteaTagCount?: number
giteaLatestCommit?: ProjectRefCommit
giteaWebhookStatus: HealthState
giteaWebhookMessage?: string
giteaWebhookHookCount?: number
giteaWebhookActivePushHookCount?: number
giteaWebhookExpectedRelayTarget?: string
giteaWebhookMatchedHookId?: number
giteaWebhookHooks: ProjectGiteaWebhookHookDiagnostic[]
environments: EnvironmentStatus[]
integrations: {
jenkins: HealthState
@@ -274,6 +281,30 @@ export interface ProjectGiteaRepositoryDiagnostic {
branchCount?: number
tagCount?: number
latestCommit?: ProjectRefCommit
webhook?: ProjectGiteaWebhookDiagnostic
updatedAt?: string
}
export interface ProjectGiteaWebhookDiagnostic {
status: 'ok' | 'missing' | 'not_configured' | 'unavailable'
message?: string
hookCount: number
activePushHookCount: number
expectedRelayConfigured: boolean
expectedRelayTarget?: string
matchedHookId?: number
hooks: ProjectGiteaWebhookHookDiagnostic[]
}
export interface ProjectGiteaWebhookHookDiagnostic {
id: number
type: string
active: boolean
events: string[]
branchFilter?: string
target?: string
matchesExpectedRelay: boolean
createdAt?: string
updatedAt?: string
}
+57
View File
@@ -60,6 +60,26 @@ function branchTagText(project: ProjectConfig): string {
return `${branchCount} branches · ${tagCount} tags`
}
function webhookCountText(project: ProjectConfig): string {
if (
project.giteaWebhookHookCount === undefined &&
project.giteaWebhookActivePushHookCount === undefined
) {
return '尚未诊断'
}
return `${project.giteaWebhookActivePushHookCount ?? 0}/${project.giteaWebhookHookCount ?? 0} 个 active push hook`
}
function webhookHookText(hook: ProjectConfig['giteaWebhookHooks'][number]): string {
const events = hook.events.length > 0 ? hook.events.join(', ') : '无事件'
const branch = hook.branchFilter ? ` · ${hook.branchFilter}` : ''
const matched = hook.matchesExpectedRelay ? ' · relay 匹配' : ''
const active = hook.active ? '启用' : '停用'
return `#${hook.id} ${hook.type} · ${active} · ${events}${branch}${matched}`
}
</script>
<template>
@@ -118,6 +138,29 @@ function branchTagText(project: ProjectConfig): string {
<dd>{{ branchTagText(row) }}</dd>
<dt>默认分支 commit</dt>
<dd class="mono">{{ commitText(row) }}</dd>
<dt>Webhook / Relay</dt>
<dd>
<StatusPill :state="row.giteaWebhookStatus" />
<span class="diagnostic-message">{{ row.giteaWebhookMessage }}</span>
</dd>
<dt>Relay 目标</dt>
<dd class="mono">{{ row.giteaWebhookExpectedRelayTarget || '-' }}</dd>
<dt>Hook 数量</dt>
<dd>{{ webhookCountText(row) }}</dd>
<dt>Hook 明细</dt>
<dd>
<div v-if="row.giteaWebhookHooks.length > 0" class="webhook-hook-list">
<div
v-for="hook in row.giteaWebhookHooks"
:key="hook.id"
class="webhook-hook-item"
>
<span>{{ webhookHookText(hook) }}</span>
<small class="mono">{{ hook.target || '-' }}</small>
</div>
</div>
<span v-else>-</span>
</dd>
</dl>
</div>
<div v-for="env in row.environments" :key="env.name" class="env-config-card">
@@ -274,6 +317,20 @@ dd {
color: #7b8798;
}
.webhook-hook-list {
display: grid;
gap: 6px;
}
.webhook-hook-item {
display: grid;
gap: 3px;
}
.webhook-hook-item small {
color: #7b8798;
}
.run-link {
color: #2563eb;
text-decoration: none;