diff --git a/src/server/model/_schema/feed.ts b/src/server/model/_schema/feed.ts new file mode 100644 index 0000000..ca5d463 --- /dev/null +++ b/src/server/model/_schema/feed.ts @@ -0,0 +1,86 @@ +import { z } from 'zod'; + +const tencentCloudAlarmEventSchema = z.object({ + sessionId: z.string(), + alarmStatus: z.union([z.literal('0'), z.literal('1')]), + alarmType: z.literal('event'), + alarmObjInfo: z.object({ + region: z.string().optional(), + appId: z.string(), + uin: z.string(), + dimensions: z.object({ + unInstanceId: z.string(), + }), + }), + alarmPolicyInfo: z.object({ + policyName: z.string(), + conditions: z.object({ + productName: z.string(), + productShowName: z.string(), + eventName: z.string(), + eventShowName: z.string(), + }), + }), + firstOccurTime: z.string(), + durationTime: z.number(), + recoverTime: z.string(), +}); + +// 定义 metric 类型 +const tencentCloudAlarmMetricSchema = z.object({ + sessionId: z.string(), + alarmStatus: z.union([z.literal('0'), z.literal('1')]), + alarmType: z.literal('metric'), + alarmLevel: z.string(), + alarmObjInfo: z.object({ + region: z.string().optional(), + namespace: z.string(), + appId: z.string(), + uin: z.string(), + dimensions: z.object({ + deviceName: z.string(), + objId: z.string(), + objName: z.string(), + unInstanceId: z.string(), + }), + }), + alarmPolicyInfo: z.object({ + policyId: z.string(), + policyType: z.string(), + policyName: z.string(), + policyTypeCName: z.string(), + policyTypeEname: z.string().optional(), + conditions: z.object({ + metricName: z.string(), + metricShowName: z.string(), + calcType: z.string().optional(), + calcValue: z.string().optional(), + currentValue: z.string().optional(), + historyValue: z.string().optional(), + unit: z.string().optional(), + calcUnit: z.string().optional(), + period: z.string().optional(), + periodNum: z.string().optional(), + alarmNotifyType: z.string().optional(), + alarmNotifyPeriod: z.number().optional(), + }), + tag: z.array(z.unknown()).optional(), + policyTags: z + .array( + z.object({ + key: z.string(), + value: z.string(), + }) + ) + .optional(), + }), + firstOccurTime: z.string(), + durationTime: z.number(), + recoverTime: z.string(), + policyDetailURL: z.string().optional(), +}); + +export const tencentCloudAlarmSchema = z.union([ + tencentCloudAlarmEventSchema, + tencentCloudAlarmMetricSchema, +]); diff --git a/src/server/model/_schema/monitor.ts b/src/server/model/_schema/monitor.ts index 44bb24a..d97b76f 100644 --- a/src/server/model/_schema/monitor.ts +++ b/src/server/model/_schema/monitor.ts @@ -5,7 +5,7 @@ export type MonitorInfoWithNotificationIds = MonitorInfo & { notifications: { id: string }[]; }; -export const MonitorPublicInfoSchema = MonitorModelSchema.pick({ +export const monitorPublicInfoSchema = MonitorModelSchema.pick({ id: true, name: true, type: true, diff --git a/src/server/model/monitor/index.ts b/src/server/model/monitor/index.ts index b0b2e6b..7b56971 100644 --- a/src/server/model/monitor/index.ts +++ b/src/server/model/monitor/index.ts @@ -1,5 +1,5 @@ import { prisma } from '../_client'; -import { MonitorPublicInfoSchema } from '../_schema/monitor'; +import { monitorPublicInfoSchema } from '../_schema/monitor'; import { MonitorManager } from './manager'; export const monitorManager = new MonitorManager(); @@ -13,7 +13,7 @@ export async function getMonitorPublicInfos(monitorIds: string[]) { }, }); - return res.map((item) => MonitorPublicInfoSchema.parse(item)); + return res.map((item) => monitorPublicInfoSchema.parse(item)); } export function getMonitorData( diff --git a/src/server/trpc/routers/feed/integration.ts b/src/server/trpc/routers/feed/integration.ts index e64e4f9..86224e5 100644 --- a/src/server/trpc/routers/feed/integration.ts +++ b/src/server/trpc/routers/feed/integration.ts @@ -5,6 +5,7 @@ import _ from 'lodash'; import { OpenApiMeta } from 'trpc-openapi'; import { OPENAPI_TAG } from '../../../utils/const'; import { createFeedEvent } from '../../../model/feed/event'; +import { tencentCloudAlarmSchema } from '../../../model/_schema/feed'; export const feedIntegrationRouter = router({ github: publicProcedure @@ -39,7 +40,7 @@ export const feedIntegrationRouter = router({ .then((res) => res?.workspaceId); if (!workspaceId) { - return 'Not found'; + throw new Error('Not found Workspace'); } if (eventType === 'push') { @@ -127,7 +128,91 @@ export const feedIntegrationRouter = router({ } } - return 'not supported'; + return 'Not supported yet'; + }), + tencentCloudAlarm: publicProcedure + .meta( + buildFeedPublicOpenapi({ + method: 'POST', + path: '/{channelId}/tencent-cloud/alarm', + summary: 'integrate with tencent-cloud webhook', + }) + ) + .input( + z + .object({ + channelId: z.string(), + }) + .passthrough() + ) + .output(z.string()) + .mutation(async ({ input, ctx }) => { + const { channelId, ...data } = input; + + const workspaceId = await prisma.feedChannel + .findFirst({ + where: { + id: channelId, + }, + select: { + workspaceId: true, + }, + }) + .then((res) => res?.workspaceId); + + if (!workspaceId) { + throw new Error('Not found Workspace'); + } + + const res = tencentCloudAlarmSchema.safeParse(data); + if (!res.success) { + throw new Error('Input not valid'); + } + + const alarm = res.data; + + if (alarm.alarmType === 'event') { + const conditions = alarm.alarmPolicyInfo.conditions; + + await createFeedEvent(workspaceId, { + channelId: channelId, + eventName: alarm.alarmType, + eventContent: `[${alarm.alarmStatus === '1' ? 'Trigger' : 'Recover'}] **${alarm.alarmObjInfo.dimensions.unInstanceId}** ${alarm.alarmPolicyInfo.policyName} ${conditions.productShowName} ${conditions.eventShowName}.`, + tags: [ + alarm.alarmObjInfo.appId, + alarm.alarmObjInfo.dimensions.unInstanceId, + ], + source: 'tencent-cloud', + senderId: alarm.alarmObjInfo.appId, + senderName: alarm.alarmPolicyInfo.policyName, + important: alarm.alarmStatus === '1', + }); + + return 'ok'; + } + + if (alarm.alarmType === 'metric') { + const conditions = alarm.alarmPolicyInfo.conditions; + + await createFeedEvent(workspaceId, { + channelId: channelId, + eventName: alarm.alarmType, + eventContent: `[${alarm.alarmStatus === '1' ? 'Trigger' : 'Recover'}] **${alarm.alarmObjInfo.dimensions.unInstanceId}** ${alarm.alarmPolicyInfo.policyName} ${conditions.metricShowName} ${conditions.calcType} ${conditions.calcValue}${conditions.calcUnit} (current: ${conditions.currentValue}${conditions.unit}) [${alarm.firstOccurTime} - ${alarm.durationTime}](keep: ${alarm.durationTime}s).`, + tags: [ + alarm.alarmObjInfo.appId, + alarm.alarmObjInfo.dimensions.unInstanceId, + ...(alarm.alarmPolicyInfo.tag ?? []).map(String), + ], + source: 'tencent-cloud', + senderId: alarm.alarmObjInfo.appId, + senderName: alarm.alarmPolicyInfo.policyName, + important: alarm.alarmStatus === '1', + }); + + return 'ok'; + } + + return 'Not supported yet'; }), }); diff --git a/src/server/trpc/routers/monitor.ts b/src/server/trpc/routers/monitor.ts index 586b1f4..945e9fa 100644 --- a/src/server/trpc/routers/monitor.ts +++ b/src/server/trpc/routers/monitor.ts @@ -29,7 +29,7 @@ import { runCodeInVM } from '../../model/monitor/provider/custom'; import { createAuditLog } from '../../model/auditLog'; import { MonitorInfoWithNotificationIds, - MonitorPublicInfoSchema, + monitorPublicInfoSchema, } from '../../model/_schema/monitor'; import { monitorPageManager } from '../../model/monitor/page/manager'; @@ -107,7 +107,7 @@ export const monitorRouter = router({ monitorIds: z.array(z.string()), }) ) - .output(z.array(MonitorPublicInfoSchema)) + .output(z.array(monitorPublicInfoSchema)) .query(async ({ input }) => { const { monitorIds } = input;