fix: get notification template error
This commit is contained in:
1 parent
a6a9976852
commit
af7a006c16
16 files changed
+6226
-1914
No files matched your search
File renamed without changes.
File diff suppressed because it is too large.
Load diff
@@ -33,9 +33,9 @@
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "6",
|
||||
"when": 1751016668000,
|
||||
"tag": "0004_lame_mercenary",
|
||||
"when": 1762361284317,
|
||||
"tag": "0004_many_avengers",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,7 @@ CREATE INDEX IF NOT EXISTS \`monitor_status_history_24h_monitor_timestamp_idx\`
|
||||
sql: `CREATE INDEX IF NOT EXISTS \`monitor_status_history_24h_timestamp_idx\` ON \`monitor_status_history_24h\` (\`timestamp\`);`
|
||||
},
|
||||
{
|
||||
name: "0004_lame_mercenary.sql",
|
||||
name: "0004_many_avengers.sql",
|
||||
sql: `CREATE TABLE IF NOT EXISTS \`settings\` (
|
||||
\`key\` text PRIMARY KEY NOT NULL,
|
||||
\`value\` text
|
||||
|
||||
+144
-77
@@ -1,4 +1,11 @@
|
||||
import { int, sqliteTable, text, real, primaryKey, index } from "drizzle-orm/sqlite-core";
|
||||
import {
|
||||
int,
|
||||
sqliteTable,
|
||||
text,
|
||||
real,
|
||||
primaryKey,
|
||||
index,
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
|
||||
// 用户表
|
||||
export const users = sqliteTable("users", {
|
||||
@@ -8,7 +15,7 @@ export const users = sqliteTable("users", {
|
||||
email: text("email"),
|
||||
role: text("role").notNull(),
|
||||
created_at: text("created_at").notNull(),
|
||||
updated_at: text("updated_at").notNull()
|
||||
updated_at: text("updated_at").notNull(),
|
||||
});
|
||||
|
||||
// 监控表
|
||||
@@ -22,54 +29,77 @@ export const monitors = sqliteTable("monitors", {
|
||||
expected_status: int("expected_status").notNull(),
|
||||
headers: text("headers").notNull(),
|
||||
body: text("body"),
|
||||
created_by: int("created_by").notNull().references(() => users.id),
|
||||
created_by: int("created_by")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
active: int("active").notNull(), // SQLite 没有布尔类型,用 int 代替
|
||||
status: text("status").default("pending"),
|
||||
response_time: int("response_time").default(0),
|
||||
last_checked: text("last_checked"),
|
||||
created_at: text("created_at").notNull(),
|
||||
updated_at: text("updated_at").notNull()
|
||||
updated_at: text("updated_at").notNull(),
|
||||
});
|
||||
|
||||
// 24小时监控状态历史表
|
||||
export const monitorStatusHistory24h = sqliteTable("monitor_status_history_24h", {
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
monitor_id: int("monitor_id").notNull().references(() => monitors.id),
|
||||
status: text("status").notNull(),
|
||||
timestamp: text("timestamp").default("CURRENT_TIMESTAMP"),
|
||||
response_time: int("response_time"),
|
||||
status_code: int("status_code"),
|
||||
error: text("error")
|
||||
}, (table) => ({
|
||||
// monitor_id 和 timestamp 的联合索引,用于优化按监控项和时间查询的性能
|
||||
monitorTimestampIdx: index("monitor_status_history_24h_monitor_timestamp_idx").on(table.monitor_id, table.timestamp),
|
||||
// timestamp 单独索引,用于优化按时间排序和范围查询的性能
|
||||
timestampIdx: index("monitor_status_history_24h_timestamp_idx").on(table.timestamp)
|
||||
}));
|
||||
export const monitorStatusHistory24h = sqliteTable(
|
||||
"monitor_status_history_24h",
|
||||
{
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
monitor_id: int("monitor_id")
|
||||
.notNull()
|
||||
.references(() => monitors.id),
|
||||
status: text("status").notNull(),
|
||||
timestamp: text("timestamp").default("CURRENT_TIMESTAMP"),
|
||||
response_time: int("response_time"),
|
||||
status_code: int("status_code"),
|
||||
error: text("error"),
|
||||
},
|
||||
(table) => ({
|
||||
// monitor_id 和 timestamp 的联合索引,用于优化按监控项和时间查询的性能
|
||||
monitorTimestampIdx: index(
|
||||
"monitor_status_history_24h_monitor_timestamp_idx"
|
||||
).on(table.monitor_id, table.timestamp),
|
||||
// timestamp 单独索引,用于优化按时间排序和范围查询的性能
|
||||
timestampIdx: index("monitor_status_history_24h_timestamp_idx").on(
|
||||
table.timestamp
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
// 监控每日统计表
|
||||
export const monitorDailyStats = sqliteTable("monitor_daily_stats", {
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
monitor_id: int("monitor_id").notNull().references(() => monitors.id),
|
||||
date: text("date").notNull(),
|
||||
total_checks: int("total_checks").notNull().default(0),
|
||||
up_checks: int("up_checks").notNull().default(0),
|
||||
down_checks: int("down_checks").notNull().default(0),
|
||||
avg_response_time: int("avg_response_time").default(0),
|
||||
min_response_time: int("min_response_time").default(0),
|
||||
max_response_time: int("max_response_time").default(0),
|
||||
availability: real("availability").default(0),
|
||||
created_at: text("created_at").notNull()
|
||||
}, (table) => ({
|
||||
monitorDateIdx: index("monitor_daily_stats_monitor_id_date_idx").on(table.monitor_id, table.date)
|
||||
}));
|
||||
export const monitorDailyStats = sqliteTable(
|
||||
"monitor_daily_stats",
|
||||
{
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
monitor_id: int("monitor_id")
|
||||
.notNull()
|
||||
.references(() => monitors.id),
|
||||
date: text("date").notNull(),
|
||||
total_checks: int("total_checks").notNull().default(0),
|
||||
up_checks: int("up_checks").notNull().default(0),
|
||||
down_checks: int("down_checks").notNull().default(0),
|
||||
avg_response_time: int("avg_response_time").default(0),
|
||||
min_response_time: int("min_response_time").default(0),
|
||||
max_response_time: int("max_response_time").default(0),
|
||||
availability: real("availability").default(0),
|
||||
created_at: text("created_at").notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
monitorDateIdx: index("monitor_daily_stats_monitor_id_date_idx").on(
|
||||
table.monitor_id,
|
||||
table.date
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
// 客户端表
|
||||
export const agents = sqliteTable("agents", {
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
name: text("name").notNull(),
|
||||
token: text("token").notNull().unique(),
|
||||
created_by: int("created_by").notNull().references(() => users.id),
|
||||
created_by: int("created_by")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
status: text("status").default("inactive"),
|
||||
created_at: text("created_at").notNull(),
|
||||
updated_at: text("updated_at").notNull(),
|
||||
@@ -81,54 +111,81 @@ export const agents = sqliteTable("agents", {
|
||||
});
|
||||
|
||||
// 客户端资源指标表
|
||||
export const agentMetrics24h = sqliteTable("agent_metrics_24h", {
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
agent_id: int("agent_id").notNull().references(() => agents.id),
|
||||
timestamp: text("timestamp").default("CURRENT_TIMESTAMP"),
|
||||
cpu_usage: real("cpu_usage"),
|
||||
cpu_cores: int("cpu_cores"),
|
||||
cpu_model: text("cpu_model"),
|
||||
memory_total: int("memory_total"),
|
||||
memory_used: int("memory_used"),
|
||||
memory_free: int("memory_free"),
|
||||
memory_usage_rate: real("memory_usage_rate"),
|
||||
load_1: real("load_1"),
|
||||
load_5: real("load_5"),
|
||||
load_15: real("load_15"),
|
||||
disk_metrics: text("disk_metrics"),
|
||||
network_metrics: text("network_metrics")
|
||||
}, (table) => ({
|
||||
// agent_id 和 timestamp 的联合索引,用于优化按代理和时间查询的性能
|
||||
agentTimestampIdx: index("agent_metrics_24h_agent_timestamp_idx").on(table.agent_id, table.timestamp)
|
||||
}));
|
||||
export const agentMetrics24h = sqliteTable(
|
||||
"agent_metrics_24h",
|
||||
{
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
agent_id: int("agent_id")
|
||||
.notNull()
|
||||
.references(() => agents.id),
|
||||
timestamp: text("timestamp").default("CURRENT_TIMESTAMP"),
|
||||
cpu_usage: real("cpu_usage"),
|
||||
cpu_cores: int("cpu_cores"),
|
||||
cpu_model: text("cpu_model"),
|
||||
memory_total: int("memory_total"),
|
||||
memory_used: int("memory_used"),
|
||||
memory_free: int("memory_free"),
|
||||
memory_usage_rate: real("memory_usage_rate"),
|
||||
load_1: real("load_1"),
|
||||
load_5: real("load_5"),
|
||||
load_15: real("load_15"),
|
||||
disk_metrics: text("disk_metrics"),
|
||||
network_metrics: text("network_metrics"),
|
||||
},
|
||||
(table) => ({
|
||||
// agent_id 和 timestamp 的联合索引,用于优化按代理和时间查询的性能
|
||||
agentTimestampIdx: index("agent_metrics_24h_agent_timestamp_idx").on(
|
||||
table.agent_id,
|
||||
table.timestamp
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
// 状态页配置表
|
||||
export const statusPageConfig = sqliteTable("status_page_config", {
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
user_id: int("user_id").notNull().references(() => users.id),
|
||||
user_id: int("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
title: text("title").notNull().default("系统状态"),
|
||||
description: text("description").default("系统当前运行状态"),
|
||||
logo_url: text("logo_url").default(""),
|
||||
custom_css: text("custom_css").default(""),
|
||||
created_at: text("created_at").default("CURRENT_TIMESTAMP"),
|
||||
updated_at: text("updated_at").default("CURRENT_TIMESTAMP")
|
||||
updated_at: text("updated_at").default("CURRENT_TIMESTAMP"),
|
||||
});
|
||||
|
||||
// 状态页监控项关联表
|
||||
export const statusPageMonitors = sqliteTable("status_page_monitors", {
|
||||
config_id: int("config_id").notNull().references(() => statusPageConfig.id, { onDelete: "cascade" }),
|
||||
monitor_id: int("monitor_id").notNull().references(() => monitors.id, { onDelete: "cascade" }),
|
||||
}, (table) => ({
|
||||
pk: primaryKey({ columns: [table.config_id, table.monitor_id] })
|
||||
}));
|
||||
export const statusPageMonitors = sqliteTable(
|
||||
"status_page_monitors",
|
||||
{
|
||||
config_id: int("config_id")
|
||||
.notNull()
|
||||
.references(() => statusPageConfig.id, { onDelete: "cascade" }),
|
||||
monitor_id: int("monitor_id")
|
||||
.notNull()
|
||||
.references(() => monitors.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(table) => ({
|
||||
pk: primaryKey({ columns: [table.config_id, table.monitor_id] }),
|
||||
})
|
||||
);
|
||||
|
||||
// 状态页客户端关联表
|
||||
export const statusPageAgents = sqliteTable("status_page_agents", {
|
||||
config_id: int("config_id").notNull().references(() => statusPageConfig.id, { onDelete: "cascade" }),
|
||||
agent_id: int("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
||||
}, (table) => ({
|
||||
pk: primaryKey({ columns: [table.config_id, table.agent_id] })
|
||||
}));
|
||||
export const statusPageAgents = sqliteTable(
|
||||
"status_page_agents",
|
||||
{
|
||||
config_id: int("config_id")
|
||||
.notNull()
|
||||
.references(() => statusPageConfig.id, { onDelete: "cascade" }),
|
||||
agent_id: int("agent_id")
|
||||
.notNull()
|
||||
.references(() => agents.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(table) => ({
|
||||
pk: primaryKey({ columns: [table.config_id, table.agent_id] }),
|
||||
})
|
||||
);
|
||||
|
||||
// 通知渠道表
|
||||
export const notificationChannels = sqliteTable("notification_channels", {
|
||||
@@ -137,9 +194,11 @@ export const notificationChannels = sqliteTable("notification_channels", {
|
||||
type: text("type").notNull(),
|
||||
config: text("config").notNull(),
|
||||
enabled: int("enabled").notNull().default(1),
|
||||
created_by: int("created_by").notNull().references(() => users.id),
|
||||
created_by: int("created_by")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
created_at: text("created_at").default("CURRENT_TIMESTAMP"),
|
||||
updated_at: text("updated_at").default("CURRENT_TIMESTAMP")
|
||||
updated_at: text("updated_at").default("CURRENT_TIMESTAMP"),
|
||||
});
|
||||
|
||||
// 通知模板表
|
||||
@@ -150,15 +209,19 @@ export const notificationTemplates = sqliteTable("notification_templates", {
|
||||
subject: text("subject").notNull(),
|
||||
content: text("content").notNull(),
|
||||
is_default: int("is_default").notNull().default(0),
|
||||
created_by: int("created_by").notNull().references(() => users.id),
|
||||
created_by: int("created_by")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
created_at: text("created_at").default("CURRENT_TIMESTAMP"),
|
||||
updated_at: text("updated_at").default("CURRENT_TIMESTAMP")
|
||||
updated_at: text("updated_at").default("CURRENT_TIMESTAMP"),
|
||||
});
|
||||
|
||||
// 通知设置表
|
||||
export const notificationSettings = sqliteTable("notification_settings", {
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
user_id: int("user_id").notNull().references(() => users.id),
|
||||
user_id: int("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id),
|
||||
target_type: text("target_type").notNull().default("global"),
|
||||
target_id: int("target_id"),
|
||||
enabled: int("enabled").notNull().default(1),
|
||||
@@ -173,7 +236,7 @@ export const notificationSettings = sqliteTable("notification_settings", {
|
||||
disk_threshold: int("disk_threshold").notNull().default(90),
|
||||
channels: text("channels").default("[]"),
|
||||
created_at: text("created_at").default("CURRENT_TIMESTAMP"),
|
||||
updated_at: text("updated_at").default("CURRENT_TIMESTAMP")
|
||||
updated_at: text("updated_at").default("CURRENT_TIMESTAMP"),
|
||||
});
|
||||
|
||||
// 通知历史记录表
|
||||
@@ -181,12 +244,16 @@ export const notificationHistory = sqliteTable("notification_history", {
|
||||
id: int("id").primaryKey({ autoIncrement: true }),
|
||||
type: text("type").notNull(),
|
||||
target_id: int("target_id"),
|
||||
channel_id: int("channel_id").notNull().references(() => notificationChannels.id),
|
||||
template_id: int("template_id").notNull().references(() => notificationTemplates.id),
|
||||
channel_id: int("channel_id")
|
||||
.notNull()
|
||||
.references(() => notificationChannels.id),
|
||||
template_id: int("template_id")
|
||||
.notNull()
|
||||
.references(() => notificationTemplates.id),
|
||||
status: text("status").notNull(),
|
||||
content: text("content").notNull(),
|
||||
error: text("error"),
|
||||
sent_at: text("sent_at").default("CURRENT_TIMESTAMP")
|
||||
sent_at: text("sent_at").default("CURRENT_TIMESTAMP"),
|
||||
});
|
||||
|
||||
// 新增:应用设置表
|
||||
|
||||
@@ -182,15 +182,35 @@ export const updateNotificationTemplate = async (
|
||||
Omit<NotificationTemplate, "id" | "created_at" | "updated_at">
|
||||
>
|
||||
): Promise<boolean> => {
|
||||
const updateData: Partial<typeof notificationTemplates.$inferInsert> = {};
|
||||
|
||||
if (template.name !== undefined) {
|
||||
updateData.name = template.name;
|
||||
}
|
||||
|
||||
if (template.type !== undefined) {
|
||||
updateData.type = template.type;
|
||||
}
|
||||
|
||||
if (template.subject !== undefined) {
|
||||
updateData.subject = template.subject;
|
||||
}
|
||||
|
||||
if (template.content !== undefined) {
|
||||
updateData.content = template.content;
|
||||
}
|
||||
|
||||
if (template.is_default !== undefined) {
|
||||
updateData.is_default = template.is_default ? 1 : 0;
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return true; // 没有需要更新的字段时视为成功
|
||||
}
|
||||
|
||||
const result = await db
|
||||
.update(notificationTemplates)
|
||||
.set({
|
||||
name: template.name,
|
||||
type: template.type,
|
||||
subject: template.subject,
|
||||
content: template.content,
|
||||
is_default: template.is_default ? 1 : 0,
|
||||
})
|
||||
.set(updateData)
|
||||
.where(and(eq(notificationTemplates.id, id), eq(notificationTemplates.created_by, userId)));
|
||||
|
||||
return result.success; // fix: 返回 success 布尔值
|
||||
|
||||
@@ -677,13 +677,23 @@ export async function sendNotification(
|
||||
const templates = await repositories.getNotificationTemplates(userId);
|
||||
console.log(`[发送通知] 获取到${templates.length}个通知模板`);
|
||||
|
||||
const defaultTemplate = templates.find(
|
||||
let defaultTemplate = templates.find(
|
||||
(t) => t.is_default && t.type === type
|
||||
);
|
||||
|
||||
if (!defaultTemplate) {
|
||||
console.error(`[发送通知] 找不到类型为${type}的默认通知模板`);
|
||||
return { success: false, results: [] };
|
||||
const fallbackTemplate = templates.find((t) => t.type === type);
|
||||
|
||||
if (!fallbackTemplate) {
|
||||
console.error(`[发送通知] 找不到类型为${type}的任何通知模板`);
|
||||
return { success: false, results: [] };
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`[发送通知] 找不到类型为${type}的默认通知模板,使用ID=${fallbackTemplate.id}作为回退模板`
|
||||
);
|
||||
|
||||
defaultTemplate = fallbackTemplate;
|
||||
}
|
||||
|
||||
console.log(
|
||||
|
||||
+341
-180
@@ -1,187 +1,313 @@
|
||||
import api from "./client";
|
||||
import type {
|
||||
NotificationChannel,
|
||||
NotificationConfig,
|
||||
NotificationTemplate,
|
||||
} from "../types/notification";
|
||||
|
||||
// 通知渠道类型
|
||||
export interface NotificationChannel {
|
||||
id: string;
|
||||
type BackendNotificationChannel = {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
config: Record<string, any>;
|
||||
enabled: boolean;
|
||||
}
|
||||
config: string | Record<string, unknown> | null;
|
||||
enabled: number | boolean;
|
||||
created_by?: number;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
// 通知模板类型
|
||||
export interface NotificationTemplate {
|
||||
id: string;
|
||||
type BackendNotificationTemplate = {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
subject: string;
|
||||
content: string;
|
||||
isDefault: boolean;
|
||||
}
|
||||
is_default: number | boolean;
|
||||
created_by?: number;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
// 通知设置类型
|
||||
export interface NotificationSettings {
|
||||
monitors: {
|
||||
enabled: boolean;
|
||||
onDown: boolean;
|
||||
onRecovery: boolean;
|
||||
channels: string[];
|
||||
};
|
||||
agents: {
|
||||
enabled: boolean;
|
||||
onOffline: boolean;
|
||||
onRecovery: boolean;
|
||||
onCpuThreshold: boolean;
|
||||
cpuThreshold: number;
|
||||
onMemoryThreshold: boolean;
|
||||
memoryThreshold: number;
|
||||
onDiskThreshold: boolean;
|
||||
diskThreshold: number;
|
||||
channels: string[];
|
||||
};
|
||||
specificMonitors: {
|
||||
[monitorId: string]: {
|
||||
enabled: boolean;
|
||||
onDown: boolean;
|
||||
onRecovery: boolean;
|
||||
channels: string[];
|
||||
};
|
||||
};
|
||||
specificAgents: {
|
||||
[agentId: string]: {
|
||||
enabled: boolean;
|
||||
onOffline: boolean;
|
||||
onRecovery: boolean;
|
||||
onCpuThreshold: boolean;
|
||||
cpuThreshold: number;
|
||||
onMemoryThreshold: boolean;
|
||||
memoryThreshold: number;
|
||||
onDiskThreshold: boolean;
|
||||
diskThreshold: number;
|
||||
channels: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
export type NotificationSettings = NotificationConfig["settings"];
|
||||
|
||||
// 通知配置响应类型
|
||||
export interface NotificationConfigResponse {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
data?: {
|
||||
channels: NotificationChannel[];
|
||||
templates: NotificationTemplate[];
|
||||
settings: NotificationSettings;
|
||||
};
|
||||
data?: NotificationConfig;
|
||||
}
|
||||
|
||||
const parseChannelConfig = (
|
||||
config: BackendNotificationChannel["config"]
|
||||
): Record<string, unknown> => {
|
||||
if (!config) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (typeof config === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(config);
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, unknown>;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("解析通知渠道配置失败:", error);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (typeof config === "object" && !Array.isArray(config)) {
|
||||
return config as Record<string, unknown>;
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const normalizeBoolean = (value: unknown, fallback = false): boolean => {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === "number") {
|
||||
return value !== 0;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
return value === "1" || value.toLowerCase() === "true";
|
||||
}
|
||||
|
||||
return fallback;
|
||||
};
|
||||
|
||||
const normalizeNumber = (value: unknown, fallback: number): number => {
|
||||
const num = Number(value);
|
||||
return Number.isFinite(num) ? num : fallback;
|
||||
};
|
||||
|
||||
const normalizeChannelIds = (value: unknown): number[] => {
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed
|
||||
.map((item) => Number(item))
|
||||
.filter((item) => Number.isInteger(item));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("解析通知渠道ID列表失败:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map((item) => Number(item))
|
||||
.filter((item) => Number.isInteger(item));
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const transformChannel = (
|
||||
channel: BackendNotificationChannel
|
||||
): NotificationChannel => ({
|
||||
id: Number(channel.id),
|
||||
name: channel.name,
|
||||
type: channel.type,
|
||||
config: parseChannelConfig(channel.config),
|
||||
enabled: normalizeBoolean(channel.enabled, true),
|
||||
createdBy: channel.created_by,
|
||||
createdAt: channel.created_at,
|
||||
updatedAt: channel.updated_at,
|
||||
});
|
||||
|
||||
const transformTemplate = (
|
||||
template: BackendNotificationTemplate
|
||||
): NotificationTemplate => ({
|
||||
id: Number(template.id),
|
||||
name: template.name,
|
||||
type: template.type,
|
||||
subject: template.subject,
|
||||
content: template.content,
|
||||
isDefault: normalizeBoolean(template.is_default, false),
|
||||
createdBy: template.created_by,
|
||||
createdAt: template.created_at,
|
||||
updatedAt: template.updated_at,
|
||||
});
|
||||
|
||||
const normalizeSettings = (settings: any): NotificationSettings => {
|
||||
const normalized: NotificationSettings = {
|
||||
monitors: {
|
||||
enabled: false,
|
||||
onDown: false,
|
||||
onRecovery: false,
|
||||
channels: [],
|
||||
},
|
||||
agents: {
|
||||
enabled: false,
|
||||
onOffline: false,
|
||||
onRecovery: false,
|
||||
onCpuThreshold: false,
|
||||
cpuThreshold: 90,
|
||||
onMemoryThreshold: false,
|
||||
memoryThreshold: 85,
|
||||
onDiskThreshold: false,
|
||||
diskThreshold: 90,
|
||||
channels: [],
|
||||
},
|
||||
specificMonitors: {},
|
||||
specificAgents: {},
|
||||
};
|
||||
|
||||
if (!settings) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
if (settings.monitors) {
|
||||
normalized.monitors = {
|
||||
enabled: normalizeBoolean(settings.monitors.enabled),
|
||||
onDown: normalizeBoolean(settings.monitors.onDown ?? settings.monitors.on_down),
|
||||
onRecovery: normalizeBoolean(
|
||||
settings.monitors.onRecovery ?? settings.monitors.on_recovery
|
||||
),
|
||||
channels: normalizeChannelIds(settings.monitors.channels),
|
||||
};
|
||||
}
|
||||
|
||||
if (settings.agents) {
|
||||
normalized.agents = {
|
||||
enabled: normalizeBoolean(settings.agents.enabled),
|
||||
onOffline: normalizeBoolean(settings.agents.onOffline ?? settings.agents.on_offline),
|
||||
onRecovery: normalizeBoolean(
|
||||
settings.agents.onRecovery ?? settings.agents.on_recovery
|
||||
),
|
||||
onCpuThreshold: normalizeBoolean(
|
||||
settings.agents.onCpuThreshold ?? settings.agents.on_cpu_threshold
|
||||
),
|
||||
cpuThreshold: normalizeNumber(settings.agents.cpuThreshold ?? settings.agents.cpu_threshold, 90),
|
||||
onMemoryThreshold: normalizeBoolean(
|
||||
settings.agents.onMemoryThreshold ?? settings.agents.on_memory_threshold
|
||||
),
|
||||
memoryThreshold: normalizeNumber(
|
||||
settings.agents.memoryThreshold ?? settings.agents.memory_threshold,
|
||||
85
|
||||
),
|
||||
onDiskThreshold: normalizeBoolean(
|
||||
settings.agents.onDiskThreshold ?? settings.agents.on_disk_threshold
|
||||
),
|
||||
diskThreshold: normalizeNumber(
|
||||
settings.agents.diskThreshold ?? settings.agents.disk_threshold,
|
||||
90
|
||||
),
|
||||
channels: normalizeChannelIds(settings.agents.channels),
|
||||
};
|
||||
}
|
||||
|
||||
if (settings.specificMonitors) {
|
||||
Object.entries(settings.specificMonitors).forEach(
|
||||
([monitorId, monitorSetting]: [string, any]) => {
|
||||
normalized.specificMonitors[monitorId] = {
|
||||
enabled: normalizeBoolean(monitorSetting?.enabled),
|
||||
onDown: normalizeBoolean(
|
||||
monitorSetting?.onDown ?? monitorSetting?.on_down
|
||||
),
|
||||
onRecovery: normalizeBoolean(
|
||||
monitorSetting?.onRecovery ?? monitorSetting?.on_recovery
|
||||
),
|
||||
channels: normalizeChannelIds(monitorSetting?.channels),
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.specificAgents) {
|
||||
Object.entries(settings.specificAgents).forEach(
|
||||
([agentId, agentSetting]: [string, any]) => {
|
||||
normalized.specificAgents[agentId] = {
|
||||
enabled: normalizeBoolean(agentSetting?.enabled),
|
||||
onOffline: normalizeBoolean(
|
||||
agentSetting?.onOffline ?? agentSetting?.on_offline
|
||||
),
|
||||
onRecovery: normalizeBoolean(
|
||||
agentSetting?.onRecovery ?? agentSetting?.on_recovery
|
||||
),
|
||||
onCpuThreshold: normalizeBoolean(
|
||||
agentSetting?.onCpuThreshold ?? agentSetting?.on_cpu_threshold
|
||||
),
|
||||
cpuThreshold: normalizeNumber(
|
||||
agentSetting?.cpuThreshold ?? agentSetting?.cpu_threshold,
|
||||
normalized.agents.cpuThreshold
|
||||
),
|
||||
onMemoryThreshold: normalizeBoolean(
|
||||
agentSetting?.onMemoryThreshold ?? agentSetting?.on_memory_threshold
|
||||
),
|
||||
memoryThreshold: normalizeNumber(
|
||||
agentSetting?.memoryThreshold ?? agentSetting?.memory_threshold,
|
||||
normalized.agents.memoryThreshold
|
||||
),
|
||||
onDiskThreshold: normalizeBoolean(
|
||||
agentSetting?.onDiskThreshold ?? agentSetting?.on_disk_threshold
|
||||
),
|
||||
diskThreshold: normalizeNumber(
|
||||
agentSetting?.diskThreshold ?? agentSetting?.disk_threshold,
|
||||
normalized.agents.diskThreshold
|
||||
),
|
||||
channels: normalizeChannelIds(agentSetting?.channels),
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
};
|
||||
|
||||
// 获取完整的通知配置
|
||||
export const getNotificationConfig =
|
||||
async (): Promise<NotificationConfigResponse> => {
|
||||
try {
|
||||
const response = await api.get<NotificationConfigResponse>(
|
||||
"/api/notifications"
|
||||
);
|
||||
const response = await api.get<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
data?: {
|
||||
channels?: BackendNotificationChannel[];
|
||||
templates?: BackendNotificationTemplate[];
|
||||
settings?: any;
|
||||
};
|
||||
}>("/api/notifications");
|
||||
|
||||
// 如果响应成功并且有数据,处理数据格式
|
||||
if (response.data.success && response.data.data) {
|
||||
// 处理渠道配置 - 确保config字段是对象而不是字符串
|
||||
if (response.data.data.channels) {
|
||||
response.data.data.channels = response.data.data.channels.map(
|
||||
(channel) => {
|
||||
// 使用类型断言访问后端特有属性
|
||||
const backendChannel = channel as any;
|
||||
return {
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
type: channel.type,
|
||||
// 将字符串类型的config解析为对象
|
||||
config:
|
||||
typeof backendChannel.config === "string"
|
||||
? JSON.parse(backendChannel.config)
|
||||
: backendChannel.config,
|
||||
enabled: !!backendChannel.enabled,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
const backendData = response.data.data;
|
||||
|
||||
// 处理模板配置 - 确保is_default是布尔类型
|
||||
if (response.data.data.templates) {
|
||||
response.data.data.templates = response.data.data.templates.map(
|
||||
(template) => {
|
||||
// 使用类型断言访问后端特有属性
|
||||
const backendTemplate = template as any;
|
||||
return {
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
type: template.type,
|
||||
subject: template.subject,
|
||||
content: template.content,
|
||||
isDefault: !!backendTemplate.is_default,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 确保settings中的所有布尔字段是真正的布尔类型而不是0/1
|
||||
if (response.data.data.settings) {
|
||||
const { settings } = response.data.data;
|
||||
|
||||
// 处理全局监控设置
|
||||
if (settings.monitors) {
|
||||
settings.monitors = {
|
||||
...settings.monitors,
|
||||
enabled: !!settings.monitors.enabled,
|
||||
onDown: !!settings.monitors.onDown,
|
||||
onRecovery: !!settings.monitors.onRecovery,
|
||||
};
|
||||
}
|
||||
|
||||
// 处理全局客户端设置
|
||||
if (settings.agents) {
|
||||
settings.agents = {
|
||||
...settings.agents,
|
||||
enabled: !!settings.agents.enabled,
|
||||
onOffline: !!settings.agents.onOffline,
|
||||
onRecovery: !!settings.agents.onRecovery,
|
||||
onCpuThreshold: !!settings.agents.onCpuThreshold,
|
||||
onMemoryThreshold: !!settings.agents.onMemoryThreshold,
|
||||
onDiskThreshold: !!settings.agents.onDiskThreshold,
|
||||
};
|
||||
}
|
||||
|
||||
// 处理特定监控设置
|
||||
if (settings.specificMonitors) {
|
||||
Object.keys(settings.specificMonitors).forEach((monitorId) => {
|
||||
const monitorSetting = settings.specificMonitors[monitorId];
|
||||
settings.specificMonitors[monitorId] = {
|
||||
...monitorSetting,
|
||||
enabled: !!monitorSetting.enabled,
|
||||
onDown: !!monitorSetting.onDown,
|
||||
onRecovery: !!monitorSetting.onRecovery,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 处理特定客户端设置
|
||||
if (settings.specificAgents) {
|
||||
Object.keys(settings.specificAgents).forEach((agentId) => {
|
||||
const agentSetting = settings.specificAgents[agentId];
|
||||
settings.specificAgents[agentId] = {
|
||||
...agentSetting,
|
||||
enabled: !!agentSetting.enabled,
|
||||
onOffline: !!agentSetting.onOffline,
|
||||
onRecovery: !!agentSetting.onRecovery,
|
||||
onCpuThreshold: !!agentSetting.onCpuThreshold,
|
||||
onMemoryThreshold: !!agentSetting.onMemoryThreshold,
|
||||
onDiskThreshold: !!agentSetting.onDiskThreshold,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!backendData) {
|
||||
return {
|
||||
success: response.data.success,
|
||||
message: response.data.message,
|
||||
};
|
||||
}
|
||||
|
||||
return response.data;
|
||||
const channels = Array.isArray(backendData.channels)
|
||||
? (backendData.channels as BackendNotificationChannel[]).map(
|
||||
transformChannel
|
||||
)
|
||||
: [];
|
||||
|
||||
const templates = Array.isArray(backendData.templates)
|
||||
? (backendData.templates as BackendNotificationTemplate[]).map(
|
||||
transformTemplate
|
||||
)
|
||||
: [];
|
||||
|
||||
return {
|
||||
success: response.data.success,
|
||||
message: response.data.message,
|
||||
data: {
|
||||
channels,
|
||||
templates,
|
||||
settings: normalizeSettings(backendData.settings),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("获取通知配置失败:", error);
|
||||
return {
|
||||
@@ -201,13 +327,17 @@ export const getNotificationChannels = async (): Promise<{
|
||||
const response = await api.get<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
data?: NotificationChannel[];
|
||||
data?: BackendNotificationChannel[];
|
||||
}>("/api/notifications/channels");
|
||||
|
||||
const channels = Array.isArray(response.data.data)
|
||||
? response.data.data.map(transformChannel)
|
||||
: [];
|
||||
|
||||
return {
|
||||
success: response.data.success,
|
||||
message: response.data.message,
|
||||
channels: response.data.data,
|
||||
channels,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("获取通知渠道失败:", error);
|
||||
@@ -228,13 +358,17 @@ export const getNotificationTemplates = async (): Promise<{
|
||||
const response = await api.get<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
data?: NotificationTemplate[];
|
||||
data?: BackendNotificationTemplate[];
|
||||
}>("/api/notifications/templates");
|
||||
|
||||
const templates = Array.isArray(response.data.data)
|
||||
? response.data.data.map(transformTemplate)
|
||||
: [];
|
||||
|
||||
return {
|
||||
success: response.data.success,
|
||||
message: response.data.message,
|
||||
templates: response.data.data,
|
||||
templates,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("获取通知模板失败:", error);
|
||||
@@ -357,17 +491,17 @@ export const saveNotificationSettings = async (
|
||||
|
||||
// 创建通知渠道
|
||||
export const createNotificationChannel = async (
|
||||
channel: Omit<NotificationChannel, "id">
|
||||
channel: Omit<NotificationChannel, "id" | "createdBy" | "createdAt" | "updatedAt">
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
channelId?: string;
|
||||
channelId?: number;
|
||||
}> => {
|
||||
try {
|
||||
const response = await api.post<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
data?: { id: string };
|
||||
data?: { id: number };
|
||||
}>("/api/notifications/channels", channel);
|
||||
|
||||
return {
|
||||
@@ -386,17 +520,25 @@ export const createNotificationChannel = async (
|
||||
|
||||
// 更新通知渠道
|
||||
export const updateNotificationChannel = async (
|
||||
id: string,
|
||||
channel: Partial<NotificationChannel>
|
||||
id: number,
|
||||
channel: Partial<
|
||||
Omit<NotificationChannel, "id" | "createdBy" | "createdAt" | "updatedAt">
|
||||
>
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}> => {
|
||||
try {
|
||||
const payload: Record<string, unknown> = {};
|
||||
if (channel.name !== undefined) payload.name = channel.name;
|
||||
if (channel.type !== undefined) payload.type = channel.type;
|
||||
if (channel.config !== undefined) payload.config = channel.config;
|
||||
if (channel.enabled !== undefined) payload.enabled = channel.enabled;
|
||||
|
||||
const response = await api.put<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}>(`/api/notifications/channels/${id}`, channel);
|
||||
}>(`/api/notifications/channels/${id}`, payload);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -410,7 +552,7 @@ export const updateNotificationChannel = async (
|
||||
|
||||
// 删除通知渠道
|
||||
export const deleteNotificationChannel = async (
|
||||
id: string
|
||||
id: number
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
@@ -433,18 +575,26 @@ export const deleteNotificationChannel = async (
|
||||
|
||||
// 创建通知模板
|
||||
export const createNotificationTemplate = async (
|
||||
template: Omit<NotificationTemplate, "id">
|
||||
template: Omit<NotificationTemplate, "id" | "createdBy" | "createdAt" | "updatedAt">
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
templateId?: string;
|
||||
templateId?: number;
|
||||
}> => {
|
||||
try {
|
||||
const payload = {
|
||||
name: template.name,
|
||||
type: template.type,
|
||||
subject: template.subject,
|
||||
content: template.content,
|
||||
is_default: template.isDefault,
|
||||
};
|
||||
|
||||
const response = await api.post<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
data?: { id: string };
|
||||
}>("/api/notifications/templates", template);
|
||||
data?: { id: number };
|
||||
}>("/api/notifications/templates", payload);
|
||||
|
||||
return {
|
||||
success: response.data.success,
|
||||
@@ -462,16 +612,27 @@ export const createNotificationTemplate = async (
|
||||
|
||||
// 更新通知模板
|
||||
export const updateNotificationTemplate = async (
|
||||
id: string,
|
||||
template: Partial<NotificationTemplate>
|
||||
id: number,
|
||||
template: Partial<
|
||||
Omit<NotificationTemplate, "id" | "createdBy" | "createdAt" | "updatedAt">
|
||||
>
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}> => {
|
||||
try {
|
||||
const response = await api.put(
|
||||
const payload: Record<string, unknown> = {};
|
||||
if (template.name !== undefined) payload.name = template.name;
|
||||
if (template.type !== undefined) payload.type = template.type;
|
||||
if (template.subject !== undefined) payload.subject = template.subject;
|
||||
if (template.content !== undefined) payload.content = template.content;
|
||||
if (template.isDefault !== undefined) {
|
||||
payload.is_default = template.isDefault;
|
||||
}
|
||||
|
||||
const response = await api.put<{ success: boolean; message?: string }>(
|
||||
`/api/notifications/templates/${id}`,
|
||||
template
|
||||
payload
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -485,7 +646,7 @@ export const updateNotificationTemplate = async (
|
||||
|
||||
// 删除通知模板
|
||||
export const deleteNotificationTemplate = async (
|
||||
id: string
|
||||
id: number
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
message?: string;
|
||||
|
||||
@@ -3,23 +3,7 @@ import { Box, Flex, Text } from "@radix-ui/themes";
|
||||
import { CheckIcon } from "@radix-ui/react-icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
// 通知渠道类型定义
|
||||
export interface NotificationChannel {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
config?: any;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
// 组件属性接口
|
||||
export interface ChannelSelectorProps {
|
||||
channels: NotificationChannel[];
|
||||
selectedChannelIds: string[];
|
||||
onChange: (channelIds: string[]) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
import { ChannelSelectorProps } from "@/types/components";
|
||||
|
||||
/**
|
||||
* 通知渠道选择器组件
|
||||
@@ -61,7 +45,7 @@ const ChannelSelector = ({
|
||||
};
|
||||
|
||||
// 处理选项点击
|
||||
const handleItemClick = (channelId: string) => {
|
||||
const handleItemClick = (channelId: number) => {
|
||||
// 切换选择状态
|
||||
const isSelected = selectedChannelIds.includes(channelId);
|
||||
const newSelectedIds = isSelected
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ArrowLeftIcon } from "@radix-ui/react-icons";
|
||||
import { getAgent, updateAgent } from "../../api/agents";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import type { Agent } from "../../types/agents";
|
||||
|
||||
const EditAgent = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -14,7 +15,7 @@ const EditAgent = () => {
|
||||
const [notFound, setNotFound] = useState(false);
|
||||
const [formData, setFormData] = useState<{
|
||||
name: string;
|
||||
status: "active" | "inactive" | "connecting" | "unknown";
|
||||
status: Agent["status"];
|
||||
}>({
|
||||
name: "",
|
||||
status: "inactive",
|
||||
@@ -37,7 +38,7 @@ const EditAgent = () => {
|
||||
|
||||
setFormData({
|
||||
name: agent.name,
|
||||
status: agent.status || "inactive",
|
||||
status: agent.status ?? "inactive",
|
||||
});
|
||||
} else {
|
||||
setNotFound(true);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -7,7 +7,13 @@ export interface Agent {
|
||||
name: string;
|
||||
hostname?: string;
|
||||
ip_addresses?: string;
|
||||
status: "active" | "inactive" | "connecting" | "unknown";
|
||||
status:
|
||||
| "active"
|
||||
| "inactive"
|
||||
| "connecting"
|
||||
| "unknown"
|
||||
| "online"
|
||||
| "offline";
|
||||
version?: string;
|
||||
os?: string;
|
||||
created_at: string;
|
||||
@@ -20,7 +26,13 @@ export interface AgentWithLatestMetrics {
|
||||
name: string;
|
||||
hostname?: string;
|
||||
ip_addresses?: string;
|
||||
status: "active" | "inactive" | "connecting" | "unknown";
|
||||
status:
|
||||
| "active"
|
||||
| "inactive"
|
||||
| "connecting"
|
||||
| "unknown"
|
||||
| "online"
|
||||
| "offline";
|
||||
version?: string;
|
||||
os?: string;
|
||||
created_at: string;
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
/**
|
||||
* 组件相关类型定义
|
||||
*/
|
||||
import { NotificationChannel } from "./notification";
|
||||
import { Monitor } from "./monitors";
|
||||
import { Agent, MetricHistory } from "./agents";
|
||||
import { NotificationChannel } from "./notification";
|
||||
|
||||
|
||||
|
||||
// ChannelSelector 组件类型
|
||||
export interface ChannelSelectorProps {
|
||||
channels: NotificationChannel[];
|
||||
selectedChannelIds: string[];
|
||||
onChange: (channelIds: string[]) => void;
|
||||
selectedChannelIds: number[];
|
||||
onChange: (channelIds: number[]) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
// StatusSummaryCard 组件类型
|
||||
export interface StatusItem {
|
||||
id: string;
|
||||
name: string;
|
||||
status: "up" | "down" | "pending" | "unknown" | "active" | "inactive";
|
||||
status:
|
||||
| "up"
|
||||
| "down"
|
||||
| "pending"
|
||||
| "unknown"
|
||||
| "active"
|
||||
| "inactive"
|
||||
| "online"
|
||||
| "offline"
|
||||
| "error";
|
||||
time?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ export interface Monitor {
|
||||
name: string;
|
||||
url: string;
|
||||
method: string;
|
||||
headers?: string;
|
||||
headers?: Record<string, string> | string | null;
|
||||
body?: string;
|
||||
expected_status?: number;
|
||||
interval: number;
|
||||
timeout: number;
|
||||
status: "up" | "down" | "pending" | "unknown";
|
||||
status: "up" | "down" | "pending" | "unknown" | "error";
|
||||
last_checked?: string;
|
||||
response_time?: number;
|
||||
user_id: number;
|
||||
|
||||
@@ -3,28 +3,26 @@ export interface NotificationChannel {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string; // telegram, resend, feishu, wecom
|
||||
config: string; // JSON字符串
|
||||
config: Record<string, unknown>;
|
||||
enabled: boolean;
|
||||
created_by: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
createdBy?: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
// 通知模板类型定义
|
||||
export interface NotificationTemplate {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string; // default, custom
|
||||
type: string; // monitor, agent
|
||||
subject: string;
|
||||
content: string;
|
||||
is_default: boolean;
|
||||
created_by: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
isDefault: boolean;
|
||||
createdBy?: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 前端接口所需的通知配置类型
|
||||
export interface NotificationConfig {
|
||||
channels: NotificationChannel[];
|
||||
@@ -34,7 +32,7 @@ export interface NotificationConfig {
|
||||
enabled: boolean;
|
||||
onDown: boolean;
|
||||
onRecovery: boolean;
|
||||
channels: string[];
|
||||
channels: number[];
|
||||
};
|
||||
agents: {
|
||||
enabled: boolean;
|
||||
@@ -46,14 +44,14 @@ export interface NotificationConfig {
|
||||
memoryThreshold: number;
|
||||
onDiskThreshold: boolean;
|
||||
diskThreshold: number;
|
||||
channels: string[];
|
||||
channels: number[];
|
||||
};
|
||||
specificMonitors: {
|
||||
[monitorId: string]: {
|
||||
enabled: boolean;
|
||||
onDown: boolean;
|
||||
onRecovery: boolean;
|
||||
channels: string[];
|
||||
channels: number[];
|
||||
};
|
||||
};
|
||||
specificAgents: {
|
||||
@@ -67,7 +65,7 @@ export interface NotificationConfig {
|
||||
memoryThreshold: number;
|
||||
onDiskThreshold: boolean;
|
||||
diskThreshold: number;
|
||||
channels: string[];
|
||||
channels: number[];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Generated
+2621
-39
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user