feat(mcp): подготовить сервер и доступ к приёмке
This commit is contained in:
commit
bf3d50e1ff
3835 files changed
+537122
No files matched your search
@@ -0,0 +1,145 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const alertTypeSchema = z.enum([
|
||||
"gre-tunnel",
|
||||
"bgp-peer",
|
||||
"bgp-prefix",
|
||||
"gre-client",
|
||||
"server",
|
||||
"rtt",
|
||||
"loss",
|
||||
"traffic",
|
||||
])
|
||||
export const alertSeveritySchema = z.enum(["critical", "warning", "info"])
|
||||
export const alertCooldownSchema = z.enum(["1м", "5м", "15м", "1ч", "4ч", "24ч"])
|
||||
export const recoveryModeSchema = z.enum(["always", "never", "conditional"])
|
||||
export const combineModeSchema = z.enum(["any", "all"])
|
||||
|
||||
export const alertGroupSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
combineMode: combineModeSchema,
|
||||
enabled: z.boolean(),
|
||||
cooldownOverride: z.union([alertCooldownSchema, z.null()]),
|
||||
})
|
||||
|
||||
export const alertRuleSchema = z
|
||||
.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
type: alertTypeSchema,
|
||||
target: z.string().optional(),
|
||||
targets: z.array(z.string().min(1)).optional(),
|
||||
groupId: z.string().nullable().optional(),
|
||||
condition: z.string().optional(),
|
||||
conditions: z.array(z.string().min(1)).optional(),
|
||||
severity: alertSeveritySchema,
|
||||
enabled: z.boolean(),
|
||||
cooldown: alertCooldownSchema,
|
||||
confirmStabilitySec: z.union([z.number().int().min(0).max(86400), z.null()]).optional(),
|
||||
recoveryMode: recoveryModeSchema.optional(),
|
||||
recoveryStabilitySec: z.union([z.number().int().min(0).max(86400), z.null()]).optional(),
|
||||
chatId: z.string(),
|
||||
})
|
||||
.refine((r) => (r.targets != null && r.targets.length > 0) || Boolean(r.target?.trim()), {
|
||||
message: "Нужен хотя бы один объект: targets или target",
|
||||
path: ["targets"],
|
||||
})
|
||||
.refine((r) => (r.conditions != null && r.conditions.length > 0) || Boolean(r.condition?.trim()), {
|
||||
message: "Нужно хотя бы одно условие: conditions или condition",
|
||||
path: ["conditions"],
|
||||
})
|
||||
|
||||
export const putRulesSchema = z.object({
|
||||
rules: z.array(alertRuleSchema),
|
||||
groups: z.array(alertGroupSchema).optional(),
|
||||
})
|
||||
|
||||
export const putTelegramSchema = z.object({
|
||||
token: z.union([z.string(), z.null()]).optional(),
|
||||
chatId: z.string().optional(),
|
||||
messageThreadId: z.union([z.number().int().positive().max(2147483647), z.null()]).optional(),
|
||||
})
|
||||
|
||||
export const rulePreviewTelegramSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
targets: z.array(z.string().min(1)).min(1),
|
||||
conditionLine: z.string().min(1),
|
||||
severity: alertSeveritySchema,
|
||||
cooldown: alertCooldownSchema,
|
||||
})
|
||||
|
||||
export const testTelegramSchema = z.object({
|
||||
token: z.string().optional(),
|
||||
chatId: z.string().optional(),
|
||||
messageThreadId: z.number().int().positive().max(2147483647).optional(),
|
||||
rulePreview: rulePreviewTelegramSchema.optional(),
|
||||
})
|
||||
|
||||
export type AlertGroup = z.infer<typeof alertGroupSchema>
|
||||
export type AlertRule = z.infer<typeof alertRuleSchema>
|
||||
export type PutRulesBody = z.infer<typeof putRulesSchema>
|
||||
export type PutTelegramBody = z.infer<typeof putTelegramSchema>
|
||||
export type RulePreviewTelegram = z.infer<typeof rulePreviewTelegramSchema>
|
||||
export type TestTelegramBody = z.infer<typeof testTelegramSchema>
|
||||
|
||||
// Public response schemas: configured credentials do not prove Telegram connectivity/delivery.
|
||||
const alertTimestampSchema = z.string().min(1).refine(value => Number.isFinite(Date.parse(value)), "Некорректное время оповещения")
|
||||
export const telegramPublicSchema = z.object({
|
||||
chatId:z.string(),tokenConfigured:z.boolean(),connected:z.boolean(),messageThreadId:z.number().int().positive().max(2147483647).nullable(),
|
||||
}).strict().refine(value => value.connected === (value.tokenConfigured && Boolean(value.chatId.trim())), "Противоречивое состояние параметров Telegram")
|
||||
export const alertRuleResponseSchema = z.object({
|
||||
id:z.string().min(1),name:z.string().min(1),type:alertTypeSchema,target:z.string(),targets:z.array(z.string().min(1)),groupId:z.string().nullable(),condition:z.string(),conditions:z.array(z.string().min(1)),severity:alertSeveritySchema,enabled:z.boolean(),cooldown:alertCooldownSchema,
|
||||
confirmStabilitySec:z.number().int().min(0).max(86400).nullable(),recoveryMode:recoveryModeSchema,recoveryStabilitySec:z.number().int().min(0).max(86400).nullable(),chatId:z.string(),lastFiredAt:alertTimestampSchema.nullable(),
|
||||
}).strict()
|
||||
export const alertHistoryResponseSchema = z.object({id:z.string().min(1),ruleName:z.string(),severity:alertSeveritySchema,message:z.string(),sent:z.boolean(),firedAt:alertTimestampSchema,source:z.enum(["db","live"])}).strict()
|
||||
export const alertTargetIssueSchema = z.object({
|
||||
kind:z.enum(["server","traffic","probe"]),target:z.string().min(1),reason:z.enum(["ambiguous","disabled-object","disabled-source"]),
|
||||
}).strict()
|
||||
export type AlertTargetIssue=z.output<typeof alertTargetIssueSchema>
|
||||
export const alertsMetaResponseSchema = z.object({
|
||||
servers:z.array(z.string()),greClients:z.array(z.string()),probeTargets:z.array(z.string()),rttLossTargets:z.array(z.string()),trafficServers:z.array(z.string()),
|
||||
serversDetail:z.array(z.object({name:z.string(),host:z.string(),site:z.string(),country:z.string(),type:z.string()}).strict()),
|
||||
targetIssues:z.array(alertTargetIssueSchema).optional(),
|
||||
}).strict()
|
||||
const alertsConfigResponseShape = {groups:z.array(alertGroupSchema.strict()),rules:z.array(alertRuleResponseSchema)}
|
||||
function validConfig(value:{groups:{id:string}[];rules:{id:string;groupId:string|null}[]}):boolean {
|
||||
const groups=new Set(value.groups.map(row=>row.id)),rules=new Set(value.rules.map(row=>row.id))
|
||||
return groups.size===value.groups.length&&rules.size===value.rules.length&&value.rules.every(row=>row.groupId===null||groups.has(row.groupId))
|
||||
}
|
||||
export const alertsRevisionSchema=z.string().regex(/^[a-f0-9]{64}$/)
|
||||
export const alertsResponseSchema = z.object({revision:alertsRevisionSchema,telegram:telegramPublicSchema,...alertsConfigResponseShape,history:z.array(alertHistoryResponseSchema),meta:alertsMetaResponseSchema}).strict().refine(validConfig,"Повторяющиеся ID или неизвестная группа правила")
|
||||
export const alertsRulesReceiptSchema = z.object({ok:z.literal(true),revision:alertsRevisionSchema,...alertsConfigResponseShape}).strict().refine(validConfig,"Повторяющиеся ID или неизвестная группа правила")
|
||||
export const telegramSettingsReceiptSchema = z.object({ok:z.literal(true),telegram:telegramPublicSchema}).strict()
|
||||
export const telegramTestReceiptSchema = z.object({ok:z.literal(true)}).strict()
|
||||
export type AlertsResponse = z.output<typeof alertsResponseSchema>
|
||||
|
||||
/** Normalize the stored rule representation without altering OR/AND or cooldown policies. */
|
||||
export function normalizeAlertsConfig(input:PutRulesBody){
|
||||
const parsed=putRulesSchema.strict().safeParse(input)
|
||||
if(!parsed.success)throw Error("Проверьте правила и группы оповещений")
|
||||
const groups=parsed.data.groups?.map(group=>({...group,name:group.name.trim()||group.id}))
|
||||
const groupIds=groups===undefined?undefined:new Set(groups.map(group=>group.id))
|
||||
const ids=new Set<string>()
|
||||
if(groups!==undefined&&groupIds!.size!==groups.length)throw Error("Повторяющиеся ID групп")
|
||||
const rules=parsed.data.rules.map(rule=>{
|
||||
if(ids.has(rule.id))throw Error("Повторяющиеся ID правил")
|
||||
ids.add(rule.id)
|
||||
const targets=(rule.targets?.length?rule.targets:[rule.target??""]).map(value=>value.trim()).filter(Boolean)
|
||||
const conditions=(rule.conditions?.length?rule.conditions:[rule.condition??""]).map(value=>value.trim()).filter(Boolean)
|
||||
if(!targets.length||!conditions.length)throw Error("Нужны непустые объекты и условия правила")
|
||||
const groupId=rule.groupId?.trim()||null
|
||||
if(groupId!==null&&groupIds!==undefined&&!groupIds.has(groupId))throw Error("Неизвестная группа правила")
|
||||
const target=targets.length<=2?targets.join(", "):targets[0]+", "+targets[1]+" +"+(targets.length-2)
|
||||
const condition=conditions.length===1?conditions[0]!:conditions.length<=3?conditions.join(" · "):conditions[0]+" · "+conditions[1]+" · +"+(conditions.length-2)
|
||||
return {...rule,target,targets,condition,conditions,groupId,confirmStabilitySec:rule.confirmStabilitySec||null,recoveryMode:rule.recoveryMode??"always",recoveryStabilitySec:rule.recoveryStabilitySec||null}
|
||||
})
|
||||
return {...(groups!==undefined?{groups}:{}),rules}
|
||||
}
|
||||
|
||||
const outboxCount=z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)
|
||||
export const alertsOutboxOverviewSchema=z.object({counts:z.object({total:outboxCount,pending:outboxCount,processing:outboxCount,unknown:outboxCount,sent:outboxCount,failed:outboxCount,unrecognized:outboxCount}).strict(),items:z.array(z.object({id:z.string().min(1),resolutionVersion:z.number().int().nonnegative().max(2147483647),status:z.enum(["processing","unknown"]),ruleName:z.string().nullable(),createdAt:alertTimestampSchema,attemptedAt:alertTimestampSchema}).strict()).max(20),itemsLimit:z.literal(20)}).strict().refine(value=>value.counts.total===value.counts.pending+value.counts.processing+value.counts.unknown+value.counts.sent+value.counts.failed+value.counts.unrecognized&&value.items.length===Math.min(20,value.counts.processing+value.counts.unknown)&&value.items.filter(item=>item.status==="processing").length<=value.counts.processing&&value.items.filter(item=>item.status==="unknown").length<=value.counts.unknown&&new Set(value.items.map(item=>item.id)).size===value.items.length,"Неполное состояние очереди")
|
||||
export type AlertsOutboxOverview=z.output<typeof alertsOutboxOverviewSchema>
|
||||
|
||||
export const alertsOutboxRetrySchema=z.object({expectedVersion:z.number().int().nonnegative().max(2147483646),acknowledgedDuplicateRisk:z.literal(true)}).strict()
|
||||
export const alertsOutboxRetryReceiptSchema=z.object({ok:z.literal(true),id:z.string().min(1),status:z.literal("pending"),resolutionVersion:z.number().int().min(1).max(2147483647)}).strict()
|
||||
Reference in new issue
Block a user