@johnny-joster/n9e-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,120 @@
1
+ import type { ToolHandler } from "openclaw/plugin-sdk";
2
+ import { N9eClient } from "../n9e-client.ts";
3
+ import { n9ePluginConfigSchema } from "../config-schema.ts";
4
+ import { SEVERITY_LABELS } from "../types.ts";
5
+
6
+ export const alertRulesToolInputSchema = {
7
+ type: "object",
8
+ properties: {
9
+ rule_name: {
10
+ type: "string",
11
+ description: "按规则名称搜索(支持模糊匹配)"
12
+ },
13
+ datasource_ids: {
14
+ type: "array",
15
+ items: { type: "number" },
16
+ description: "数据源ID列表"
17
+ },
18
+ disabled: {
19
+ type: "number",
20
+ enum: [0, 1],
21
+ description: "规则状态: 0=启用, 1=禁用"
22
+ },
23
+ limit: {
24
+ type: "number",
25
+ minimum: 1,
26
+ maximum: 1000,
27
+ default: 50,
28
+ description: "返回数量限制"
29
+ }
30
+ }
31
+ } as const;
32
+
33
+ export const alertRulesToolHandler: ToolHandler = async (input, context) => {
34
+ try {
35
+ // Parse and validate config
36
+ const rawConfig = context.config.plugins?.entries?.["n9e-plugin"]?.config;
37
+ if (!rawConfig) {
38
+ return {
39
+ content: [{
40
+ type: "text",
41
+ text: "❌ 插件配置未找到,请在配置文件中添加n9e-plugin配置"
42
+ }],
43
+ isError: true
44
+ };
45
+ }
46
+
47
+ const config = n9ePluginConfigSchema.parse(rawConfig);
48
+ const client = new N9eClient(config);
49
+
50
+ // Query alert rules
51
+ const { total, list } = await client.getAlertRules({
52
+ rule_name: input.rule_name,
53
+ datasource_ids: input.datasource_ids,
54
+ disabled: input.disabled as 0 | 1 | undefined,
55
+ limit: input.limit || 50
56
+ });
57
+
58
+ // Format response
59
+ const rules = list.map(rule => ({
60
+ id: rule.id,
61
+ name: rule.name,
62
+ note: rule.note,
63
+ severity: rule.severity,
64
+ severity_label: SEVERITY_LABELS[rule.severity],
65
+ disabled: rule.disabled === 1,
66
+ status: rule.disabled === 1 ? "禁用" : "启用",
67
+ prom_ql: rule.prom_ql,
68
+ prom_for_duration: rule.prom_for_duration,
69
+ prom_eval_interval: rule.prom_eval_interval,
70
+ notify_channels: rule.notify_channels,
71
+ datasource_ids: rule.datasource_ids,
72
+ group_id: rule.group_id,
73
+ cluster: rule.cluster,
74
+ created_at: new Date(rule.create_at * 1000).toLocaleString("zh-CN", {
75
+ timeZone: "Asia/Shanghai"
76
+ }),
77
+ created_by: rule.create_by,
78
+ updated_at: new Date(rule.update_at * 1000).toLocaleString("zh-CN", {
79
+ timeZone: "Asia/Shanghai"
80
+ }),
81
+ updated_by: rule.update_by
82
+ }));
83
+
84
+ // Statistics
85
+ const enabledCount = rules.filter(r => !r.disabled).length;
86
+ const disabledCount = rules.filter(r => r.disabled).length;
87
+ const severityCount = rules.reduce((acc, rule) => {
88
+ const label = rule.severity_label;
89
+ acc[label] = (acc[label] || 0) + 1;
90
+ return acc;
91
+ }, {} as Record<string, number>);
92
+
93
+ const result = {
94
+ summary: {
95
+ total,
96
+ showing: rules.length,
97
+ enabled_count: enabledCount,
98
+ disabled_count: disabledCount,
99
+ severity_distribution: severityCount
100
+ },
101
+ rules
102
+ };
103
+
104
+ return {
105
+ content: [{
106
+ type: "text",
107
+ text: JSON.stringify(result, null, 2)
108
+ }]
109
+ };
110
+ } catch (error) {
111
+ context.logger.error("Failed to query alert rules:", error);
112
+ return {
113
+ content: [{
114
+ type: "text",
115
+ text: `❌ 查询告警规则失败: ${error instanceof Error ? error.message : String(error)}`
116
+ }],
117
+ isError: true
118
+ };
119
+ }
120
+ };
@@ -0,0 +1,134 @@
1
+ import type { ToolHandler } from "openclaw/plugin-sdk";
2
+ import { N9eClient } from "../n9e-client.ts";
3
+ import { n9ePluginConfigSchema } from "../config-schema.ts";
4
+ import { SEVERITY_LABELS, type AlertSeverity } from "../types.ts";
5
+
6
+ export const historicalAlertsToolInputSchema = {
7
+ type: "object",
8
+ properties: {
9
+ severity: {
10
+ type: "number",
11
+ enum: [1, 2, 3],
12
+ description: "严重级别: 1=严重, 2=警告, 3=提示"
13
+ },
14
+ rule_name: {
15
+ type: "string",
16
+ description: "按规则名称筛选(支持模糊匹配)"
17
+ },
18
+ stime: {
19
+ type: "number",
20
+ description: "开始时间戳(秒)"
21
+ },
22
+ etime: {
23
+ type: "number",
24
+ description: "结束时间戳(秒)"
25
+ },
26
+ limit: {
27
+ type: "number",
28
+ minimum: 1,
29
+ maximum: 1000,
30
+ default: 50,
31
+ description: "返回数量限制"
32
+ }
33
+ }
34
+ } as const;
35
+
36
+ export const historicalAlertsToolHandler: ToolHandler = async (input, context) => {
37
+ try {
38
+ // Parse and validate config
39
+ const rawConfig = context.config.plugins?.entries?.["n9e-plugin"]?.config;
40
+ if (!rawConfig) {
41
+ return {
42
+ content: [{
43
+ type: "text",
44
+ text: "❌ 插件配置未找到,请在配置文件中添加n9e-plugin配置"
45
+ }],
46
+ isError: true
47
+ };
48
+ }
49
+
50
+ const config = n9ePluginConfigSchema.parse(rawConfig);
51
+ const client = new N9eClient(config);
52
+
53
+ // Validate time range
54
+ if (input.stime && input.etime && input.stime >= input.etime) {
55
+ return {
56
+ content: [{
57
+ type: "text",
58
+ text: "❌ 开始时间必须早于结束时间"
59
+ }],
60
+ isError: true
61
+ };
62
+ }
63
+
64
+ // Query historical alerts
65
+ const { total, list } = await client.getHistoricalAlerts({
66
+ severity: input.severity as AlertSeverity | undefined,
67
+ rule_name: input.rule_name,
68
+ stime: input.stime,
69
+ etime: input.etime,
70
+ limit: input.limit || 50
71
+ });
72
+
73
+ // Format response
74
+ const alerts = list.map(alert => {
75
+ const tags = alert.tags ? JSON.parse(alert.tags) : {};
76
+ return {
77
+ id: alert.id,
78
+ rule_name: alert.rule_name,
79
+ severity: alert.severity,
80
+ severity_label: SEVERITY_LABELS[alert.severity],
81
+ trigger_time: new Date(alert.trigger_time * 1000).toLocaleString("zh-CN", {
82
+ timeZone: "Asia/Shanghai"
83
+ }),
84
+ recover_time: alert.recover_time
85
+ ? new Date(alert.recover_time * 1000).toLocaleString("zh-CN", {
86
+ timeZone: "Asia/Shanghai"
87
+ })
88
+ : null,
89
+ is_recovered: alert.is_recovered === 1,
90
+ target_ident: alert.target_ident,
91
+ trigger_value: alert.trigger_value,
92
+ tags,
93
+ rule_note: alert.rule_note,
94
+ group_name: alert.group_name
95
+ };
96
+ });
97
+
98
+ // Statistics
99
+ const severityCount = alerts.reduce((acc, alert) => {
100
+ const label = alert.severity_label;
101
+ acc[label] = (acc[label] || 0) + 1;
102
+ return acc;
103
+ }, {} as Record<string, number>);
104
+
105
+ const recoveredCount = alerts.filter(a => a.is_recovered).length;
106
+
107
+ const result = {
108
+ summary: {
109
+ total,
110
+ showing: alerts.length,
111
+ severity_distribution: severityCount,
112
+ recovered_count: recoveredCount,
113
+ unrecovered_count: alerts.length - recoveredCount
114
+ },
115
+ alerts
116
+ };
117
+
118
+ return {
119
+ content: [{
120
+ type: "text",
121
+ text: JSON.stringify(result, null, 2)
122
+ }]
123
+ };
124
+ } catch (error) {
125
+ context.logger.error("Failed to query historical alerts:", error);
126
+ return {
127
+ content: [{
128
+ type: "text",
129
+ text: `❌ 查询历史告警失败: ${error instanceof Error ? error.message : String(error)}`
130
+ }],
131
+ isError: true
132
+ };
133
+ }
134
+ };
package/src/types.ts ADDED
@@ -0,0 +1,180 @@
1
+ // Configuration types
2
+ export interface N9ePluginConfig {
3
+ apiBaseUrl: string;
4
+ apiKey: string;
5
+ timeout: number;
6
+ }
7
+
8
+ // Alert severity mapping
9
+ export type AlertSeverity = 1 | 2 | 3; // 1=严重, 2=警告, 3=提示
10
+
11
+ export const SEVERITY_LABELS: Record<AlertSeverity, string> = {
12
+ 1: "严重",
13
+ 2: "警告",
14
+ 3: "提示"
15
+ };
16
+
17
+ // Active alert (current event)
18
+ export interface AlertCurEvent {
19
+ id: number;
20
+ cluster: string;
21
+ group_id: number;
22
+ group_name?: string;
23
+ hash: string;
24
+ rule_id: number;
25
+ rule_name: string;
26
+ rule_note: string;
27
+ severity: AlertSeverity;
28
+ prom_for_duration: number;
29
+ prom_ql: string;
30
+ prom_eval_interval: number;
31
+ callbacks: string;
32
+ runbook_url?: string;
33
+ notify_recovered: number;
34
+ notify_channels: string;
35
+ notify_groups: string;
36
+ notify_repeat_step: number;
37
+ notify_max_number: number;
38
+ recover_duration: number;
39
+ created_at: number;
40
+ updated_at: number;
41
+ tags: string;
42
+ target_ident: string;
43
+ target_note: string;
44
+ first_trigger_time?: number;
45
+ trigger_time: number;
46
+ trigger_value: string;
47
+ annotations?: string;
48
+ rule_config?: string;
49
+ }
50
+
51
+ // Historical alert event
52
+ export interface AlertHisEvent {
53
+ id: number;
54
+ is_recovered: number;
55
+ cluster: string;
56
+ group_id: number;
57
+ group_name?: string;
58
+ hash: string;
59
+ rule_id: number;
60
+ rule_name: string;
61
+ rule_note: string;
62
+ severity: AlertSeverity;
63
+ prom_for_duration: number;
64
+ prom_ql: string;
65
+ prom_eval_interval: number;
66
+ callbacks: string;
67
+ runbook_url?: string;
68
+ notify_recovered: number;
69
+ notify_channels: string;
70
+ notify_groups: string;
71
+ notify_repeat_step: number;
72
+ notify_max_number: number;
73
+ recover_duration: number;
74
+ created_at: number;
75
+ updated_at: number;
76
+ tags: string;
77
+ target_ident: string;
78
+ target_note: string;
79
+ first_trigger_time?: number;
80
+ trigger_time: number;
81
+ trigger_value: string;
82
+ recover_time?: number;
83
+ last_eval_time?: number;
84
+ annotations?: string;
85
+ rule_config?: string;
86
+ }
87
+
88
+ // Alert rule
89
+ export interface AlertRule {
90
+ id: number;
91
+ group_id: number;
92
+ cluster: string;
93
+ name: string;
94
+ note: string;
95
+ severity: AlertSeverity;
96
+ disabled: number; // 0=enabled, 1=disabled
97
+ prom_for_duration: number;
98
+ prom_ql: string;
99
+ prom_eval_interval: number;
100
+ enable_stime?: string;
101
+ enable_etime?: string;
102
+ enable_days_of_week?: string;
103
+ enable_in_bg?: number;
104
+ notify_recovered: number;
105
+ notify_channels: string;
106
+ notify_repeat_step: number;
107
+ notify_max_number: number;
108
+ recover_duration: number;
109
+ callbacks: string;
110
+ runbook_url?: string;
111
+ append_tags?: string;
112
+ create_at: number;
113
+ create_by: string;
114
+ update_at: number;
115
+ update_by: string;
116
+ datasource_ids?: number[];
117
+ extra_config?: string;
118
+ }
119
+
120
+ // Alert mute
121
+ export interface AlertMute {
122
+ id: number;
123
+ group_id: number;
124
+ cluster: string;
125
+ tags: string;
126
+ cause: string;
127
+ btime: number;
128
+ etime: number;
129
+ disabled: number; // 0=enabled, 1=disabled
130
+ create_at: number;
131
+ create_by: string;
132
+ update_at: number;
133
+ update_by: string;
134
+ }
135
+
136
+ // Query parameters
137
+ export interface ActiveAlertQuery {
138
+ severity?: AlertSeverity;
139
+ rule_name?: string;
140
+ limit?: number;
141
+ query?: string;
142
+ p?: number; // page number
143
+ }
144
+
145
+ export interface HistoricalAlertQuery {
146
+ severity?: AlertSeverity;
147
+ rule_name?: string;
148
+ stime?: number; // start timestamp (seconds)
149
+ etime?: number; // end timestamp (seconds)
150
+ limit?: number;
151
+ p?: number;
152
+ }
153
+
154
+ export interface AlertRuleQuery {
155
+ rule_name?: string;
156
+ datasource_ids?: number[];
157
+ disabled?: 0 | 1;
158
+ p?: number;
159
+ limit?: number;
160
+ }
161
+
162
+ export interface AlertMuteQuery {
163
+ query?: string;
164
+ p?: number;
165
+ limit?: number;
166
+ }
167
+
168
+ // API response wrapper
169
+ export interface N9eApiResponse<T> {
170
+ dat: T;
171
+ err: string;
172
+ }
173
+
174
+ export interface N9eListResponse<T> {
175
+ dat: {
176
+ list: T[];
177
+ total: number;
178
+ };
179
+ err: string;
180
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "resolveJsonModule": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": false,
13
+ "outDir": "./dist",
14
+ "allowImportingTsExtensions": true,
15
+ "noEmit": true
16
+ },
17
+ "include": ["src/**/*", "index.ts"],
18
+ "exclude": ["node_modules"]
19
+ }