feat: 展示 Gitea webhook relay 诊断
- types/api: 接入后端 webhook 诊断响应并安全降级 - store: 将 webhook 状态并入项目 Gitea 集成状态 - ProjectsView: 展示 relay 目标、hook 数量和匹配明细
This commit is contained in:
@@ -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
@@ -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'],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user