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 = {
|
type DashboardOptions = {
|
||||||
includeAdminConfig?: boolean
|
includeAdminConfig?: boolean
|
||||||
}
|
}
|
||||||
|
type ProjectGiteaWebhookStatus = NonNullable<
|
||||||
|
ProjectGiteaRepositoryDiagnostic['webhook']
|
||||||
|
>['status']
|
||||||
type BackendVariableHelpValue =
|
type BackendVariableHelpValue =
|
||||||
| string
|
| string
|
||||||
| {
|
| {
|
||||||
@@ -798,6 +801,9 @@ function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] {
|
|||||||
tagPolicy: tagPolicyLabel(project.environments),
|
tagPolicy: tagPolicyLabel(project.environments),
|
||||||
giteaStatus: 'warning',
|
giteaStatus: 'warning',
|
||||||
giteaMessage: '尚未执行 Gitea 诊断',
|
giteaMessage: '尚未执行 Gitea 诊断',
|
||||||
|
giteaWebhookStatus: 'warning',
|
||||||
|
giteaWebhookMessage: '尚未执行 Webhook 诊断',
|
||||||
|
giteaWebhookHooks: [],
|
||||||
environments: project.environments.map((environment) => {
|
environments: project.environments.map((environment) => {
|
||||||
return {
|
return {
|
||||||
name: environment.name,
|
name: environment.name,
|
||||||
@@ -861,10 +867,55 @@ function sanitizeProjectGiteaRepositoryDiagnostic(
|
|||||||
authoredAt: optionalSafeText(value.latestCommit.authoredAt),
|
authoredAt: optionalSafeText(value.latestCommit.authoredAt),
|
||||||
}
|
}
|
||||||
: undefined,
|
: 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),
|
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(
|
function toProjectGiteaDiagnosticStatus(
|
||||||
value: ProjectGiteaRepositoryDiagnostic['status'],
|
value: ProjectGiteaRepositoryDiagnostic['status'],
|
||||||
): ProjectGiteaRepositoryDiagnostic['status'] {
|
): ProjectGiteaRepositoryDiagnostic['status'] {
|
||||||
|
|||||||
+52
-1
@@ -21,6 +21,10 @@ import type {
|
|||||||
SystemSetting,
|
SystemSetting,
|
||||||
} from '../types/devops'
|
} 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 giteaStatus = giteaDiagnosticState(diagnostic.status)
|
||||||
|
const webhook = diagnostic.webhook
|
||||||
|
const giteaWebhookStatus = webhook ? giteaDiagnosticState(webhook.status) : 'warning'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
@@ -217,9 +223,18 @@ export const usePlatformStore = defineStore('platform', () => {
|
|||||||
giteaBranchCount: diagnostic.branchCount,
|
giteaBranchCount: diagnostic.branchCount,
|
||||||
giteaTagCount: diagnostic.tagCount,
|
giteaTagCount: diagnostic.tagCount,
|
||||||
giteaLatestCommit: diagnostic.latestCommit,
|
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: {
|
integrations: {
|
||||||
...project.integrations,
|
...project.integrations,
|
||||||
gitea: giteaStatus,
|
gitea: aggregateGiteaState(giteaStatus, giteaWebhookStatus),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -390,6 +405,42 @@ function giteaDiagnosticMessage(
|
|||||||
return 'Gitea 仓库不可用'
|
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(
|
function projectEnvironmentKey(
|
||||||
projectKey: string,
|
projectKey: string,
|
||||||
environment: ReleaseRun['environment'],
|
environment: ReleaseRun['environment'],
|
||||||
|
|||||||
@@ -215,6 +215,13 @@ export interface ProjectConfig {
|
|||||||
giteaBranchCount?: number
|
giteaBranchCount?: number
|
||||||
giteaTagCount?: number
|
giteaTagCount?: number
|
||||||
giteaLatestCommit?: ProjectRefCommit
|
giteaLatestCommit?: ProjectRefCommit
|
||||||
|
giteaWebhookStatus: HealthState
|
||||||
|
giteaWebhookMessage?: string
|
||||||
|
giteaWebhookHookCount?: number
|
||||||
|
giteaWebhookActivePushHookCount?: number
|
||||||
|
giteaWebhookExpectedRelayTarget?: string
|
||||||
|
giteaWebhookMatchedHookId?: number
|
||||||
|
giteaWebhookHooks: ProjectGiteaWebhookHookDiagnostic[]
|
||||||
environments: EnvironmentStatus[]
|
environments: EnvironmentStatus[]
|
||||||
integrations: {
|
integrations: {
|
||||||
jenkins: HealthState
|
jenkins: HealthState
|
||||||
@@ -274,6 +281,30 @@ export interface ProjectGiteaRepositoryDiagnostic {
|
|||||||
branchCount?: number
|
branchCount?: number
|
||||||
tagCount?: number
|
tagCount?: number
|
||||||
latestCommit?: ProjectRefCommit
|
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
|
updatedAt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,26 @@ function branchTagText(project: ProjectConfig): string {
|
|||||||
|
|
||||||
return `${branchCount} branches · ${tagCount} tags`
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -118,6 +138,29 @@ function branchTagText(project: ProjectConfig): string {
|
|||||||
<dd>{{ branchTagText(row) }}</dd>
|
<dd>{{ branchTagText(row) }}</dd>
|
||||||
<dt>默认分支 commit</dt>
|
<dt>默认分支 commit</dt>
|
||||||
<dd class="mono">{{ commitText(row) }}</dd>
|
<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>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="env in row.environments" :key="env.name" class="env-config-card">
|
<div v-for="env in row.environments" :key="env.name" class="env-config-card">
|
||||||
@@ -274,6 +317,20 @@ dd {
|
|||||||
color: #7b8798;
|
color: #7b8798;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.webhook-hook-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-hook-item {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-hook-item small {
|
||||||
|
color: #7b8798;
|
||||||
|
}
|
||||||
|
|
||||||
.run-link {
|
.run-link {
|
||||||
color: #2563eb;
|
color: #2563eb;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user