feat: 补充集成配置变量说明

This commit is contained in:
湛兮
2026-06-11 23:47:46 +08:00
parent 48b0e12272
commit cf1bebf625
3 changed files with 125 additions and 1 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ pnpm prisma:migrate:dev --name init
## API ## API
- `GET /health`: dependency configuration and health summary. - `GET /health`: dependency configuration and health summary.
- `GET /settings/integration-config`: Jenkins/Gitea/notification/LLM/Database/Redis/Secret Encryption Key configuration status. The response only exposes environment variable names, never secret values. - `GET /settings/integration-config`: Jenkins/Gitea/notification/LLM/Database/Redis/Secret Encryption Key configuration status and variable help text. The response only exposes environment variable names and safe descriptions, never secret values.
- `GET /projects`: configured project list. - `GET /projects`: configured project list.
- `GET /projects/:key`: one project by key. - `GET /projects/:key`: one project by key.
- `GET /deploy-runs`: latest deploy runs. - `GET /deploy-runs`: latest deploy runs.
+117
View File
@@ -6,6 +6,7 @@ import {
IntegrationConfigState, IntegrationConfigState,
IntegrationConfigStatus, IntegrationConfigStatus,
IntegrationConfigStatusResponse, IntegrationConfigStatusResponse,
IntegrationVariableHelp,
} from './settings.types'; } from './settings.types';
type EnvStringKey = { type EnvStringKey = {
@@ -21,6 +22,7 @@ type IntegrationDefinition = {
required: EnvStringKey[]; required: EnvStringKey[];
optional?: EnvKey[]; optional?: EnvKey[];
note?: string; note?: string;
variableHelp?: Partial<Record<EnvKey, IntegrationVariableHelp>>;
}; };
const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [ const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [
@@ -29,6 +31,24 @@ const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [
name: 'Jenkins', name: 'Jenkins',
required: ['JENKINS_BASE_URL', 'JENKINS_USERNAME', 'JENKINS_API_TOKEN'], required: ['JENKINS_BASE_URL', 'JENKINS_USERNAME', 'JENKINS_API_TOKEN'],
optional: ['JENKINS_AUTO_SYNC_ENABLED', 'JENKINS_AUTO_SYNC_INTERVAL_MS'], optional: ['JENKINS_AUTO_SYNC_ENABLED', 'JENKINS_AUTO_SYNC_INTERVAL_MS'],
variableHelp: {
JENKINS_BASE_URL:
variableHelp('Jenkins 服务端 API 地址,只能由后端服务访问。', {
docUrl: 'https://www.jenkins.io/doc/book/using/remote-access-api/',
}),
JENKINS_USERNAME: variableHelp('用于调用 Jenkins Remote API 的服务账号。'),
JENKINS_API_TOKEN: variableHelp(
'Jenkins 服务账号 API token,只能保存在服务端环境变量或后续加密密钥表。',
),
JENKINS_AUTO_SYNC_ENABLED: variableHelp(
'是否启用轻量自动同步器,自动扫描 queued/running 发布单。',
{ example: 'true' },
),
JENKINS_AUTO_SYNC_INTERVAL_MS: variableHelp(
'自动同步 Jenkins 状态的轮询间隔,单位毫秒。',
{ example: '30000' },
),
},
}, },
{ {
key: 'gitea', key: 'gitea',
@@ -36,6 +56,16 @@ const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [
required: ['GITEA_BASE_URL', 'GITEA_TOKEN'], required: ['GITEA_BASE_URL', 'GITEA_TOKEN'],
optional: ['GITEA_WEBHOOK_SECRET'], optional: ['GITEA_WEBHOOK_SECRET'],
note: 'GITEA_WEBHOOK_SECRET 用于开启 webhook 签名校验。', note: 'GITEA_WEBHOOK_SECRET 用于开启 webhook 签名校验。',
variableHelp: {
GITEA_BASE_URL: variableHelp('Gitea 服务端 API 地址,用于读取仓库、分支和 tag。'),
GITEA_TOKEN: variableHelp(
'Gitea API token,只能由后端持有,用于读取 refs 和后续仓库元数据。',
{ docUrl: 'https://docs.gitea.com/development/api-usage' },
),
GITEA_WEBHOOK_SECRET: variableHelp(
'Gitea push webhook 签名密钥;配置后后端会校验 webhook 来源。',
),
},
}, },
{ {
key: 'notification', key: 'notification',
@@ -48,27 +78,82 @@ const INTEGRATION_DEFINITIONS: IntegrationDefinition[] = [
'DEVOPS_PUBLIC_URL', 'DEVOPS_PUBLIC_URL',
], ],
note: '当前不强制配置机器人;后续可按 provider 接入企微、飞书或通用 webhook。', note: '当前不强制配置机器人;后续可按 provider 接入企微、飞书或通用 webhook。',
variableHelp: {
NOTIFICATION_PROVIDER: variableHelp(
'通知 provider,可选 wecom、feishu、generic;未配置时通知节点会 skipped。',
{ example: 'wecom' },
),
WECOM_WEBHOOK_URL: variableHelp(
'企业微信群机器人的服务端 webhook,只能保存在后端。',
{
docUrl: 'https://developer.work.weixin.qq.com/document/path/91770',
},
),
FEISHU_WEBHOOK_URL: variableHelp(
'飞书自定义机器人的服务端 webhook,只能保存在后端。',
{
docUrl:
'https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot',
},
),
NOTIFICATION_WEBHOOK_URL: variableHelp(
'内部通知网关或通用 webhook 的服务端地址,适合接自建消息中转服务。',
),
DEVOPS_PUBLIC_URL: variableHelp('通知消息中跳转回 DevOps 平台的公开访问地址。', {
example: 'https://devops.mrzhan.top',
}),
},
}, },
{ {
key: 'llm', key: 'llm',
name: 'LLM Agent', name: 'LLM Agent',
required: ['LLM_BASE_URL', 'LLM_API_KEY', 'LLM_MODEL'], required: ['LLM_BASE_URL', 'LLM_API_KEY', 'LLM_MODEL'],
variableHelp: {
LLM_BASE_URL: variableHelp('LLM 兼容接口地址,只允许后端代理调用。'),
LLM_API_KEY: variableHelp(
'LLM API key,只能保存在后端环境变量或后续加密密钥表。',
),
LLM_MODEL: variableHelp('DevOps Agent 使用的模型名称。'),
},
}, },
{ {
key: 'database', key: 'database',
name: 'MySQL / Prisma', name: 'MySQL / Prisma',
required: ['DATABASE_URL'], required: ['DATABASE_URL'],
optional: ['USE_DATABASE_READS', 'PRISMA_CONNECT_ON_BOOT'], optional: ['USE_DATABASE_READS', 'PRISMA_CONNECT_ON_BOOT'],
variableHelp: {
DATABASE_URL: variableHelp(
'Prisma 连接 MySQL 的服务端连接串,前端和审计日志不得暴露原文。',
{ docUrl: 'https://www.prisma.io/docs/orm/reference/connection-urls' },
),
USE_DATABASE_READS: variableHelp('是否启用 MySQL 持久化读写路径。', {
example: 'true',
}),
PRISMA_CONNECT_ON_BOOT: variableHelp(
'是否在服务启动时主动连接数据库,用于尽早发现连接问题。',
{ example: 'true' },
),
},
}, },
{ {
key: 'redis', key: 'redis',
name: 'Redis / BullMQ', name: 'Redis / BullMQ',
required: ['REDIS_URL'], required: ['REDIS_URL'],
variableHelp: {
REDIS_URL: variableHelp(
'Redis 连接串,后续用于 BullMQ 队列、通知重试和 Jenkins 日志异步任务。',
),
},
}, },
{ {
key: 'secrets', key: 'secrets',
name: '服务端密钥加密', name: '服务端密钥加密',
required: ['SECRET_ENCRYPTION_KEY'], required: ['SECRET_ENCRYPTION_KEY'],
variableHelp: {
SECRET_ENCRYPTION_KEY: variableHelp(
'服务端加密密钥,用于后续加密保存 Jenkins/Gitea/通知/LLM 等敏感配置。',
),
},
}, },
]; ];
@@ -113,6 +198,9 @@ export class SettingsService {
missing, missing,
...(definition.optional ? { optional: [...definition.optional] } : {}), ...(definition.optional ? { optional: [...definition.optional] } : {}),
...(definition.note ? { note: definition.note } : {}), ...(definition.note ? { note: definition.note } : {}),
...(definition.variableHelp
? { variableHelp: this.pickVariableHelp(definition) }
: {}),
}; };
} }
@@ -143,6 +231,9 @@ export class SettingsService {
provider && !providerWebhook provider && !providerWebhook
? `NOTIFICATION_PROVIDER=${provider} 暂不支持;可选 wecom、feishu、generic。` ? `NOTIFICATION_PROVIDER=${provider} 暂不支持;可选 wecom、feishu、generic。`
: definition.note, : definition.note,
...(definition.variableHelp
? { variableHelp: this.pickVariableHelp(definition, required) }
: {}),
}; };
} }
@@ -188,4 +279,30 @@ export class SettingsService {
return configuredCount > 0 ? 'partial' : 'missing'; return configuredCount > 0 ? 'partial' : 'missing';
} }
private pickVariableHelp(
definition: IntegrationDefinition,
extraKeys: EnvKey[] = [],
): Record<string, IntegrationVariableHelp> {
const visibleKeys = new Set<EnvKey>([
...definition.required,
...(definition.optional ?? []),
...extraKeys,
]);
const helpEntries = Object.entries(definition.variableHelp ?? {}).filter(
([key]) => visibleKeys.has(key as EnvKey),
);
return Object.fromEntries(helpEntries);
}
}
function variableHelp(
description: string,
options: Pick<IntegrationVariableHelp, 'example' | 'docUrl'> = {},
): IntegrationVariableHelp {
return {
description,
...options,
};
} }
+7
View File
@@ -10,6 +10,12 @@ export type IntegrationConfigKey =
export type IntegrationConfigState = 'configured' | 'partial' | 'missing'; export type IntegrationConfigState = 'configured' | 'partial' | 'missing';
export type IntegrationVariableHelp = {
description: string;
example?: string;
docUrl?: string;
};
export type IntegrationConfigStatus = { export type IntegrationConfigStatus = {
key: IntegrationConfigKey; key: IntegrationConfigKey;
name: string; name: string;
@@ -19,6 +25,7 @@ export type IntegrationConfigStatus = {
missing: string[]; missing: string[];
optional?: string[]; optional?: string[];
note?: string; note?: string;
variableHelp?: Record<string, IntegrationVariableHelp>;
}; };
export type IntegrationConfigStatusResponse = { export type IntegrationConfigStatusResponse = {