diff --git a/src/services/devopsApi.ts b/src/services/devopsApi.ts index 3e41633..bd5ae94 100644 --- a/src/services/devopsApi.ts +++ b/src/services/devopsApi.ts @@ -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'] { diff --git a/src/stores/platform.ts b/src/stores/platform.ts index 126409d..01a61ff 100644 --- a/src/stores/platform.ts +++ b/src/stores/platform.ts @@ -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'] { + 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'], diff --git a/src/types/devops.ts b/src/types/devops.ts index 7ccd12f..6cc23b3 100644 --- a/src/types/devops.ts +++ b/src/types/devops.ts @@ -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 } diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index 2e7b252..53bfe00 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -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}` +}