mirror of
https://github.com/ccfos/nightingale.git
synced 2026-03-03 14:38:55 +00:00
Compare commits
398 Commits
docker_upd
...
refactor-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbcd2a0ca1 | ||
|
|
3f865e2d19 | ||
|
|
e3ea65d098 | ||
|
|
93159f07fd | ||
|
|
7d410baa2d | ||
|
|
20b30c3e2c | ||
|
|
8805bf6598 | ||
|
|
fe6a64dae8 | ||
|
|
2c564a2c58 | ||
|
|
ae3c13224d | ||
|
|
9a4015f13f | ||
|
|
274ca09551 | ||
|
|
3d9b4fc14e | ||
|
|
07436a5e0d | ||
|
|
f7b2f1acb9 | ||
|
|
4f4287030a | ||
|
|
e25e712c48 | ||
|
|
66951d7e77 | ||
|
|
f5ff27cd18 | ||
|
|
9e3f6e6285 | ||
|
|
48e3df2cb4 | ||
|
|
ac5d69dba4 | ||
|
|
597351c424 | ||
|
|
1f6b2e341a | ||
|
|
035752ace2 | ||
|
|
60a1437207 | ||
|
|
e31414bc8c | ||
|
|
785a294845 | ||
|
|
98933eee34 | ||
|
|
20905810d7 | ||
|
|
c1bde83639 | ||
|
|
782a0e9616 | ||
|
|
6a3720bc8b | ||
|
|
de252359d6 | ||
|
|
deb313ca3d | ||
|
|
d119de56be | ||
|
|
f05417fa23 | ||
|
|
9ab2eb591f | ||
|
|
3f476d770f | ||
|
|
ced6759686 | ||
|
|
eba3014c59 | ||
|
|
3aeb4e16e9 | ||
|
|
3b62722251 | ||
|
|
fb1cc4868e | ||
|
|
4a0dcf0dbf | ||
|
|
4f913f146e | ||
|
|
533560f432 | ||
|
|
cf7b479a1b | ||
|
|
2e4c29a0de | ||
|
|
6f0ceb94c6 | ||
|
|
800d7ba04b | ||
|
|
fb6a6d2b93 | ||
|
|
cf2b19ae90 | ||
|
|
fb1cc93613 | ||
|
|
c2bba796c2 | ||
|
|
a02bf83842 | ||
|
|
cd9f129e2d | ||
|
|
e85c80bdcf | ||
|
|
7e83e0c482 | ||
|
|
92ac3125f3 | ||
|
|
a61feca369 | ||
|
|
8b0b811919 | ||
|
|
8742526c7f | ||
|
|
ee757cfd92 | ||
|
|
b12cfea379 | ||
|
|
45365e3e03 | ||
|
|
1b676eefd2 | ||
|
|
0092dc44fd | ||
|
|
4941b376f3 | ||
|
|
e46813cd17 | ||
|
|
58ebd224c2 | ||
|
|
95ece6e16f | ||
|
|
b82cbd06fa | ||
|
|
16210892da | ||
|
|
a452d63a56 | ||
|
|
51c7abedd3 | ||
|
|
6d0a2420a8 | ||
|
|
9cf687b73d | ||
|
|
49c9e41df5 | ||
|
|
2ec2e64213 | ||
|
|
867a61c8dc | ||
|
|
12263d1453 | ||
|
|
c0cacb2e64 | ||
|
|
0637b343b1 | ||
|
|
2473e144ef | ||
|
|
00a37d6de7 | ||
|
|
50c664e6bf | ||
|
|
22b7d20455 | ||
|
|
141262e5a5 | ||
|
|
4717abfa77 | ||
|
|
1bf1a01c32 | ||
|
|
05b714de38 | ||
|
|
11377d4e5f | ||
|
|
46ea46fdfe | ||
|
|
d4f0483238 | ||
|
|
a79610f5ea | ||
|
|
d9fb71b9a0 | ||
|
|
37057fa0cf | ||
|
|
b234128a45 | ||
|
|
67a2d57966 | ||
|
|
3a1516877e | ||
|
|
53f31d175f | ||
|
|
25323e9ce2 | ||
|
|
3136596add | ||
|
|
e7200b0b23 | ||
|
|
dfb19c1dde | ||
|
|
2363b35263 | ||
|
|
99367aaf88 | ||
|
|
ad17ef328f | ||
|
|
5f149f6a38 | ||
|
|
73ed57301b | ||
|
|
138b929db4 | ||
|
|
4585e94cd1 | ||
|
|
69ad6344f5 | ||
|
|
a55665bd14 | ||
|
|
b5e2053b0c | ||
|
|
94265eab9f | ||
|
|
eb79d473b0 | ||
|
|
c4e0a9962f | ||
|
|
ee613616ca | ||
|
|
6bbf00c371 | ||
|
|
f9f45d315d | ||
|
|
84f215b7f1 | ||
|
|
016220bb2a | ||
|
|
ba1eb73ace | ||
|
|
b304091fb3 | ||
|
|
840eaea667 | ||
|
|
956cc9fd68 | ||
|
|
e78e212f83 | ||
|
|
cdc2d4c039 | ||
|
|
cd4b0c4f94 | ||
|
|
53ada6cc40 | ||
|
|
2e6cb0f21d | ||
|
|
4287591a6b | ||
|
|
2fe0c21e36 | ||
|
|
bfa043aeba | ||
|
|
f4336ca5e9 | ||
|
|
8125cb7090 | ||
|
|
0ae1e7fbc4 | ||
|
|
88f8111a56 | ||
|
|
dbfaa519ba | ||
|
|
402e803146 | ||
|
|
5eae14a3c9 | ||
|
|
e0bfc45f5a | ||
|
|
7d8fb7aab7 | ||
|
|
846ef00aed | ||
|
|
f2f730e88c | ||
|
|
311a9405e4 | ||
|
|
6c53981883 | ||
|
|
f23f960368 | ||
|
|
f593c6d310 | ||
|
|
3fb5ea96bc | ||
|
|
30c697a3df | ||
|
|
1d50d05329 | ||
|
|
840221d9ec | ||
|
|
e52a76921f | ||
|
|
80fdb37129 | ||
|
|
bbef4aa8d9 | ||
|
|
35eba3b1e1 | ||
|
|
28a1230d26 | ||
|
|
86dd6a9608 | ||
|
|
f7a40b7324 | ||
|
|
e2e8eb837d | ||
|
|
020f7ae07e | ||
|
|
8311667930 | ||
|
|
741ab94150 | ||
|
|
5d6ca183be | ||
|
|
0f937ad6d0 | ||
|
|
ab38f220f7 | ||
|
|
a8c0b3bfd5 | ||
|
|
5d1629bf0b | ||
|
|
da7fa40c70 | ||
|
|
f1f0ee193f | ||
|
|
deccccead0 | ||
|
|
47b4464ad8 | ||
|
|
3cf4a2edc1 | ||
|
|
350f3a66dd | ||
|
|
f8edcabb05 | ||
|
|
00cafc613d | ||
|
|
8c614dc8a1 | ||
|
|
216c9d8852 | ||
|
|
741e3eb89b | ||
|
|
bc06684694 | ||
|
|
2539cb9c1a | ||
|
|
32dd3d5098 | ||
|
|
b6cf382b86 | ||
|
|
03d19a797c | ||
|
|
98cbc14039 | ||
|
|
248bb50b3e | ||
|
|
01f1dcf93e | ||
|
|
fdac82b8dc | ||
|
|
0f926cb218 | ||
|
|
de35b61b52 | ||
|
|
341aa3f070 | ||
|
|
f47254e72d | ||
|
|
0b75d4d2ed | ||
|
|
d204aa0cd4 | ||
|
|
4f6584a41d | ||
|
|
8f8f24ccfe | ||
|
|
0f2257b8bb | ||
|
|
8bd99f13c1 | ||
|
|
f8deb89592 | ||
|
|
701407581b | ||
|
|
ba2ee05bc0 | ||
|
|
c6e649129e | ||
|
|
329249ea99 | ||
|
|
65d8a30396 | ||
|
|
e29a45c4a3 | ||
|
|
438078cdc5 | ||
|
|
ae07ba7523 | ||
|
|
f201b12dd8 | ||
|
|
ee5322f406 | ||
|
|
60a2e0c963 | ||
|
|
2b55ed9b46 | ||
|
|
68eb7cb57e | ||
|
|
6387b601b1 | ||
|
|
af58fa8802 | ||
|
|
80daea5744 | ||
|
|
bf9a471484 | ||
|
|
195ed9761c | ||
|
|
fdc0123681 | ||
|
|
6fd75ae552 | ||
|
|
10c462a477 | ||
|
|
694c43292a | ||
|
|
cfa78dc9e2 | ||
|
|
cc80f5b685 | ||
|
|
58f4a11669 | ||
|
|
4f57624a67 | ||
|
|
9558520dcd | ||
|
|
8ded3623a4 | ||
|
|
12fcca2faf | ||
|
|
9dc20fc674 | ||
|
|
16430550d1 | ||
|
|
b34b66785d | ||
|
|
2cf38b6027 | ||
|
|
e1b4edaa68 | ||
|
|
97f3f70d57 | ||
|
|
cee0ce6620 | ||
|
|
89b659695f | ||
|
|
d52848ab1b | ||
|
|
314a8d71ef | ||
|
|
bfa85cd8f1 | ||
|
|
2254cb1f87 | ||
|
|
17cbfb8453 | ||
|
|
6b89b7b4a5 | ||
|
|
4fe5828d8d | ||
|
|
98422d696e | ||
|
|
e3103faeae | ||
|
|
0b23ddffb2 | ||
|
|
37fa12e214 | ||
|
|
328f8ac125 | ||
|
|
744749d22b | ||
|
|
a56fd039b4 | ||
|
|
e16867b72a | ||
|
|
ff6756447b | ||
|
|
546980a906 | ||
|
|
f93e2ad4b6 | ||
|
|
68732d6b31 | ||
|
|
c3b8146e7f | ||
|
|
271b7ca8a5 | ||
|
|
2fa87cc428 | ||
|
|
ffa7c4ee79 | ||
|
|
86c4374238 | ||
|
|
4ee8f1b9ad | ||
|
|
47dcd2b054 | ||
|
|
d099e6b85c | ||
|
|
320401e8f3 | ||
|
|
6d75244f8f | ||
|
|
05ac5d51b5 | ||
|
|
2b7c9a9673 | ||
|
|
f76bfdf6b3 | ||
|
|
7ebb70b896 | ||
|
|
960ed6bf70 | ||
|
|
b4fd4f1087 | ||
|
|
109d6db1fc | ||
|
|
07e2d9ed10 | ||
|
|
c5cd6c0337 | ||
|
|
fe1d566326 | ||
|
|
cedc918a09 | ||
|
|
1e6c0865dd | ||
|
|
7649986b55 | ||
|
|
86a82b409a | ||
|
|
f6ad9bdf82 | ||
|
|
a647526084 | ||
|
|
44ed90e181 | ||
|
|
3e7273701d | ||
|
|
d77ed30940 | ||
|
|
5ae80e67a3 | ||
|
|
184389be33 | ||
|
|
c1f022001f | ||
|
|
616d56d515 | ||
|
|
10a0b5099e | ||
|
|
0815605298 | ||
|
|
2df3216b32 | ||
|
|
74491c666d | ||
|
|
29a2eb6f2f | ||
|
|
baf56746ce | ||
|
|
5867c5af8f | ||
|
|
4a358f5cff | ||
|
|
13f2b008fd | ||
|
|
84400cd657 | ||
|
|
f2a3a6933e | ||
|
|
0a4d1cad4c | ||
|
|
08f472f9ee | ||
|
|
7f73945c8d | ||
|
|
56a7860b5a | ||
|
|
25dab86b8e | ||
|
|
35b90ca162 | ||
|
|
5babee6de3 | ||
|
|
7567d440a9 | ||
|
|
2ecd799dab | ||
|
|
5b3561f983 | ||
|
|
cce3711c02 | ||
|
|
9cdbda0828 | ||
|
|
9c4775fd38 | ||
|
|
212e0aa4c3 | ||
|
|
05300ec0e9 | ||
|
|
67fb49e54e | ||
|
|
7164b696b1 | ||
|
|
8728167733 | ||
|
|
6e80a63b68 | ||
|
|
9e43a22ec3 | ||
|
|
49d8ed4a6f | ||
|
|
c7b537e6c7 | ||
|
|
f1cdd2fa46 | ||
|
|
3d5ad02274 | ||
|
|
1cb9f4becf | ||
|
|
0d0dafbe49 | ||
|
|
048d1df2d1 | ||
|
|
4fb4154e30 | ||
|
|
0be69bbccd | ||
|
|
7015a40256 | ||
|
|
03cca642e9 | ||
|
|
579fd3780b | ||
|
|
a85d91c10e | ||
|
|
af31c496a1 | ||
|
|
f9efbaa954 | ||
|
|
d541ec7f20 | ||
|
|
1d847e2c6f | ||
|
|
2fedf4f075 | ||
|
|
e9a02c4c80 | ||
|
|
8beaccdded | ||
|
|
af6003da6d | ||
|
|
76ac2cd013 | ||
|
|
859876e3f8 | ||
|
|
7d49e7fb34 | ||
|
|
6c42ae9077 | ||
|
|
15dcc60407 | ||
|
|
5b811b7003 | ||
|
|
55d670fe3c | ||
|
|
ac3a5e52c7 | ||
|
|
2abe00e251 | ||
|
|
1bd3c29e39 | ||
|
|
1a8087bda7 | ||
|
|
72b4c2b1ec | ||
|
|
38e6820d7b | ||
|
|
765b3a57fe | ||
|
|
1c4a32f8fa | ||
|
|
3f258fcebf | ||
|
|
140f2cbfa8 | ||
|
|
6aacd77492 | ||
|
|
ef3f46f8b7 | ||
|
|
0cdd25d2cf | ||
|
|
5d02ce0636 | ||
|
|
0cd1228ba7 | ||
|
|
0595401d14 | ||
|
|
d724f8cc8e | ||
|
|
a3f5d458d7 | ||
|
|
76bfb130b0 | ||
|
|
184bb78e3b | ||
|
|
6a41af2cb2 | ||
|
|
faa149cc87 | ||
|
|
24592fe480 | ||
|
|
4be53082e0 | ||
|
|
ae8c9c668c | ||
|
|
b0c15af04f | ||
|
|
c05b710aff | ||
|
|
4299c48aef | ||
|
|
ae0523dec0 | ||
|
|
e18a6bda7b | ||
|
|
e64be95f1c | ||
|
|
a1aa0150f8 | ||
|
|
32f9cb5996 | ||
|
|
3b7e692b01 | ||
|
|
6491eba1da | ||
|
|
bb7ea7e809 | ||
|
|
169930e3b8 | ||
|
|
8e14047f36 | ||
|
|
fd29a96f7b | ||
|
|
820c12f230 | ||
|
|
ff3550e7b3 | ||
|
|
b65e43351d | ||
|
|
3fb74b632b | ||
|
|
253e54344d | ||
|
|
f1ee7d24a6 | ||
|
|
475673b3e7 | ||
|
|
dd49afef01 | ||
|
|
d0c842fe87 |
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -11,7 +11,7 @@ body:
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Relevant server.conf | webapi.conf
|
||||
label: Your config.toml
|
||||
description: Place config in the toml code section. This will be automatically formatted into toml, so no need for backticks.
|
||||
render: toml
|
||||
validations:
|
||||
@@ -20,7 +20,7 @@ body:
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant logs
|
||||
description: categraf | telegraf | server | webapi | prometheus | chrome request/response ...
|
||||
description: categraf | telegraf | n9e | prometheus | chrome request/response ...
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -30,6 +30,7 @@ _test
|
||||
/dist
|
||||
/etc/*.local.yml
|
||||
/etc/*.local.conf
|
||||
/etc/rsa/*
|
||||
/etc/plugins/*.local.yml
|
||||
/etc/script/rules.yaml
|
||||
/etc/script/alert-rules.json
|
||||
@@ -43,10 +44,14 @@ _test
|
||||
/n9e
|
||||
/docker/pub
|
||||
/docker/n9e
|
||||
/docker/mysqldata
|
||||
/docker/experience_pg_vm/pgdata
|
||||
/docker/compose-bridge/mysqldata
|
||||
/docker/compose-host-network/mysqldata
|
||||
/docker/compose-host-network-metric-log/mysqldata
|
||||
/docker/compose-host-network-metric-log/n9e-logs
|
||||
/docker/compose-postgres/pgdata
|
||||
/etc.local*
|
||||
/front/statik/statik.go
|
||||
/docker/compose-bridge/etc-nightingale/rsa/
|
||||
|
||||
.alerts
|
||||
.idea
|
||||
|
||||
83
README.md
83
README.md
@@ -1,6 +1,9 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/ccfos/nightingale">
|
||||
<img src="doc/img/nightingale_logo_h.png" alt="nightingale - cloud native monitoring" width="240" /></a>
|
||||
<img src="doc/img/Nightingale_L_V.png" alt="nightingale - cloud native monitoring" width="100" /></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<b>开源告警管理专家 一体化的可观测平台</b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -11,64 +14,86 @@
|
||||
<a href="https://github.com/ccfos/nightingale/graphs/contributors">
|
||||
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors-anon/ccfos/nightingale"/></a>
|
||||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ccfos/nightingale">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/ccfos/nightingale">
|
||||
<br/><img alt="GitHub Repo issues" src="https://img.shields.io/github/issues/ccfos/nightingale">
|
||||
<img alt="GitHub Repo issues closed" src="https://img.shields.io/github/issues-closed/ccfos/nightingale">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/ccfos/nightingale">
|
||||
<img alt="GitHub latest release" src="https://img.shields.io/github/v/release/ccfos/nightingale"/>
|
||||
<img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue"/>
|
||||
<a href="https://n9e-talk.slack.com/">
|
||||
<img alt="GitHub contributors" src="https://img.shields.io/badge/join%20slack-%23n9e-brightgreen.svg"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
告警管理专家,一体化的开源可观测平台
|
||||
</p>
|
||||
|
||||
|
||||
[English](./README_en.md) | [中文](./README.md)
|
||||
|
||||
夜莺Nightingale是中国计算机学会托管的开源云原生可观测工具,最早由滴滴于 2020 年孵化并开源,并于 2022 年正式捐赠予中国计算机学会。夜莺采用 All-in-One 的设计理念,集数据采集、可视化、监控告警、数据分析于一体,与云原生生态紧密集成,融入了顶级互联网公司可观测性最佳实践,沉淀了众多社区专家经验,开箱即用。
|
||||
## 夜莺 Nightingale 是什么
|
||||
|
||||
## 资料
|
||||
夜莺监控是一款开源云原生观测分析工具,采用 All-in-One 的设计理念,集数据采集、可视化、监控告警、数据分析于一体,与云原生生态紧密集成,提供开箱即用的企业级监控分析和告警能力。夜莺于 2020 年 3 月 20 日,在 github 上发布 v1 版本,已累计迭代 100 多个版本。
|
||||
|
||||
- 文档:[flashcat.cloud/docs](https://flashcat.cloud/docs/)
|
||||
- 提问:[answer.flashcat.cloud](https://answer.flashcat.cloud/)
|
||||
- 报Bug:[github.com/ccfos/nightingale/issues](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml)
|
||||
夜莺最初由滴滴开发和开源,并于 2022 年 5 月 11 日,捐赠予中国计算机学会开源发展委员会(CCF ODC),为 CCF ODC 成立后接受捐赠的第一个开源项目。夜莺的核心研发团队,也是 Open-Falcon 项目原核心研发人员,从 2014 年(Open-Falcon 是 2014 年开源)算起来,也有 10 年了,只为把监控这个事情做好。
|
||||
|
||||
|
||||
## 功能和特点
|
||||
## 快速开始
|
||||
- 👉[文档中心](https://flashcat.cloud/docs/) | [下载中心](https://flashcat.cloud/download/nightingale/)
|
||||
- ❤️[报告 Bug](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml)
|
||||
- ℹ️为了提供更快速的访问体验,上述文档和下载站点托管于 [FlashcatCloud](https://flashcat.cloud)
|
||||
|
||||
- 统一接入各种时序库:支持对接 Prometheus、VictoriaMetrics、Thanos、Mimir、M3DB 等多种时序库,实现统一告警管理
|
||||
- 专业告警能力:内置支持多种告警规则,可以扩展支持所有通知媒介,支持告警屏蔽、告警抑制、告警自愈、告警事件管理
|
||||
- 高性能可视化引擎:支持多种图表样式,内置众多Dashboard模版,也可导入Grafana模版,开箱即用,开源协议商业友好
|
||||
- 无缝搭配 [Flashduty](https://flashcat.cloud/product/flashcat-duty/):实现告警聚合收敛、认领、升级、排班、IM集成,确保告警处理不遗漏,减少打扰,更好协同
|
||||
- 支持所有常见采集器:支持 [Categraf](https://flashcat.cloud/product/categraf)、telegraf、grafana-agent、datadog-agent、各种 exporter 作为采集器,没有什么数据是不能监控的
|
||||
- 一体化观测平台:从 v6 版本开始,支持接入 ElasticSearch、Jaeger 数据源,实现日志、链路、指标多维度的统一可观测
|
||||
## 功能特点
|
||||
|
||||
- 对接多种时序库:支持对接 Prometheus、VictoriaMetrics、Thanos、Mimir、M3DB、TDengine 等多种时序库,实现统一告警管理。
|
||||
- 专业告警能力:内置支持多种告警规则,可以扩展支持常见通知媒介,支持告警屏蔽/抑制/订阅/自愈、告警事件管理。
|
||||
- 高性能可视化引擎:支持多种图表样式,内置众多 Dashboard 模版,也可导入 Grafana 模版,开箱即用,开源协议商业友好。
|
||||
- 支持常见采集器:支持 [Categraf](https://flashcat.cloud/product/categraf)、Telegraf、Grafana-agent、Datadog-agent、各种 Exporter 作为采集器,没有什么数据是不能监控的。
|
||||
- 👀无缝搭配 [Flashduty](https://flashcat.cloud/product/flashcat-duty/):实现告警聚合收敛、认领、升级、排班、IM集成,确保告警处理不遗漏,减少打扰,高效协同。
|
||||
|
||||
|
||||
## 产品演示
|
||||
## 截图演示
|
||||
|
||||

|
||||
即时查询,类似 Prometheus 内置的查询分析页面,做 ad-hoc 查询,夜莺做了一些 UI 优化,同时提供了一些内置 promql 指标,让不太了解 promql 的用户也可以快速查询。
|
||||
|
||||
## 部署架构
|
||||

|
||||
|
||||

|
||||
当然,也可以直接通过指标视图查看,有了指标视图,即时查询基本可以不用了,或者只有高端玩家使用即时查询,普通用户直接通过指标视图查询即可。
|
||||
|
||||
## 加入交流群
|
||||

|
||||
|
||||
欢迎加入 QQ 交流群,群号:479290895,QQ 群适合群友互助,夜莺研发人员通常不在群里。如果要报 bug 请到[这里](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml),提问到[这里](https://answer.flashcat.cloud/)。
|
||||
夜莺内置了常用仪表盘,可以直接导入使用。也可以导入 Grafana 仪表盘,不过只能兼容 Grafana 基本图表,如果已经习惯了 Grafana 建议继续使用 Grafana 看图,把夜莺作为一个告警引擎使用。
|
||||
|
||||
## Stargazers over time
|
||||

|
||||
|
||||
除了内置的仪表盘,也内置了很多告警规则,开箱即用。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 产品架构
|
||||
|
||||
社区使用夜莺最多的场景就是使用夜莺做告警引擎,对接多套时序库,统一告警规则管理。绘图仍然使用 Grafana 居多。作为一个告警引擎,夜莺的产品架构如下:
|
||||
|
||||

|
||||
|
||||
对于个别边缘机房,如果和中心夜莺服务端网络链路不好,希望提升告警可用性,我们也提供边缘机房告警引擎下沉部署模式,这个模式下,即便网络割裂,告警功能也不受影响。
|
||||
|
||||

|
||||
|
||||
## 交流渠道
|
||||
- 报告Bug,优先推荐提交[夜莺GitHub Issue](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml)
|
||||
- 推荐完整浏览[夜莺文档站点](https://flashcat.cloud/docs/content/flashcat-monitor/nightingale-v7/introduction/),了解更多信息
|
||||
- 推荐搜索关注夜莺公众号,第一时间获取社区动态:`夜莺监控Nightingale`
|
||||
- 日常答疑、技术分享、用户之间的交流,统一使用知识星球,大伙可以免费加入交流,[入口在这里](https://download.flashcat.cloud/ulric/20240319095409.png)
|
||||
|
||||
## 广受关注
|
||||
[](https://star-history.com/#ccfos/nightingale&Date)
|
||||
|
||||
|
||||
## Contributors
|
||||
## 社区共建
|
||||
- ❇️请阅读浏览[夜莺开源项目和社区治理架构草案](./doc/community-governance.md),真诚欢迎每一位用户、开发者、公司以及组织,使用夜莺监控、积极反馈 Bug、提交功能需求、分享最佳实践,共建专业、活跃的夜莺开源社区。
|
||||
- 夜莺贡献者❤️
|
||||
<a href="https://github.com/ccfos/nightingale/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=ccfos/nightingale" />
|
||||
</a>
|
||||
|
||||
## 社区治理
|
||||
[夜莺开源项目和社区治理架构(草案)](./doc/community-governance.md)
|
||||
|
||||
## License
|
||||
[Apache License V2.0](https://github.com/didi/nightingale/blob/main/LICENSE)
|
||||
- [Apache License V2.0](https://github.com/didi/nightingale/blob/main/LICENSE)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/ccfos/nightingale">
|
||||
<img src="doc/img/nightingale_logo_h.png" alt="nightingale - cloud native monitoring" width="240" /></a>
|
||||
<img src="doc/img/Nightingale_L_V.png" alt="nightingale - cloud native monitoring" width="240" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -25,7 +25,7 @@
|
||||
We recommend upgrading your <b>Prometheus + AlertManager + Grafana</b> combination to Nightingale!
|
||||
</p>
|
||||
|
||||
[English](./README.md) | [中文](./README_ZH.md)
|
||||
[English](./README_en.md) | [中文](./README.md)
|
||||
|
||||
|
||||
## Highlighted Features
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
## Getting Started
|
||||
|
||||
[English Doc](https://n9e.github.io/) | [中文文档](http://n9e.flashcat.cloud/)
|
||||
[https://n9e.github.io/](https://n9e.github.io/)
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ package aconf
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/toolkits/pkg/runner"
|
||||
)
|
||||
|
||||
type Alert struct {
|
||||
@@ -48,16 +46,9 @@ type RedisPub struct {
|
||||
ChannelKey string
|
||||
}
|
||||
|
||||
type Ibex struct {
|
||||
Address string
|
||||
BasicAuthUser string
|
||||
BasicAuthPass string
|
||||
Timeout int64
|
||||
}
|
||||
|
||||
func (a *Alert) PreCheck() {
|
||||
func (a *Alert) PreCheck(configDir string) {
|
||||
if a.Alerting.TemplatesDir == "" {
|
||||
a.Alerting.TemplatesDir = path.Join(runner.Cwd, "etc", "template")
|
||||
a.Alerting.TemplatesDir = path.Join(configDir, "template")
|
||||
}
|
||||
|
||||
if a.Alerting.NotifyConcurrency == 0 {
|
||||
|
||||
@@ -24,6 +24,10 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/pconf"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/writer"
|
||||
"github.com/ccfos/nightingale/v6/storage"
|
||||
"github.com/ccfos/nightingale/v6/tdengine"
|
||||
|
||||
"github.com/flashcatcloud/ibex/src/cmd/ibex"
|
||||
)
|
||||
|
||||
func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
@@ -39,26 +43,40 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
|
||||
ctx := ctx.NewContext(context.Background(), nil, false, config.CenterApi)
|
||||
|
||||
var redis storage.Redis
|
||||
redis, err = storage.NewRedis(config.Redis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
syncStats := memsto.NewSyncStats()
|
||||
alertStats := astats.NewSyncStats()
|
||||
|
||||
configCache := memsto.NewConfigCache(ctx, syncStats, nil, "")
|
||||
targetCache := memsto.NewTargetCache(ctx, syncStats, nil)
|
||||
busiGroupCache := memsto.NewBusiGroupCache(ctx, syncStats)
|
||||
alertMuteCache := memsto.NewAlertMuteCache(ctx, syncStats)
|
||||
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
|
||||
notifyConfigCache := memsto.NewNotifyConfigCache(ctx)
|
||||
notifyConfigCache := memsto.NewNotifyConfigCache(ctx, configCache)
|
||||
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
|
||||
userCache := memsto.NewUserCache(ctx, syncStats)
|
||||
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
|
||||
taskTplsCache := memsto.NewTaskTplCache(ctx)
|
||||
|
||||
promClients := prom.NewPromClient(ctx, config.Alert.Heartbeat)
|
||||
promClients := prom.NewPromClient(ctx)
|
||||
tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat)
|
||||
|
||||
externalProcessors := process.NewExternalProcessors()
|
||||
|
||||
Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, userCache, userGroupCache)
|
||||
Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, taskTplsCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache)
|
||||
|
||||
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
|
||||
rt := router.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
|
||||
|
||||
if config.Ibex.Enable {
|
||||
ibex.ServerStart(false, nil, redis, config.HTTP.APIForService.BasicAuth, config.Alert.Heartbeat, &config.CenterApi, r, nil, config.Ibex, config.HTTP.Port)
|
||||
}
|
||||
|
||||
rt.Config(r)
|
||||
dumper.ConfigRouter(r)
|
||||
|
||||
@@ -71,25 +89,28 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
}
|
||||
|
||||
func Start(alertc aconf.Alert, pushgwc pconf.Pushgw, syncStats *memsto.Stats, alertStats *astats.Stats, externalProcessors *process.ExternalProcessorsType, targetCache *memsto.TargetCacheType, busiGroupCache *memsto.BusiGroupCacheType,
|
||||
alertMuteCache *memsto.AlertMuteCacheType, alertRuleCache *memsto.AlertRuleCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context, promClients *prom.PromClientMap, userCache *memsto.UserCacheType, userGroupCache *memsto.UserGroupCacheType) {
|
||||
alertMuteCache *memsto.AlertMuteCacheType, alertRuleCache *memsto.AlertRuleCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, taskTplsCache *memsto.TaskTplCache, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context,
|
||||
promClients *prom.PromClientMap, tdendgineClients *tdengine.TdengineClientMap, userCache *memsto.UserCacheType, userGroupCache *memsto.UserGroupCacheType) {
|
||||
alertSubscribeCache := memsto.NewAlertSubscribeCache(ctx, syncStats)
|
||||
recordingRuleCache := memsto.NewRecordingRuleCache(ctx, syncStats)
|
||||
targetsOfAlertRulesCache := memsto.NewTargetOfAlertRuleCache(ctx, alertc.Heartbeat.EngineName, syncStats)
|
||||
|
||||
go models.InitNotifyConfig(ctx, alertc.Alerting.TemplatesDir)
|
||||
|
||||
naming := naming.NewNaming(ctx, alertc.Heartbeat)
|
||||
naming := naming.NewNaming(ctx, alertc.Heartbeat, alertStats)
|
||||
|
||||
writers := writer.NewWriters(pushgwc)
|
||||
record.NewScheduler(alertc, recordingRuleCache, promClients, writers, alertStats)
|
||||
|
||||
eval.NewScheduler(alertc, externalProcessors, alertRuleCache, targetCache, busiGroupCache, alertMuteCache, datasourceCache, promClients, naming, ctx, alertStats)
|
||||
eval.NewScheduler(alertc, externalProcessors, alertRuleCache, targetCache, targetsOfAlertRulesCache,
|
||||
busiGroupCache, alertMuteCache, datasourceCache, promClients, tdendgineClients, naming, ctx, alertStats)
|
||||
|
||||
dp := dispatch.NewDispatch(alertRuleCache, userCache, userGroupCache, alertSubscribeCache, targetCache, notifyConfigCache, alertc.Alerting, ctx)
|
||||
dp := dispatch.NewDispatch(alertRuleCache, userCache, userGroupCache, alertSubscribeCache, targetCache, notifyConfigCache, taskTplsCache, alertc.Alerting, ctx, alertStats)
|
||||
consumer := dispatch.NewConsumer(alertc.Alerting, ctx, dp)
|
||||
|
||||
go dp.ReloadTpls()
|
||||
go consumer.LoopConsume()
|
||||
|
||||
go queue.ReportQueueSize(alertStats)
|
||||
go sender.StartEmailSender(notifyConfigCache.GetSMTP()) // todo
|
||||
go sender.InitEmailSender(notifyConfigCache)
|
||||
}
|
||||
|
||||
@@ -10,22 +10,77 @@ const (
|
||||
)
|
||||
|
||||
type Stats struct {
|
||||
CounterSampleTotal *prometheus.CounterVec
|
||||
CounterAlertsTotal *prometheus.CounterVec
|
||||
GaugeAlertQueueSize prometheus.Gauge
|
||||
GaugeSampleQueueSize *prometheus.GaugeVec
|
||||
RequestDuration *prometheus.HistogramVec
|
||||
ForwardDuration *prometheus.HistogramVec
|
||||
AlertNotifyTotal *prometheus.CounterVec
|
||||
AlertNotifyErrorTotal *prometheus.CounterVec
|
||||
CounterAlertsTotal *prometheus.CounterVec
|
||||
GaugeAlertQueueSize prometheus.Gauge
|
||||
CounterRuleEval *prometheus.CounterVec
|
||||
CounterQueryDataErrorTotal *prometheus.CounterVec
|
||||
CounterQueryDataTotal *prometheus.CounterVec
|
||||
CounterRecordEval *prometheus.CounterVec
|
||||
CounterRecordEvalErrorTotal *prometheus.CounterVec
|
||||
CounterMuteTotal *prometheus.CounterVec
|
||||
CounterRuleEvalErrorTotal *prometheus.CounterVec
|
||||
CounterHeartbeatErrorTotal *prometheus.CounterVec
|
||||
CounterSubEventTotal *prometheus.CounterVec
|
||||
}
|
||||
|
||||
func NewSyncStats() *Stats {
|
||||
// 从各个接收接口接收到的监控数据总量
|
||||
CounterSampleTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
CounterRuleEval := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "samples_received_total",
|
||||
Help: "Total number samples received.",
|
||||
}, []string{"cluster", "channel"})
|
||||
Name: "rule_eval_total",
|
||||
Help: "Number of rule eval.",
|
||||
}, []string{})
|
||||
|
||||
CounterRuleEvalErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "rule_eval_error_total",
|
||||
Help: "Number of rule eval error.",
|
||||
}, []string{"datasource", "stage"})
|
||||
|
||||
CounterQueryDataErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "query_data_error_total",
|
||||
Help: "Number of rule eval query data error.",
|
||||
}, []string{"datasource"})
|
||||
|
||||
CounterQueryDataTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "query_data_total",
|
||||
Help: "Number of rule eval query data.",
|
||||
}, []string{"datasource"})
|
||||
|
||||
CounterRecordEval := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "record_eval_total",
|
||||
Help: "Number of record eval.",
|
||||
}, []string{"datasource"})
|
||||
|
||||
CounterRecordEvalErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "record_eval_error_total",
|
||||
Help: "Number of record eval error.",
|
||||
}, []string{"datasource"})
|
||||
|
||||
AlertNotifyTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "alert_notify_total",
|
||||
Help: "Number of send msg.",
|
||||
}, []string{"channel"})
|
||||
|
||||
AlertNotifyErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "alert_notify_error_total",
|
||||
Help: "Number of send msg.",
|
||||
}, []string{"channel"})
|
||||
|
||||
// 产生的告警总量
|
||||
CounterAlertsTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
@@ -33,7 +88,7 @@ func NewSyncStats() *Stats {
|
||||
Subsystem: subsystem,
|
||||
Name: "alerts_total",
|
||||
Help: "Total number alert events.",
|
||||
}, []string{"cluster"})
|
||||
}, []string{"cluster", "type", "busi_group"})
|
||||
|
||||
// 内存中的告警事件队列的长度
|
||||
GaugeAlertQueueSize := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
@@ -43,51 +98,56 @@ func NewSyncStats() *Stats {
|
||||
Help: "The size of alert queue.",
|
||||
})
|
||||
|
||||
// 数据转发队列,各个队列的长度
|
||||
GaugeSampleQueueSize := prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
CounterMuteTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "sample_queue_size",
|
||||
Help: "The size of sample queue.",
|
||||
}, []string{"cluster", "channel_number"})
|
||||
Name: "mute_total",
|
||||
Help: "Number of mute.",
|
||||
}, []string{"group"})
|
||||
|
||||
// 一些重要的请求,比如接收数据的请求,应该统计一下延迟情况
|
||||
RequestDuration := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Buckets: []float64{.01, .1, 1},
|
||||
Name: "http_request_duration_seconds",
|
||||
Help: "HTTP request latencies in seconds.",
|
||||
}, []string{"code", "path", "method"},
|
||||
)
|
||||
CounterSubEventTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "sub_event_total",
|
||||
Help: "Number of sub event.",
|
||||
}, []string{"group"})
|
||||
|
||||
// 发往后端TSDB,延迟如何
|
||||
ForwardDuration := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Buckets: []float64{.1, 1, 10},
|
||||
Name: "forward_duration_seconds",
|
||||
Help: "Forward samples to TSDB. latencies in seconds.",
|
||||
}, []string{"cluster", "channel_number"},
|
||||
)
|
||||
CounterHeartbeatErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "heartbeat_error_count",
|
||||
Help: "Number of heartbeat error.",
|
||||
}, []string{})
|
||||
|
||||
prometheus.MustRegister(
|
||||
CounterSampleTotal,
|
||||
CounterAlertsTotal,
|
||||
GaugeAlertQueueSize,
|
||||
GaugeSampleQueueSize,
|
||||
RequestDuration,
|
||||
ForwardDuration,
|
||||
AlertNotifyTotal,
|
||||
AlertNotifyErrorTotal,
|
||||
CounterRuleEval,
|
||||
CounterQueryDataTotal,
|
||||
CounterQueryDataErrorTotal,
|
||||
CounterRecordEval,
|
||||
CounterRecordEvalErrorTotal,
|
||||
CounterMuteTotal,
|
||||
CounterRuleEvalErrorTotal,
|
||||
CounterHeartbeatErrorTotal,
|
||||
CounterSubEventTotal,
|
||||
)
|
||||
|
||||
return &Stats{
|
||||
CounterSampleTotal: CounterSampleTotal,
|
||||
CounterAlertsTotal: CounterAlertsTotal,
|
||||
GaugeAlertQueueSize: GaugeAlertQueueSize,
|
||||
GaugeSampleQueueSize: GaugeSampleQueueSize,
|
||||
RequestDuration: RequestDuration,
|
||||
ForwardDuration: ForwardDuration,
|
||||
CounterAlertsTotal: CounterAlertsTotal,
|
||||
GaugeAlertQueueSize: GaugeAlertQueueSize,
|
||||
AlertNotifyTotal: AlertNotifyTotal,
|
||||
AlertNotifyErrorTotal: AlertNotifyErrorTotal,
|
||||
CounterRuleEval: CounterRuleEval,
|
||||
CounterQueryDataTotal: CounterQueryDataTotal,
|
||||
CounterQueryDataErrorTotal: CounterQueryDataErrorTotal,
|
||||
CounterRecordEval: CounterRecordEval,
|
||||
CounterRecordEvalErrorTotal: CounterRecordEvalErrorTotal,
|
||||
CounterMuteTotal: CounterMuteTotal,
|
||||
CounterRuleEvalErrorTotal: CounterRuleEvalErrorTotal,
|
||||
CounterHeartbeatErrorTotal: CounterHeartbeatErrorTotal,
|
||||
CounterSubEventTotal: CounterSubEventTotal,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ type AnomalyPoint struct {
|
||||
Severity int `json:"severity"`
|
||||
Triggered bool `json:"triggered"`
|
||||
Query string `json:"query"`
|
||||
Values string `json:"values"`
|
||||
}
|
||||
|
||||
func NewAnomalyPoint(key string, labels map[string]string, ts int64, value float64, severity int) AnomalyPoint {
|
||||
|
||||
@@ -22,6 +22,14 @@ func MatchTags(eventTagsMap map[string]string, itags []models.TagFilter) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
func MatchGroupsName(groupName string, groupFilter []models.TagFilter) bool {
|
||||
for _, filter := range groupFilter {
|
||||
if !matchTag(groupName, filter) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func matchTag(value string, filter models.TagFilter) bool {
|
||||
switch filter.Func {
|
||||
|
||||
@@ -61,16 +61,27 @@ func (e *Consumer) consume(events []interface{}, sema *semaphore.Semaphore) {
|
||||
func (e *Consumer) consumeOne(event *models.AlertCurEvent) {
|
||||
LogEvent(event, "consume")
|
||||
|
||||
eventType := "alert"
|
||||
if event.IsRecovered {
|
||||
eventType = "recovery"
|
||||
}
|
||||
|
||||
e.dispatch.Astats.CounterAlertsTotal.WithLabelValues(event.Cluster, eventType, event.GroupName).Inc()
|
||||
|
||||
if err := event.ParseRule("rule_name"); err != nil {
|
||||
logger.Warningf("ruleid:%d failed to parse rule name: %v", event.RuleId, err)
|
||||
event.RuleName = fmt.Sprintf("failed to parse rule name: %v", err)
|
||||
}
|
||||
|
||||
if err := event.ParseRule("rule_note"); err != nil {
|
||||
logger.Warningf("ruleid:%d failed to parse rule note: %v", event.RuleId, err)
|
||||
event.RuleNote = fmt.Sprintf("failed to parse rule note: %v", err)
|
||||
}
|
||||
|
||||
if err := event.ParseRule("annotations"); err != nil {
|
||||
event.Annotations = fmt.Sprintf("failed to parse rule note: %v", err)
|
||||
logger.Warningf("ruleid:%d failed to parse annotations: %v", event.RuleId, err)
|
||||
event.Annotations = fmt.Sprintf("failed to parse annotations: %v", err)
|
||||
event.AnnotationsJSON["error"] = event.Annotations
|
||||
}
|
||||
|
||||
e.persist(event)
|
||||
@@ -89,9 +100,11 @@ func (e *Consumer) persist(event *models.AlertCurEvent) {
|
||||
|
||||
if !e.ctx.IsCenter {
|
||||
event.DB2FE()
|
||||
err := poster.PostByUrls(e.ctx, "/v1/n9e/event-persist", event)
|
||||
var err error
|
||||
event.Id, err = poster.PostByUrlsWithResp[int64](e.ctx, "/v1/n9e/event-persist", event)
|
||||
if err != nil {
|
||||
logger.Errorf("event%+v persist err:%v", event, err)
|
||||
logger.Errorf("event:%+v persist err:%v", event, err)
|
||||
e.dispatch.Astats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", event.DatasourceId), "persist_event").Inc()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -99,5 +112,6 @@ func (e *Consumer) persist(event *models.AlertCurEvent) {
|
||||
err := models.EventPersist(e.ctx, event)
|
||||
if err != nil {
|
||||
logger.Errorf("event%+v persist err:%v", event, err)
|
||||
e.dispatch.Astats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", event.DatasourceId), "persist_event").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/alert/common"
|
||||
"github.com/ccfos/nightingale/v6/alert/sender"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
@@ -25,6 +26,7 @@ type Dispatch struct {
|
||||
alertSubscribeCache *memsto.AlertSubscribeCacheType
|
||||
targetCache *memsto.TargetCacheType
|
||||
notifyConfigCache *memsto.NotifyConfigCacheType
|
||||
taskTplsCache *memsto.TaskTplCache
|
||||
|
||||
alerting aconf.Alerting
|
||||
|
||||
@@ -33,7 +35,8 @@ type Dispatch struct {
|
||||
ExtraSenders map[string]sender.Sender
|
||||
BeforeSenderHook func(*models.AlertCurEvent) bool
|
||||
|
||||
ctx *ctx.Context
|
||||
ctx *ctx.Context
|
||||
Astats *astats.Stats
|
||||
|
||||
RwLock sync.RWMutex
|
||||
}
|
||||
@@ -41,7 +44,7 @@ type Dispatch struct {
|
||||
// 创建一个 Notify 实例
|
||||
func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.UserCacheType, userGroupCache *memsto.UserGroupCacheType,
|
||||
alertSubscribeCache *memsto.AlertSubscribeCacheType, targetCache *memsto.TargetCacheType, notifyConfigCache *memsto.NotifyConfigCacheType,
|
||||
alerting aconf.Alerting, ctx *ctx.Context) *Dispatch {
|
||||
taskTplsCache *memsto.TaskTplCache, alerting aconf.Alerting, ctx *ctx.Context, astats *astats.Stats) *Dispatch {
|
||||
notify := &Dispatch{
|
||||
alertRuleCache: alertRuleCache,
|
||||
userCache: userCache,
|
||||
@@ -49,6 +52,7 @@ func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.Us
|
||||
alertSubscribeCache: alertSubscribeCache,
|
||||
targetCache: targetCache,
|
||||
notifyConfigCache: notifyConfigCache,
|
||||
taskTplsCache: taskTplsCache,
|
||||
|
||||
alerting: alerting,
|
||||
|
||||
@@ -57,7 +61,8 @@ func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.Us
|
||||
ExtraSenders: make(map[string]sender.Sender),
|
||||
BeforeSenderHook: func(*models.AlertCurEvent) bool { return true },
|
||||
|
||||
ctx: ctx,
|
||||
ctx: ctx,
|
||||
Astats: astats,
|
||||
}
|
||||
return notify
|
||||
}
|
||||
@@ -86,17 +91,17 @@ func (e *Dispatch) relaodTpls() error {
|
||||
|
||||
senders := map[string]sender.Sender{
|
||||
models.Email: sender.NewSender(models.Email, tmpTpls, smtp),
|
||||
models.Dingtalk: sender.NewSender(models.Dingtalk, tmpTpls, smtp),
|
||||
models.Wecom: sender.NewSender(models.Wecom, tmpTpls, smtp),
|
||||
models.Feishu: sender.NewSender(models.Feishu, tmpTpls, smtp),
|
||||
models.Mm: sender.NewSender(models.Mm, tmpTpls, smtp),
|
||||
models.Telegram: sender.NewSender(models.Telegram, tmpTpls, smtp),
|
||||
models.FeishuCard: sender.NewSender(models.FeishuCard, tmpTpls, smtp),
|
||||
models.Dingtalk: sender.NewSender(models.Dingtalk, tmpTpls),
|
||||
models.Wecom: sender.NewSender(models.Wecom, tmpTpls),
|
||||
models.Feishu: sender.NewSender(models.Feishu, tmpTpls),
|
||||
models.Mm: sender.NewSender(models.Mm, tmpTpls),
|
||||
models.Telegram: sender.NewSender(models.Telegram, tmpTpls),
|
||||
models.FeishuCard: sender.NewSender(models.FeishuCard, tmpTpls),
|
||||
}
|
||||
|
||||
e.RwLock.RLock()
|
||||
for channel, sender := range e.ExtraSenders {
|
||||
senders[channel] = sender
|
||||
for channelName, extraSender := range e.ExtraSenders {
|
||||
senders[channelName] = extraSender
|
||||
}
|
||||
e.RwLock.RUnlock()
|
||||
|
||||
@@ -170,12 +175,25 @@ func (e *Dispatch) handleSubs(event *models.AlertCurEvent) {
|
||||
|
||||
// handleSub 处理订阅规则的event,注意这里event要使用值传递,因为后面会修改event的状态
|
||||
func (e *Dispatch) handleSub(sub *models.AlertSubscribe, event models.AlertCurEvent) {
|
||||
if sub.IsDisabled() || !sub.MatchCluster(event.DatasourceId) {
|
||||
if sub.IsDisabled() {
|
||||
return
|
||||
}
|
||||
|
||||
if !sub.MatchCluster(event.DatasourceId) {
|
||||
return
|
||||
}
|
||||
|
||||
if !sub.MatchProd(event.RuleProd) {
|
||||
return
|
||||
}
|
||||
|
||||
if !common.MatchTags(event.TagsMap, sub.ITags) {
|
||||
return
|
||||
}
|
||||
// event BusiGroups filter
|
||||
if !common.MatchGroupsName(event.GroupName, sub.IBusiGroups) {
|
||||
return
|
||||
}
|
||||
if sub.ForDuration > (event.TriggerTime - event.FirstTriggerTime) {
|
||||
return
|
||||
}
|
||||
@@ -193,10 +211,11 @@ func (e *Dispatch) handleSub(sub *models.AlertSubscribe, event models.AlertCurEv
|
||||
}
|
||||
}
|
||||
|
||||
e.Astats.CounterSubEventTotal.WithLabelValues(event.GroupName).Inc()
|
||||
sub.ModifyEvent(&event)
|
||||
LogEvent(&event, "subscribe")
|
||||
|
||||
event.SubRuleId = sub.Id
|
||||
|
||||
LogEvent(&event, "subscribe")
|
||||
e.HandleEventNotify(&event, true)
|
||||
}
|
||||
|
||||
@@ -204,7 +223,7 @@ func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, not
|
||||
needSend := e.BeforeSenderHook(event)
|
||||
if needSend {
|
||||
for channel, uids := range notifyTarget.ToChannelUserMap() {
|
||||
ctx := sender.BuildMessageContext(rule, []*models.AlertCurEvent{event}, uids, e.userCache)
|
||||
msgCtx := sender.BuildMessageContext(rule, []*models.AlertCurEvent{event}, uids, e.userCache, e.Astats)
|
||||
e.RwLock.RLock()
|
||||
s := e.Senders[channel]
|
||||
e.RwLock.RUnlock()
|
||||
@@ -212,18 +231,25 @@ func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, not
|
||||
logger.Debugf("no sender for channel: %s", channel)
|
||||
continue
|
||||
}
|
||||
s.Send(ctx)
|
||||
|
||||
var event *models.AlertCurEvent
|
||||
if len(msgCtx.Events) > 0 {
|
||||
event = msgCtx.Events[0]
|
||||
}
|
||||
|
||||
logger.Debugf("send to channel:%s event:%+v users:%+v", channel, event, msgCtx.Users)
|
||||
s.Send(msgCtx)
|
||||
}
|
||||
}
|
||||
|
||||
// handle event callbacks
|
||||
sender.SendCallbacks(e.ctx, notifyTarget.ToCallbackList(), event, e.targetCache, e.userCache, e.notifyConfigCache.GetIbex())
|
||||
sender.SendCallbacks(e.ctx, notifyTarget.ToCallbackList(), event, e.targetCache, e.userCache, e.taskTplsCache, e.Astats)
|
||||
|
||||
// handle global webhooks
|
||||
sender.SendWebhooks(notifyTarget.ToWebhookList(), event)
|
||||
sender.SendWebhooks(notifyTarget.ToWebhookList(), event, e.Astats)
|
||||
|
||||
// handle plugin call
|
||||
go sender.MayPluginNotify(e.genNoticeBytes(event), e.notifyConfigCache.GetNotifyScript())
|
||||
go sender.MayPluginNotify(e.genNoticeBytes(event), e.notifyConfigCache.GetNotifyScript(), e.Astats)
|
||||
}
|
||||
|
||||
type Notice struct {
|
||||
|
||||
@@ -18,11 +18,12 @@ func LogEvent(event *models.AlertCurEvent, location string, err ...error) {
|
||||
}
|
||||
|
||||
logger.Infof(
|
||||
"event(%s %s) %s: rule_id=%d cluster:%s %v%s@%d %s",
|
||||
"event(%s %s) %s: rule_id=%d sub_id:%d cluster:%s %v%s@%d %s",
|
||||
event.Hash,
|
||||
status,
|
||||
location,
|
||||
event.RuleId,
|
||||
event.SubRuleId,
|
||||
event.Cluster,
|
||||
event.TagsJSON,
|
||||
event.TriggerValue,
|
||||
|
||||
@@ -3,6 +3,7 @@ package eval
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
@@ -12,6 +13,8 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/tdengine"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
@@ -23,13 +26,15 @@ type Scheduler struct {
|
||||
|
||||
aconf aconf.Alert
|
||||
|
||||
alertRuleCache *memsto.AlertRuleCacheType
|
||||
targetCache *memsto.TargetCacheType
|
||||
busiGroupCache *memsto.BusiGroupCacheType
|
||||
alertMuteCache *memsto.AlertMuteCacheType
|
||||
datasourceCache *memsto.DatasourceCacheType
|
||||
alertRuleCache *memsto.AlertRuleCacheType
|
||||
targetCache *memsto.TargetCacheType
|
||||
targetsOfAlertRuleCache *memsto.TargetsOfAlertRuleCacheType
|
||||
busiGroupCache *memsto.BusiGroupCacheType
|
||||
alertMuteCache *memsto.AlertMuteCacheType
|
||||
datasourceCache *memsto.DatasourceCacheType
|
||||
|
||||
promClients *prom.PromClientMap
|
||||
promClients *prom.PromClientMap
|
||||
tdengineClients *tdengine.TdengineClientMap
|
||||
|
||||
naming *naming.Naming
|
||||
|
||||
@@ -37,23 +42,26 @@ type Scheduler struct {
|
||||
stats *astats.Stats
|
||||
}
|
||||
|
||||
func NewScheduler(aconf aconf.Alert, externalProcessors *process.ExternalProcessorsType, arc *memsto.AlertRuleCacheType, targetCache *memsto.TargetCacheType,
|
||||
busiGroupCache *memsto.BusiGroupCacheType, alertMuteCache *memsto.AlertMuteCacheType, datasourceCache *memsto.DatasourceCacheType, promClients *prom.PromClientMap, naming *naming.Naming,
|
||||
ctx *ctx.Context, stats *astats.Stats) *Scheduler {
|
||||
func NewScheduler(aconf aconf.Alert, externalProcessors *process.ExternalProcessorsType, arc *memsto.AlertRuleCacheType,
|
||||
targetCache *memsto.TargetCacheType, toarc *memsto.TargetsOfAlertRuleCacheType,
|
||||
busiGroupCache *memsto.BusiGroupCacheType, alertMuteCache *memsto.AlertMuteCacheType, datasourceCache *memsto.DatasourceCacheType,
|
||||
promClients *prom.PromClientMap, tdengineClients *tdengine.TdengineClientMap, naming *naming.Naming, ctx *ctx.Context, stats *astats.Stats) *Scheduler {
|
||||
scheduler := &Scheduler{
|
||||
aconf: aconf,
|
||||
alertRules: make(map[string]*AlertRuleWorker),
|
||||
|
||||
ExternalProcessors: externalProcessors,
|
||||
|
||||
alertRuleCache: arc,
|
||||
targetCache: targetCache,
|
||||
busiGroupCache: busiGroupCache,
|
||||
alertMuteCache: alertMuteCache,
|
||||
datasourceCache: datasourceCache,
|
||||
alertRuleCache: arc,
|
||||
targetCache: targetCache,
|
||||
targetsOfAlertRuleCache: toarc,
|
||||
busiGroupCache: busiGroupCache,
|
||||
alertMuteCache: alertMuteCache,
|
||||
datasourceCache: datasourceCache,
|
||||
|
||||
promClients: promClients,
|
||||
naming: naming,
|
||||
promClients: promClients,
|
||||
tdengineClients: tdengineClients,
|
||||
naming: naming,
|
||||
|
||||
ctx: ctx,
|
||||
stats: stats,
|
||||
@@ -85,10 +93,13 @@ func (s *Scheduler) syncAlertRules() {
|
||||
if rule == nil {
|
||||
continue
|
||||
}
|
||||
if rule.IsPrometheusRule() {
|
||||
|
||||
ruleType := rule.GetRuleType()
|
||||
if rule.IsPrometheusRule() || rule.IsLokiRule() || rule.IsTdengineRule() {
|
||||
datasourceIds := s.promClients.Hit(rule.DatasourceIdsJson)
|
||||
datasourceIds = append(datasourceIds, s.tdengineClients.Hit(rule.DatasourceIdsJson)...)
|
||||
for _, dsId := range datasourceIds {
|
||||
if !naming.DatasourceHashRing.IsHit(dsId, fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
|
||||
if !naming.DatasourceHashRing.IsHit(strconv.FormatInt(dsId, 10), fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
|
||||
continue
|
||||
}
|
||||
ds := s.datasourceCache.GetById(dsId)
|
||||
@@ -97,22 +108,27 @@ func (s *Scheduler) syncAlertRules() {
|
||||
continue
|
||||
}
|
||||
|
||||
if ds.PluginType != ruleType {
|
||||
logger.Debugf("datasource %d category is %s not %s", dsId, ds.PluginType, ruleType)
|
||||
continue
|
||||
}
|
||||
|
||||
if ds.Status != "enabled" {
|
||||
logger.Debugf("datasource %d status is %s", dsId, ds.Status)
|
||||
continue
|
||||
}
|
||||
processor := process.NewProcessor(rule, dsId, s.alertRuleCache, s.targetCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.promClients, s.ctx, s.stats)
|
||||
processor := process.NewProcessor(s.aconf.Heartbeat.EngineName, rule, dsId, s.alertRuleCache, s.targetCache, s.targetsOfAlertRuleCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.ctx, s.stats)
|
||||
|
||||
alertRule := NewAlertRuleWorker(rule, dsId, processor, s.promClients, s.ctx)
|
||||
alertRule := NewAlertRuleWorker(rule, dsId, processor, s.promClients, s.tdengineClients, s.ctx)
|
||||
alertRuleWorkers[alertRule.Hash()] = alertRule
|
||||
}
|
||||
} else if rule.IsHostRule() && s.ctx.IsCenter {
|
||||
} else if rule.IsHostRule() {
|
||||
// all host rule will be processed by center instance
|
||||
if !naming.DatasourceHashRing.IsHit(naming.HostDatasource, fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
|
||||
if !naming.DatasourceHashRing.IsHit(s.aconf.Heartbeat.EngineName, strconv.FormatInt(rule.Id, 10), s.aconf.Heartbeat.Endpoint) {
|
||||
continue
|
||||
}
|
||||
processor := process.NewProcessor(rule, 0, s.alertRuleCache, s.targetCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.promClients, s.ctx, s.stats)
|
||||
alertRule := NewAlertRuleWorker(rule, 0, processor, s.promClients, s.ctx)
|
||||
processor := process.NewProcessor(s.aconf.Heartbeat.EngineName, rule, 0, s.alertRuleCache, s.targetCache, s.targetsOfAlertRuleCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.ctx, s.stats)
|
||||
alertRule := NewAlertRuleWorker(rule, 0, processor, s.promClients, s.tdengineClients, s.ctx)
|
||||
alertRuleWorkers[alertRule.Hash()] = alertRule
|
||||
} else {
|
||||
// 如果 rule 不是通过 prometheus engine 来告警的,则创建为 externalRule
|
||||
@@ -128,7 +144,7 @@ func (s *Scheduler) syncAlertRules() {
|
||||
logger.Debugf("datasource %d status is %s", dsId, ds.Status)
|
||||
continue
|
||||
}
|
||||
processor := process.NewProcessor(rule, dsId, s.alertRuleCache, s.targetCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.promClients, s.ctx, s.stats)
|
||||
processor := process.NewProcessor(s.aconf.Heartbeat.EngineName, rule, dsId, s.alertRuleCache, s.targetCache, s.targetsOfAlertRuleCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.ctx, s.stats)
|
||||
externalRuleWorkers[processor.Key()] = processor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,8 +13,11 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/alert/process"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/hash"
|
||||
"github.com/ccfos/nightingale/v6/pkg/parser"
|
||||
promsdk "github.com/ccfos/nightingale/v6/pkg/prom"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/tdengine"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
"github.com/toolkits/pkg/str"
|
||||
@@ -28,19 +33,29 @@ type AlertRuleWorker struct {
|
||||
|
||||
processor *process.Processor
|
||||
|
||||
promClients *prom.PromClientMap
|
||||
ctx *ctx.Context
|
||||
promClients *prom.PromClientMap
|
||||
tdengineClients *tdengine.TdengineClientMap
|
||||
ctx *ctx.Context
|
||||
}
|
||||
|
||||
func NewAlertRuleWorker(rule *models.AlertRule, datasourceId int64, processor *process.Processor, promClients *prom.PromClientMap, ctx *ctx.Context) *AlertRuleWorker {
|
||||
const (
|
||||
GET_RULE_CONFIG = "get_rule_config"
|
||||
GET_PROCESSOR = "get_processor"
|
||||
CHECK_QUERY = "check_query_config"
|
||||
GET_CLIENT = "get_client"
|
||||
QUERY_DATA = "query_data"
|
||||
)
|
||||
|
||||
func NewAlertRuleWorker(rule *models.AlertRule, datasourceId int64, processor *process.Processor, promClients *prom.PromClientMap, tdengineClients *tdengine.TdengineClientMap, ctx *ctx.Context) *AlertRuleWorker {
|
||||
arw := &AlertRuleWorker{
|
||||
datasourceId: datasourceId,
|
||||
quit: make(chan struct{}),
|
||||
rule: rule,
|
||||
processor: processor,
|
||||
|
||||
promClients: promClients,
|
||||
ctx: ctx,
|
||||
promClients: promClients,
|
||||
tdengineClients: tdengineClients,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
return arw
|
||||
@@ -87,17 +102,23 @@ func (arw *AlertRuleWorker) Start() {
|
||||
func (arw *AlertRuleWorker) Eval() {
|
||||
cachedRule := arw.rule
|
||||
if cachedRule == nil {
|
||||
//logger.Errorf("rule_eval:%s rule not found", arw.Key())
|
||||
// logger.Errorf("rule_eval:%s rule not found", arw.Key())
|
||||
return
|
||||
}
|
||||
arw.processor.Stats.CounterRuleEval.WithLabelValues().Inc()
|
||||
|
||||
typ := cachedRule.GetRuleType()
|
||||
var lst []common.AnomalyPoint
|
||||
var anomalyPoints []common.AnomalyPoint
|
||||
var recoverPoints []common.AnomalyPoint
|
||||
switch typ {
|
||||
case models.PROMETHEUS:
|
||||
lst = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
|
||||
anomalyPoints = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
|
||||
case models.HOST:
|
||||
lst = arw.GetHostAnomalyPoint(cachedRule.RuleConfig)
|
||||
anomalyPoints = arw.GetHostAnomalyPoint(cachedRule.RuleConfig)
|
||||
case models.TDENGINE:
|
||||
anomalyPoints, recoverPoints = arw.GetTdengineAnomalyPoint(cachedRule, arw.processor.DatasourceId())
|
||||
case models.LOKI:
|
||||
anomalyPoints = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -107,7 +128,38 @@ func (arw *AlertRuleWorker) Eval() {
|
||||
return
|
||||
}
|
||||
|
||||
arw.processor.Handle(lst, "inner", arw.inhibit)
|
||||
if arw.inhibit {
|
||||
pointsMap := make(map[string]common.AnomalyPoint)
|
||||
for _, point := range recoverPoints {
|
||||
// 对于恢复的事件,合并处理
|
||||
tagHash := process.TagHash(point)
|
||||
|
||||
p, exists := pointsMap[tagHash]
|
||||
if !exists {
|
||||
pointsMap[tagHash] = point
|
||||
continue
|
||||
}
|
||||
|
||||
if p.Severity > point.Severity {
|
||||
hash := process.Hash(cachedRule.Id, arw.processor.DatasourceId(), p)
|
||||
arw.processor.DeleteProcessEvent(hash)
|
||||
|
||||
pointsMap[tagHash] = point
|
||||
}
|
||||
}
|
||||
|
||||
for _, point := range pointsMap {
|
||||
str := fmt.Sprintf("%v", point.Value)
|
||||
arw.processor.RecoverSingle(process.Hash(cachedRule.Id, arw.processor.DatasourceId(), point), point.Timestamp, &str)
|
||||
}
|
||||
} else {
|
||||
for _, point := range recoverPoints {
|
||||
str := fmt.Sprintf("%v", point.Value)
|
||||
arw.processor.RecoverSingle(process.Hash(cachedRule.Id, arw.processor.DatasourceId(), point), point.Timestamp, &str)
|
||||
}
|
||||
}
|
||||
|
||||
arw.processor.Handle(anomalyPoints, "inner", arw.inhibit)
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) Stop() {
|
||||
@@ -122,11 +174,13 @@ func (arw *AlertRuleWorker) GetPromAnomalyPoint(ruleConfig string) []common.Anom
|
||||
var rule *models.PromRuleConfig
|
||||
if err := json.Unmarshal([]byte(ruleConfig), &rule); err != nil {
|
||||
logger.Errorf("rule_eval:%s rule_config:%s, error:%v", arw.Key(), ruleConfig, err)
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return lst
|
||||
}
|
||||
|
||||
if rule == nil {
|
||||
logger.Errorf("rule_eval:%s rule_config:%s, error:rule is nil", arw.Key(), ruleConfig)
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return lst
|
||||
}
|
||||
|
||||
@@ -138,26 +192,33 @@ func (arw *AlertRuleWorker) GetPromAnomalyPoint(ruleConfig string) []common.Anom
|
||||
|
||||
promql := strings.TrimSpace(query.PromQl)
|
||||
if promql == "" {
|
||||
logger.Errorf("rule_eval:%s promql is blank", arw.Key())
|
||||
logger.Warningf("rule_eval:%s promql is blank", arw.Key())
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), CHECK_QUERY).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
if arw.promClients.IsNil(arw.datasourceId) {
|
||||
logger.Errorf("rule_eval:%s error reader client is nil", arw.Key())
|
||||
logger.Warningf("rule_eval:%s error reader client is nil", arw.Key())
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_CLIENT).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
readerClient := arw.promClients.GetCli(arw.datasourceId)
|
||||
|
||||
var warnings promsdk.Warnings
|
||||
arw.processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
value, warnings, err := readerClient.Query(context.Background(), promql, time.Now())
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s promql:%s, error:%v", arw.Key(), promql, err)
|
||||
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
if len(warnings) > 0 {
|
||||
logger.Errorf("rule_eval:%s promql:%s, warnings:%v", arw.Key(), promql, warnings)
|
||||
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -172,6 +233,61 @@ func (arw *AlertRuleWorker) GetPromAnomalyPoint(ruleConfig string) []common.Anom
|
||||
return lst
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) GetTdengineAnomalyPoint(rule *models.AlertRule, dsId int64) ([]common.AnomalyPoint, []common.AnomalyPoint) {
|
||||
// 获取查询和规则判断条件
|
||||
points := []common.AnomalyPoint{}
|
||||
recoverPoints := []common.AnomalyPoint{}
|
||||
ruleConfig := strings.TrimSpace(rule.RuleConfig)
|
||||
if ruleConfig == "" {
|
||||
logger.Warningf("rule_eval:%d promql is blank", rule.Id)
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return points, recoverPoints
|
||||
}
|
||||
|
||||
var ruleQuery models.RuleQuery
|
||||
err := json.Unmarshal([]byte(ruleConfig), &ruleQuery)
|
||||
if err != nil {
|
||||
logger.Warningf("rule_eval:%d promql parse error:%s", rule.Id, err.Error())
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId())).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return points, recoverPoints
|
||||
}
|
||||
|
||||
arw.inhibit = ruleQuery.Inhibit
|
||||
if len(ruleQuery.Queries) > 0 {
|
||||
seriesStore := make(map[uint64]models.DataResp)
|
||||
seriesTagIndex := make(map[uint64][]uint64)
|
||||
|
||||
for _, query := range ruleQuery.Queries {
|
||||
arw.processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
cli := arw.tdengineClients.GetCli(dsId)
|
||||
if cli == nil {
|
||||
logger.Warningf("rule_eval:%d tdengine client is nil", rule.Id)
|
||||
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_CLIENT).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
series, err := cli.Query(query)
|
||||
arw.processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
if err != nil {
|
||||
logger.Warningf("rule_eval rid:%d query data error: %v", rule.Id, err)
|
||||
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
// 此条日志很重要,是告警判断的现场值
|
||||
logger.Debugf("rule_eval rid:%d req:%+v resp:%+v", rule.Id, query, series)
|
||||
MakeSeriesMap(series, seriesTagIndex, seriesStore)
|
||||
}
|
||||
|
||||
points, recoverPoints = GetAnomalyPoint(rule.Id, ruleQuery, seriesTagIndex, seriesStore)
|
||||
}
|
||||
|
||||
return points, recoverPoints
|
||||
}
|
||||
|
||||
func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.AnomalyPoint {
|
||||
var lst []common.AnomalyPoint
|
||||
var severity int
|
||||
@@ -179,11 +295,13 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
|
||||
var rule *models.HostRuleConfig
|
||||
if err := json.Unmarshal([]byte(ruleConfig), &rule); err != nil {
|
||||
logger.Errorf("rule_eval:%s rule_config:%s, error:%v", arw.Key(), ruleConfig, err)
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return lst
|
||||
}
|
||||
|
||||
if rule == nil {
|
||||
logger.Errorf("rule_eval:%s rule_config:%s, error:rule is nil", arw.Key(), ruleConfig)
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
|
||||
return lst
|
||||
}
|
||||
|
||||
@@ -194,15 +312,42 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
|
||||
arw.severity = trigger.Severity
|
||||
}
|
||||
|
||||
query := models.GetHostsQuery(rule.Queries)
|
||||
switch trigger.Type {
|
||||
case "target_miss":
|
||||
t := now - int64(trigger.Duration)
|
||||
targets, err := models.MissTargetGetsByFilter(arw.ctx, query, t)
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s query:%v, error:%v", arw.Key(), query, err)
|
||||
|
||||
var idents, engineIdents, missEngineIdents []string
|
||||
var exists bool
|
||||
if arw.ctx.IsCenter {
|
||||
// 如果是中心节点, 将不再上报数据的主机 engineName 为空的机器,也加入到 targets 中
|
||||
missEngineIdents, exists = arw.processor.TargetsOfAlertRuleCache.Get("", arw.rule.Id)
|
||||
if !exists {
|
||||
logger.Debugf("rule_eval:%s targets not found engineName:%s", arw.Key(), arw.processor.EngineName)
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
}
|
||||
}
|
||||
idents = append(idents, missEngineIdents...)
|
||||
|
||||
engineIdents, exists = arw.processor.TargetsOfAlertRuleCache.Get(arw.processor.EngineName, arw.rule.Id)
|
||||
if !exists {
|
||||
logger.Warningf("rule_eval:%s targets not found engineName:%s", arw.Key(), arw.processor.EngineName)
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
}
|
||||
idents = append(idents, engineIdents...)
|
||||
|
||||
if len(idents) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var missTargets []string
|
||||
targetUpdateTimeMap := arw.processor.TargetCache.GetHostUpdateTime(idents)
|
||||
for ident, updateTime := range targetUpdateTimeMap {
|
||||
if updateTime < t {
|
||||
missTargets = append(missTargets, ident)
|
||||
}
|
||||
}
|
||||
|
||||
targets := arw.processor.TargetCache.Gets(missTargets)
|
||||
for _, target := range targets {
|
||||
m := make(map[string]string)
|
||||
target.FillTagsMap()
|
||||
@@ -219,20 +364,43 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
|
||||
lst = append(lst, common.NewAnomalyPoint(trigger.Type, m, now, float64(now-target.UpdateAt), trigger.Severity))
|
||||
}
|
||||
case "offset":
|
||||
targets, err := models.TargetGetsByFilter(arw.ctx, query, 0, 0)
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s query:%v, error:%v", arw.Key(), query, err)
|
||||
idents, exists := arw.processor.TargetsOfAlertRuleCache.Get(arw.processor.EngineName, arw.rule.Id)
|
||||
if !exists {
|
||||
logger.Warningf("rule_eval:%s targets not found", arw.Key())
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
continue
|
||||
}
|
||||
var targetMap = make(map[string]*models.Target)
|
||||
|
||||
targets := arw.processor.TargetCache.Gets(idents)
|
||||
targetMap := make(map[string]*models.Target)
|
||||
for _, target := range targets {
|
||||
targetMap[target.Ident] = target
|
||||
}
|
||||
|
||||
hostOffsetMap := arw.processor.TargetCache.GetOffsetHost(targets, now, int64(trigger.Duration))
|
||||
for host, offset := range hostOffsetMap {
|
||||
offsetIdents := make(map[string]int64)
|
||||
targetsMeta := arw.processor.TargetCache.GetHostMetas(targets)
|
||||
for ident, meta := range targetsMeta {
|
||||
if meta.CpuNum <= 0 {
|
||||
// means this target is not collect by categraf, do not check offset
|
||||
continue
|
||||
}
|
||||
|
||||
if target, exists := targetMap[ident]; exists {
|
||||
if now-target.UpdateAt > 120 {
|
||||
// means this target is not a active host, do not check offset
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
offset := meta.Offset
|
||||
if math.Abs(float64(offset)) > float64(trigger.Duration) {
|
||||
offsetIdents[ident] = offset
|
||||
}
|
||||
}
|
||||
|
||||
for host, offset := range offsetIdents {
|
||||
m := make(map[string]string)
|
||||
target, exists := targetMap[host]
|
||||
target, exists := arw.processor.TargetCache.Get(host)
|
||||
if exists {
|
||||
target.FillTagsMap()
|
||||
for k, v := range target.TagsMap {
|
||||
@@ -250,18 +418,22 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
|
||||
}
|
||||
case "pct_target_miss":
|
||||
t := now - int64(trigger.Duration)
|
||||
count, err := models.MissTargetCountByFilter(arw.ctx, query, t)
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s query:%v, error:%v", arw.Key(), query, err)
|
||||
idents, exists := arw.processor.TargetsOfAlertRuleCache.Get(arw.processor.EngineName, arw.rule.Id)
|
||||
if !exists {
|
||||
logger.Warningf("rule_eval:%s targets not found", arw.Key())
|
||||
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
total, err := models.TargetCountByFilter(arw.ctx, query)
|
||||
if err != nil {
|
||||
logger.Errorf("rule_eval:%s query:%v, error:%v", arw.Key(), query, err)
|
||||
continue
|
||||
var missTargets []string
|
||||
targetUpdateTimeMap := arw.processor.TargetCache.GetHostUpdateTime(idents)
|
||||
for ident, updateTime := range targetUpdateTimeMap {
|
||||
if updateTime < t {
|
||||
missTargets = append(missTargets, ident)
|
||||
}
|
||||
}
|
||||
pct := float64(count) / float64(total) * 100
|
||||
|
||||
pct := float64(len(missTargets)) / float64(len(idents)) * 100
|
||||
if pct >= float64(trigger.Percent) {
|
||||
lst = append(lst, common.NewAnomalyPoint(trigger.Type, nil, now, pct, trigger.Severity))
|
||||
}
|
||||
@@ -269,3 +441,92 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
|
||||
}
|
||||
return lst
|
||||
}
|
||||
|
||||
func GetAnomalyPoint(ruleId int64, ruleQuery models.RuleQuery, seriesTagIndex map[uint64][]uint64, seriesStore map[uint64]models.DataResp) ([]common.AnomalyPoint, []common.AnomalyPoint) {
|
||||
points := []common.AnomalyPoint{}
|
||||
recoverPoints := []common.AnomalyPoint{}
|
||||
|
||||
for _, trigger := range ruleQuery.Triggers {
|
||||
for _, seriesHash := range seriesTagIndex {
|
||||
sort.Slice(seriesHash, func(i, j int) bool {
|
||||
return seriesHash[i] < seriesHash[j]
|
||||
})
|
||||
|
||||
m := make(map[string]float64)
|
||||
var ts int64
|
||||
var sample models.DataResp
|
||||
var value float64
|
||||
for _, serieHash := range seriesHash {
|
||||
series, exists := seriesStore[serieHash]
|
||||
if !exists {
|
||||
logger.Warningf("rule_eval rid:%d series:%+v not found", ruleId, series)
|
||||
continue
|
||||
}
|
||||
t, v, exists := series.Last()
|
||||
if !exists {
|
||||
logger.Warningf("rule_eval rid:%d series:%+v value not found", ruleId, series)
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.Contains(trigger.Exp, "$"+series.Ref) {
|
||||
// 表达式中不包含该变量
|
||||
continue
|
||||
}
|
||||
|
||||
m["$"+series.Ref] = v
|
||||
m["$"+series.Ref+"."+series.MetricName()] = v
|
||||
ts = int64(t)
|
||||
sample = series
|
||||
value = v
|
||||
}
|
||||
isTriggered := parser.Calc(trigger.Exp, m)
|
||||
// 此条日志很重要,是告警判断的现场值
|
||||
logger.Infof("rule_eval rid:%d trigger:%+v exp:%s res:%v m:%v", ruleId, trigger, trigger.Exp, isTriggered, m)
|
||||
|
||||
var values string
|
||||
for k, v := range m {
|
||||
if !strings.Contains(k, ".") {
|
||||
continue
|
||||
}
|
||||
values += fmt.Sprintf("%s:%v ", k, v)
|
||||
}
|
||||
|
||||
point := common.AnomalyPoint{
|
||||
Key: sample.MetricName(),
|
||||
Labels: sample.Metric,
|
||||
Timestamp: int64(ts),
|
||||
Value: value,
|
||||
Values: values,
|
||||
Severity: trigger.Severity,
|
||||
Triggered: isTriggered,
|
||||
Query: fmt.Sprintf("query:%+v trigger:%+v", ruleQuery.Queries, trigger),
|
||||
}
|
||||
|
||||
if sample.Query != "" {
|
||||
point.Query = sample.Query
|
||||
}
|
||||
|
||||
if isTriggered {
|
||||
points = append(points, point)
|
||||
} else {
|
||||
recoverPoints = append(recoverPoints, point)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return points, recoverPoints
|
||||
}
|
||||
|
||||
func MakeSeriesMap(series []models.DataResp, seriesTagIndex map[uint64][]uint64, seriesStore map[uint64]models.DataResp) {
|
||||
for i := 0; i < len(series); i++ {
|
||||
serieHash := hash.GetHash(series[i].Metric, series[i].Ref)
|
||||
tagHash := hash.GetTagHash(series[i].Metric)
|
||||
seriesStore[serieHash] = series[i]
|
||||
|
||||
// 将曲线按照相同的 tag 分组
|
||||
if _, exists := seriesTagIndex[tagHash]; !exists {
|
||||
seriesTagIndex[tagHash] = make([]uint64, 0)
|
||||
}
|
||||
seriesTagIndex[tagHash] = append(seriesTagIndex[tagHash], serieHash)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func matchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int
|
||||
}
|
||||
|
||||
// 如果不是全局的,判断 匹配的 datasource id
|
||||
if !(len(mute.DatasourceIdsJson) != 0 && mute.DatasourceIdsJson[0] == 0) && event.DatasourceId != 0 {
|
||||
if len(mute.DatasourceIdsJson) != 0 && mute.DatasourceIdsJson[0] != 0 && event.DatasourceId != 0 {
|
||||
idm := make(map[int64]struct{}, len(mute.DatasourceIdsJson))
|
||||
for i := 0; i < len(mute.DatasourceIdsJson); i++ {
|
||||
idm[mute.DatasourceIdsJson[i]] = struct{}{}
|
||||
@@ -172,7 +172,7 @@ func matchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int
|
||||
|
||||
for i := 0; i < len(mute.PeriodicMutesJson); i++ {
|
||||
if strings.Contains(mute.PeriodicMutesJson[i].EnableDaysOfWeek, triggerWeek) {
|
||||
if mute.PeriodicMutesJson[i].EnableStime == mute.PeriodicMutesJson[i].EnableEtime {
|
||||
if mute.PeriodicMutesJson[i].EnableStime == mute.PeriodicMutesJson[i].EnableEtime || (mute.PeriodicMutesJson[i].EnableStime == "00:00" && mute.PeriodicMutesJson[i].EnableEtime == "23:59") {
|
||||
matchTime = true
|
||||
break
|
||||
} else if mute.PeriodicMutesJson[i].EnableStime < mute.PeriodicMutesJson[i].EnableEtime {
|
||||
@@ -209,5 +209,9 @@ func matchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int
|
||||
return false
|
||||
}
|
||||
|
||||
if mute.ITags == nil || len(mute.ITags) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return common.MatchTags(event.TagsMap, mute.ITags)
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ const NodeReplicas = 500
|
||||
|
||||
type DatasourceHashRingType struct {
|
||||
sync.RWMutex
|
||||
Rings map[int64]*consistent.Consistent
|
||||
Rings map[string]*consistent.Consistent
|
||||
}
|
||||
|
||||
// for alert_rule sharding
|
||||
var HostDatasource int64 = 99999999
|
||||
var DatasourceHashRing = DatasourceHashRingType{Rings: make(map[int64]*consistent.Consistent)}
|
||||
var DatasourceHashRing = DatasourceHashRingType{Rings: make(map[string]*consistent.Consistent)}
|
||||
|
||||
func NewConsistentHashRing(replicas int32, nodes []string) *consistent.Consistent {
|
||||
ret := consistent.New()
|
||||
@@ -28,7 +28,7 @@ func NewConsistentHashRing(replicas int32, nodes []string) *consistent.Consisten
|
||||
return ret
|
||||
}
|
||||
|
||||
func RebuildConsistentHashRing(datasourceId int64, nodes []string) {
|
||||
func RebuildConsistentHashRing(datasourceId string, nodes []string) {
|
||||
r := consistent.New()
|
||||
r.NumberOfReplicas = NodeReplicas
|
||||
for i := 0; i < len(nodes); i++ {
|
||||
@@ -36,10 +36,10 @@ func RebuildConsistentHashRing(datasourceId int64, nodes []string) {
|
||||
}
|
||||
|
||||
DatasourceHashRing.Set(datasourceId, r)
|
||||
logger.Infof("hash ring %d rebuild %+v", datasourceId, r.Members())
|
||||
logger.Infof("hash ring %s rebuild %+v", datasourceId, r.Members())
|
||||
}
|
||||
|
||||
func (chr *DatasourceHashRingType) GetNode(datasourceId int64, pk string) (string, error) {
|
||||
func (chr *DatasourceHashRingType) GetNode(datasourceId string, pk string) (string, error) {
|
||||
chr.Lock()
|
||||
defer chr.Unlock()
|
||||
_, exists := chr.Rings[datasourceId]
|
||||
@@ -50,28 +50,34 @@ func (chr *DatasourceHashRingType) GetNode(datasourceId int64, pk string) (strin
|
||||
return chr.Rings[datasourceId].Get(pk)
|
||||
}
|
||||
|
||||
func (chr *DatasourceHashRingType) IsHit(datasourceId int64, pk string, currentNode string) bool {
|
||||
func (chr *DatasourceHashRingType) IsHit(datasourceId string, pk string, currentNode string) bool {
|
||||
node, err := chr.GetNode(datasourceId, pk)
|
||||
if err != nil {
|
||||
if !errors.Is(err, consistent.ErrEmptyCircle) {
|
||||
logger.Debugf("rule id:%s is not work, datasource id:%d failed to get node from hashring:%v", pk, datasourceId, err)
|
||||
logger.Errorf("rule id:%s is not work, datasource id:%s failed to get node from hashring:%v", pk, datasourceId, err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return node == currentNode
|
||||
}
|
||||
|
||||
func (chr *DatasourceHashRingType) Set(datasourceId int64, r *consistent.Consistent) {
|
||||
func (chr *DatasourceHashRingType) Set(datasourceId string, r *consistent.Consistent) {
|
||||
chr.Lock()
|
||||
defer chr.Unlock()
|
||||
chr.Rings[datasourceId] = r
|
||||
}
|
||||
|
||||
func (chr *DatasourceHashRingType) Clear() {
|
||||
func (chr *DatasourceHashRingType) Del(datasourceId string) {
|
||||
chr.Lock()
|
||||
defer chr.Unlock()
|
||||
delete(chr.Rings, datasourceId)
|
||||
}
|
||||
|
||||
func (chr *DatasourceHashRingType) Clear(engineName string) {
|
||||
chr.Lock()
|
||||
defer chr.Unlock()
|
||||
for id := range chr.Rings {
|
||||
if id == HostDatasource {
|
||||
if id == engineName {
|
||||
continue
|
||||
}
|
||||
delete(chr.Rings, id)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
@@ -17,12 +18,14 @@ import (
|
||||
type Naming struct {
|
||||
ctx *ctx.Context
|
||||
heartbeatConfig aconf.HeartbeatConfig
|
||||
astats *astats.Stats
|
||||
}
|
||||
|
||||
func NewNaming(ctx *ctx.Context, heartbeat aconf.HeartbeatConfig) *Naming {
|
||||
func NewNaming(ctx *ctx.Context, heartbeat aconf.HeartbeatConfig, alertStats *astats.Stats) *Naming {
|
||||
naming := &Naming{
|
||||
ctx: ctx,
|
||||
heartbeatConfig: heartbeat,
|
||||
astats: alertStats,
|
||||
}
|
||||
naming.Heartbeats()
|
||||
return naming
|
||||
@@ -30,9 +33,11 @@ func NewNaming(ctx *ctx.Context, heartbeat aconf.HeartbeatConfig) *Naming {
|
||||
|
||||
// local servers
|
||||
var localss map[int64]string
|
||||
var localHostServers map[string]string
|
||||
|
||||
func (n *Naming) Heartbeats() error {
|
||||
localss = make(map[int64]string)
|
||||
localHostServers = make(map[string]string)
|
||||
if err := n.heartbeat(); err != nil {
|
||||
fmt.Println("failed to heartbeat:", err)
|
||||
return err
|
||||
@@ -74,42 +79,67 @@ func (n *Naming) loopHeartbeat() {
|
||||
|
||||
func (n *Naming) heartbeat() error {
|
||||
var datasourceIds []int64
|
||||
var myDatasourceIds []int64
|
||||
var err error
|
||||
|
||||
// 在页面上维护实例和集群的对应关系
|
||||
datasourceIds, err = models.GetDatasourceIdsByEngineName(n.ctx, n.heartbeatConfig.EngineName)
|
||||
if err != nil {
|
||||
return err
|
||||
// 如果是中心机房的 leader 机器,沟通全量的数据源的 hash ring,给智能告警模块使用
|
||||
|
||||
if n.IamLeader() {
|
||||
datasources, err := models.GetDatasources(n.ctx)
|
||||
if err != nil {
|
||||
logger.Warningf("get datasources err:%v", err)
|
||||
return err
|
||||
}
|
||||
for i := 0; i < len(datasources); i++ {
|
||||
datasourceIds = append(datasourceIds, datasources[i].Id)
|
||||
}
|
||||
|
||||
myDatasourceIds, err = models.GetDatasourceIdsByEngineName(n.ctx, n.heartbeatConfig.EngineName)
|
||||
if err != nil {
|
||||
logger.Warningf("get datasource ids by engine name err:%v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 在页面上维护实例和集群的对应关系
|
||||
datasourceIds, err = models.GetDatasourceIdsByEngineName(n.ctx, n.heartbeatConfig.EngineName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
myDatasourceIds = datasourceIds
|
||||
}
|
||||
|
||||
if len(datasourceIds) == 0 {
|
||||
// 上报心跳
|
||||
if len(myDatasourceIds) == 0 {
|
||||
err := models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, 0)
|
||||
if err != nil {
|
||||
logger.Warningf("heartbeat with cluster %s err:%v", "", err)
|
||||
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(datasourceIds); i++ {
|
||||
err := models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, datasourceIds[i])
|
||||
for i := 0; i < len(myDatasourceIds); i++ {
|
||||
err := models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, myDatasourceIds[i])
|
||||
if err != nil {
|
||||
logger.Warningf("heartbeat with cluster %d err:%v", datasourceIds[i], err)
|
||||
logger.Warningf("heartbeat with cluster %d err:%v", myDatasourceIds[i], err)
|
||||
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(datasourceIds) == 0 {
|
||||
DatasourceHashRing.Clear()
|
||||
DatasourceHashRing.Clear(n.heartbeatConfig.EngineName)
|
||||
for dsId := range localss {
|
||||
if dsId == HostDatasource {
|
||||
continue
|
||||
}
|
||||
delete(localss, dsId)
|
||||
}
|
||||
}
|
||||
|
||||
newDatasource := make(map[int64]struct{})
|
||||
for i := 0; i < len(datasourceIds); i++ {
|
||||
newDatasource[datasourceIds[i]] = struct{}{}
|
||||
servers, err := n.ActiveServers(datasourceIds[i])
|
||||
if err != nil {
|
||||
logger.Warningf("hearbeat %d get active server err:%v", datasourceIds[i], err)
|
||||
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -121,36 +151,42 @@ func (n *Naming) heartbeat() error {
|
||||
continue
|
||||
}
|
||||
|
||||
RebuildConsistentHashRing(datasourceIds[i], servers)
|
||||
RebuildConsistentHashRing(fmt.Sprintf("%d", datasourceIds[i]), servers)
|
||||
localss[datasourceIds[i]] = newss
|
||||
}
|
||||
|
||||
if n.ctx.IsCenter {
|
||||
// 如果是中心节点,还需要处理 host 类型的告警规则,host 类型告警规则,和数据源无关,想复用下数据源的 hash ring,想用一个虚假的数据源 id 来处理
|
||||
// if is center node, we need to handle host type alerting rules, host type alerting rules are not related to datasource, we want to reuse the hash ring of datasource, we want to use a fake datasource id to handle it
|
||||
err := models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, HostDatasource)
|
||||
if err != nil {
|
||||
logger.Warningf("heartbeat with cluster %s err:%v", "", err)
|
||||
for dsId := range localss {
|
||||
if _, exists := newDatasource[dsId]; !exists {
|
||||
delete(localss, dsId)
|
||||
DatasourceHashRing.Del(fmt.Sprintf("%d", dsId))
|
||||
}
|
||||
|
||||
servers, err := n.ActiveServers(HostDatasource)
|
||||
if err != nil {
|
||||
logger.Warningf("hearbeat %d get active server err:%v", HostDatasource, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Strings(servers)
|
||||
newss := strings.Join(servers, " ")
|
||||
|
||||
oldss, exists := localss[HostDatasource]
|
||||
if exists && oldss == newss {
|
||||
return nil
|
||||
}
|
||||
|
||||
RebuildConsistentHashRing(HostDatasource, servers)
|
||||
localss[HostDatasource] = newss
|
||||
}
|
||||
|
||||
// host 告警使用的是 hash ring
|
||||
err = models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, HostDatasource)
|
||||
if err != nil {
|
||||
logger.Warningf("heartbeat with cluster %s err:%v", "", err)
|
||||
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
|
||||
}
|
||||
|
||||
servers, err := n.ActiveServersByEngineName()
|
||||
if err != nil {
|
||||
logger.Warningf("hearbeat %d get active server err:%v", HostDatasource, err)
|
||||
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Strings(servers)
|
||||
newss := strings.Join(servers, " ")
|
||||
|
||||
oldss, exists := localHostServers[n.heartbeatConfig.EngineName]
|
||||
if exists && oldss == newss {
|
||||
return nil
|
||||
}
|
||||
|
||||
RebuildConsistentHashRing(n.heartbeatConfig.EngineName, servers)
|
||||
localHostServers[n.heartbeatConfig.EngineName] = newss
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
28
alert/naming/leader.go
Normal file
28
alert/naming/leader.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package naming
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
func (n *Naming) IamLeader() bool {
|
||||
if !n.ctx.IsCenter {
|
||||
return false
|
||||
}
|
||||
|
||||
servers, err := n.ActiveServersByEngineName()
|
||||
if err != nil {
|
||||
logger.Errorf("failed to get active servers: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(servers) == 0 {
|
||||
logger.Errorf("active servers empty")
|
||||
return false
|
||||
}
|
||||
|
||||
sort.Strings(servers)
|
||||
|
||||
return n.heartbeatConfig.Endpoint == servers[0]
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/tplx"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
@@ -49,6 +48,7 @@ type HandleEventFunc func(event *models.AlertCurEvent)
|
||||
|
||||
type Processor struct {
|
||||
datasourceId int64
|
||||
EngineName string
|
||||
|
||||
rule *models.AlertRule
|
||||
fires *AlertCurEventMap
|
||||
@@ -61,15 +61,15 @@ type Processor struct {
|
||||
targetNote string
|
||||
groupName string
|
||||
|
||||
atertRuleCache *memsto.AlertRuleCacheType
|
||||
TargetCache *memsto.TargetCacheType
|
||||
BusiGroupCache *memsto.BusiGroupCacheType
|
||||
alertMuteCache *memsto.AlertMuteCacheType
|
||||
datasourceCache *memsto.DatasourceCacheType
|
||||
alertRuleCache *memsto.AlertRuleCacheType
|
||||
TargetCache *memsto.TargetCacheType
|
||||
TargetsOfAlertRuleCache *memsto.TargetsOfAlertRuleCacheType
|
||||
BusiGroupCache *memsto.BusiGroupCacheType
|
||||
alertMuteCache *memsto.AlertMuteCacheType
|
||||
datasourceCache *memsto.DatasourceCacheType
|
||||
|
||||
promClients *prom.PromClientMap
|
||||
ctx *ctx.Context
|
||||
stats *astats.Stats
|
||||
ctx *ctx.Context
|
||||
Stats *astats.Stats
|
||||
|
||||
HandleFireEventHook HandleEventFunc
|
||||
HandleRecoverEventHook HandleEventFunc
|
||||
@@ -93,23 +93,25 @@ func (p *Processor) Hash() string {
|
||||
))
|
||||
}
|
||||
|
||||
func NewProcessor(rule *models.AlertRule, datasourceId int64, atertRuleCache *memsto.AlertRuleCacheType, targetCache *memsto.TargetCacheType,
|
||||
busiGroupCache *memsto.BusiGroupCacheType, alertMuteCache *memsto.AlertMuteCacheType, datasourceCache *memsto.DatasourceCacheType, promClients *prom.PromClientMap, ctx *ctx.Context,
|
||||
func NewProcessor(engineName string, rule *models.AlertRule, datasourceId int64, alertRuleCache *memsto.AlertRuleCacheType,
|
||||
targetCache *memsto.TargetCacheType, targetsOfAlertRuleCache *memsto.TargetsOfAlertRuleCacheType,
|
||||
busiGroupCache *memsto.BusiGroupCacheType, alertMuteCache *memsto.AlertMuteCacheType, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context,
|
||||
stats *astats.Stats) *Processor {
|
||||
|
||||
p := &Processor{
|
||||
EngineName: engineName,
|
||||
datasourceId: datasourceId,
|
||||
rule: rule,
|
||||
|
||||
TargetCache: targetCache,
|
||||
BusiGroupCache: busiGroupCache,
|
||||
alertMuteCache: alertMuteCache,
|
||||
atertRuleCache: atertRuleCache,
|
||||
datasourceCache: datasourceCache,
|
||||
TargetCache: targetCache,
|
||||
TargetsOfAlertRuleCache: targetsOfAlertRuleCache,
|
||||
BusiGroupCache: busiGroupCache,
|
||||
alertMuteCache: alertMuteCache,
|
||||
alertRuleCache: alertRuleCache,
|
||||
datasourceCache: datasourceCache,
|
||||
|
||||
promClients: promClients,
|
||||
ctx: ctx,
|
||||
stats: stats,
|
||||
ctx: ctx,
|
||||
Stats: stats,
|
||||
|
||||
HandleFireEventHook: func(event *models.AlertCurEvent) {},
|
||||
HandleRecoverEventHook: func(event *models.AlertCurEvent) {},
|
||||
@@ -125,11 +127,13 @@ func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inh
|
||||
// 这些信息的修改是不会引起worker restart的,但是确实会影响告警处理逻辑
|
||||
// 所以,这里直接从memsto.AlertRuleCache中获取并覆盖
|
||||
p.inhibit = inhibit
|
||||
cachedRule := p.atertRuleCache.Get(p.rule.Id)
|
||||
cachedRule := p.alertRuleCache.Get(p.rule.Id)
|
||||
if cachedRule == nil {
|
||||
logger.Errorf("rule not found %+v", anomalyPoints)
|
||||
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "handle_event").Inc()
|
||||
return
|
||||
}
|
||||
|
||||
p.rule = cachedRule
|
||||
now := time.Now().Unix()
|
||||
alertingKeys := map[string]struct{}{}
|
||||
@@ -142,11 +146,14 @@ func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inh
|
||||
hash := event.Hash
|
||||
alertingKeys[hash] = struct{}{}
|
||||
if mute.IsMuted(cachedRule, event, p.TargetCache, p.alertMuteCache) {
|
||||
p.Stats.CounterMuteTotal.WithLabelValues(event.GroupName).Inc()
|
||||
logger.Debugf("rule_eval:%s event:%v is muted", p.Key(), event)
|
||||
continue
|
||||
}
|
||||
|
||||
if p.EventMuteHook(event) {
|
||||
p.Stats.CounterMuteTotal.WithLabelValues(event.GroupName).Inc()
|
||||
logger.Debugf("rule_eval:%s event:%v is muted by hook", p.Key(), event)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -158,7 +165,7 @@ func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inh
|
||||
p.handleEvent(events)
|
||||
}
|
||||
|
||||
p.HandleRecover(alertingKeys, now)
|
||||
p.HandleRecover(alertingKeys, now, inhibit)
|
||||
}
|
||||
|
||||
func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, now int64) *models.AlertCurEvent {
|
||||
@@ -172,6 +179,12 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
|
||||
}
|
||||
|
||||
event := p.rule.GenerateNewEvent(p.ctx)
|
||||
|
||||
bg := p.BusiGroupCache.GetByBusiGroupId(p.rule.GroupId)
|
||||
if bg != nil {
|
||||
event.GroupName = bg.Name
|
||||
}
|
||||
|
||||
event.TriggerTime = anomalyPoint.Timestamp
|
||||
event.TagsMap = p.tagsMap
|
||||
event.DatasourceId = p.datasourceId
|
||||
@@ -180,8 +193,8 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
|
||||
event.TargetIdent = p.target
|
||||
event.TargetNote = p.targetNote
|
||||
event.TriggerValue = anomalyPoint.ReadableValue()
|
||||
event.TriggerValues = anomalyPoint.Values
|
||||
event.TagsJSON = p.tagsArr
|
||||
event.GroupName = p.groupName
|
||||
event.Tags = strings.Join(p.tagsArr, ",,")
|
||||
event.IsRecovered = false
|
||||
event.Callbacks = p.rule.Callbacks
|
||||
@@ -194,6 +207,11 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
|
||||
event.ExtraConfig = p.rule.ExtraConfigJSON
|
||||
event.PromQl = anomalyPoint.Query
|
||||
|
||||
if event.TriggerValues != "" && strings.Count(event.TriggerValues, "$") > 1 {
|
||||
// TriggerValues 有多个变量,将多个变量都放到 TriggerValue 中
|
||||
event.TriggerValue = event.TriggerValues
|
||||
}
|
||||
|
||||
if from == "inner" {
|
||||
event.LastEvalTime = now
|
||||
} else {
|
||||
@@ -202,7 +220,7 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
|
||||
return event
|
||||
}
|
||||
|
||||
func (p *Processor) HandleRecover(alertingKeys map[string]struct{}, now int64) {
|
||||
func (p *Processor) HandleRecover(alertingKeys map[string]struct{}, now int64, inhibit bool) {
|
||||
for _, hash := range p.pendings.Keys() {
|
||||
if _, has := alertingKeys[hash]; has {
|
||||
continue
|
||||
@@ -210,19 +228,63 @@ func (p *Processor) HandleRecover(alertingKeys map[string]struct{}, now int64) {
|
||||
p.pendings.Delete(hash)
|
||||
}
|
||||
|
||||
hashArr := make([]string, 0, len(alertingKeys))
|
||||
for hash := range p.fires.GetAll() {
|
||||
if _, has := alertingKeys[hash]; has {
|
||||
continue
|
||||
}
|
||||
p.RecoverSingle(hash, now, nil)
|
||||
|
||||
hashArr = append(hashArr, hash)
|
||||
}
|
||||
p.HandleRecoverEvent(hashArr, now, inhibit)
|
||||
|
||||
}
|
||||
|
||||
func (p *Processor) RecoverSingle(hash string, now int64, value *string) {
|
||||
func (p *Processor) HandleRecoverEvent(hashArr []string, now int64, inhibit bool) {
|
||||
cachedRule := p.rule
|
||||
if cachedRule == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !inhibit {
|
||||
for _, hash := range hashArr {
|
||||
p.RecoverSingle(hash, now, nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
eventMap := make(map[string]models.AlertCurEvent)
|
||||
for _, hash := range hashArr {
|
||||
event, has := p.fires.Get(hash)
|
||||
if !has {
|
||||
continue
|
||||
}
|
||||
|
||||
e, exists := eventMap[event.Tags]
|
||||
if !exists {
|
||||
eventMap[event.Tags] = *event
|
||||
continue
|
||||
}
|
||||
|
||||
if e.Severity > event.Severity {
|
||||
// hash 对应的恢复事件的被抑制了,把之前的事件删除
|
||||
p.fires.Delete(e.Hash)
|
||||
p.pendings.Delete(e.Hash)
|
||||
eventMap[event.Tags] = *event
|
||||
}
|
||||
}
|
||||
|
||||
for _, event := range eventMap {
|
||||
p.RecoverSingle(event.Hash, now, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) RecoverSingle(hash string, now int64, value *string, values ...string) {
|
||||
cachedRule := p.rule
|
||||
if cachedRule == nil {
|
||||
return
|
||||
}
|
||||
|
||||
event, has := p.fires.Get(hash)
|
||||
if !has {
|
||||
return
|
||||
@@ -234,6 +296,9 @@ func (p *Processor) RecoverSingle(hash string, now int64, value *string) {
|
||||
}
|
||||
if value != nil {
|
||||
event.TriggerValue = *value
|
||||
if len(values) > 0 {
|
||||
event.TriggerValues = values[0]
|
||||
}
|
||||
}
|
||||
|
||||
// 没查到触发阈值的vector,姑且就认为这个vector的值恢复了
|
||||
@@ -350,10 +415,10 @@ func (p *Processor) pushEventToQueue(e *models.AlertCurEvent) {
|
||||
p.fires.Set(e.Hash, e)
|
||||
}
|
||||
|
||||
p.stats.CounterAlertsTotal.WithLabelValues(fmt.Sprintf("%d", e.DatasourceId)).Inc()
|
||||
dispatch.LogEvent(e, "push_queue")
|
||||
if !queue.EventQueue.PushFront(e) {
|
||||
logger.Warningf("event_push_queue: queue is full, event:%+v", e)
|
||||
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "push_event_queue").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,12 +428,21 @@ func (p *Processor) RecoverAlertCurEventFromDb() {
|
||||
curEvents, err := models.AlertCurEventGetByRuleIdAndDsId(p.ctx, p.rule.Id, p.datasourceId)
|
||||
if err != nil {
|
||||
logger.Errorf("recover event from db for rule:%s failed, err:%s", p.Key(), err)
|
||||
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "get_recover_event").Inc()
|
||||
p.fires = NewAlertCurEventMap(nil)
|
||||
return
|
||||
}
|
||||
|
||||
fireMap := make(map[string]*models.AlertCurEvent)
|
||||
for _, event := range curEvents {
|
||||
if event.Cate == models.HOST {
|
||||
target, exists := p.TargetCache.Get(event.TargetIdent)
|
||||
if exists && target.EngineName != p.EngineName && !(p.ctx.IsCenter && target.EngineName == "") {
|
||||
// 如果是 host rule,且 target 的 engineName 不是当前的 engineName 或者是中心机房 target EngineName 为空,就跳过
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
event.DB2Mem()
|
||||
fireMap[event.Hash] = event
|
||||
}
|
||||
@@ -428,7 +502,13 @@ func (p *Processor) mayHandleIdent() {
|
||||
if target, exists := p.TargetCache.Get(ident); exists {
|
||||
p.target = target.Ident
|
||||
p.targetNote = target.Note
|
||||
} else {
|
||||
p.target = ident
|
||||
p.targetNote = ""
|
||||
}
|
||||
} else {
|
||||
p.target = ""
|
||||
p.targetNote = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +520,11 @@ func (p *Processor) mayHandleGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) DeleteProcessEvent(hash string) {
|
||||
p.fires.Delete(hash)
|
||||
p.pendings.Delete(hash)
|
||||
}
|
||||
|
||||
func labelMapToArr(m map[string]string) []string {
|
||||
numLabels := len(m)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/writer"
|
||||
@@ -18,18 +19,18 @@ type RecordRuleContext struct {
|
||||
datasourceId int64
|
||||
quit chan struct{}
|
||||
|
||||
rule *models.RecordingRule
|
||||
// writers *writer.WritersType
|
||||
rule *models.RecordingRule
|
||||
promClients *prom.PromClientMap
|
||||
stats *astats.Stats
|
||||
}
|
||||
|
||||
func NewRecordRuleContext(rule *models.RecordingRule, datasourceId int64, promClients *prom.PromClientMap, writers *writer.WritersType) *RecordRuleContext {
|
||||
func NewRecordRuleContext(rule *models.RecordingRule, datasourceId int64, promClients *prom.PromClientMap, writers *writer.WritersType, stats *astats.Stats) *RecordRuleContext {
|
||||
return &RecordRuleContext{
|
||||
datasourceId: datasourceId,
|
||||
quit: make(chan struct{}),
|
||||
rule: rule,
|
||||
promClients: promClients,
|
||||
//writers: writers,
|
||||
stats: stats,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +71,7 @@ func (rrc *RecordRuleContext) Start() {
|
||||
}
|
||||
|
||||
func (rrc *RecordRuleContext) Eval() {
|
||||
rrc.stats.CounterRecordEval.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
|
||||
promql := strings.TrimSpace(rrc.rule.PromQl)
|
||||
if promql == "" {
|
||||
logger.Errorf("eval:%s promql is blank", rrc.Key())
|
||||
@@ -78,23 +80,30 @@ func (rrc *RecordRuleContext) Eval() {
|
||||
|
||||
if rrc.promClients.IsNil(rrc.datasourceId) {
|
||||
logger.Errorf("eval:%s reader client is nil", rrc.Key())
|
||||
rrc.stats.CounterRecordEvalErrorTotal.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
|
||||
return
|
||||
}
|
||||
|
||||
value, warnings, err := rrc.promClients.GetCli(rrc.datasourceId).Query(context.Background(), promql, time.Now())
|
||||
if err != nil {
|
||||
logger.Errorf("eval:%s promql:%s, error:%v", rrc.Key(), promql, err)
|
||||
rrc.stats.CounterRecordEvalErrorTotal.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
|
||||
return
|
||||
}
|
||||
|
||||
if len(warnings) > 0 {
|
||||
logger.Errorf("eval:%s promql:%s, warnings:%v", rrc.Key(), promql, warnings)
|
||||
rrc.stats.CounterRecordEvalErrorTotal.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
|
||||
return
|
||||
}
|
||||
|
||||
ts := ConvertToTimeSeries(value, rrc.rule)
|
||||
if len(ts) != 0 {
|
||||
rrc.promClients.GetWriterCli(rrc.datasourceId).Write(ts)
|
||||
err := rrc.promClients.GetWriterCli(rrc.datasourceId).Write(ts)
|
||||
if err != nil {
|
||||
logger.Errorf("eval:%s promql:%s, error:%v", rrc.Key(), promql, err)
|
||||
rrc.stats.CounterRecordEvalErrorTotal.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const (
|
||||
LabelName = "__name__"
|
||||
)
|
||||
|
||||
func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*prompb.TimeSeries) {
|
||||
func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []prompb.TimeSeries) {
|
||||
switch value.Type() {
|
||||
case model.ValVector:
|
||||
items, ok := value.(model.Vector)
|
||||
@@ -31,7 +31,7 @@ func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*
|
||||
s.Timestamp = time.Unix(item.Timestamp.Unix(), 0).UnixNano() / 1e6
|
||||
s.Value = float64(item.Value)
|
||||
l := labelsToLabelsProto(item.Metric, rule)
|
||||
lst = append(lst, &prompb.TimeSeries{
|
||||
lst = append(lst, prompb.TimeSeries{
|
||||
Labels: l,
|
||||
Samples: []prompb.Sample{s},
|
||||
})
|
||||
@@ -63,7 +63,7 @@ func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*
|
||||
Value: float64(v.Value),
|
||||
})
|
||||
}
|
||||
lst = append(lst, &prompb.TimeSeries{
|
||||
lst = append(lst, prompb.TimeSeries{
|
||||
Labels: l,
|
||||
Samples: slst,
|
||||
})
|
||||
@@ -78,7 +78,7 @@ func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*
|
||||
return
|
||||
}
|
||||
|
||||
lst = append(lst, &prompb.TimeSeries{
|
||||
lst = append(lst, prompb.TimeSeries{
|
||||
Labels: nil,
|
||||
Samples: []prompb.Sample{{Value: float64(item.Value), Timestamp: time.Unix(item.Timestamp.Unix(), 0).UnixNano() / 1e6}},
|
||||
})
|
||||
@@ -89,9 +89,9 @@ func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*
|
||||
return
|
||||
}
|
||||
|
||||
func labelsToLabelsProto(labels model.Metric, rule *models.RecordingRule) (result []*prompb.Label) {
|
||||
func labelsToLabelsProto(labels model.Metric, rule *models.RecordingRule) (result []prompb.Label) {
|
||||
//name
|
||||
nameLs := &prompb.Label{
|
||||
nameLs := prompb.Label{
|
||||
Name: LabelName,
|
||||
Value: rule.Name,
|
||||
}
|
||||
@@ -101,7 +101,7 @@ func labelsToLabelsProto(labels model.Metric, rule *models.RecordingRule) (resul
|
||||
continue
|
||||
}
|
||||
if model.LabelNameRE.MatchString(string(k)) {
|
||||
result = append(result, &prompb.Label{
|
||||
result = append(result, prompb.Label{
|
||||
Name: string(k),
|
||||
Value: string(v),
|
||||
})
|
||||
@@ -111,7 +111,7 @@ func labelsToLabelsProto(labels model.Metric, rule *models.RecordingRule) (resul
|
||||
for _, v := range rule.AppendTagsJSON {
|
||||
index := strings.Index(v, "=")
|
||||
if model.LabelNameRE.MatchString(v[:index]) {
|
||||
result = append(result, &prompb.Label{
|
||||
result = append(result, prompb.Label{
|
||||
Name: v[:index],
|
||||
Value: v[index+1:],
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ package record
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
@@ -68,11 +69,11 @@ func (s *Scheduler) syncRecordRules() {
|
||||
|
||||
datasourceIds := s.promClients.Hit(rule.DatasourceIdsJson)
|
||||
for _, dsId := range datasourceIds {
|
||||
if !naming.DatasourceHashRing.IsHit(dsId, fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
|
||||
if !naming.DatasourceHashRing.IsHit(strconv.FormatInt(dsId, 10), fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
|
||||
continue
|
||||
}
|
||||
|
||||
recordRule := NewRecordRuleContext(rule, dsId, s.promClients, s.writers)
|
||||
recordRule := NewRecordRuleContext(rule, dsId, s.promClients, s.writers, s.stats)
|
||||
recordRules[recordRule.Hash()] = recordRule
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -72,8 +73,6 @@ func (rt *Router) pushEventToQueue(c *gin.Context) {
|
||||
event.NotifyChannels = strings.Join(event.NotifyChannelsJSON, " ")
|
||||
event.NotifyGroups = strings.Join(event.NotifyGroupsJSON, " ")
|
||||
|
||||
rt.AlertStats.CounterAlertsTotal.WithLabelValues(event.Cluster).Inc()
|
||||
|
||||
dispatch.LogEvent(event, "http_push_queue")
|
||||
if !queue.EventQueue.PushFront(event) {
|
||||
msg := fmt.Sprintf("event:%+v push_queue err: queue is full", event)
|
||||
@@ -87,7 +86,8 @@ func (rt *Router) eventPersist(c *gin.Context) {
|
||||
var event *models.AlertCurEvent
|
||||
ginx.BindJSON(c, &event)
|
||||
event.FE2DB()
|
||||
ginx.NewRender(c).Message(models.EventPersist(rt.Ctx, event))
|
||||
err := models.EventPersist(rt.Ctx, event)
|
||||
ginx.NewRender(c).Data(event.Id, err)
|
||||
}
|
||||
|
||||
type eventForm struct {
|
||||
@@ -103,7 +103,7 @@ func (rt *Router) makeEvent(c *gin.Context) {
|
||||
ginx.BindJSON(c, &events)
|
||||
//now := time.Now().Unix()
|
||||
for i := 0; i < len(events); i++ {
|
||||
node, err := naming.DatasourceHashRing.GetNode(events[i].DatasourceId, fmt.Sprintf("%d", events[i].RuleId))
|
||||
node, err := naming.DatasourceHashRing.GetNode(strconv.FormatInt(events[i].DatasourceId, 10), fmt.Sprintf("%d", events[i].RuleId))
|
||||
if err != nil {
|
||||
logger.Warningf("event:%+v get node err:%v", events[i], err)
|
||||
ginx.Bomb(200, "event node not exists")
|
||||
|
||||
@@ -2,21 +2,24 @@ package sender
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ibex"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
|
||||
imodels "github.com/flashcatcloud/ibex/src/models"
|
||||
"github.com/flashcatcloud/ibex/src/storage"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType, ibexConf aconf.Ibex) {
|
||||
func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType,
|
||||
taskTplCache *memsto.TaskTplCache, stats *astats.Stats) {
|
||||
for _, url := range urls {
|
||||
if url == "" {
|
||||
continue
|
||||
@@ -24,7 +27,7 @@ func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent,
|
||||
|
||||
if strings.HasPrefix(url, "${ibex}") {
|
||||
if !event.IsRecovered {
|
||||
handleIbex(ctx, url, event, targetCache, userCache, ibexConf)
|
||||
handleIbex(ctx, url, event, targetCache, userCache, taskTplCache)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -33,36 +36,29 @@ func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent,
|
||||
url = "http://" + url
|
||||
}
|
||||
|
||||
stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
|
||||
resp, code, err := poster.PostJSON(url, 5*time.Second, event, 3)
|
||||
if err != nil {
|
||||
logger.Errorf("event_callback_fail(rule_id=%d url=%s), resp: %s, err: %v, code: %d", event.RuleId, url, string(resp), err, code)
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues("rule_callback").Inc()
|
||||
} else {
|
||||
logger.Infof("event_callback_succ(rule_id=%d url=%s), resp: %s, code: %d", event.RuleId, url, string(resp), code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TaskForm struct {
|
||||
Title string `json:"title"`
|
||||
Account string `json:"account"`
|
||||
Batch int `json:"batch"`
|
||||
Tolerance int `json:"tolerance"`
|
||||
Timeout int `json:"timeout"`
|
||||
Pause string `json:"pause"`
|
||||
Script string `json:"script"`
|
||||
Args string `json:"args"`
|
||||
Stdin string `json:"stdin"`
|
||||
Action string `json:"action"`
|
||||
Creator string `json:"creator"`
|
||||
Hosts []string `json:"hosts"`
|
||||
}
|
||||
|
||||
type TaskCreateReply struct {
|
||||
Err string `json:"err"`
|
||||
Dat int64 `json:"dat"` // task.id
|
||||
}
|
||||
|
||||
func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType, ibexConf aconf.Ibex) {
|
||||
func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType,
|
||||
taskTplCache *memsto.TaskTplCache) {
|
||||
if imodels.DB() == nil {
|
||||
logger.Warning("event_callback_ibex: db is nil")
|
||||
return
|
||||
}
|
||||
|
||||
arr := strings.Split(url, "/")
|
||||
|
||||
var idstr string
|
||||
@@ -92,12 +88,7 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
|
||||
return
|
||||
}
|
||||
|
||||
tpl, err := models.TaskTplGet(ctx, "id = ?", id)
|
||||
if err != nil {
|
||||
logger.Errorf("event_callback_ibex: failed to get tpl: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tpl := taskTplCache.Get(id)
|
||||
if tpl == nil {
|
||||
logger.Errorf("event_callback_ibex: no such tpl(%d)", id)
|
||||
return
|
||||
@@ -105,7 +96,7 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
|
||||
|
||||
// check perm
|
||||
// tpl.GroupId - host - account 三元组校验权限
|
||||
can, err := canDoIbex(ctx, tpl.UpdateBy, tpl, host, targetCache, userCache)
|
||||
can, err := canDoIbex(tpl.UpdateBy, tpl, host, targetCache, userCache)
|
||||
if err != nil {
|
||||
logger.Errorf("event_callback_ibex: check perm fail: %v", err)
|
||||
return
|
||||
@@ -141,61 +132,43 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
|
||||
}
|
||||
|
||||
// call ibex
|
||||
in := TaskForm{
|
||||
Title: tpl.Title + " FH: " + host,
|
||||
Account: tpl.Account,
|
||||
Batch: tpl.Batch,
|
||||
Tolerance: tpl.Tolerance,
|
||||
Timeout: tpl.Timeout,
|
||||
Pause: tpl.Pause,
|
||||
Script: tpl.Script,
|
||||
Args: tpl.Args,
|
||||
Stdin: string(tags),
|
||||
Action: "start",
|
||||
Creator: tpl.UpdateBy,
|
||||
Hosts: []string{host},
|
||||
in := models.TaskForm{
|
||||
Title: tpl.Title + " FH: " + host,
|
||||
Account: tpl.Account,
|
||||
Batch: tpl.Batch,
|
||||
Tolerance: tpl.Tolerance,
|
||||
Timeout: tpl.Timeout,
|
||||
Pause: tpl.Pause,
|
||||
Script: tpl.Script,
|
||||
Args: tpl.Args,
|
||||
Stdin: string(tags),
|
||||
Action: "start",
|
||||
Creator: tpl.UpdateBy,
|
||||
Hosts: []string{host},
|
||||
AlertTriggered: true,
|
||||
}
|
||||
|
||||
var res TaskCreateReply
|
||||
err = ibex.New(
|
||||
ibexConf.Address,
|
||||
ibexConf.BasicAuthUser,
|
||||
ibexConf.BasicAuthPass,
|
||||
ibexConf.Timeout,
|
||||
).
|
||||
Path("/ibex/v1/tasks").
|
||||
In(in).
|
||||
Out(&res).
|
||||
POST()
|
||||
|
||||
id, err = TaskAdd(in, tpl.UpdateBy, ctx.IsCenter)
|
||||
if err != nil {
|
||||
logger.Errorf("event_callback_ibex: call ibex fail: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if res.Err != "" {
|
||||
logger.Errorf("event_callback_ibex: call ibex response error: %v", res.Err)
|
||||
return
|
||||
}
|
||||
|
||||
// write db
|
||||
record := models.TaskRecord{
|
||||
Id: res.Dat,
|
||||
EventId: event.Id,
|
||||
GroupId: tpl.GroupId,
|
||||
IbexAddress: ibexConf.Address,
|
||||
IbexAuthUser: ibexConf.BasicAuthUser,
|
||||
IbexAuthPass: ibexConf.BasicAuthPass,
|
||||
Title: in.Title,
|
||||
Account: in.Account,
|
||||
Batch: in.Batch,
|
||||
Tolerance: in.Tolerance,
|
||||
Timeout: in.Timeout,
|
||||
Pause: in.Pause,
|
||||
Script: in.Script,
|
||||
Args: in.Args,
|
||||
CreateAt: time.Now().Unix(),
|
||||
CreateBy: in.Creator,
|
||||
Id: id,
|
||||
EventId: event.Id,
|
||||
GroupId: tpl.GroupId,
|
||||
Title: in.Title,
|
||||
Account: in.Account,
|
||||
Batch: in.Batch,
|
||||
Tolerance: in.Tolerance,
|
||||
Timeout: in.Timeout,
|
||||
Pause: in.Pause,
|
||||
Script: in.Script,
|
||||
Args: in.Args,
|
||||
CreateAt: time.Now().Unix(),
|
||||
CreateBy: in.Creator,
|
||||
}
|
||||
|
||||
if err = record.Add(ctx); err != nil {
|
||||
@@ -203,7 +176,7 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
|
||||
}
|
||||
}
|
||||
|
||||
func canDoIbex(ctx *ctx.Context, username string, tpl *models.TaskTpl, host string, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType) (bool, error) {
|
||||
func canDoIbex(username string, tpl *models.TaskTpl, host string, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType) (bool, error) {
|
||||
user := userCache.GetByUsername(username)
|
||||
if user != nil && user.IsAdmin() {
|
||||
return true, nil
|
||||
@@ -216,3 +189,88 @@ func canDoIbex(ctx *ctx.Context, username string, tpl *models.TaskTpl, host stri
|
||||
|
||||
return target.GroupId == tpl.GroupId, nil
|
||||
}
|
||||
|
||||
func TaskAdd(f models.TaskForm, authUser string, isCenter bool) (int64, error) {
|
||||
hosts := cleanHosts(f.Hosts)
|
||||
if len(hosts) == 0 {
|
||||
return 0, fmt.Errorf("arg(hosts) empty")
|
||||
}
|
||||
|
||||
taskMeta := &imodels.TaskMeta{
|
||||
Title: f.Title,
|
||||
Account: f.Account,
|
||||
Batch: f.Batch,
|
||||
Tolerance: f.Tolerance,
|
||||
Timeout: f.Timeout,
|
||||
Pause: f.Pause,
|
||||
Script: f.Script,
|
||||
Args: f.Args,
|
||||
Stdin: f.Stdin,
|
||||
Creator: f.Creator,
|
||||
}
|
||||
|
||||
err := taskMeta.CleanFields()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
taskMeta.HandleFH(hosts[0])
|
||||
|
||||
// 任务类型分为"告警规则触发"和"n9e center用户下发"两种;
|
||||
// 边缘机房"告警规则触发"的任务不需要规划,并且它可能是失联的,无法使用db资源,所以放入redis缓存中,直接下发给agentd执行
|
||||
if !isCenter && f.AlertTriggered {
|
||||
if err := taskMeta.Create(); err != nil {
|
||||
// 当网络不连通时,生成唯一的id,防止边缘机房中不同任务的id相同;
|
||||
// 方法是,redis自增id去防止同一个机房的不同n9e edge生成的id相同;
|
||||
// 但没法防止不同边缘机房生成同样的id,所以,生成id的数据不会上报存入数据库,只用于闭环执行。
|
||||
taskMeta.Id, err = storage.IdGet()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
taskHost := imodels.TaskHost{
|
||||
Id: taskMeta.Id,
|
||||
Host: hosts[0],
|
||||
Status: "running",
|
||||
}
|
||||
if err = taskHost.Create(); err != nil {
|
||||
logger.Warningf("task_add_fail: authUser=%s title=%s err=%s", authUser, taskMeta.Title, err.Error())
|
||||
}
|
||||
|
||||
// 缓存任务元信息和待下发的任务
|
||||
err = taskMeta.Cache(hosts[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
} else {
|
||||
// 如果是中心机房,还是保持之前的逻辑
|
||||
err = taskMeta.Save(hosts, f.Action)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
logger.Infof("task_add_succ: authUser=%s title=%s", authUser, taskMeta.Title)
|
||||
return taskMeta.Id, nil
|
||||
}
|
||||
|
||||
func cleanHosts(formHosts []string) []string {
|
||||
cnt := len(formHosts)
|
||||
arr := make([]string, 0, cnt)
|
||||
for i := 0; i < cnt; i++ {
|
||||
item := strings.TrimSpace(formHosts[i])
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(item, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
arr = append(arr, item)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
|
||||
@@ -40,7 +41,7 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
|
||||
if len(urls) == 0 {
|
||||
return
|
||||
}
|
||||
message := BuildTplMessage(ds.tpl, ctx.Events)
|
||||
message := BuildTplMessage(models.Dingtalk, ds.tpl, ctx.Events)
|
||||
|
||||
for _, url := range urls {
|
||||
var body dingtalk
|
||||
@@ -66,7 +67,8 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
|
||||
},
|
||||
}
|
||||
}
|
||||
ds.doSend(url, body)
|
||||
|
||||
doSend(url, body, models.Dingtalk, ctx.Stats)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +83,7 @@ func (ds *DingtalkSender) extract(users []*models.User) ([]string, []string) {
|
||||
}
|
||||
if token, has := user.ExtractToken(models.Dingtalk); has {
|
||||
url := token
|
||||
if !strings.HasPrefix(token, "https://") {
|
||||
if !strings.HasPrefix(token, "https://") && !strings.HasPrefix(token, "http://") {
|
||||
url = "https://oapi.dingtalk.com/robot/send?access_token=" + token
|
||||
}
|
||||
urls = append(urls, url)
|
||||
@@ -90,11 +92,14 @@ func (ds *DingtalkSender) extract(users []*models.User) ([]string, []string) {
|
||||
return urls, ats
|
||||
}
|
||||
|
||||
func (ds *DingtalkSender) doSend(url string, body dingtalk) {
|
||||
func doSend(url string, body interface{}, channel string, stats *astats.Stats) {
|
||||
stats.AlertNotifyTotal.WithLabelValues(channel).Inc()
|
||||
|
||||
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
|
||||
if err != nil {
|
||||
logger.Errorf("dingtalk_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
|
||||
logger.Errorf("%s_sender: result=fail url=%s code=%d error=%v req:%v response=%s", channel, url, code, err, body, string(res))
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
|
||||
} else {
|
||||
logger.Infof("dingtalk_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
|
||||
logger.Infof("%s_sender: result=succ url=%s code=%d req:%v response=%s", channel, url, code, body, string(res))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package sender
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"html/template"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
@@ -29,12 +31,14 @@ func (es *EmailSender) Send(ctx MessageContext) {
|
||||
var subject string
|
||||
|
||||
if es.subjectTpl != nil {
|
||||
subject = BuildTplMessage(es.subjectTpl, []*models.AlertCurEvent{ctx.Events[0]})
|
||||
subject = BuildTplMessage(models.Email, es.subjectTpl, []*models.AlertCurEvent{ctx.Events[0]})
|
||||
} else {
|
||||
subject = ctx.Events[0].RuleName
|
||||
}
|
||||
content := BuildTplMessage(es.contentTpl, ctx.Events)
|
||||
content := BuildTplMessage(models.Email, es.contentTpl, ctx.Events)
|
||||
es.WriteEmail(subject, content, tos)
|
||||
|
||||
ctx.Stats.AlertNotifyTotal.WithLabelValues(models.Email).Add(float64(len(tos)))
|
||||
}
|
||||
|
||||
func extract(users []*models.User) []string {
|
||||
@@ -47,7 +51,7 @@ func extract(users []*models.User) []string {
|
||||
return tos
|
||||
}
|
||||
|
||||
func (es *EmailSender) SendEmail(subject, content string, tos []string, stmp aconf.SMTPConfig) {
|
||||
func SendEmail(subject, content string, tos []string, stmp aconf.SMTPConfig) error {
|
||||
conf := stmp
|
||||
|
||||
d := gomail.NewDialer(conf.Host, conf.Port, conf.User, conf.Pass)
|
||||
@@ -64,8 +68,9 @@ func (es *EmailSender) SendEmail(subject, content string, tos []string, stmp aco
|
||||
|
||||
err := d.DialAndSend(m)
|
||||
if err != nil {
|
||||
logger.Errorf("email_sender: failed to send: %v", err)
|
||||
return errors.New("email_sender: failed to send: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *EmailSender) WriteEmail(subject, content string, tos []string) {
|
||||
@@ -81,12 +86,18 @@ func (es *EmailSender) WriteEmail(subject, content string, tos []string) {
|
||||
|
||||
func dialSmtp(d *gomail.Dialer) gomail.SendCloser {
|
||||
for {
|
||||
if s, err := d.Dial(); err != nil {
|
||||
logger.Errorf("email_sender: failed to dial smtp: %s", err)
|
||||
select {
|
||||
case <-mailQuit:
|
||||
// Note that Sendcloser is not obtained below,
|
||||
// and the outgoing signal (with configuration changes) exits the current dial
|
||||
return nil
|
||||
default:
|
||||
if s, err := d.Dial(); err != nil {
|
||||
logger.Errorf("email_sender: failed to dial smtp: %s", err)
|
||||
} else {
|
||||
return s
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
} else {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,21 +105,40 @@ func dialSmtp(d *gomail.Dialer) gomail.SendCloser {
|
||||
var mailQuit = make(chan struct{})
|
||||
|
||||
func RestartEmailSender(smtp aconf.SMTPConfig) {
|
||||
close(mailQuit)
|
||||
mailQuit = make(chan struct{})
|
||||
StartEmailSender(smtp)
|
||||
// Notify internal start exit
|
||||
mailQuit <- struct{}{}
|
||||
startEmailSender(smtp)
|
||||
}
|
||||
|
||||
func StartEmailSender(smtp aconf.SMTPConfig) {
|
||||
var smtpConfig aconf.SMTPConfig
|
||||
|
||||
func InitEmailSender(ncc *memsto.NotifyConfigCacheType) {
|
||||
mailch = make(chan *gomail.Message, 100000)
|
||||
go updateSmtp(ncc)
|
||||
smtpConfig = ncc.GetSMTP()
|
||||
startEmailSender(smtpConfig)
|
||||
}
|
||||
|
||||
func updateSmtp(ncc *memsto.NotifyConfigCacheType) {
|
||||
for {
|
||||
time.Sleep(1 * time.Minute)
|
||||
smtp := ncc.GetSMTP()
|
||||
if smtpConfig.Host != smtp.Host || smtpConfig.Batch != smtp.Batch || smtpConfig.From != smtp.From ||
|
||||
smtpConfig.Pass != smtp.Pass || smtpConfig.User != smtp.User || smtpConfig.Port != smtp.Port ||
|
||||
smtpConfig.InsecureSkipVerify != smtp.InsecureSkipVerify { //diff
|
||||
smtpConfig = smtp
|
||||
RestartEmailSender(smtp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startEmailSender(smtp aconf.SMTPConfig) {
|
||||
conf := smtp
|
||||
|
||||
if conf.Host == "" || conf.Port == 0 {
|
||||
logger.Warning("SMTP configurations invalid")
|
||||
return
|
||||
}
|
||||
logger.Infof("start email sender... %+v", conf)
|
||||
logger.Infof("start email sender... conf.Host:%+v,conf.Port:%+v", conf.Host, conf.Port)
|
||||
|
||||
d := gomail.NewDialer(conf.Host, conf.Port, conf.User, conf.Pass)
|
||||
if conf.InsecureSkipVerify {
|
||||
@@ -129,6 +159,12 @@ func StartEmailSender(smtp aconf.SMTPConfig) {
|
||||
|
||||
if !open {
|
||||
s = dialSmtp(d)
|
||||
if s == nil {
|
||||
// Indicates that the dialing failed and exited the current goroutine directly,
|
||||
// but put the Message back in the mailch
|
||||
mailch <- m
|
||||
return
|
||||
}
|
||||
open = true
|
||||
}
|
||||
if err := gomail.Send(s, m); err != nil {
|
||||
@@ -140,6 +176,12 @@ func StartEmailSender(smtp aconf.SMTPConfig) {
|
||||
}
|
||||
|
||||
s = dialSmtp(d)
|
||||
if s == nil {
|
||||
// Indicates that the dialing failed and exited the current goroutine directly,
|
||||
// but put the Message back in the mailch
|
||||
mailch <- m
|
||||
return
|
||||
}
|
||||
open = true
|
||||
|
||||
if err := gomail.Send(s, m); err != nil {
|
||||
|
||||
@@ -3,12 +3,8 @@ package sender
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type feishuContent struct {
|
||||
@@ -35,7 +31,7 @@ func (fs *FeishuSender) Send(ctx MessageContext) {
|
||||
return
|
||||
}
|
||||
urls, ats := fs.extract(ctx.Users)
|
||||
message := BuildTplMessage(fs.tpl, ctx.Events)
|
||||
message := BuildTplMessage(models.Feishu, fs.tpl, ctx.Events)
|
||||
for _, url := range urls {
|
||||
body := feishu{
|
||||
Msgtype: "text",
|
||||
@@ -49,7 +45,7 @@ func (fs *FeishuSender) Send(ctx MessageContext) {
|
||||
IsAtAll: false,
|
||||
}
|
||||
}
|
||||
fs.doSend(url, body)
|
||||
doSend(url, body, models.Feishu, ctx.Stats)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +59,7 @@ func (fs *FeishuSender) extract(users []*models.User) ([]string, []string) {
|
||||
}
|
||||
if token, has := user.ExtractToken(models.Feishu); has {
|
||||
url := token
|
||||
if !strings.HasPrefix(token, "https://") {
|
||||
if !strings.HasPrefix(token, "https://") && !strings.HasPrefix(token, "http://") {
|
||||
url = "https://open.feishu.cn/open-apis/bot/v2/hook/" + token
|
||||
}
|
||||
urls = append(urls, url)
|
||||
@@ -71,12 +67,3 @@ func (fs *FeishuSender) extract(users []*models.User) ([]string, []string) {
|
||||
}
|
||||
return urls, ats
|
||||
}
|
||||
|
||||
func (fs *FeishuSender) doSend(url string, body feishu) {
|
||||
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
|
||||
if err != nil {
|
||||
logger.Errorf("feishu_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
|
||||
} else {
|
||||
logger.Infof("feishu_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,8 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type Conf struct {
|
||||
@@ -100,7 +96,7 @@ func (fs *FeishuCardSender) Send(ctx MessageContext) {
|
||||
return
|
||||
}
|
||||
urls, _ := fs.extract(ctx.Users)
|
||||
message := BuildTplMessage(fs.tpl, ctx.Events)
|
||||
message := BuildTplMessage(models.FeishuCard, fs.tpl, ctx.Events)
|
||||
color := "red"
|
||||
lowerUnicode := strings.ToLower(message)
|
||||
if strings.Count(lowerUnicode, Recovered) > 0 && strings.Count(lowerUnicode, Triggered) > 0 {
|
||||
@@ -115,7 +111,7 @@ func (fs *FeishuCardSender) Send(ctx MessageContext) {
|
||||
body.Card.Elements[0].Text.Content = message
|
||||
body.Card.Elements[2].Elements[0].Content = SendTitle
|
||||
for _, url := range urls {
|
||||
fs.doSend(url, body)
|
||||
doSend(url, body, models.FeishuCard, ctx.Stats)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +121,7 @@ func (fs *FeishuCardSender) extract(users []*models.User) ([]string, []string) {
|
||||
for i := range users {
|
||||
if token, has := users[i].ExtractToken(models.FeishuCard); has {
|
||||
url := token
|
||||
if !strings.HasPrefix(token, "https://") {
|
||||
if !strings.HasPrefix(token, "https://") && !strings.HasPrefix(token, "http://") {
|
||||
url = "https://open.feishu.cn/open-apis/bot/v2/hook/" + strings.TrimSpace(token)
|
||||
}
|
||||
urls = append(urls, url)
|
||||
@@ -133,12 +129,3 @@ func (fs *FeishuCardSender) extract(users []*models.User) ([]string, []string) {
|
||||
}
|
||||
return urls, ats
|
||||
}
|
||||
|
||||
func (fs *FeishuCardSender) doSend(url string, body feishuCard) {
|
||||
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
|
||||
if err != nil {
|
||||
logger.Errorf("feishucard_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
|
||||
} else {
|
||||
logger.Debugf("feishucard_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@ import (
|
||||
"html/template"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
type MatterMostMessage struct {
|
||||
Text string
|
||||
Tokens []string
|
||||
Stats *astats.Stats
|
||||
}
|
||||
|
||||
type mm struct {
|
||||
@@ -36,11 +36,12 @@ func (ms *MmSender) Send(ctx MessageContext) {
|
||||
if len(urls) == 0 {
|
||||
return
|
||||
}
|
||||
message := BuildTplMessage(ms.tpl, ctx.Events)
|
||||
message := BuildTplMessage(models.Mm, ms.tpl, ctx.Events)
|
||||
|
||||
SendMM(MatterMostMessage{
|
||||
Text: message,
|
||||
Tokens: urls,
|
||||
Stats: ctx.Stats,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -87,13 +88,7 @@ func SendMM(message MatterMostMessage) {
|
||||
Username: username,
|
||||
Text: txt + message.Text,
|
||||
}
|
||||
|
||||
res, code, err := poster.PostJSON(ur, time.Second*5, body, 3)
|
||||
if err != nil {
|
||||
logger.Errorf("mm_sender: result=fail url=%s code=%d error=%v response=%s", ur, code, err, string(res))
|
||||
} else {
|
||||
logger.Infof("mm_sender: result=succ url=%s code=%d response=%s", ur, code, string(res))
|
||||
}
|
||||
doSend(ur, body, models.Mm, message.Stats)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/toolkits/pkg/file"
|
||||
@@ -13,20 +14,22 @@ import (
|
||||
"github.com/toolkits/pkg/sys"
|
||||
)
|
||||
|
||||
func MayPluginNotify(noticeBytes []byte, notifyScript models.NotifyScript) {
|
||||
func MayPluginNotify(noticeBytes []byte, notifyScript models.NotifyScript, stats *astats.Stats) {
|
||||
if len(noticeBytes) == 0 {
|
||||
return
|
||||
}
|
||||
alertingCallScript(noticeBytes, notifyScript)
|
||||
alertingCallScript(noticeBytes, notifyScript, stats)
|
||||
}
|
||||
|
||||
func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
|
||||
func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript, stats *astats.Stats) {
|
||||
// not enable or no notify.py? do nothing
|
||||
config := notifyScript
|
||||
if !config.Enable || config.Content == "" {
|
||||
return
|
||||
}
|
||||
|
||||
channel := "script"
|
||||
stats.AlertNotifyTotal.WithLabelValues(channel).Inc()
|
||||
fpath := ".notify_scriptt"
|
||||
if config.Type == 1 {
|
||||
fpath = config.Content
|
||||
@@ -36,6 +39,7 @@ func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
|
||||
oldContent, err := file.ToString(fpath)
|
||||
if err != nil {
|
||||
logger.Errorf("event_script_notify_fail: read script file err: %v", err)
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,12 +52,14 @@ func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
|
||||
_, err := file.WriteString(fpath, config.Content)
|
||||
if err != nil {
|
||||
logger.Errorf("event_script_notify_fail: write script file err: %v", err)
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Chmod(fpath, 0777)
|
||||
if err != nil {
|
||||
logger.Errorf("event_script_notify_fail: chmod script file err: %v", err)
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -83,13 +89,14 @@ func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("event_script_notify_fail: kill process %s occur error %v", fpath, err)
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("event_script_notify_fail: exec script %s occur error: %v, output: %s", fpath, err, buf.String())
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"html/template"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
)
|
||||
@@ -20,10 +21,11 @@ type (
|
||||
Users []*models.User
|
||||
Rule *models.AlertRule
|
||||
Events []*models.AlertCurEvent
|
||||
Stats *astats.Stats
|
||||
}
|
||||
)
|
||||
|
||||
func NewSender(key string, tpls map[string]*template.Template, smtp aconf.SMTPConfig) Sender {
|
||||
func NewSender(key string, tpls map[string]*template.Template, smtp ...aconf.SMTPConfig) Sender {
|
||||
switch key {
|
||||
case models.Dingtalk:
|
||||
return &DingtalkSender{tpl: tpls[models.Dingtalk]}
|
||||
@@ -34,7 +36,7 @@ func NewSender(key string, tpls map[string]*template.Template, smtp aconf.SMTPCo
|
||||
case models.FeishuCard:
|
||||
return &FeishuCardSender{tpl: tpls[models.FeishuCard]}
|
||||
case models.Email:
|
||||
return &EmailSender{subjectTpl: tpls["mailsubject"], contentTpl: tpls[models.Email], smtp: smtp}
|
||||
return &EmailSender{subjectTpl: tpls[models.EmailSubject], contentTpl: tpls[models.Email], smtp: smtp[0]}
|
||||
case models.Mm:
|
||||
return &MmSender{tpl: tpls[models.Mm]}
|
||||
case models.Telegram:
|
||||
@@ -43,20 +45,21 @@ func NewSender(key string, tpls map[string]*template.Template, smtp aconf.SMTPCo
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildMessageContext(rule *models.AlertRule, events []*models.AlertCurEvent, uids []int64, userCache *memsto.UserCacheType) MessageContext {
|
||||
func BuildMessageContext(rule *models.AlertRule, events []*models.AlertCurEvent, uids []int64, userCache *memsto.UserCacheType, stats *astats.Stats) MessageContext {
|
||||
users := userCache.GetByUserIds(uids)
|
||||
return MessageContext{
|
||||
Rule: rule,
|
||||
Events: events,
|
||||
Users: users,
|
||||
Stats: stats,
|
||||
}
|
||||
}
|
||||
|
||||
type BuildTplMessageFunc func(tpl *template.Template, events []*models.AlertCurEvent) string
|
||||
type BuildTplMessageFunc func(channel string, tpl *template.Template, events []*models.AlertCurEvent) string
|
||||
|
||||
var BuildTplMessage BuildTplMessageFunc = buildTplMessage
|
||||
|
||||
func buildTplMessage(tpl *template.Template, events []*models.AlertCurEvent) string {
|
||||
func buildTplMessage(channel string, tpl *template.Template, events []*models.AlertCurEvent) string {
|
||||
if tpl == nil {
|
||||
return "tpl for current sender not found, please check configuration"
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@ package sender
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
type TelegramMessage struct {
|
||||
Text string
|
||||
Tokens []string
|
||||
Stats *astats.Stats
|
||||
}
|
||||
|
||||
type telegram struct {
|
||||
@@ -30,11 +30,12 @@ func (ts *TelegramSender) Send(ctx MessageContext) {
|
||||
return
|
||||
}
|
||||
tokens := ts.extract(ctx.Users)
|
||||
message := BuildTplMessage(ts.tpl, ctx.Events)
|
||||
message := BuildTplMessage(models.Telegram, ts.tpl, ctx.Events)
|
||||
|
||||
SendTelegram(TelegramMessage{
|
||||
Text: message,
|
||||
Tokens: tokens,
|
||||
Stats: ctx.Stats,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,7 +56,7 @@ func SendTelegram(message TelegramMessage) {
|
||||
continue
|
||||
}
|
||||
var url string
|
||||
if strings.HasPrefix(message.Tokens[i], "https://") {
|
||||
if strings.HasPrefix(message.Tokens[i], "https://") || strings.HasPrefix(message.Tokens[i], "http://") {
|
||||
url = message.Tokens[i]
|
||||
} else {
|
||||
array := strings.Split(message.Tokens[i], "/")
|
||||
@@ -72,11 +73,6 @@ func SendTelegram(message TelegramMessage) {
|
||||
Text: message.Text,
|
||||
}
|
||||
|
||||
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
|
||||
if err != nil {
|
||||
logger.Errorf("telegram_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
|
||||
} else {
|
||||
logger.Infof("telegram_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
|
||||
}
|
||||
doSend(url, body, models.Telegram, message.Stats)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,17 @@ package sender
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent) {
|
||||
func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
|
||||
for _, conf := range webhooks {
|
||||
if conf.Url == "" || !conf.Enable {
|
||||
continue
|
||||
@@ -37,7 +38,7 @@ func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent) {
|
||||
|
||||
if len(conf.Headers) > 0 && len(conf.Headers)%2 == 0 {
|
||||
for i := 0; i < len(conf.Headers); i += 2 {
|
||||
if conf.Headers[i] == "host" {
|
||||
if conf.Headers[i] == "host" || conf.Headers[i] == "Host" {
|
||||
req.Host = conf.Headers[i+1]
|
||||
continue
|
||||
}
|
||||
@@ -50,9 +51,11 @@ func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent) {
|
||||
Timeout: time.Duration(conf.Timeout) * time.Second,
|
||||
}
|
||||
|
||||
stats.AlertNotifyTotal.WithLabelValues("webhook").Inc()
|
||||
var resp *http.Response
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
stats.AlertNotifyErrorTotal.WithLabelValues("webhook").Inc()
|
||||
logger.Errorf("event_webhook_fail, ruleId: [%d], eventId: [%d], url: [%s], error: [%s]", event.RuleId, event.Id, conf.Url, err)
|
||||
continue
|
||||
}
|
||||
@@ -60,9 +63,9 @@ func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent) {
|
||||
var body []byte
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
body, _ = ioutil.ReadAll(resp.Body)
|
||||
body, _ = io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
logger.Debugf("event_webhook_succ, url: %s, response code: %d, body: %s", conf.Url, resp.StatusCode, string(body))
|
||||
logger.Debugf("event_webhook_succ, url: %s, response code: %d, body: %s event:%+v", conf.Url, resp.StatusCode, string(body), event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,8 @@ package sender
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type wecomMarkdown struct {
|
||||
@@ -29,7 +25,7 @@ func (ws *WecomSender) Send(ctx MessageContext) {
|
||||
return
|
||||
}
|
||||
urls := ws.extract(ctx.Users)
|
||||
message := BuildTplMessage(ws.tpl, ctx.Events)
|
||||
message := BuildTplMessage(models.Wecom, ws.tpl, ctx.Events)
|
||||
for _, url := range urls {
|
||||
body := wecom{
|
||||
Msgtype: "markdown",
|
||||
@@ -37,7 +33,7 @@ func (ws *WecomSender) Send(ctx MessageContext) {
|
||||
Content: message,
|
||||
},
|
||||
}
|
||||
ws.doSend(url, body)
|
||||
doSend(url, body, models.Wecom, ctx.Stats)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +42,7 @@ func (ws *WecomSender) extract(users []*models.User) []string {
|
||||
for _, user := range users {
|
||||
if token, has := user.ExtractToken(models.Wecom); has {
|
||||
url := token
|
||||
if !strings.HasPrefix(token, "https://") {
|
||||
if !strings.HasPrefix(token, "https://") && !strings.HasPrefix(token, "http://") {
|
||||
url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + token
|
||||
}
|
||||
urls = append(urls, url)
|
||||
@@ -54,12 +50,3 @@ func (ws *WecomSender) extract(users []*models.User) []string {
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
||||
func (ws *WecomSender) doSend(url string, body wecom) {
|
||||
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
|
||||
if err != nil {
|
||||
logger.Errorf("wecom_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
|
||||
} else {
|
||||
logger.Infof("wecom_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cconf
|
||||
|
||||
import "time"
|
||||
|
||||
type Center struct {
|
||||
Plugins []Plugin
|
||||
MetricsYamlFile string
|
||||
@@ -9,6 +11,7 @@ type Center struct {
|
||||
MetricDesc MetricDescType
|
||||
AnonymousAccess AnonymousAccess
|
||||
UseFileAssets bool
|
||||
FlashDuty FlashDuty
|
||||
}
|
||||
|
||||
type Plugin struct {
|
||||
@@ -18,6 +21,12 @@ type Plugin struct {
|
||||
TypeName string `json:"plugin_type_name"`
|
||||
}
|
||||
|
||||
type FlashDuty struct {
|
||||
Api string
|
||||
Headers map[string]string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type AnonymousAccess struct {
|
||||
PromQuerier bool
|
||||
AlertDetail bool
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package cconf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/toolkits/pkg/file"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var Operations = Operation{}
|
||||
@@ -26,6 +28,14 @@ func LoadOpsYaml(configDir string, opsYamlFile string) error {
|
||||
if !file.IsExist(fp) {
|
||||
return nil
|
||||
}
|
||||
|
||||
hash, _ := file.MD5(fp)
|
||||
if hash == "2f91a9ed265cf2024e266dc1d538ee77" {
|
||||
// ops.yaml 是老的默认文件,删除
|
||||
file.Remove(fp)
|
||||
return nil
|
||||
}
|
||||
|
||||
return file.ReadYaml(fp, &Operations)
|
||||
}
|
||||
|
||||
@@ -36,3 +46,151 @@ func GetAllOps(ops []Ops) []string {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func MergeOperationConf() error {
|
||||
opsBuiltIn := Operation{}
|
||||
err := yaml.Unmarshal([]byte(builtInOps), &opsBuiltIn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse builtInOps: %s", err.Error())
|
||||
}
|
||||
configOpsMap := make(map[string]struct{})
|
||||
for _, op := range Operations.Ops {
|
||||
configOpsMap[op.Name] = struct{}{}
|
||||
}
|
||||
//If the opBu.Name is not a constant in the target (Operations.Ops), add Ops from the built-in options
|
||||
for _, opBu := range opsBuiltIn.Ops {
|
||||
if _, has := configOpsMap[opBu.Name]; !has {
|
||||
Operations.Ops = append(Operations.Ops, opBu)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
builtInOps = `
|
||||
ops:
|
||||
- name: dashboards
|
||||
cname: 仪表盘
|
||||
ops:
|
||||
- "/dashboards"
|
||||
- "/dashboards/add"
|
||||
- "/dashboards/put"
|
||||
- "/dashboards/del"
|
||||
- "/dashboards-built-in"
|
||||
|
||||
- name: alert
|
||||
cname: 告警规则
|
||||
ops:
|
||||
- "/alert-rules"
|
||||
- "/alert-rules/add"
|
||||
- "/alert-rules/put"
|
||||
- "/alert-rules/del"
|
||||
- "/alert-rules-built-in"
|
||||
- name: alert-mutes
|
||||
cname: 告警静默管理
|
||||
ops:
|
||||
- "/alert-mutes"
|
||||
- "/alert-mutes/add"
|
||||
- "/alert-mutes/put"
|
||||
- "/alert-mutes/del"
|
||||
|
||||
- name: alert-subscribes
|
||||
cname: 告警订阅管理
|
||||
ops:
|
||||
- "/alert-subscribes"
|
||||
- "/alert-subscribes/add"
|
||||
- "/alert-subscribes/put"
|
||||
- "/alert-subscribes/del"
|
||||
|
||||
- name: alert-events
|
||||
cname: 告警事件管理
|
||||
ops:
|
||||
- "/alert-cur-events"
|
||||
- "/alert-cur-events/del"
|
||||
- "/alert-his-events"
|
||||
|
||||
- name: recording-rules
|
||||
cname: 记录规则管理
|
||||
ops:
|
||||
- "/recording-rules"
|
||||
- "/recording-rules/add"
|
||||
- "/recording-rules/put"
|
||||
- "/recording-rules/del"
|
||||
|
||||
- name: metric
|
||||
cname: 时序指标
|
||||
ops:
|
||||
- "/metric/explorer"
|
||||
- "/object/explorer"
|
||||
|
||||
- name: log
|
||||
cname: 日志分析
|
||||
ops:
|
||||
- "/log/explorer"
|
||||
- "/log/index-patterns"
|
||||
|
||||
- name: targets
|
||||
cname: 基础设施
|
||||
ops:
|
||||
- "/targets"
|
||||
- "/targets/add"
|
||||
- "/targets/put"
|
||||
- "/targets/del"
|
||||
- "/targets/bind"
|
||||
|
||||
- name: job
|
||||
cname: 任务管理
|
||||
ops:
|
||||
- "/job-tpls"
|
||||
- "/job-tpls/add"
|
||||
- "/job-tpls/put"
|
||||
- "/job-tpls/del"
|
||||
- "/job-tasks"
|
||||
- "/job-tasks/add"
|
||||
- "/job-tasks/put"
|
||||
- "/ibex-settings"
|
||||
|
||||
- name: user
|
||||
cname: 用户管理
|
||||
ops:
|
||||
- "/users"
|
||||
- "/user-groups"
|
||||
- "/user-groups/add"
|
||||
- "/user-groups/put"
|
||||
- "/user-groups/del"
|
||||
|
||||
- name: permissions
|
||||
cname: 权限管理
|
||||
ops:
|
||||
- "/permissions"
|
||||
|
||||
- name: busi-groups
|
||||
cname: 业务分组管理
|
||||
ops:
|
||||
- "/busi-groups"
|
||||
- "/busi-groups/add"
|
||||
- "/busi-groups/put"
|
||||
- "/busi-groups/del"
|
||||
|
||||
- name: builtin-metrics
|
||||
cname: 指标视图
|
||||
ops:
|
||||
- "/metrics-built-in"
|
||||
- "/builtin-metrics/add"
|
||||
- "/builtin-metrics/put"
|
||||
- "/builtin-metrics/del"
|
||||
|
||||
- name: system
|
||||
cname: 系统信息
|
||||
ops:
|
||||
- "/help/variable-configs"
|
||||
- "/help/version"
|
||||
- "/help/servers"
|
||||
- "/help/source"
|
||||
- "/help/sso"
|
||||
- "/help/notification-tpls"
|
||||
- "/help/notification-settings"
|
||||
- "/help/migrate"
|
||||
- "/site-settings"
|
||||
`
|
||||
)
|
||||
|
||||
@@ -15,8 +15,14 @@ var Plugins = []Plugin{
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
Category: "logging",
|
||||
Type: "jaeger",
|
||||
TypeName: "Jaeger",
|
||||
Category: "loki",
|
||||
Type: "loki",
|
||||
TypeName: "Loki",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
Category: "timeseries",
|
||||
Type: "tdengine",
|
||||
TypeName: "TDengine",
|
||||
},
|
||||
}
|
||||
|
||||
105
center/cconf/rsa/rsa_conf.go
Normal file
105
center/cconf/rsa/rsa_conf.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package rsa
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/httpx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/secu"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/toolkits/pkg/file"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
func InitRSAConfig(ctx *ctx.Context, rsaConfig *httpx.RSAConfig) error {
|
||||
|
||||
// 1.Load RSA keys from Database
|
||||
rsaPassWord, err := models.ConfigsGet(ctx, models.RSA_PASSWORD)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "cannot query config(%s)", models.RSA_PASSWORD)
|
||||
}
|
||||
privateKeyVal, err := models.ConfigsGet(ctx, models.RSA_PRIVATE_KEY)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "cannot query config(%s)", models.RSA_PRIVATE_KEY)
|
||||
}
|
||||
publicKeyVal, err := models.ConfigsGet(ctx, models.RSA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "cannot query config(%s)", models.RSA_PUBLIC_KEY)
|
||||
}
|
||||
if rsaPassWord != "" && privateKeyVal != "" && publicKeyVal != "" {
|
||||
rsaConfig.RSAPassWord = rsaPassWord
|
||||
rsaConfig.RSAPrivateKey = []byte(privateKeyVal)
|
||||
rsaConfig.RSAPublicKey = []byte(publicKeyVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2.Read RSA configuration from file if exists
|
||||
if file.IsExist(rsaConfig.RSAPrivateKeyPath) && file.IsExist(rsaConfig.RSAPublicKeyPath) {
|
||||
//password already read from config
|
||||
rsaConfig.RSAPrivateKey, rsaConfig.RSAPublicKey, err = readConfigFile(rsaConfig)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed to read rsa config from file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 3.Generate RSA keys if not exist
|
||||
rsaConfig.RSAPassWord, rsaConfig.RSAPrivateKey, rsaConfig.RSAPublicKey, err = initRSAKeyPairs(ctx, rsaConfig.RSAPassWord)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed to generate rsa key pair")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initRSAKeyPairs(ctx *ctx.Context, rsaPassWord string) (password string, privateByte, publicByte []byte, err error) {
|
||||
|
||||
// Generate RSA keys
|
||||
|
||||
// Generate RSA password
|
||||
if rsaPassWord != "" {
|
||||
logger.Debug("Using existing RSA password")
|
||||
password = rsaPassWord
|
||||
err = models.ConfigsSet(ctx, models.RSA_PASSWORD, password)
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "failed to set config(%s)", models.RSA_PASSWORD)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
password, err = models.InitRSAPassWord(ctx)
|
||||
if err != nil {
|
||||
err = errors.WithMessage(err, "failed to generate rsa password")
|
||||
return
|
||||
}
|
||||
}
|
||||
privateByte, publicByte, err = secu.GenerateRsaKeyPair(password)
|
||||
if err != nil {
|
||||
err = errors.WithMessage(err, "failed to generate rsa key pair")
|
||||
return
|
||||
}
|
||||
// Save generated RSA keys
|
||||
err = models.ConfigsSet(ctx, models.RSA_PRIVATE_KEY, string(privateByte))
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "failed to set config(%s)", models.RSA_PRIVATE_KEY)
|
||||
return
|
||||
}
|
||||
err = models.ConfigsSet(ctx, models.RSA_PUBLIC_KEY, string(publicByte))
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "failed to set config(%s)", models.RSA_PUBLIC_KEY)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readConfigFile(rsaConfig *httpx.RSAConfig) (privateBuf, publicBuf []byte, err error) {
|
||||
publicBuf, err = os.ReadFile(rsaConfig.RSAPublicKeyPath)
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "could not read RSAPublicKeyPath %q", rsaConfig.RSAPublicKeyPath)
|
||||
return
|
||||
}
|
||||
privateBuf, err = os.ReadFile(rsaConfig.RSAPrivateKeyPath)
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "could not read RSAPrivateKeyPath %q", rsaConfig.RSAPrivateKeyPath)
|
||||
}
|
||||
return
|
||||
}
|
||||
15
center/cconf/sql_tpl.go
Normal file
15
center/cconf/sql_tpl.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package cconf
|
||||
|
||||
var TDengineSQLTpl = map[string]string{
|
||||
"load5": "SELECT _wstart as ts, last(load5) FROM $database.system WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval) fill(null)",
|
||||
"process_total": "SELECT _wstart as ts, last(total) FROM $database.processes WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval) fill(null)",
|
||||
"thread_total": "SELECT _wstart as ts, last(total) FROM $database.threads WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval) fill(null)",
|
||||
"cpu_idle": "SELECT _wstart as ts, last(usage_idle) * -1 + 100 FROM $database.cpu WHERE (host = '$server' and cpu = 'cpu-total') and _ts >= $from and _ts <= $to interval($interval) fill(null)",
|
||||
"mem_used_percent": "SELECT _wstart as ts, last(used_percent) FROM $database.mem WHERE (host = '$server') and _ts >= $from and _ts <= $to interval($interval) fill(null)",
|
||||
"disk_used_percent": "SELECT _wstart as ts, last(used_percent) FROM $database.disk WHERE (host = '$server' and path = '/') and _ts >= $from and _ts <= $to interval($interval) fill(null)",
|
||||
"cpu_context_switches": "select ts, derivative(context_switches, 1s, 0) as context FROM (SELECT _wstart as ts, avg(context_switches) as context_switches FROM $database.kernel WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval) )",
|
||||
"tcp": "SELECT _wstart as ts, avg(tcp_close) as CLOSED, avg(tcp_close_wait) as CLOSE_WAIT, avg(tcp_closing) as CLOSING, avg(tcp_established) as ESTABLISHED, avg(tcp_fin_wait1) as FIN_WAIT1, avg(tcp_fin_wait2) as FIN_WAIT2, avg(tcp_last_ack) as LAST_ACK, avg(tcp_syn_recv) as SYN_RECV, avg(tcp_syn_sent) as SYN_SENT, avg(tcp_time_wait) as TIME_WAIT FROM $database.netstat WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval)",
|
||||
"net_bytes_recv": "SELECT _wstart as ts, derivative(bytes_recv,1s, 1) as bytes_in FROM $database.net WHERE host = '$server' and interface = '$netif' and _ts >= $from and _ts <= $to group by tbname",
|
||||
"net_bytes_sent": "SELECT _wstart as ts, derivative(bytes_sent,1s, 1) as bytes_out FROM $database.net WHERE host = '$server' and interface = '$netif' and _ts >= $from and _ts <= $to group by tbname",
|
||||
"disk_total": "SELECT _wstart as ts, avg(total) AS total, avg(used) as used FROM $database.disk WHERE path = '$mountpoint' and _ts >= $from and _ts <= $to interval($interval) group by host",
|
||||
}
|
||||
@@ -7,8 +7,13 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/alert"
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/alert/process"
|
||||
alertrt "github.com/ccfos/nightingale/v6/alert/router"
|
||||
"github.com/ccfos/nightingale/v6/center/cconf"
|
||||
"github.com/ccfos/nightingale/v6/center/cconf/rsa"
|
||||
"github.com/ccfos/nightingale/v6/center/cstats"
|
||||
"github.com/ccfos/nightingale/v6/center/integration"
|
||||
"github.com/ccfos/nightingale/v6/center/metas"
|
||||
centerrt "github.com/ccfos/nightingale/v6/center/router"
|
||||
"github.com/ccfos/nightingale/v6/center/sso"
|
||||
"github.com/ccfos/nightingale/v6/conf"
|
||||
"github.com/ccfos/nightingale/v6/dumper"
|
||||
@@ -16,17 +21,19 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/models/migrate"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/flashduty"
|
||||
"github.com/ccfos/nightingale/v6/pkg/httpx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/i18nx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/logx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/version"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/idents"
|
||||
pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/writer"
|
||||
"github.com/ccfos/nightingale/v6/storage"
|
||||
"github.com/ccfos/nightingale/v6/tdengine"
|
||||
|
||||
alertrt "github.com/ccfos/nightingale/v6/alert/router"
|
||||
centerrt "github.com/ccfos/nightingale/v6/center/router"
|
||||
pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router"
|
||||
"github.com/flashcatcloud/ibex/src/cmd/ibex"
|
||||
)
|
||||
|
||||
func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
@@ -38,55 +45,69 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
cconf.LoadMetricsYaml(configDir, config.Center.MetricsYamlFile)
|
||||
cconf.LoadOpsYaml(configDir, config.Center.OpsYamlFile)
|
||||
|
||||
cconf.MergeOperationConf()
|
||||
|
||||
logxClean, err := logx.Init(config.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i18nx.Init()
|
||||
i18nx.Init(configDir)
|
||||
cstats.Init()
|
||||
flashduty.Init(config.Center.FlashDuty)
|
||||
|
||||
db, err := storage.New(config.DB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx := ctx.NewContext(context.Background(), db, true)
|
||||
models.InitRoot(ctx)
|
||||
migrate.Migrate(db)
|
||||
models.InitRoot(ctx)
|
||||
|
||||
redis, err := storage.NewRedis(config.Redis)
|
||||
err = rsa.InitRSAConfig(ctx, &config.HTTP.RSA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
integration.Init(ctx, config.Center.BuiltinIntegrationsDir)
|
||||
var redis storage.Redis
|
||||
redis, err = storage.NewRedis(config.Redis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metas := metas.New(redis)
|
||||
idents := idents.New(ctx)
|
||||
idents := idents.New(ctx, redis)
|
||||
|
||||
syncStats := memsto.NewSyncStats()
|
||||
alertStats := astats.NewSyncStats()
|
||||
|
||||
sso := sso.Init(config.Center, ctx)
|
||||
|
||||
configCache := memsto.NewConfigCache(ctx, syncStats, config.HTTP.RSA.RSAPrivateKey, config.HTTP.RSA.RSAPassWord)
|
||||
busiGroupCache := memsto.NewBusiGroupCache(ctx, syncStats)
|
||||
targetCache := memsto.NewTargetCache(ctx, syncStats, redis)
|
||||
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
|
||||
alertMuteCache := memsto.NewAlertMuteCache(ctx, syncStats)
|
||||
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
|
||||
notifyConfigCache := memsto.NewNotifyConfigCache(ctx)
|
||||
notifyConfigCache := memsto.NewNotifyConfigCache(ctx, configCache)
|
||||
userCache := memsto.NewUserCache(ctx, syncStats)
|
||||
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
|
||||
taskTplCache := memsto.NewTaskTplCache(ctx)
|
||||
|
||||
promClients := prom.NewPromClient(ctx, config.Alert.Heartbeat)
|
||||
sso := sso.Init(config.Center, ctx, configCache)
|
||||
promClients := prom.NewPromClient(ctx)
|
||||
tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat)
|
||||
|
||||
externalProcessors := process.NewExternalProcessors()
|
||||
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, userCache, userGroupCache)
|
||||
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, taskTplCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache)
|
||||
|
||||
writers := writer.NewWriters(config.Pushgw)
|
||||
|
||||
httpx.InitRSAConfig(&config.HTTP.RSA)
|
||||
go version.GetGithubVersion()
|
||||
|
||||
alertrtRouter := alertrt.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
|
||||
centerRouter := centerrt.New(config.HTTP, config.Center, cconf.Operations, dsCache, notifyConfigCache, promClients, redis, sso, ctx, metas, idents, targetCache, userCache, userGroupCache)
|
||||
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, targetCache, busiGroupCache, idents, writers, ctx)
|
||||
centerRouter := centerrt.New(config.HTTP, config.Center, config.Alert, cconf.Operations, dsCache, notifyConfigCache, promClients, tdengineClients,
|
||||
redis, sso, ctx, metas, idents, targetCache, userCache, userGroupCache)
|
||||
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, config.Alert, targetCache, busiGroupCache, idents, metas, writers, ctx)
|
||||
|
||||
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
|
||||
|
||||
@@ -95,6 +116,11 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
pushgwRouter.Config(r)
|
||||
dumper.ConfigRouter(r)
|
||||
|
||||
if config.Ibex.Enable {
|
||||
migrate.MigrateIbexTables(db)
|
||||
ibex.ServerStart(true, db, redis, config.HTTP.APIForService.BasicAuth, config.Alert.Heartbeat, &config.CenterApi, r, centerRouter, config.Ibex, config.HTTP.Port)
|
||||
}
|
||||
|
||||
httpClean := httpx.Init(config.HTTP, r)
|
||||
|
||||
return func() {
|
||||
|
||||
228
center/integration/init.go
Normal file
228
center/integration/init.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/toolkits/pkg/file"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
"github.com/toolkits/pkg/runner"
|
||||
)
|
||||
|
||||
func Init(ctx *ctx.Context, builtinIntegrationsDir string) {
|
||||
fp := builtinIntegrationsDir
|
||||
if fp == "" {
|
||||
fp = path.Join(runner.Cwd, "integrations")
|
||||
}
|
||||
|
||||
// var fileList []string
|
||||
dirList, err := file.DirsUnder(fp)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
for _, dir := range dirList {
|
||||
// components icon
|
||||
componentDir := fp + "/" + dir
|
||||
component := models.BuiltinComponent{
|
||||
Ident: dir,
|
||||
}
|
||||
|
||||
// get logo name
|
||||
// /api/n9e/integrations/icon/AliYun/aliyun.png
|
||||
files, err := file.FilesUnder(componentDir + "/icon")
|
||||
if err == nil && len(files) > 0 {
|
||||
component.Logo = "/api/n9e/integrations/icon/" + component.Ident + "/" + files[0]
|
||||
} else if err != nil {
|
||||
logger.Warningf("read builtin component icon dir fail %s %v", component.Ident, err)
|
||||
}
|
||||
|
||||
// get description
|
||||
files, err = file.FilesUnder(componentDir + "/markdown")
|
||||
if err == nil && len(files) > 0 {
|
||||
var readmeFile string
|
||||
for _, file := range files {
|
||||
if strings.HasSuffix(strings.ToLower(file), "md") {
|
||||
readmeFile = file
|
||||
}
|
||||
}
|
||||
if readmeFile != "" {
|
||||
component.Readme, _ = file.ReadString(readmeFile)
|
||||
}
|
||||
} else if err != nil {
|
||||
logger.Warningf("read builtin component markdown dir fail %s %v", component.Ident, err)
|
||||
}
|
||||
|
||||
exists, _ := models.BuiltinComponentExists(ctx, &component)
|
||||
if !exists {
|
||||
err = component.Add(ctx, "system")
|
||||
if err != nil {
|
||||
logger.Warning("add builtin component fail ", component, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// alerts
|
||||
files, err = file.FilesUnder(componentDir + "/alerts")
|
||||
if err == nil && len(files) > 0 {
|
||||
for _, f := range files {
|
||||
fp := componentDir + "/alerts/" + f
|
||||
bs, err := file.ReadBytes(fp)
|
||||
if err != nil {
|
||||
logger.Warning("read builtin component alerts file fail ", f, err)
|
||||
continue
|
||||
}
|
||||
|
||||
alerts := []models.AlertRule{}
|
||||
err = json.Unmarshal(bs, &alerts)
|
||||
if err != nil {
|
||||
logger.Warning("parse builtin component alerts file fail ", f, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, alert := range alerts {
|
||||
content, err := json.Marshal(alert)
|
||||
if err != nil {
|
||||
logger.Warning("marshal builtin alert fail ", alert, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cate := strings.Replace(f, ".json", "", -1)
|
||||
builtinAlert := models.BuiltinPayload{
|
||||
Component: component.Ident,
|
||||
Type: "alert",
|
||||
Cate: cate,
|
||||
Name: alert.Name,
|
||||
Content: string(content),
|
||||
}
|
||||
|
||||
exists, err := models.BuiltinPayloadExists(ctx, &builtinAlert)
|
||||
if err != nil {
|
||||
logger.Warning("check builtin alert exists fail ", builtinAlert, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
|
||||
err = builtinAlert.Add(ctx, "system")
|
||||
if err != nil {
|
||||
logger.Warningf("add builtin alert:%+v fail %v", builtinAlert, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dashboards
|
||||
files, err = file.FilesUnder(componentDir + "/dashboards")
|
||||
if err == nil && len(files) > 0 {
|
||||
for _, f := range files {
|
||||
fp := componentDir + "/dashboards/" + f
|
||||
bs, err := file.ReadBytes(fp)
|
||||
if err != nil {
|
||||
logger.Warning("read builtin component dashboards file fail ", f, err)
|
||||
continue
|
||||
}
|
||||
|
||||
dashboard := BuiltinBoard{}
|
||||
err = json.Unmarshal(bs, &dashboard)
|
||||
if err != nil {
|
||||
logger.Warning("parse builtin component dashboards file fail ", f, err)
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := json.Marshal(dashboard)
|
||||
if err != nil {
|
||||
logger.Warning("marshal builtin dashboard fail ", dashboard, err)
|
||||
continue
|
||||
}
|
||||
|
||||
builtinDashboard := models.BuiltinPayload{
|
||||
Component: component.Ident,
|
||||
Type: "dashboard",
|
||||
Cate: "",
|
||||
Name: dashboard.Name,
|
||||
Content: string(content),
|
||||
}
|
||||
|
||||
exists, err := models.BuiltinPayloadExists(ctx, &builtinDashboard)
|
||||
if err != nil {
|
||||
logger.Warning("check builtin dashboard exists fail ", builtinDashboard, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
|
||||
err = builtinDashboard.Add(ctx, "system")
|
||||
if err != nil {
|
||||
logger.Warning("add builtin dashboard fail ", builtinDashboard, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
logger.Warningf("read builtin component dash dir fail %s %v", component.Ident, err)
|
||||
}
|
||||
|
||||
// metrics
|
||||
files, err = file.FilesUnder(componentDir + "/metrics")
|
||||
if err == nil && len(files) > 0 {
|
||||
for _, f := range files {
|
||||
fp := componentDir + "/metrics/" + f
|
||||
bs, err := file.ReadBytes(fp)
|
||||
if err != nil {
|
||||
logger.Warning("read builtin component metrics file fail", f, err)
|
||||
continue
|
||||
}
|
||||
|
||||
metrics := []models.BuiltinMetric{}
|
||||
err = json.Unmarshal(bs, &metrics)
|
||||
if err != nil {
|
||||
logger.Warning("parse builtin component metrics file fail", f, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, metric := range metrics {
|
||||
exists, err := models.BuiltinMetricExists(ctx, &metric)
|
||||
if err != nil {
|
||||
logger.Warning("check builtin metric exists fail", metric, err)
|
||||
continue
|
||||
}
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
err = metric.Add(ctx, "system")
|
||||
if err != nil {
|
||||
logger.Warning("add builtin metric fail", metric, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
logger.Warningf("read builtin component metrics dir fail %s %v", component.Ident, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BuiltinBoard struct {
|
||||
Id int64 `json:"id" gorm:"primaryKey"`
|
||||
GroupId int64 `json:"group_id"`
|
||||
Name string `json:"name"`
|
||||
Ident string `json:"ident"`
|
||||
Tags string `json:"tags"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
CreateBy string `json:"create_by"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
UpdateBy string `json:"update_by"`
|
||||
Configs interface{} `json:"configs" gorm:"-"`
|
||||
Public int `json:"public"` // 0: false, 1: true
|
||||
PublicCate int `json:"public_cate"` // 0: anonymous, 1: login, 2: busi
|
||||
Bgids []int64 `json:"bgids" gorm:"-"`
|
||||
BuiltIn int `json:"built_in"` // 0: false, 1: true
|
||||
Hide int `json:"hide"` // 0: false, 1: true
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package metas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -90,15 +91,41 @@ func (s *Set) updateMeta(items map[string]models.HostMeta) {
|
||||
}
|
||||
|
||||
func (s *Set) updateTargets(m map[string]models.HostMeta) error {
|
||||
if s.redis == nil {
|
||||
logger.Warningf("redis is nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
count := int64(len(m))
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
newMap := make(map[string]interface{}, count)
|
||||
extendMap := make(map[string]interface{})
|
||||
for ident, meta := range m {
|
||||
if meta.ExtendInfo != nil {
|
||||
extendMeta := meta.ExtendInfo
|
||||
meta.ExtendInfo = make(map[string]interface{})
|
||||
extendMetaStr, err := json.Marshal(extendMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
extendMap[models.WrapExtendIdent(ident)] = extendMetaStr
|
||||
}
|
||||
newMap[models.WrapIdent(ident)] = meta
|
||||
}
|
||||
err := storage.MSet(context.Background(), s.redis, newMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(extendMap) > 0 {
|
||||
err = storage.MSet(context.Background(), s.redis, extendMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
"github.com/ccfos/nightingale/v6/center/cconf"
|
||||
"github.com/ccfos/nightingale/v6/center/cstats"
|
||||
"github.com/ccfos/nightingale/v6/center/metas"
|
||||
@@ -17,12 +18,15 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/aop"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/httpx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/version"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/idents"
|
||||
"github.com/ccfos/nightingale/v6/storage"
|
||||
"github.com/ccfos/nightingale/v6/tdengine"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
"github.com/toolkits/pkg/runner"
|
||||
)
|
||||
@@ -30,10 +34,12 @@ import (
|
||||
type Router struct {
|
||||
HTTP httpx.Config
|
||||
Center cconf.Center
|
||||
Alert aconf.Alert
|
||||
Operations cconf.Operation
|
||||
DatasourceCache *memsto.DatasourceCacheType
|
||||
NotifyConfigCache *memsto.NotifyConfigCacheType
|
||||
PromClients *prom.PromClientMap
|
||||
TdendgineClients *tdengine.TdengineClientMap
|
||||
Redis storage.Redis
|
||||
MetaSet *metas.Set
|
||||
IdentSet *idents.Set
|
||||
@@ -44,16 +50,16 @@ type Router struct {
|
||||
Ctx *ctx.Context
|
||||
}
|
||||
|
||||
func New(httpConfig httpx.Config, center cconf.Center, operations cconf.Operation, ds *memsto.DatasourceCacheType, ncc *memsto.NotifyConfigCacheType,
|
||||
pc *prom.PromClientMap, redis storage.Redis, sso *sso.SsoClient, ctx *ctx.Context, metaSet *metas.Set, idents *idents.Set, tc *memsto.TargetCacheType,
|
||||
uc *memsto.UserCacheType, ugc *memsto.UserGroupCacheType) *Router {
|
||||
func New(httpConfig httpx.Config, center cconf.Center, alert aconf.Alert, operations cconf.Operation, ds *memsto.DatasourceCacheType, ncc *memsto.NotifyConfigCacheType, pc *prom.PromClientMap, tdendgineClients *tdengine.TdengineClientMap, redis storage.Redis, sso *sso.SsoClient, ctx *ctx.Context, metaSet *metas.Set, idents *idents.Set, tc *memsto.TargetCacheType, uc *memsto.UserCacheType, ugc *memsto.UserGroupCacheType) *Router {
|
||||
return &Router{
|
||||
HTTP: httpConfig,
|
||||
Center: center,
|
||||
Alert: alert,
|
||||
Operations: operations,
|
||||
DatasourceCache: ds,
|
||||
NotifyConfigCache: ncc,
|
||||
PromClients: pc,
|
||||
TdendgineClients: tdendgineClients,
|
||||
Redis: redis,
|
||||
MetaSet: metaSet,
|
||||
IdentSet: idents,
|
||||
@@ -86,14 +92,14 @@ func languageDetector(i18NHeaderKey string) gin.HandlerFunc {
|
||||
lang := c.GetHeader(headerKey)
|
||||
if lang != "" {
|
||||
if strings.HasPrefix(lang, "zh") {
|
||||
c.Request.Header.Set("X-Language", "zh")
|
||||
c.Request.Header.Set("X-Language", "zh_CN")
|
||||
} else if strings.HasPrefix(lang, "en") {
|
||||
c.Request.Header.Set("X-Language", "en")
|
||||
} else {
|
||||
c.Request.Header.Set("X-Language", lang)
|
||||
}
|
||||
} else {
|
||||
c.Request.Header.Set("X-Language", "en")
|
||||
c.Request.Header.Set("X-Language", "zh_CN")
|
||||
}
|
||||
}
|
||||
c.Next()
|
||||
@@ -160,15 +166,31 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.POST("/query-range-batch", rt.promBatchQueryRange)
|
||||
pages.POST("/query-instant-batch", rt.promBatchQueryInstant)
|
||||
pages.GET("/datasource/brief", rt.datasourceBriefs)
|
||||
|
||||
pages.POST("/ds-query", rt.QueryData)
|
||||
pages.POST("/logs-query", rt.QueryLog)
|
||||
|
||||
pages.POST("/tdengine-databases", rt.tdengineDatabases)
|
||||
pages.POST("/tdengine-tables", rt.tdengineTables)
|
||||
pages.POST("/tdengine-columns", rt.tdengineColumns)
|
||||
|
||||
} else {
|
||||
pages.Any("/proxy/:id/*url", rt.auth(), rt.dsProxy)
|
||||
pages.POST("/query-range-batch", rt.auth(), rt.promBatchQueryRange)
|
||||
pages.POST("/query-instant-batch", rt.auth(), rt.promBatchQueryInstant)
|
||||
pages.GET("/datasource/brief", rt.auth(), rt.datasourceBriefs)
|
||||
pages.GET("/datasource/brief", rt.auth(), rt.user(), rt.datasourceBriefs)
|
||||
|
||||
pages.POST("/ds-query", rt.auth(), rt.QueryData)
|
||||
pages.POST("/logs-query", rt.auth(), rt.QueryLog)
|
||||
|
||||
pages.POST("/tdengine-databases", rt.auth(), rt.tdengineDatabases)
|
||||
pages.POST("/tdengine-tables", rt.auth(), rt.tdengineTables)
|
||||
pages.POST("/tdengine-columns", rt.auth(), rt.tdengineColumns)
|
||||
}
|
||||
|
||||
pages.GET("/sql-template", rt.QuerySqlTemplate)
|
||||
pages.POST("/auth/login", rt.jwtMock(), rt.loginPost)
|
||||
pages.POST("/auth/logout", rt.jwtMock(), rt.auth(), rt.logoutPost)
|
||||
pages.POST("/auth/logout", rt.jwtMock(), rt.auth(), rt.user(), rt.logoutPost)
|
||||
pages.POST("/auth/refresh", rt.jwtMock(), rt.refreshPost)
|
||||
pages.POST("/auth/captcha", rt.jwtMock(), rt.generateCaptcha)
|
||||
pages.POST("/auth/captcha-verify", rt.jwtMock(), rt.captchaVerify)
|
||||
@@ -207,6 +229,20 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.POST("/metric-views", rt.auth(), rt.user(), rt.metricViewAdd)
|
||||
pages.PUT("/metric-views", rt.auth(), rt.user(), rt.metricViewPut)
|
||||
|
||||
pages.GET("/builtin-metric-filters", rt.auth(), rt.user(), rt.metricFilterGets)
|
||||
pages.DELETE("/builtin-metric-filters", rt.auth(), rt.user(), rt.metricFilterDel)
|
||||
pages.POST("/builtin-metric-filters", rt.auth(), rt.user(), rt.metricFilterAdd)
|
||||
pages.PUT("/builtin-metric-filters", rt.auth(), rt.user(), rt.metricFilterPut)
|
||||
pages.POST("/builtin-metric-promql", rt.auth(), rt.user(), rt.getMetricPromql)
|
||||
|
||||
pages.POST("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics/add"), rt.builtinMetricsAdd)
|
||||
pages.PUT("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics/put"), rt.builtinMetricsPut)
|
||||
pages.DELETE("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics/del"), rt.builtinMetricsDel)
|
||||
pages.GET("/builtin-metrics", rt.auth(), rt.user(), rt.builtinMetricsGets)
|
||||
pages.GET("/builtin-metrics/types", rt.auth(), rt.user(), rt.builtinMetricsTypes)
|
||||
pages.GET("/builtin-metrics/types/default", rt.auth(), rt.user(), rt.builtinMetricsDefaultTypes)
|
||||
pages.GET("/builtin-metrics/collectors", rt.auth(), rt.user(), rt.builtinMetricsCollectors)
|
||||
|
||||
pages.GET("/user-groups", rt.auth(), rt.user(), rt.userGroupGets)
|
||||
pages.POST("/user-groups", rt.auth(), rt.user(), rt.perm("/user-groups/add"), rt.userGroupAdd)
|
||||
pages.GET("/user-group/:id", rt.auth(), rt.user(), rt.userGroupGet)
|
||||
@@ -226,6 +262,7 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.GET("/busi-group/:id/perm/:perm", rt.auth(), rt.user(), rt.checkBusiGroupPerm)
|
||||
|
||||
pages.GET("/targets", rt.auth(), rt.user(), rt.targetGets)
|
||||
pages.GET("/target/extra-meta", rt.auth(), rt.user(), rt.targetExtendInfoByIdent)
|
||||
pages.POST("/target/list", rt.auth(), rt.user(), rt.targetGetsByHostFilter)
|
||||
pages.DELETE("/targets", rt.auth(), rt.user(), rt.perm("/targets/del"), rt.targetDel)
|
||||
pages.GET("/targets/tags", rt.auth(), rt.user(), rt.targetGetTags)
|
||||
@@ -243,10 +280,14 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.GET("/builtin-boards-cates", rt.auth(), rt.user(), rt.builtinBoardCateGets)
|
||||
pages.POST("/builtin-boards-detail", rt.auth(), rt.user(), rt.builtinBoardDetailGets)
|
||||
pages.GET("/integrations/icon/:cate/:name", rt.builtinIcon)
|
||||
pages.GET("/integrations/makedown/:cate", rt.builtinMarkdown)
|
||||
|
||||
pages.GET("/busi-groups/public-boards", rt.auth(), rt.user(), rt.perm("/dashboards"), rt.publicBoardGets)
|
||||
pages.GET("/busi-groups/boards", rt.auth(), rt.user(), rt.perm("/dashboards"), rt.boardGetsByGids)
|
||||
pages.GET("/busi-group/:id/boards", rt.auth(), rt.user(), rt.perm("/dashboards"), rt.bgro(), rt.boardGets)
|
||||
pages.POST("/busi-group/:id/boards", rt.auth(), rt.user(), rt.perm("/dashboards/add"), rt.bgrw(), rt.boardAdd)
|
||||
pages.POST("/busi-group/:id/board/:bid/clone", rt.auth(), rt.user(), rt.perm("/dashboards/add"), rt.bgrw(), rt.boardClone)
|
||||
pages.POST("/busi-groups/boards/clones", rt.auth(), rt.user(), rt.perm("/dashboards/add"), rt.boardBatchClone)
|
||||
|
||||
pages.GET("/board/:bid", rt.boardGet)
|
||||
pages.GET("/board/:bid/pure", rt.boardPureGet)
|
||||
@@ -260,7 +301,9 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
|
||||
pages.GET("/alert-rules/builtin/alerts-cates", rt.auth(), rt.user(), rt.builtinAlertCateGets)
|
||||
pages.GET("/alert-rules/builtin/list", rt.auth(), rt.user(), rt.builtinAlertRules)
|
||||
pages.GET("/alert-rules/callbacks", rt.auth(), rt.user(), rt.alertRuleCallbacks)
|
||||
|
||||
pages.GET("/busi-groups/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules"), rt.alertRuleGetsByGids)
|
||||
pages.GET("/busi-group/:id/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules"), rt.alertRuleGets)
|
||||
pages.POST("/busi-group/:id/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.bgrw(), rt.alertRuleAddByFE)
|
||||
pages.POST("/busi-group/:id/alert-rules/import", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.bgrw(), rt.alertRuleAddByImport)
|
||||
@@ -268,8 +311,9 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.PUT("/busi-group/:id/alert-rules/fields", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.bgrw(), rt.alertRulePutFields)
|
||||
pages.PUT("/busi-group/:id/alert-rule/:arid", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.alertRulePutByFE)
|
||||
pages.GET("/alert-rule/:arid", rt.auth(), rt.user(), rt.perm("/alert-rules"), rt.alertRuleGet)
|
||||
pages.PUT("/busi-group/:id/alert-rule/:arid/validate", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.alertRuleValidation)
|
||||
pages.PUT("/busi-group/alert-rule/validate", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.alertRuleValidation)
|
||||
|
||||
pages.GET("/busi-groups/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules"), rt.recordingRuleGetsByGids)
|
||||
pages.GET("/busi-group/:id/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules"), rt.recordingRuleGets)
|
||||
pages.POST("/busi-group/:id/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules/add"), rt.bgrw(), rt.recordingRuleAddByFE)
|
||||
pages.DELETE("/busi-group/:id/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules/del"), rt.bgrw(), rt.recordingRuleDel)
|
||||
@@ -277,12 +321,16 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.GET("/recording-rule/:rrid", rt.auth(), rt.user(), rt.perm("/recording-rules"), rt.recordingRuleGet)
|
||||
pages.PUT("/busi-group/:id/recording-rules/fields", rt.auth(), rt.user(), rt.perm("/recording-rules/put"), rt.recordingRulePutFields)
|
||||
|
||||
pages.GET("/busi-groups/alert-mutes", rt.auth(), rt.user(), rt.perm("/alert-mutes"), rt.alertMuteGetsByGids)
|
||||
pages.GET("/busi-group/:id/alert-mutes", rt.auth(), rt.user(), rt.perm("/alert-mutes"), rt.bgro(), rt.alertMuteGetsByBG)
|
||||
pages.POST("/busi-group/:id/alert-mutes/preview", rt.auth(), rt.user(), rt.perm("/alert-mutes/add"), rt.bgrw(), rt.alertMutePreview)
|
||||
pages.POST("/busi-group/:id/alert-mutes", rt.auth(), rt.user(), rt.perm("/alert-mutes/add"), rt.bgrw(), rt.alertMuteAdd)
|
||||
pages.DELETE("/busi-group/:id/alert-mutes", rt.auth(), rt.user(), rt.perm("/alert-mutes/del"), rt.bgrw(), rt.alertMuteDel)
|
||||
pages.PUT("/busi-group/:id/alert-mute/:amid", rt.auth(), rt.user(), rt.perm("/alert-mutes/put"), rt.alertMutePutByFE)
|
||||
pages.GET("/busi-group/:id/alert-mute/:amid", rt.auth(), rt.user(), rt.perm("/alert-mutes"), rt.alertMuteGet)
|
||||
pages.PUT("/busi-group/:id/alert-mutes/fields", rt.auth(), rt.user(), rt.perm("/alert-mutes/put"), rt.bgrw(), rt.alertMutePutFields)
|
||||
|
||||
pages.GET("/busi-groups/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes"), rt.alertSubscribeGetsByGids)
|
||||
pages.GET("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes"), rt.bgro(), rt.alertSubscribeGets)
|
||||
pages.GET("/alert-subscribe/:sid", rt.auth(), rt.user(), rt.perm("/alert-subscribes"), rt.alertSubscribeGet)
|
||||
pages.POST("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/add"), rt.bgrw(), rt.alertSubscribeAdd)
|
||||
@@ -303,12 +351,14 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.POST("/alert-cur-events/card/details", rt.auth(), rt.alertCurEventsCardDetails)
|
||||
pages.GET("/alert-his-events/list", rt.auth(), rt.alertHisEventsList)
|
||||
pages.DELETE("/alert-cur-events", rt.auth(), rt.user(), rt.perm("/alert-cur-events/del"), rt.alertCurEventDel)
|
||||
pages.GET("/alert-cur-events/stats", rt.auth(), rt.alertCurEventsStatistics)
|
||||
|
||||
pages.GET("/alert-aggr-views", rt.auth(), rt.alertAggrViewGets)
|
||||
pages.DELETE("/alert-aggr-views", rt.auth(), rt.user(), rt.alertAggrViewDel)
|
||||
pages.POST("/alert-aggr-views", rt.auth(), rt.user(), rt.alertAggrViewAdd)
|
||||
pages.PUT("/alert-aggr-views", rt.auth(), rt.user(), rt.alertAggrViewPut)
|
||||
|
||||
pages.GET("/busi-groups/task-tpls", rt.auth(), rt.user(), rt.perm("/job-tpls"), rt.taskTplGetsByGids)
|
||||
pages.GET("/busi-group/:id/task-tpls", rt.auth(), rt.user(), rt.perm("/job-tpls"), rt.bgro(), rt.taskTplGets)
|
||||
pages.POST("/busi-group/:id/task-tpls", rt.auth(), rt.user(), rt.perm("/job-tpls/add"), rt.bgrw(), rt.taskTplAdd)
|
||||
pages.DELETE("/busi-group/:id/task-tpl/:tid", rt.auth(), rt.user(), rt.perm("/job-tpls/del"), rt.bgrw(), rt.taskTplDel)
|
||||
@@ -317,15 +367,14 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.GET("/busi-group/:id/task-tpl/:tid", rt.auth(), rt.user(), rt.perm("/job-tpls"), rt.bgro(), rt.taskTplGet)
|
||||
pages.PUT("/busi-group/:id/task-tpl/:tid", rt.auth(), rt.user(), rt.perm("/job-tpls/put"), rt.bgrw(), rt.taskTplPut)
|
||||
|
||||
pages.GET("/busi-groups/tasks", rt.auth(), rt.user(), rt.perm("/job-tasks"), rt.taskGetsByGids)
|
||||
pages.GET("/busi-group/:id/tasks", rt.auth(), rt.user(), rt.perm("/job-tasks"), rt.bgro(), rt.taskGets)
|
||||
pages.POST("/busi-group/:id/tasks", rt.auth(), rt.user(), rt.perm("/job-tasks/add"), rt.bgrw(), rt.taskAdd)
|
||||
pages.GET("/busi-group/:id/task/*url", rt.auth(), rt.user(), rt.perm("/job-tasks"), rt.taskProxy)
|
||||
pages.PUT("/busi-group/:id/task/*url", rt.auth(), rt.user(), rt.perm("/job-tasks/put"), rt.bgrw(), rt.taskProxy)
|
||||
|
||||
pages.GET("/servers", rt.auth(), rt.admin(), rt.serversGet)
|
||||
pages.GET("/server-clusters", rt.auth(), rt.admin(), rt.serverClustersGet)
|
||||
pages.GET("/servers", rt.auth(), rt.user(), rt.perm("/help/servers"), rt.serversGet)
|
||||
pages.GET("/server-clusters", rt.auth(), rt.user(), rt.perm("/help/servers"), rt.serverClustersGet)
|
||||
|
||||
pages.POST("/datasource/list", rt.auth(), rt.datasourceList)
|
||||
pages.POST("/datasource/list", rt.auth(), rt.user(), rt.datasourceList)
|
||||
pages.POST("/datasource/plugin/list", rt.auth(), rt.pluginList)
|
||||
pages.POST("/datasource/upsert", rt.auth(), rt.admin(), rt.datasourceUpsert)
|
||||
pages.POST("/datasource/desc", rt.auth(), rt.admin(), rt.datasourceGet)
|
||||
@@ -341,38 +390,77 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.PUT("/role/:id/ops", rt.auth(), rt.admin(), rt.roleBindOperation)
|
||||
pages.GET("/operation", rt.operations)
|
||||
|
||||
pages.GET("/notify-tpls", rt.auth(), rt.admin(), rt.notifyTplGets)
|
||||
pages.PUT("/notify-tpl/content", rt.auth(), rt.admin(), rt.notifyTplUpdateContent)
|
||||
pages.PUT("/notify-tpl", rt.auth(), rt.admin(), rt.notifyTplUpdate)
|
||||
pages.POST("/notify-tpl", rt.auth(), rt.admin(), rt.notifyTplAdd)
|
||||
pages.DELETE("/notify-tpl/:id", rt.auth(), rt.admin(), rt.notifyTplDel)
|
||||
pages.POST("/notify-tpl/preview", rt.auth(), rt.admin(), rt.notifyTplPreview)
|
||||
pages.GET("/notify-tpls", rt.auth(), rt.user(), rt.notifyTplGets)
|
||||
pages.PUT("/notify-tpl/content", rt.auth(), rt.user(), rt.notifyTplUpdateContent)
|
||||
pages.PUT("/notify-tpl", rt.auth(), rt.user(), rt.notifyTplUpdate)
|
||||
pages.POST("/notify-tpl", rt.auth(), rt.user(), rt.notifyTplAdd)
|
||||
pages.DELETE("/notify-tpl/:id", rt.auth(), rt.user(), rt.notifyTplDel)
|
||||
pages.POST("/notify-tpl/preview", rt.auth(), rt.user(), rt.notifyTplPreview)
|
||||
|
||||
pages.GET("/sso-configs", rt.auth(), rt.admin(), rt.ssoConfigGets)
|
||||
pages.PUT("/sso-config", rt.auth(), rt.admin(), rt.ssoConfigUpdate)
|
||||
|
||||
pages.GET("/webhooks", rt.auth(), rt.admin(), rt.webhookGets)
|
||||
pages.GET("/webhooks", rt.auth(), rt.user(), rt.webhookGets)
|
||||
pages.PUT("/webhooks", rt.auth(), rt.admin(), rt.webhookPuts)
|
||||
|
||||
pages.GET("/notify-script", rt.auth(), rt.admin(), rt.notifyScriptGet)
|
||||
pages.GET("/notify-script", rt.auth(), rt.user(), rt.perm("/help/notification-settings"), rt.notifyScriptGet)
|
||||
pages.PUT("/notify-script", rt.auth(), rt.admin(), rt.notifyScriptPut)
|
||||
|
||||
pages.GET("/notify-channel", rt.auth(), rt.admin(), rt.notifyChannelGets)
|
||||
pages.GET("/notify-channel", rt.auth(), rt.user(), rt.perm("/help/notification-settings"), rt.notifyChannelGets)
|
||||
pages.PUT("/notify-channel", rt.auth(), rt.admin(), rt.notifyChannelPuts)
|
||||
|
||||
pages.GET("/notify-contact", rt.auth(), rt.admin(), rt.notifyContactGets)
|
||||
pages.GET("/notify-contact", rt.auth(), rt.user(), rt.perm("/help/notification-settings"), rt.notifyContactGets)
|
||||
pages.PUT("/notify-contact", rt.auth(), rt.admin(), rt.notifyContactPuts)
|
||||
|
||||
pages.GET("/notify-config", rt.auth(), rt.admin(), rt.notifyConfigGet)
|
||||
pages.GET("/notify-config", rt.auth(), rt.user(), rt.perm("/help/notification-settings"), rt.notifyConfigGet)
|
||||
pages.PUT("/notify-config", rt.auth(), rt.admin(), rt.notifyConfigPut)
|
||||
pages.PUT("/smtp-config-test", rt.auth(), rt.admin(), rt.attemptSendEmail)
|
||||
|
||||
pages.GET("/es-index-pattern", rt.auth(), rt.esIndexPatternGet)
|
||||
pages.GET("/es-index-pattern-list", rt.auth(), rt.esIndexPatternGetList)
|
||||
pages.POST("/es-index-pattern", rt.auth(), rt.admin(), rt.esIndexPatternAdd)
|
||||
pages.PUT("/es-index-pattern", rt.auth(), rt.admin(), rt.esIndexPatternPut)
|
||||
pages.DELETE("/es-index-pattern", rt.auth(), rt.admin(), rt.esIndexPatternDel)
|
||||
|
||||
pages.GET("/user-variable-configs", rt.auth(), rt.user(), rt.perm("/help/variable-configs"), rt.userVariableConfigGets)
|
||||
pages.POST("/user-variable-config", rt.auth(), rt.user(), rt.perm("/help/variable-configs"), rt.userVariableConfigAdd)
|
||||
pages.PUT("/user-variable-config/:id", rt.auth(), rt.user(), rt.perm("/help/variable-configs"), rt.userVariableConfigPut)
|
||||
pages.DELETE("/user-variable-config/:id", rt.auth(), rt.user(), rt.perm("/help/variable-configs"), rt.userVariableConfigDel)
|
||||
|
||||
pages.GET("/config", rt.auth(), rt.admin(), rt.configGetByKey)
|
||||
pages.PUT("/config", rt.auth(), rt.admin(), rt.configPutByKey)
|
||||
pages.GET("/site-info", rt.siteInfo)
|
||||
|
||||
// for admin api
|
||||
pages.GET("/user/busi-groups", rt.auth(), rt.admin(), rt.userBusiGroupsGets)
|
||||
|
||||
pages.POST("/builtin-components", rt.auth(), rt.user(), rt.perm("/builtin-components/add"), rt.builtinComponentsAdd)
|
||||
pages.GET("/builtin-components", rt.auth(), rt.user(), rt.perm("/builtin-components"), rt.builtinComponentsGets)
|
||||
pages.PUT("/builtin-components", rt.auth(), rt.user(), rt.perm("/builtin-components/put"), rt.builtinComponentsPut)
|
||||
pages.DELETE("/builtin-components", rt.auth(), rt.user(), rt.perm("/builtin-components/del"), rt.builtinComponentsDel)
|
||||
|
||||
pages.POST("/builtin-payloads", rt.auth(), rt.user(), rt.perm("/builtin-payloads/add"), rt.builtinPayloadsAdd)
|
||||
pages.GET("/builtin-payloads", rt.auth(), rt.user(), rt.perm("/builtin-payloads"), rt.builtinPayloadsGets)
|
||||
pages.GET("/builtin-payload/:id", rt.auth(), rt.user(), rt.perm("/builtin-payloads"), rt.builtinPayloadGet)
|
||||
pages.PUT("/builtin-payloads", rt.auth(), rt.user(), rt.perm("/builtin-payloads/put"), rt.builtinPayloadsPut)
|
||||
pages.DELETE("/builtin-payloads", rt.auth(), rt.user(), rt.perm("/builtin-payloads/del"), rt.builtinPayloadsDel)
|
||||
}
|
||||
|
||||
r.GET("/api/n9e/versions", func(c *gin.Context) {
|
||||
v := version.Version
|
||||
lastIndex := strings.LastIndex(version.Version, "-")
|
||||
if lastIndex != -1 {
|
||||
v = version.Version[:lastIndex]
|
||||
}
|
||||
|
||||
gv := version.GithubVersion.Load()
|
||||
if gv != nil {
|
||||
ginx.NewRender(c).Data(gin.H{"version": v, "github_verison": gv.(string)}, nil)
|
||||
} else {
|
||||
ginx.NewRender(c).Data(gin.H{"version": v, "github_verison": ""}, nil)
|
||||
}
|
||||
})
|
||||
|
||||
if rt.HTTP.APIForService.Enable {
|
||||
service := r.Group("/v1/n9e")
|
||||
if len(rt.HTTP.APIForService.BasicAuth) > 0 {
|
||||
@@ -381,6 +469,8 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
{
|
||||
service.Any("/prometheus/*url", rt.dsProxy)
|
||||
service.POST("/users", rt.userAddPost)
|
||||
service.PUT("/user/:id", rt.userProfilePutByService)
|
||||
service.DELETE("/user/:id", rt.userDel)
|
||||
service.GET("/users", rt.userFindAll)
|
||||
|
||||
service.GET("/user-groups", rt.userGroupGetsByService)
|
||||
@@ -393,6 +483,7 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
service.PUT("/targets/note", rt.targetUpdateNoteByService)
|
||||
|
||||
service.POST("/alert-rules", rt.alertRuleAddByService)
|
||||
service.POST("/alert-rule-add", rt.alertRuleAddOneByService)
|
||||
service.DELETE("/alert-rules", rt.alertRuleDelByService)
|
||||
service.PUT("/alert-rule/:arid", rt.alertRulePutByService)
|
||||
service.GET("/alert-rule/:arid", rt.alertRuleGet)
|
||||
@@ -418,6 +509,10 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
service.GET("/alert-his-events", rt.alertHisEventsList)
|
||||
service.GET("/alert-his-event/:eid", rt.alertHisEventGet)
|
||||
|
||||
service.GET("/task-tpl/:tid", rt.taskTplGetByService)
|
||||
service.GET("/task-tpls", rt.taskTplGetsByService)
|
||||
service.GET("/task-tpl/statistics", rt.taskTplStatistics)
|
||||
|
||||
service.GET("/config/:id", rt.configGet)
|
||||
service.GET("/configs", rt.configsGet)
|
||||
service.GET("/config", rt.configGetByKey)
|
||||
@@ -433,6 +528,11 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
service.GET("/notify-tpls", rt.notifyTplGets)
|
||||
|
||||
service.POST("/task-record-add", rt.taskRecordAdd)
|
||||
|
||||
service.GET("/user-variable/decrypt", rt.userVariableGetDecryptByService)
|
||||
|
||||
service.GET("/targets-of-alert-rule", rt.targetsOfAlertRule)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
@@ -182,10 +183,19 @@ func (rt *Router) alertCurEventDel(c *gin.Context) {
|
||||
ginx.BindJSON(c, &f)
|
||||
f.Verify()
|
||||
|
||||
rt.checkCurEventBusiGroupRWPermission(c, f.Ids)
|
||||
|
||||
ginx.NewRender(c).Message(models.AlertCurEventDel(rt.Ctx, f.Ids))
|
||||
}
|
||||
|
||||
func (rt *Router) checkCurEventBusiGroupRWPermission(c *gin.Context, ids []int64) {
|
||||
set := make(map[int64]struct{})
|
||||
|
||||
for i := 0; i < len(f.Ids); i++ {
|
||||
event, err := models.AlertCurEventGetById(rt.Ctx, f.Ids[i])
|
||||
// event group id is 0, ignore perm check
|
||||
set[0] = struct{}{}
|
||||
|
||||
for i := 0; i < len(ids); i++ {
|
||||
event, err := models.AlertCurEventGetById(rt.Ctx, ids[i])
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if _, has := set[event.GroupId]; !has {
|
||||
@@ -193,8 +203,6 @@ func (rt *Router) alertCurEventDel(c *gin.Context) {
|
||||
set[event.GroupId] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(models.AlertCurEventDel(rt.Ctx, f.Ids))
|
||||
}
|
||||
|
||||
func (rt *Router) alertCurEventGet(c *gin.Context) {
|
||||
@@ -208,3 +216,8 @@ func (rt *Router) alertCurEventGet(c *gin.Context) {
|
||||
|
||||
ginx.NewRender(c).Data(event, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) alertCurEventsStatistics(c *gin.Context) {
|
||||
|
||||
ginx.NewRender(c).Data(models.AlertCurEventStatistics(rt.Ctx, time.Now()), nil)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/i18n"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
// Return all, front-end search and paging
|
||||
@@ -27,6 +28,37 @@ func (rt *Router) alertRuleGets(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(ars, err)
|
||||
}
|
||||
|
||||
func (rt *Router) alertRuleGetsByGids(c *gin.Context) {
|
||||
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
|
||||
if len(gids) > 0 {
|
||||
for _, gid := range gids {
|
||||
rt.bgroCheck(c, gid)
|
||||
}
|
||||
} else {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
if !me.IsAdmin() {
|
||||
var err error
|
||||
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if len(gids) == 0 {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ars, err := models.AlertRuleGetsByBGIds(rt.Ctx, gids)
|
||||
if err == nil {
|
||||
cache := make(map[int64]*models.UserGroup)
|
||||
for i := 0; i < len(ars); i++ {
|
||||
ars[i].FillNotifyGroups(rt.Ctx, cache)
|
||||
ars[i].FillSeverities()
|
||||
}
|
||||
}
|
||||
ginx.NewRender(c).Data(ars, err)
|
||||
}
|
||||
|
||||
func (rt *Router) alertRulesGetByService(c *gin.Context) {
|
||||
prods := []string{}
|
||||
prodStr := ginx.QueryStr(c, "prods", "")
|
||||
@@ -101,6 +133,17 @@ func (rt *Router) alertRuleAddByService(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(reterr, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) alertRuleAddOneByService(c *gin.Context) {
|
||||
var f models.AlertRule
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
err := f.FE2DB()
|
||||
ginx.Dangerous(err)
|
||||
|
||||
err = f.Add(rt.Ctx)
|
||||
ginx.NewRender(c).Data(f.Id, err)
|
||||
}
|
||||
|
||||
func (rt *Router) alertRuleAddForService(lst []models.AlertRule, username string) map[string]string {
|
||||
count := len(lst)
|
||||
// alert rule name -> error string
|
||||
@@ -273,22 +316,11 @@ func (rt *Router) alertRuleGet(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(ar, err)
|
||||
}
|
||||
|
||||
//pre validation before save rule
|
||||
// pre validation before save rule
|
||||
func (rt *Router) alertRuleValidation(c *gin.Context) {
|
||||
var f models.AlertRule //new
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
arid := ginx.UrlParamInt64(c, "arid")
|
||||
ar, err := models.AlertRuleGetById(rt.Ctx, arid)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if ar == nil {
|
||||
ginx.NewRender(c, http.StatusNotFound).Message("No such AlertRule")
|
||||
return
|
||||
}
|
||||
|
||||
rt.bgrwCheck(c, ar.GroupId)
|
||||
|
||||
if len(f.NotifyChannelsJSON) > 0 && len(f.NotifyGroupsJSON) > 0 { //Validation NotifyChannels
|
||||
ngids := make([]int64, 0, len(f.NotifyChannelsJSON))
|
||||
for i := range f.NotifyGroupsJSON {
|
||||
@@ -305,6 +337,15 @@ func (rt *Router) alertRuleValidation(c *gin.Context) {
|
||||
ancs := make([]string, 0, len(f.NotifyChannelsJSON)) //absent Notify Channels
|
||||
for i := range f.NotifyChannelsJSON {
|
||||
flag := true
|
||||
//ignore non-default channels
|
||||
switch f.NotifyChannelsJSON[i] {
|
||||
case models.Dingtalk, models.Wecom, models.Feishu, models.Mm,
|
||||
models.Telegram, models.Email, models.FeishuCard:
|
||||
// do nothing
|
||||
default:
|
||||
continue
|
||||
}
|
||||
//default channels
|
||||
for ui := range users {
|
||||
if _, b := users[ui].ExtractToken(f.NotifyChannelsJSON[i]); b {
|
||||
flag = false
|
||||
@@ -317,7 +358,7 @@ func (rt *Router) alertRuleValidation(c *gin.Context) {
|
||||
}
|
||||
|
||||
if len(ancs) > 0 {
|
||||
ginx.NewRender(c).Message(i18n.Sprintf(c.GetHeader("X-Language"), "All users are missing notify channel configurations. Please check for missing tokens (each channel should be configured with at least one user). %s", ancs))
|
||||
ginx.NewRender(c).Message("All users are missing notify channel configurations. Please check for missing tokens (each channel should be configured with at least one user). %s", ancs)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -325,3 +366,25 @@ func (rt *Router) alertRuleValidation(c *gin.Context) {
|
||||
|
||||
ginx.NewRender(c).Message("")
|
||||
}
|
||||
|
||||
func (rt *Router) alertRuleCallbacks(c *gin.Context) {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
bussGroupIds, err := models.MyBusiGroupIds(rt.Ctx, user.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ars, err := models.AlertRuleGetsByBGIds(rt.Ctx, bussGroupIds)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
var callbacks []string
|
||||
callbackFilter := make(map[string]struct{})
|
||||
for i := range ars {
|
||||
for _, callback := range ars[i].CallbacksJSON {
|
||||
if _, ok := callbackFilter[callback]; !ok {
|
||||
callbackFilter[callback] = struct{}{}
|
||||
callbacks = append(callbacks, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(callbacks, nil)
|
||||
}
|
||||
|
||||
@@ -8,27 +8,61 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
// Return all, front-end search and paging
|
||||
func (rt *Router) alertSubscribeGets(c *gin.Context) {
|
||||
bgid := ginx.UrlParamInt64(c, "id")
|
||||
lst, err := models.AlertSubscribeGets(rt.Ctx, bgid)
|
||||
if err == nil {
|
||||
ugcache := make(map[int64]*models.UserGroup)
|
||||
for i := 0; i < len(lst); i++ {
|
||||
ginx.Dangerous(lst[i].FillUserGroups(rt.Ctx, ugcache))
|
||||
}
|
||||
ginx.Dangerous(err)
|
||||
|
||||
rulecache := make(map[int64]string)
|
||||
for i := 0; i < len(lst); i++ {
|
||||
ginx.Dangerous(lst[i].FillRuleName(rt.Ctx, rulecache))
|
||||
}
|
||||
ugcache := make(map[int64]*models.UserGroup)
|
||||
rulecache := make(map[int64]string)
|
||||
|
||||
for i := 0; i < len(lst); i++ {
|
||||
ginx.Dangerous(lst[i].FillDatasourceIds(rt.Ctx))
|
||||
for i := 0; i < len(lst); i++ {
|
||||
ginx.Dangerous(lst[i].FillUserGroups(rt.Ctx, ugcache))
|
||||
ginx.Dangerous(lst[i].FillRuleNames(rt.Ctx, rulecache))
|
||||
ginx.Dangerous(lst[i].FillDatasourceIds(rt.Ctx))
|
||||
ginx.Dangerous(lst[i].DB2FE())
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
func (rt *Router) alertSubscribeGetsByGids(c *gin.Context) {
|
||||
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
|
||||
if len(gids) > 0 {
|
||||
for _, gid := range gids {
|
||||
rt.bgroCheck(c, gid)
|
||||
}
|
||||
} else {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
if !me.IsAdmin() {
|
||||
var err error
|
||||
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if len(gids) == 0 {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lst, err := models.AlertSubscribeGetsByBGIds(rt.Ctx, gids)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ugcache := make(map[int64]*models.UserGroup)
|
||||
rulecache := make(map[int64]string)
|
||||
|
||||
for i := 0; i < len(lst); i++ {
|
||||
ginx.Dangerous(lst[i].FillUserGroups(rt.Ctx, ugcache))
|
||||
ginx.Dangerous(lst[i].FillRuleNames(rt.Ctx, rulecache))
|
||||
ginx.Dangerous(lst[i].FillDatasourceIds(rt.Ctx))
|
||||
ginx.Dangerous(lst[i].DB2FE())
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
@@ -47,7 +81,7 @@ func (rt *Router) alertSubscribeGet(c *gin.Context) {
|
||||
ginx.Dangerous(sub.FillUserGroups(rt.Ctx, ugcache))
|
||||
|
||||
rulecache := make(map[int64]string)
|
||||
ginx.Dangerous(sub.FillRuleName(rt.Ctx, rulecache))
|
||||
ginx.Dangerous(sub.FillRuleNames(rt.Ctx, rulecache))
|
||||
ginx.Dangerous(sub.FillDatasourceIds(rt.Ctx))
|
||||
ginx.Dangerous(sub.DB2FE())
|
||||
|
||||
@@ -79,6 +113,9 @@ func (rt *Router) alertSubscribePut(c *gin.Context) {
|
||||
for i := 0; i < len(fs); i++ {
|
||||
fs[i].UpdateBy = username
|
||||
fs[i].UpdateAt = timestamp
|
||||
//After adding the function of batch subscription alert rules, rule_ids is used instead of rule_id.
|
||||
//When the subscription rules are updated, set rule_id=0 to prevent the wrong subscription caused by the old rule_id.
|
||||
fs[i].RuleId = 0
|
||||
ginx.Dangerous(fs[i].Update(
|
||||
rt.Ctx,
|
||||
"name",
|
||||
@@ -88,6 +125,7 @@ func (rt *Router) alertSubscribePut(c *gin.Context) {
|
||||
"datasource_ids",
|
||||
"cluster",
|
||||
"rule_id",
|
||||
"rule_ids",
|
||||
"tags",
|
||||
"redefine_severity",
|
||||
"new_severity",
|
||||
@@ -101,6 +139,8 @@ func (rt *Router) alertSubscribePut(c *gin.Context) {
|
||||
"redefine_webhooks",
|
||||
"severities",
|
||||
"extra_config",
|
||||
"busi_groups",
|
||||
"note",
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/i18n"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
type boardForm struct {
|
||||
Name string `json:"name"`
|
||||
Ident string `json:"ident"`
|
||||
Tags string `json:"tags"`
|
||||
Configs string `json:"configs"`
|
||||
Public int `json:"public"`
|
||||
Name string `json:"name"`
|
||||
Ident string `json:"ident"`
|
||||
Tags string `json:"tags"`
|
||||
Configs string `json:"configs"`
|
||||
Public int `json:"public"`
|
||||
PublicCate int `json:"public_cate"`
|
||||
Bgids []int64 `json:"bgids"`
|
||||
}
|
||||
|
||||
func (rt *Router) boardAdd(c *gin.Context) {
|
||||
@@ -65,6 +69,28 @@ func (rt *Router) boardGet(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if board.PublicCate == models.PublicLogin {
|
||||
rt.auth()(c)
|
||||
} else if board.PublicCate == models.PublicBusi {
|
||||
rt.auth()(c)
|
||||
rt.user()(c)
|
||||
|
||||
me := c.MustGet("user").(*models.User)
|
||||
if !me.IsAdmin() {
|
||||
bgids, err := models.MyBusiGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
if len(bgids) == 0 {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
}
|
||||
|
||||
ok, err := models.BoardBusigroupCheck(rt.Ctx, board.Id, bgids)
|
||||
ginx.Dangerous(err)
|
||||
if !ok {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(board, nil)
|
||||
}
|
||||
|
||||
@@ -192,10 +218,20 @@ func (rt *Router) boardPutPublic(c *gin.Context) {
|
||||
}
|
||||
|
||||
bo.Public = f.Public
|
||||
bo.PublicCate = f.PublicCate
|
||||
|
||||
if bo.PublicCate == models.PublicBusi {
|
||||
err := models.BoardBusigroupUpdate(rt.Ctx, bo.Id, f.Bgids)
|
||||
ginx.Dangerous(err)
|
||||
} else {
|
||||
err := models.BoardBusigroupDelByBoardId(rt.Ctx, bo.Id)
|
||||
ginx.Dangerous(err)
|
||||
}
|
||||
|
||||
bo.UpdateBy = me.Username
|
||||
bo.UpdateAt = time.Now().Unix()
|
||||
|
||||
err := bo.Update(rt.Ctx, "public", "update_by", "update_at")
|
||||
err := bo.Update(rt.Ctx, "public", "public_cate", "update_by", "update_at")
|
||||
ginx.NewRender(c).Data(bo, err)
|
||||
}
|
||||
|
||||
@@ -207,21 +243,64 @@ func (rt *Router) boardGets(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(boards, err)
|
||||
}
|
||||
|
||||
func (rt *Router) publicBoardGets(c *gin.Context) {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
|
||||
bgids, err := models.MyBusiGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
boardIds, err := models.BoardIdsByBusiGroupIds(rt.Ctx, bgids)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
boards, err := models.BoardGets(rt.Ctx, "", "public=1 and (public_cate in (?) or id in (?))", []int64{0, 1}, boardIds)
|
||||
ginx.NewRender(c).Data(boards, err)
|
||||
}
|
||||
|
||||
func (rt *Router) boardGetsByGids(c *gin.Context) {
|
||||
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
|
||||
query := ginx.QueryStr(c, "query", "")
|
||||
|
||||
if len(gids) > 0 {
|
||||
for _, gid := range gids {
|
||||
rt.bgroCheck(c, gid)
|
||||
}
|
||||
} else {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
if !me.IsAdmin() {
|
||||
var err error
|
||||
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if len(gids) == 0 {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boardBusigroups, err := models.BoardBusigroupGets(rt.Ctx)
|
||||
ginx.Dangerous(err)
|
||||
m := make(map[int64][]int64)
|
||||
for _, boardBusigroup := range boardBusigroups {
|
||||
m[boardBusigroup.BoardId] = append(m[boardBusigroup.BoardId], boardBusigroup.BusiGroupId)
|
||||
}
|
||||
|
||||
boards, err := models.BoardGetsByBGIds(rt.Ctx, gids, query)
|
||||
ginx.Dangerous(err)
|
||||
for i := 0; i < len(boards); i++ {
|
||||
if ids, ok := m[boards[i].Id]; ok {
|
||||
boards[i].Bgids = ids
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(boards, err)
|
||||
}
|
||||
|
||||
func (rt *Router) boardClone(c *gin.Context) {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
bo := rt.Board(ginx.UrlParamInt64(c, "bid"))
|
||||
|
||||
newBoard := &models.Board{
|
||||
Name: bo.Name + " Copy",
|
||||
Tags: bo.Tags,
|
||||
GroupId: bo.GroupId,
|
||||
CreateBy: me.Username,
|
||||
UpdateBy: me.Username,
|
||||
}
|
||||
|
||||
if bo.Ident != "" {
|
||||
newBoard.Ident = uuid.NewString()
|
||||
}
|
||||
newBoard := bo.Clone(me.Username, bo.GroupId, " Cloned")
|
||||
|
||||
ginx.Dangerous(newBoard.Add(rt.Ctx))
|
||||
|
||||
@@ -235,3 +314,39 @@ func (rt *Router) boardClone(c *gin.Context) {
|
||||
|
||||
ginx.NewRender(c).Message(nil)
|
||||
}
|
||||
|
||||
type boardsForm struct {
|
||||
BoardIds []int64 `json:"board_ids"`
|
||||
Bgids []int64 `json:"bgids"`
|
||||
}
|
||||
|
||||
func (rt *Router) boardBatchClone(c *gin.Context) {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
var f boardsForm
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
for _, bgid := range f.Bgids {
|
||||
rt.bgrwCheck(c, bgid)
|
||||
}
|
||||
|
||||
reterr := make(map[string]string, len(f.BoardIds))
|
||||
lang := c.GetHeader("X-Language")
|
||||
|
||||
for _, bgid := range f.Bgids {
|
||||
for _, bid := range f.BoardIds {
|
||||
bo := rt.Board(bid)
|
||||
newBoard := bo.Clone(me.Username, bgid, "")
|
||||
payload, err := models.BoardPayloadGet(rt.Ctx, bo.Id)
|
||||
if err != nil {
|
||||
reterr[fmt.Sprintf("%s-%d", newBoard.Name, bgid)] = i18n.Sprintf(lang, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if err = newBoard.AtomicAdd(rt.Ctx, payload); err != nil {
|
||||
reterr[fmt.Sprintf("%s-%d", newBoard.Name, bgid)] = i18n.Sprintf(lang, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(reterr, nil)
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ func (rt *Router) builtinBoardCateGets(c *gin.Context) {
|
||||
}
|
||||
|
||||
me := c.MustGet("user").(*models.User)
|
||||
buildinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
|
||||
builtinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
|
||||
if err != nil {
|
||||
logger.Warningf("get builtin favorites fail: %v", err)
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func (rt *Router) builtinBoardCateGets(c *gin.Context) {
|
||||
}
|
||||
boardCate.Boards = boards
|
||||
|
||||
if _, ok := buildinFavoritesMap[dir]; ok {
|
||||
if _, ok := builtinFavoritesMap[dir]; ok {
|
||||
boardCate.Favorite = true
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func (rt *Router) builtinAlertCateGets(c *gin.Context) {
|
||||
}
|
||||
|
||||
me := c.MustGet("user").(*models.User)
|
||||
buildinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
|
||||
builtinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
|
||||
if err != nil {
|
||||
logger.Warningf("get builtin favorites fail: %v", err)
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func (rt *Router) builtinAlertCateGets(c *gin.Context) {
|
||||
alertCate.IconUrl = fmt.Sprintf("/api/n9e/integrations/icon/%s/%s", dir, iconFiles[0])
|
||||
}
|
||||
|
||||
if _, ok := buildinFavoritesMap[dir]; ok {
|
||||
if _, ok := builtinFavoritesMap[dir]; ok {
|
||||
alertCate.Favorite = true
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ func (rt *Router) builtinAlertRules(c *gin.Context) {
|
||||
}
|
||||
|
||||
me := c.MustGet("user").(*models.User)
|
||||
buildinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
|
||||
builtinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
|
||||
if err != nil {
|
||||
logger.Warningf("get builtin favorites fail: %v", err)
|
||||
}
|
||||
@@ -274,7 +274,7 @@ func (rt *Router) builtinAlertRules(c *gin.Context) {
|
||||
alertCate.IconUrl = fmt.Sprintf("/api/n9e/integrations/icon/%s/%s", dir, iconFiles[0])
|
||||
}
|
||||
|
||||
if _, ok := buildinFavoritesMap[dir]; ok {
|
||||
if _, ok := builtinFavoritesMap[dir]; ok {
|
||||
alertCate.Favorite = true
|
||||
}
|
||||
|
||||
@@ -315,3 +315,26 @@ func (rt *Router) builtinIcon(c *gin.Context) {
|
||||
iconPath := fp + "/" + cate + "/icon/" + ginx.UrlParamStr(c, "name")
|
||||
c.File(path.Join(iconPath))
|
||||
}
|
||||
|
||||
func (rt *Router) builtinMarkdown(c *gin.Context) {
|
||||
fp := rt.Center.BuiltinIntegrationsDir
|
||||
if fp == "" {
|
||||
fp = path.Join(runner.Cwd, "integrations")
|
||||
}
|
||||
cate := ginx.UrlParamStr(c, "cate")
|
||||
|
||||
var markdown []byte
|
||||
markdownDir := fp + "/" + cate + "/markdown"
|
||||
markdownFiles, err := file.FilesUnder(markdownDir)
|
||||
if err != nil {
|
||||
logger.Warningf("get markdown fail: %v", err)
|
||||
} else if len(markdownFiles) > 0 {
|
||||
f := markdownFiles[0]
|
||||
fn := markdownDir + "/" + f
|
||||
markdown, err = file.ReadBytes(fn)
|
||||
if err != nil {
|
||||
logger.Warningf("get collect fail: %v", err)
|
||||
}
|
||||
}
|
||||
ginx.NewRender(c).Data(string(markdown), nil)
|
||||
}
|
||||
|
||||
66
center/router/router_builtin_componet.go
Normal file
66
center/router/router_builtin_componet.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
)
|
||||
|
||||
func (rt *Router) builtinComponentsAdd(c *gin.Context) {
|
||||
var lst []models.BuiltinComponent
|
||||
ginx.BindJSON(c, &lst)
|
||||
|
||||
username := Username(c)
|
||||
|
||||
count := len(lst)
|
||||
if count == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, "input json is empty")
|
||||
}
|
||||
|
||||
reterr := make(map[string]string)
|
||||
for i := 0; i < count; i++ {
|
||||
if err := lst[i].Add(rt.Ctx, username); err != nil {
|
||||
reterr[lst[i].Ident] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(reterr, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) builtinComponentsGets(c *gin.Context) {
|
||||
query := ginx.QueryStr(c, "query", "")
|
||||
|
||||
bc, err := models.BuiltinComponentGets(rt.Ctx, query)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Data(bc, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) builtinComponentsPut(c *gin.Context) {
|
||||
var req models.BuiltinComponent
|
||||
ginx.BindJSON(c, &req)
|
||||
|
||||
bc, err := models.BuiltinComponentGet(rt.Ctx, "id = ?", req.ID)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if bc == nil {
|
||||
ginx.NewRender(c, http.StatusNotFound).Message("No such builtin component")
|
||||
return
|
||||
}
|
||||
|
||||
username := Username(c)
|
||||
req.UpdatedBy = username
|
||||
|
||||
ginx.NewRender(c).Message(bc.Update(rt.Ctx, req))
|
||||
}
|
||||
|
||||
func (rt *Router) builtinComponentsDel(c *gin.Context) {
|
||||
var req idsForm
|
||||
ginx.BindJSON(c, &req)
|
||||
|
||||
req.Verify()
|
||||
|
||||
ginx.NewRender(c).Message(models.BuiltinComponentDels(rt.Ctx, req.Ids))
|
||||
}
|
||||
120
center/router/router_builtin_metric_filter.go
Normal file
120
center/router/router_builtin_metric_filter.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/prom"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
)
|
||||
|
||||
func (rt *Router) metricFilterGets(c *gin.Context) {
|
||||
lst, err := models.MetricFilterGets(rt.Ctx, "")
|
||||
ginx.Dangerous(err)
|
||||
me := c.MustGet("user").(*models.User)
|
||||
|
||||
gids, err := models.MyGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
arr := make([]models.MetricFilter, 0)
|
||||
|
||||
for _, f := range lst {
|
||||
if me.Username == f.CreateBy {
|
||||
arr = append(arr, f)
|
||||
continue
|
||||
}
|
||||
|
||||
if HasPerm(gids, f.GroupsPerm, false) {
|
||||
arr = append(arr, f)
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(arr, err)
|
||||
}
|
||||
|
||||
func (rt *Router) metricFilterAdd(c *gin.Context) {
|
||||
var f models.MetricFilter
|
||||
ginx.BindJSON(c, &f)
|
||||
me := c.MustGet("user").(*models.User)
|
||||
|
||||
f.CreateBy = me.Username
|
||||
f.UpdateBy = me.Username
|
||||
ginx.Dangerous(f.Add(rt.Ctx))
|
||||
ginx.NewRender(c).Data(f, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) metricFilterDel(c *gin.Context) {
|
||||
var f idsForm
|
||||
ginx.BindJSON(c, &f)
|
||||
f.Verify()
|
||||
|
||||
me := c.MustGet("user").(*models.User)
|
||||
|
||||
for _, id := range f.Ids {
|
||||
old, err := models.MetricFilterGet(rt.Ctx, id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if me.Username != old.CreateBy {
|
||||
gids, err := models.MyGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if !HasPerm(gids, old.GroupsPerm, true) {
|
||||
ginx.NewRender(c).Message("no permission")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(models.MetricFilterDel(rt.Ctx, f.Ids))
|
||||
}
|
||||
|
||||
func (rt *Router) metricFilterPut(c *gin.Context) {
|
||||
var f models.MetricFilter
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
me := c.MustGet("user").(*models.User)
|
||||
old, err := models.MetricFilterGet(rt.Ctx, f.ID)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if me.Username != old.CreateBy {
|
||||
gids, err := models.MyGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if !HasPerm(gids, old.GroupsPerm, true) {
|
||||
ginx.NewRender(c).Message("no permission")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
f.UpdateBy = me.Username
|
||||
ginx.NewRender(c).Message(f.Update(rt.Ctx))
|
||||
}
|
||||
|
||||
type metricPromqlReq struct {
|
||||
LabelFilter string `json:"label_filter"`
|
||||
Promql string `json:"promql"`
|
||||
}
|
||||
|
||||
func (rt *Router) getMetricPromql(c *gin.Context) {
|
||||
var req metricPromqlReq
|
||||
ginx.BindJSON(c, &req)
|
||||
|
||||
promql := prom.AddLabelToPromQL(req.LabelFilter, req.Promql)
|
||||
ginx.NewRender(c).Data(promql, nil)
|
||||
}
|
||||
|
||||
func HasPerm(gids []int64, gps []models.GroupPerm, checkWrite bool) bool {
|
||||
gmap := make(map[int64]struct{})
|
||||
for _, gp := range gps {
|
||||
if checkWrite && !gp.Write {
|
||||
continue
|
||||
}
|
||||
gmap[gp.Gid] = struct{}{}
|
||||
}
|
||||
|
||||
for _, gid := range gids {
|
||||
if _, ok := gmap[gid]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
113
center/router/router_builtin_metrics.go
Normal file
113
center/router/router_builtin_metrics.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
)
|
||||
|
||||
// single or import
|
||||
func (rt *Router) builtinMetricsAdd(c *gin.Context) {
|
||||
var lst []models.BuiltinMetric
|
||||
ginx.BindJSON(c, &lst)
|
||||
username := Username(c)
|
||||
count := len(lst)
|
||||
if count == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, "input json is empty")
|
||||
}
|
||||
|
||||
lang := c.GetHeader("X-Language")
|
||||
if lang == "" {
|
||||
lang = "zh_CN"
|
||||
}
|
||||
|
||||
reterr := make(map[string]string)
|
||||
for i := 0; i < count; i++ {
|
||||
lst[i].Lang = lang
|
||||
if err := lst[i].Add(rt.Ctx, username); err != nil {
|
||||
reterr[lst[i].Name] = err.Error()
|
||||
}
|
||||
}
|
||||
ginx.NewRender(c).Data(reterr, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) builtinMetricsGets(c *gin.Context) {
|
||||
collector := ginx.QueryStr(c, "collector", "")
|
||||
typ := ginx.QueryStr(c, "typ", "")
|
||||
query := ginx.QueryStr(c, "query", "")
|
||||
limit := ginx.QueryInt(c, "limit", 20)
|
||||
lang := c.GetHeader("X-Language")
|
||||
unit := ginx.QueryStr(c, "unit", "")
|
||||
if lang == "" {
|
||||
lang = "zh_CN"
|
||||
}
|
||||
|
||||
bm, err := models.BuiltinMetricGets(rt.Ctx, lang, collector, typ, query, unit, limit, ginx.Offset(c, limit))
|
||||
ginx.Dangerous(err)
|
||||
|
||||
total, err := models.BuiltinMetricCount(rt.Ctx, lang, collector, typ, query, unit)
|
||||
ginx.Dangerous(err)
|
||||
ginx.NewRender(c).Data(gin.H{
|
||||
"list": bm,
|
||||
"total": total,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) builtinMetricsPut(c *gin.Context) {
|
||||
var req models.BuiltinMetric
|
||||
ginx.BindJSON(c, &req)
|
||||
|
||||
bm, err := models.BuiltinMetricGet(rt.Ctx, "id = ?", req.ID)
|
||||
ginx.Dangerous(err)
|
||||
if bm == nil {
|
||||
ginx.NewRender(c, http.StatusNotFound).Message("No such builtin metric")
|
||||
return
|
||||
}
|
||||
username := Username(c)
|
||||
|
||||
req.UpdatedBy = username
|
||||
ginx.NewRender(c).Message(bm.Update(rt.Ctx, req))
|
||||
}
|
||||
|
||||
func (rt *Router) builtinMetricsDel(c *gin.Context) {
|
||||
var req idsForm
|
||||
ginx.BindJSON(c, &req)
|
||||
req.Verify()
|
||||
|
||||
ginx.NewRender(c).Message(models.BuiltinMetricDels(rt.Ctx, req.Ids))
|
||||
}
|
||||
|
||||
func (rt *Router) builtinMetricsDefaultTypes(c *gin.Context) {
|
||||
lst := []string{
|
||||
"Linux",
|
||||
"cAdvisor",
|
||||
"Ping",
|
||||
"MySQL",
|
||||
"Redis",
|
||||
"Kafka",
|
||||
"Elasticsearch",
|
||||
"PostgreSQL",
|
||||
"MongoDB",
|
||||
"Memcached",
|
||||
}
|
||||
ginx.NewRender(c).Data(lst, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) builtinMetricsTypes(c *gin.Context) {
|
||||
collector := ginx.QueryStr(c, "collector", "")
|
||||
query := ginx.QueryStr(c, "query", "")
|
||||
lang := c.GetHeader("X-Language")
|
||||
|
||||
ginx.NewRender(c).Data(models.BuiltinMetricTypes(rt.Ctx, lang, collector, query))
|
||||
}
|
||||
|
||||
func (rt *Router) builtinMetricsCollectors(c *gin.Context) {
|
||||
typ := ginx.QueryStr(c, "typ", "")
|
||||
query := ginx.QueryStr(c, "query", "")
|
||||
lang := c.GetHeader("X-Language")
|
||||
|
||||
ginx.NewRender(c).Data(models.BuiltinMetricCollectors(rt.Ctx, lang, typ, query))
|
||||
}
|
||||
91
center/router/router_builtin_payload.go
Normal file
91
center/router/router_builtin_payload.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
)
|
||||
|
||||
func (rt *Router) builtinPayloadsAdd(c *gin.Context) {
|
||||
var lst []models.BuiltinPayload
|
||||
ginx.BindJSON(c, &lst)
|
||||
|
||||
username := Username(c)
|
||||
|
||||
count := len(lst)
|
||||
if count == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, "input json is empty")
|
||||
}
|
||||
|
||||
reterr := make(map[string]string)
|
||||
for i := 0; i < count; i++ {
|
||||
if err := lst[i].Add(rt.Ctx, username); err != nil {
|
||||
reterr[lst[i].Name] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(reterr, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) builtinPayloadsGets(c *gin.Context) {
|
||||
typ := ginx.QueryStr(c, "type", "")
|
||||
component := ginx.QueryStr(c, "component", "")
|
||||
cate := ginx.QueryStr(c, "cate", "")
|
||||
name := ginx.QueryStr(c, "name", "")
|
||||
limit := ginx.QueryInt(c, "limit", 20)
|
||||
|
||||
lst, err := models.BuiltinPayloadGets(rt.Ctx, typ, component, cate, name, limit, ginx.Offset(c, limit))
|
||||
ginx.Dangerous(err)
|
||||
m := make(map[string][]*models.BuiltinPayload)
|
||||
for _, v := range lst {
|
||||
if _, ok := m[v.Cate]; !ok {
|
||||
m[v.Cate] = make([]*models.BuiltinPayload, 0)
|
||||
}
|
||||
m[v.Cate] = append(m[v.Cate], v)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(m, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) builtinPayloadGet(c *gin.Context) {
|
||||
id := ginx.UrlParamInt64(c, "id")
|
||||
|
||||
bp, err := models.BuiltinPayloadGet(rt.Ctx, "id = ?", id)
|
||||
if err != nil {
|
||||
ginx.Bomb(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
if bp == nil {
|
||||
ginx.Bomb(http.StatusNotFound, "builtin payload not found")
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(bp, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) builtinPayloadsPut(c *gin.Context) {
|
||||
var req models.BuiltinPayload
|
||||
ginx.BindJSON(c, &req)
|
||||
|
||||
bp, err := models.BuiltinPayloadGet(rt.Ctx, "id = ?", req.ID)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if bp == nil {
|
||||
ginx.NewRender(c, http.StatusNotFound).Message("No such builtin payload")
|
||||
return
|
||||
}
|
||||
|
||||
username := Username(c)
|
||||
req.UpdatedBy = username
|
||||
|
||||
ginx.NewRender(c).Message(bp.Update(rt.Ctx, req))
|
||||
}
|
||||
|
||||
func (rt *Router) builtinPayloadsDel(c *gin.Context) {
|
||||
var req idsForm
|
||||
ginx.BindJSON(c, &req)
|
||||
|
||||
req.Verify()
|
||||
|
||||
ginx.NewRender(c).Message(models.BuiltinPayloadDels(rt.Ctx, req.Ids))
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func (rt *Router) generateCaptcha(c *gin.Context) {
|
||||
var driver = captcha.NewDriverMath(60, 200, 0, captcha.OptionShowHollowLine, nil, nil, []string{"wqy-microhei.ttc"})
|
||||
cc := captcha.NewCaptcha(driver, rt.newCaptchaRedisStore())
|
||||
//data:image/png;base64
|
||||
id, b64s, err := cc.Generate()
|
||||
id, b64s, _, err := cc.Generate()
|
||||
|
||||
if err != nil {
|
||||
ginx.NewRender(c).Message(err)
|
||||
|
||||
@@ -62,3 +62,8 @@ func (rt *Router) contactKeysGets(c *gin.Context) {
|
||||
|
||||
ginx.NewRender(c).Data(labelAndKeys, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) siteInfo(c *gin.Context) {
|
||||
config, err := models.ConfigsGet(rt.Ctx, "site_info")
|
||||
ginx.NewRender(c).Data(config, err)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
@@ -25,28 +26,49 @@ func (rt *Router) configGetByKey(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(config, err)
|
||||
}
|
||||
|
||||
func (rt *Router) configPutByKey(c *gin.Context) {
|
||||
var f models.Configs
|
||||
ginx.BindJSON(c, &f)
|
||||
username := c.MustGet("username").(string)
|
||||
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, f.Ckey, f.Cval, username))
|
||||
}
|
||||
|
||||
func (rt *Router) configsDel(c *gin.Context) {
|
||||
var f idsForm
|
||||
ginx.BindJSON(c, &f)
|
||||
ginx.NewRender(c).Message(models.ConfigsDel(rt.Ctx, f.Ids))
|
||||
}
|
||||
|
||||
func (rt *Router) configsPut(c *gin.Context) {
|
||||
func (rt *Router) configsPut(c *gin.Context) { //for APIForService
|
||||
var arr []models.Configs
|
||||
ginx.BindJSON(c, &arr)
|
||||
|
||||
username := c.GetString("user")
|
||||
if username == "" {
|
||||
username = "default"
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
for i := 0; i < len(arr); i++ {
|
||||
arr[i].UpdateBy = username
|
||||
arr[i].UpdateAt = now
|
||||
ginx.Dangerous(arr[i].Update(rt.Ctx))
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(nil)
|
||||
}
|
||||
|
||||
func (rt *Router) configsPost(c *gin.Context) {
|
||||
func (rt *Router) configsPost(c *gin.Context) { //for APIForService
|
||||
var arr []models.Configs
|
||||
ginx.BindJSON(c, &arr)
|
||||
|
||||
username := c.GetString("user")
|
||||
if username == "" {
|
||||
username = "default"
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
for i := 0; i < len(arr); i++ {
|
||||
arr[i].CreateBy = username
|
||||
arr[i].UpdateBy = username
|
||||
arr[i].CreateAt = now
|
||||
arr[i].UpdateAt = now
|
||||
ginx.Dangerous(arr[i].Add(rt.Ctx))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -25,6 +26,11 @@ type listReq struct {
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceList(c *gin.Context) {
|
||||
if rt.DatasourceCache.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req listReq
|
||||
ginx.BindJSON(c, &req)
|
||||
|
||||
@@ -32,8 +38,10 @@ func (rt *Router) datasourceList(c *gin.Context) {
|
||||
category := req.Category
|
||||
name := req.Name
|
||||
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
list, err := models.GetDatasourcesGetsBy(rt.Ctx, typ, category, name, "")
|
||||
Render(c, list, err)
|
||||
Render(c, rt.DatasourceCache.DatasourceFilter(list, user), err)
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceGetsByService(c *gin.Context) {
|
||||
@@ -42,29 +50,40 @@ func (rt *Router) datasourceGetsByService(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
type datasourceBrief struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PluginType string `json:"plugin_type"`
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceBriefs(c *gin.Context) {
|
||||
var dss []datasourceBrief
|
||||
var dss []*models.Datasource
|
||||
list, err := models.GetDatasourcesGetsBy(rt.Ctx, "", "", "", "")
|
||||
ginx.Dangerous(err)
|
||||
|
||||
for i := range list {
|
||||
dss = append(dss, datasourceBrief{
|
||||
Id: list[i].Id,
|
||||
Name: list[i].Name,
|
||||
PluginType: list[i].PluginType,
|
||||
})
|
||||
for _, item := range list {
|
||||
item.AuthJson.BasicAuthPassword = ""
|
||||
if item.PluginType != models.PROMETHEUS {
|
||||
item.SettingsJson = nil
|
||||
} else {
|
||||
for k, v := range item.SettingsJson {
|
||||
if strings.HasPrefix(k, "prometheus.") {
|
||||
item.SettingsJson[strings.TrimPrefix(k, "prometheus.")] = v
|
||||
delete(item.SettingsJson, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
dss = append(dss, item)
|
||||
}
|
||||
|
||||
if !rt.Center.AnonymousAccess.PromQuerier {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
dss = rt.DatasourceCache.DatasourceFilter(dss, user)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(dss, err)
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceUpsert(c *gin.Context) {
|
||||
if rt.DatasourceCache.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req models.Datasource
|
||||
ginx.BindJSON(c, &req)
|
||||
username := Username(c)
|
||||
@@ -94,7 +113,7 @@ func (rt *Router) datasourceUpsert(c *gin.Context) {
|
||||
}
|
||||
err = req.Add(rt.Ctx)
|
||||
} else {
|
||||
err = req.Update(rt.Ctx, "name", "description", "cluster_name", "settings", "http", "auth", "updated_by", "updated_at")
|
||||
err = req.Update(rt.Ctx, "name", "description", "cluster_name", "settings", "http", "auth", "updated_by", "updated_at", "is_default")
|
||||
}
|
||||
|
||||
Render(c, nil, err)
|
||||
@@ -127,14 +146,33 @@ func DatasourceCheck(ds models.Datasource) error {
|
||||
if ds.PluginType == models.PROMETHEUS {
|
||||
subPath := "/api/v1/query"
|
||||
query := url.Values{}
|
||||
if strings.Contains(fullURL, "loki") {
|
||||
if ds.HTTPJson.IsLoki() {
|
||||
subPath = "/api/v1/labels"
|
||||
} else {
|
||||
query.Add("query", "1+1")
|
||||
}
|
||||
fullURL = fmt.Sprintf("%s%s?%s", ds.HTTPJson.Url, subPath, query.Encode())
|
||||
|
||||
req, err = http.NewRequest("POST", fullURL, nil)
|
||||
req, err = http.NewRequest("GET", fullURL, nil)
|
||||
if err != nil {
|
||||
logger.Errorf("Error creating request: %v", err)
|
||||
return fmt.Errorf("request url:%s failed", fullURL)
|
||||
}
|
||||
} else if ds.PluginType == models.TDENGINE {
|
||||
fullURL = fmt.Sprintf("%s/rest/sql", ds.HTTPJson.Url)
|
||||
req, err = http.NewRequest("POST", fullURL, strings.NewReader("show databases"))
|
||||
if err != nil {
|
||||
logger.Errorf("Error creating request: %v", err)
|
||||
return fmt.Errorf("request url:%s failed", fullURL)
|
||||
}
|
||||
}
|
||||
|
||||
if ds.PluginType == models.LOKI {
|
||||
subPath := "/api/v1/labels"
|
||||
|
||||
fullURL = fmt.Sprintf("%s%s", ds.HTTPJson.Url, subPath)
|
||||
|
||||
req, err = http.NewRequest("GET", fullURL, nil)
|
||||
if err != nil {
|
||||
logger.Errorf("Error creating request: %v", err)
|
||||
return fmt.Errorf("request url:%s failed", fullURL)
|
||||
@@ -158,13 +196,19 @@ func DatasourceCheck(ds models.Datasource) error {
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
logger.Errorf("Error making request: %v\n", resp.StatusCode)
|
||||
return fmt.Errorf("request url:%s failed code:%d", fullURL, resp.StatusCode)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("request url:%s failed code:%d body:%s", fullURL, resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceGet(c *gin.Context) {
|
||||
if rt.DatasourceCache.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req models.Datasource
|
||||
ginx.BindJSON(c, &req)
|
||||
err := req.Get(rt.Ctx)
|
||||
@@ -172,6 +216,11 @@ func (rt *Router) datasourceGet(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceUpdataStatus(c *gin.Context) {
|
||||
if rt.DatasourceCache.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req models.Datasource
|
||||
ginx.BindJSON(c, &req)
|
||||
username := Username(c)
|
||||
@@ -181,6 +230,11 @@ func (rt *Router) datasourceUpdataStatus(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceDel(c *gin.Context) {
|
||||
if rt.DatasourceCache.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var ids []int64
|
||||
ginx.BindJSON(c, &ids)
|
||||
err := models.DatasourceDel(rt.Ctx, ids)
|
||||
@@ -193,8 +247,3 @@ func (rt *Router) getDatasourceIds(c *gin.Context) {
|
||||
|
||||
ginx.NewRender(c).Data(datasourceIds, err)
|
||||
}
|
||||
|
||||
func Username(c *gin.Context) string {
|
||||
|
||||
return c.MustGet("username").(string)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ func (rt *Router) esIndexPatternPut(c *gin.Context) {
|
||||
}
|
||||
|
||||
f.UpdateBy = c.MustGet("username").(string)
|
||||
|
||||
ginx.NewRender(c).Message(esIndexPattern.Update(rt.Ctx, f))
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ func (rt *Router) esIndexPatternGetList(c *gin.Context) {
|
||||
} else {
|
||||
lst, err = models.EsIndexPatternGets(rt.Ctx, "")
|
||||
}
|
||||
|
||||
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ibex"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
)
|
||||
|
||||
@@ -44,6 +41,10 @@ func (rt *Router) statistic(c *gin.Context) {
|
||||
statistics, err = models.DatasourceStatistics(rt.Ctx)
|
||||
ginx.NewRender(c).Data(statistics, err)
|
||||
return
|
||||
case "user_variable":
|
||||
statistics, err = models.ConfigsUserVariableStatistics(rt.Ctx)
|
||||
ginx.NewRender(c).Data(statistics, err)
|
||||
return
|
||||
default:
|
||||
ginx.Bomb(http.StatusBadRequest, "invalid name")
|
||||
}
|
||||
@@ -65,7 +66,8 @@ func queryDatasourceIds(c *gin.Context) []int64 {
|
||||
}
|
||||
|
||||
type idsForm struct {
|
||||
Ids []int64 `json:"ids"`
|
||||
Ids []int64 `json:"ids"`
|
||||
IsSyncToFlashDuty bool `json:"is_sync_to_flashduty"`
|
||||
}
|
||||
|
||||
func (f idsForm) Verify() {
|
||||
@@ -130,27 +132,11 @@ type TaskCreateReply struct {
|
||||
Dat int64 `json:"dat"` // task.id
|
||||
}
|
||||
|
||||
// return task.id, error
|
||||
func TaskCreate(v interface{}, ibexc aconf.Ibex) (int64, error) {
|
||||
var res TaskCreateReply
|
||||
err := ibex.New(
|
||||
ibexc.Address,
|
||||
ibexc.BasicAuthUser,
|
||||
ibexc.BasicAuthPass,
|
||||
ibexc.Timeout,
|
||||
).
|
||||
Path("/ibex/v1/tasks").
|
||||
In(v).
|
||||
Out(&res).
|
||||
POST()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
func Username(c *gin.Context) string {
|
||||
username := c.GetString(gin.AuthUserKey)
|
||||
if username == "" {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
username = user.Username
|
||||
}
|
||||
|
||||
if res.Err != "" {
|
||||
return 0, fmt.Errorf("response.err: %v", res.Err)
|
||||
}
|
||||
|
||||
return res.Dat, nil
|
||||
return username
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@ import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
func (rt *Router) heartbeat(c *gin.Context) {
|
||||
@@ -44,18 +47,51 @@ func (rt *Router) heartbeat(c *gin.Context) {
|
||||
req.RemoteAddr = c.ClientIP()
|
||||
}
|
||||
|
||||
if req.EngineName == "" {
|
||||
req.EngineName = rt.Alert.Heartbeat.EngineName
|
||||
}
|
||||
|
||||
rt.MetaSet.Set(req.Hostname, req)
|
||||
var items = make(map[string]struct{})
|
||||
items[req.Hostname] = struct{}{}
|
||||
rt.IdentSet.MSet(items)
|
||||
|
||||
gid := ginx.QueryInt64(c, "gid", 0)
|
||||
if target, has := rt.TargetCache.Get(req.Hostname); has && target != nil {
|
||||
gid := ginx.QueryInt64(c, "gid", 0)
|
||||
hostIp := strings.TrimSpace(req.HostIp)
|
||||
|
||||
if gid != 0 {
|
||||
target, has := rt.TargetCache.Get(req.Hostname)
|
||||
if has && target.GroupId != gid {
|
||||
err = models.TargetUpdateBgid(rt.Ctx, []string{req.Hostname}, gid, false)
|
||||
filed := make(map[string]interface{})
|
||||
if gid != 0 && gid != target.GroupId {
|
||||
filed["group_id"] = gid
|
||||
}
|
||||
|
||||
if hostIp != "" && hostIp != target.HostIp {
|
||||
filed["host_ip"] = hostIp
|
||||
}
|
||||
|
||||
if len(req.GlobalLabels) > 0 {
|
||||
lst := []string{}
|
||||
for k, v := range req.GlobalLabels {
|
||||
lst = append(lst, k+"="+v)
|
||||
}
|
||||
sort.Strings(lst)
|
||||
labels := strings.Join(lst, " ")
|
||||
if target.Tags != labels {
|
||||
filed["tags"] = labels
|
||||
}
|
||||
}
|
||||
|
||||
if req.EngineName != "" && req.EngineName != target.EngineName {
|
||||
filed["engine_name"] = req.EngineName
|
||||
}
|
||||
|
||||
if len(filed) > 0 {
|
||||
err := target.UpdateFieldsMap(rt.Ctx, filed)
|
||||
if err != nil {
|
||||
logger.Errorf("update target fields failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
logger.Debugf("heartbeat field:%+v target: %v", filed, *target)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(err)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/cas"
|
||||
@@ -14,10 +13,10 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/oauth2x"
|
||||
"github.com/ccfos/nightingale/v6/pkg/oidcx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/secu"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
@@ -51,22 +50,26 @@ func (rt *Router) loginPost(c *gin.Context) {
|
||||
}
|
||||
authPassWord = decPassWord
|
||||
}
|
||||
user, err := models.PassLogin(rt.Ctx, f.Username, authPassWord)
|
||||
if err != nil {
|
||||
// pass validate fail, try ldap
|
||||
if rt.Sso.LDAP.Enable {
|
||||
roles := strings.Join(rt.Sso.LDAP.DefaultRoles, " ")
|
||||
user, err = models.LdapLogin(rt.Ctx, f.Username, authPassWord, roles, rt.Sso.LDAP)
|
||||
if err != nil {
|
||||
logger.Debugf("ldap login failed: %v username: %s", err, f.Username)
|
||||
ginx.NewRender(c).Message(err)
|
||||
|
||||
var user *models.User
|
||||
var err error
|
||||
lc := rt.Sso.LDAP.Copy()
|
||||
if lc.Enable {
|
||||
user, err = ldapx.LdapLogin(rt.Ctx, f.Username, authPassWord, lc.DefaultRoles, lc)
|
||||
if err != nil {
|
||||
logger.Debugf("ldap login failed: %v username: %s", err, f.Username)
|
||||
var errLoginInN9e error
|
||||
// to use n9e as the minimum guarantee for login
|
||||
if user, errLoginInN9e = models.PassLogin(rt.Ctx, f.Username, authPassWord); errLoginInN9e != nil {
|
||||
ginx.NewRender(c).Message("ldap login failed: %v; n9e login failed: %v", err, errLoginInN9e)
|
||||
return
|
||||
}
|
||||
user.RolesLst = strings.Fields(user.Roles)
|
||||
} else {
|
||||
ginx.NewRender(c).Message(err)
|
||||
return
|
||||
user.RolesLst = strings.Fields(user.Roles)
|
||||
}
|
||||
} else {
|
||||
user, err = models.PassLogin(rt.Ctx, f.Username, authPassWord)
|
||||
ginx.Dangerous(err)
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
@@ -89,7 +92,7 @@ func (rt *Router) loginPost(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) logoutPost(c *gin.Context) {
|
||||
logger.Infof("username:%s login from:%s", c.GetString("username"), c.ClientIP())
|
||||
logger.Infof("username:%s logout from:%s", c.GetString("username"), c.ClientIP())
|
||||
metadata, err := rt.extractTokenMetadata(c.Request)
|
||||
if err != nil {
|
||||
ginx.NewRender(c, http.StatusBadRequest).Message("failed to parse jwt token")
|
||||
@@ -102,7 +105,18 @@ func (rt *Router) logoutPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message("")
|
||||
var logoutAddr string
|
||||
user := c.MustGet("user").(*models.User)
|
||||
switch user.Belong {
|
||||
case "oidc":
|
||||
logoutAddr = rt.Sso.OIDC.GetSsoLogoutAddr()
|
||||
case "cas":
|
||||
logoutAddr = rt.Sso.CAS.GetSsoLogoutAddr()
|
||||
case "oauth2":
|
||||
logoutAddr = rt.Sso.OAuth2.GetSsoLogoutAddr()
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(logoutAddr, nil)
|
||||
}
|
||||
|
||||
type refreshForm struct {
|
||||
@@ -230,7 +244,7 @@ func (rt *Router) loginCallback(c *gin.Context) {
|
||||
|
||||
ret, err := rt.Sso.OIDC.Callback(rt.Redis, c.Request.Context(), code, state)
|
||||
if err != nil {
|
||||
logger.Debugf("sso.callback() get ret %+v error %v", ret, err)
|
||||
logger.Errorf("sso_callback fail. code:%s, state:%s, get ret: %+v. error: %v", code, state, ret, err)
|
||||
ginx.NewRender(c).Data(CallbackOutput{}, err)
|
||||
return
|
||||
}
|
||||
@@ -240,39 +254,12 @@ func (rt *Router) loginCallback(c *gin.Context) {
|
||||
|
||||
if user != nil {
|
||||
if rt.Sso.OIDC.CoverAttributes {
|
||||
if ret.Nickname != "" {
|
||||
user.Nickname = ret.Nickname
|
||||
}
|
||||
|
||||
if ret.Email != "" {
|
||||
user.Email = ret.Email
|
||||
}
|
||||
|
||||
if ret.Phone != "" {
|
||||
user.Phone = ret.Phone
|
||||
}
|
||||
|
||||
user.UpdateAt = time.Now().Unix()
|
||||
user.Update(rt.Ctx, "email", "nickname", "phone", "update_at")
|
||||
updatedFields := user.UpdateSsoFields("oidc", ret.Nickname, ret.Phone, ret.Email)
|
||||
ginx.Dangerous(user.Update(rt.Ctx, "update_at", updatedFields...))
|
||||
}
|
||||
} else {
|
||||
now := time.Now().Unix()
|
||||
user = &models.User{
|
||||
Username: ret.Username,
|
||||
Password: "******",
|
||||
Nickname: ret.Nickname,
|
||||
Phone: ret.Phone,
|
||||
Email: ret.Email,
|
||||
Portrait: "",
|
||||
Roles: strings.Join(rt.Sso.OIDC.DefaultRoles, " "),
|
||||
RolesLst: rt.Sso.OIDC.DefaultRoles,
|
||||
Contacts: []byte("{}"),
|
||||
CreateAt: now,
|
||||
UpdateAt: now,
|
||||
CreateBy: "oidc",
|
||||
UpdateBy: "oidc",
|
||||
}
|
||||
|
||||
user = new(models.User)
|
||||
user.FullSsoFields("oidc", ret.Username, ret.Nickname, ret.Phone, ret.Email, rt.Sso.OIDC.DefaultRoles)
|
||||
// create user from oidc
|
||||
ginx.Dangerous(user.Add(rt.Ctx))
|
||||
}
|
||||
@@ -350,38 +337,12 @@ func (rt *Router) loginCallbackCas(c *gin.Context) {
|
||||
ginx.Dangerous(err)
|
||||
if user != nil {
|
||||
if rt.Sso.CAS.CoverAttributes {
|
||||
if ret.Nickname != "" {
|
||||
user.Nickname = ret.Nickname
|
||||
}
|
||||
|
||||
if ret.Email != "" {
|
||||
user.Email = ret.Email
|
||||
}
|
||||
|
||||
if ret.Phone != "" {
|
||||
user.Phone = ret.Phone
|
||||
}
|
||||
|
||||
user.UpdateAt = time.Now().Unix()
|
||||
ginx.Dangerous(user.Update(rt.Ctx, "email", "nickname", "phone", "update_at"))
|
||||
updatedFields := user.UpdateSsoFields("cas", ret.Nickname, ret.Phone, ret.Email)
|
||||
ginx.Dangerous(user.Update(rt.Ctx, "update_at", updatedFields...))
|
||||
}
|
||||
} else {
|
||||
now := time.Now().Unix()
|
||||
user = &models.User{
|
||||
Username: ret.Username,
|
||||
Password: "******",
|
||||
Nickname: ret.Nickname,
|
||||
Portrait: "",
|
||||
Roles: strings.Join(rt.Sso.CAS.DefaultRoles, " "),
|
||||
RolesLst: rt.Sso.CAS.DefaultRoles,
|
||||
Contacts: []byte("{}"),
|
||||
Phone: ret.Phone,
|
||||
Email: ret.Email,
|
||||
CreateAt: now,
|
||||
UpdateAt: now,
|
||||
CreateBy: "CAS",
|
||||
UpdateBy: "CAS",
|
||||
}
|
||||
user = new(models.User)
|
||||
user.FullSsoFields("cas", ret.Username, ret.Nickname, ret.Phone, ret.Email, rt.Sso.CAS.DefaultRoles)
|
||||
// create user from cas
|
||||
ginx.Dangerous(user.Add(rt.Ctx))
|
||||
}
|
||||
@@ -452,39 +413,12 @@ func (rt *Router) loginCallbackOAuth(c *gin.Context) {
|
||||
|
||||
if user != nil {
|
||||
if rt.Sso.OAuth2.CoverAttributes {
|
||||
if ret.Nickname != "" {
|
||||
user.Nickname = ret.Nickname
|
||||
}
|
||||
|
||||
if ret.Email != "" {
|
||||
user.Email = ret.Email
|
||||
}
|
||||
|
||||
if ret.Phone != "" {
|
||||
user.Phone = ret.Phone
|
||||
}
|
||||
|
||||
user.UpdateAt = time.Now().Unix()
|
||||
user.Update(rt.Ctx, "email", "nickname", "phone", "update_at")
|
||||
updatedFields := user.UpdateSsoFields("oauth2", ret.Nickname, ret.Phone, ret.Email)
|
||||
ginx.Dangerous(user.Update(rt.Ctx, "update_at", updatedFields...))
|
||||
}
|
||||
} else {
|
||||
now := time.Now().Unix()
|
||||
user = &models.User{
|
||||
Username: ret.Username,
|
||||
Password: "******",
|
||||
Nickname: ret.Nickname,
|
||||
Phone: ret.Phone,
|
||||
Email: ret.Email,
|
||||
Portrait: "",
|
||||
Roles: strings.Join(rt.Sso.OAuth2.DefaultRoles, " "),
|
||||
RolesLst: rt.Sso.OAuth2.DefaultRoles,
|
||||
Contacts: []byte("{}"),
|
||||
CreateAt: now,
|
||||
UpdateAt: now,
|
||||
CreateBy: "oauth2",
|
||||
UpdateBy: "oauth2",
|
||||
}
|
||||
|
||||
user = new(models.User)
|
||||
user.FullSsoFields("oauth2", ret.Username, ret.Nickname, ret.Phone, ret.Email, rt.Sso.OAuth2.DefaultRoles)
|
||||
// create user from oidc
|
||||
ginx.Dangerous(user.Add(rt.Ctx))
|
||||
}
|
||||
@@ -515,10 +449,23 @@ type SsoConfigOutput struct {
|
||||
}
|
||||
|
||||
func (rt *Router) ssoConfigNameGet(c *gin.Context) {
|
||||
var oidcDisplayName, casDisplayName, oauthDisplayName string
|
||||
if rt.Sso.OIDC != nil {
|
||||
oidcDisplayName = rt.Sso.OIDC.GetDisplayName()
|
||||
}
|
||||
|
||||
if rt.Sso.CAS != nil {
|
||||
casDisplayName = rt.Sso.CAS.GetDisplayName()
|
||||
}
|
||||
|
||||
if rt.Sso.OAuth2 != nil {
|
||||
oauthDisplayName = rt.Sso.OAuth2.GetDisplayName()
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(SsoConfigOutput{
|
||||
OidcDisplayName: rt.Sso.OIDC.GetDisplayName(),
|
||||
CasDisplayName: rt.Sso.CAS.GetDisplayName(),
|
||||
OauthDisplayName: rt.Sso.OAuth2.GetDisplayName(),
|
||||
OidcDisplayName: oidcDisplayName,
|
||||
CasDisplayName: casDisplayName,
|
||||
OauthDisplayName: oauthDisplayName,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
@@ -543,8 +490,7 @@ func (rt *Router) ssoConfigUpdate(c *gin.Context) {
|
||||
var config oidcx.Config
|
||||
err := toml.Unmarshal([]byte(f.Content), &config)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
err = rt.Sso.OIDC.Reload(config)
|
||||
rt.Sso.OIDC, err = oidcx.New(config)
|
||||
ginx.Dangerous(err)
|
||||
case "CAS":
|
||||
var config cas.Config
|
||||
@@ -568,7 +514,7 @@ type RSAConfigOutput struct {
|
||||
|
||||
func (rt *Router) rsaConfigGet(c *gin.Context) {
|
||||
publicKey := ""
|
||||
if rt.HTTP.RSA.OpenRSA {
|
||||
if len(rt.HTTP.RSA.RSAPublicKey) > 0 {
|
||||
publicKey = base64.StdEncoding.EncodeToString(rt.HTTP.RSA.RSAPublicKey)
|
||||
}
|
||||
ginx.NewRender(c).Data(RSAConfigOutput{
|
||||
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/common"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
// Return all, front-end search and paging
|
||||
@@ -19,6 +21,31 @@ func (rt *Router) alertMuteGetsByBG(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
func (rt *Router) alertMuteGetsByGids(c *gin.Context) {
|
||||
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
|
||||
if len(gids) > 0 {
|
||||
for _, gid := range gids {
|
||||
rt.bgroCheck(c, gid)
|
||||
}
|
||||
} else {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
if !me.IsAdmin() {
|
||||
var err error
|
||||
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if len(gids) == 0 {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lst, err := models.AlertMuteGetsByBGIds(rt.Ctx, gids)
|
||||
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
func (rt *Router) alertMuteGets(c *gin.Context) {
|
||||
prods := strings.Fields(ginx.QueryStr(c, "prods", ""))
|
||||
bgid := ginx.QueryInt64(c, "bgid", -1)
|
||||
@@ -29,16 +56,41 @@ func (rt *Router) alertMuteGets(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) alertMuteAdd(c *gin.Context) {
|
||||
|
||||
var f models.AlertMute
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
username := c.MustGet("username").(string)
|
||||
f.CreateBy = username
|
||||
f.GroupId = ginx.UrlParamInt64(c, "id")
|
||||
|
||||
ginx.NewRender(c).Message(f.Add(rt.Ctx))
|
||||
}
|
||||
|
||||
// Preview events (alert_cur_event) that match the mute strategy based on the following criteria:
|
||||
// business group ID (group_id, group_id), product (prod, rule_prod),
|
||||
// alert event severity (severities, severity), and event tags (tags, tags).
|
||||
// For products of type not 'host', also consider the category (cate, cate) and datasource ID (datasource_ids, datasource_id).
|
||||
func (rt *Router) alertMutePreview(c *gin.Context) {
|
||||
//Generally the match of events would be less.
|
||||
|
||||
var f models.AlertMute
|
||||
ginx.BindJSON(c, &f)
|
||||
f.GroupId = ginx.UrlParamInt64(c, "id")
|
||||
ginx.Dangerous(f.Verify()) //verify and parse tags json to ITags
|
||||
events, err := models.AlertCurEventGetsFromAlertMute(rt.Ctx, &f)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
matchEvents := make([]*models.AlertCurEvent, 0, len(events))
|
||||
for i := 0; i < len(events); i++ {
|
||||
events[i].DB2Mem()
|
||||
if common.MatchTags(events[i].TagsMap, f.ITags) {
|
||||
matchEvents = append(matchEvents, events[i])
|
||||
}
|
||||
}
|
||||
ginx.NewRender(c).Data(matchEvents, err)
|
||||
|
||||
}
|
||||
|
||||
func (rt *Router) alertMuteAddByService(c *gin.Context) {
|
||||
var f models.AlertMute
|
||||
ginx.BindJSON(c, &f)
|
||||
@@ -54,6 +106,14 @@ func (rt *Router) alertMuteDel(c *gin.Context) {
|
||||
ginx.NewRender(c).Message(models.AlertMuteDel(rt.Ctx, f.Ids))
|
||||
}
|
||||
|
||||
// alertMuteGet returns the alert mute by ID
|
||||
func (rt *Router) alertMuteGet(c *gin.Context) {
|
||||
amid := ginx.UrlParamInt64(c, "amid")
|
||||
am, err := models.AlertMuteGetById(rt.Ctx, amid)
|
||||
am.DB2FE()
|
||||
ginx.NewRender(c).Data(am, err)
|
||||
}
|
||||
|
||||
func (rt *Router) alertMutePutByFE(c *gin.Context) {
|
||||
var f models.AlertMute
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
@@ -92,6 +92,10 @@ func (rt *Router) jwtAuth() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *Router) Auth() gin.HandlerFunc {
|
||||
return rt.auth()
|
||||
}
|
||||
|
||||
func (rt *Router) auth() gin.HandlerFunc {
|
||||
if rt.HTTP.ProxyAuth.Enable {
|
||||
return rt.proxyAuth()
|
||||
@@ -120,6 +124,10 @@ func (rt *Router) jwtMock() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *Router) User() gin.HandlerFunc {
|
||||
return rt.user()
|
||||
}
|
||||
|
||||
func (rt *Router) user() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userid := c.MustGet("userid").(int64)
|
||||
@@ -174,6 +182,10 @@ func (rt *Router) bgro() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
// bgrw 逐步要被干掉,不安全
|
||||
func (rt *Router) Bgrw() gin.HandlerFunc {
|
||||
return rt.bgrw()
|
||||
}
|
||||
|
||||
func (rt *Router) bgrw() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
@@ -233,6 +245,10 @@ func (rt *Router) bgroCheck(c *gin.Context, bgid int64) {
|
||||
c.Set("busi_group", bg)
|
||||
}
|
||||
|
||||
func (rt *Router) Perm(operation string) gin.HandlerFunc {
|
||||
return rt.perm(operation)
|
||||
}
|
||||
|
||||
func (rt *Router) perm(operation string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
|
||||
@@ -2,15 +2,19 @@ package router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/aconf"
|
||||
"github.com/ccfos/nightingale/v6/alert/sender"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/ccfos/nightingale/v6/pkg/tplx"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
func (rt *Router) webhookGets(c *gin.Context) {
|
||||
@@ -41,8 +45,8 @@ func (rt *Router) webhookPuts(c *gin.Context) {
|
||||
|
||||
data, err := json.Marshal(webhooks)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.WEBHOOKKEY, string(data)))
|
||||
username := c.MustGet("username").(string)
|
||||
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, models.WEBHOOKKEY, string(data), username))
|
||||
}
|
||||
|
||||
func (rt *Router) notifyScriptGet(c *gin.Context) {
|
||||
@@ -65,8 +69,8 @@ func (rt *Router) notifyScriptPut(c *gin.Context) {
|
||||
|
||||
data, err := json.Marshal(notifyScript)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.NOTIFYSCRIPT, string(data)))
|
||||
username := c.MustGet("username").(string)
|
||||
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, models.NOTIFYSCRIPT, string(data), username))
|
||||
}
|
||||
|
||||
func (rt *Router) notifyChannelGets(c *gin.Context) {
|
||||
@@ -101,8 +105,8 @@ func (rt *Router) notifyChannelPuts(c *gin.Context) {
|
||||
|
||||
data, err := json.Marshal(notifyChannels)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.NOTIFYCHANNEL, string(data)))
|
||||
username := c.MustGet("username").(string)
|
||||
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, models.NOTIFYCHANNEL, string(data), username))
|
||||
}
|
||||
|
||||
func (rt *Router) notifyContactGets(c *gin.Context) {
|
||||
@@ -137,8 +141,8 @@ func (rt *Router) notifyContactPuts(c *gin.Context) {
|
||||
|
||||
data, err := json.Marshal(notifyContacts)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.NOTIFYCONTACT, string(data)))
|
||||
username := c.MustGet("username").(string)
|
||||
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, models.NOTIFYCONTACT, string(data), username))
|
||||
}
|
||||
|
||||
func (rt *Router) notifyConfigGet(c *gin.Context) {
|
||||
@@ -158,36 +162,65 @@ func (rt *Router) notifyConfigGet(c *gin.Context) {
|
||||
func (rt *Router) notifyConfigPut(c *gin.Context) {
|
||||
var f models.Configs
|
||||
ginx.BindJSON(c, &f)
|
||||
userVariableMap := rt.NotifyConfigCache.ConfigCache.Get()
|
||||
text := tplx.ReplaceTemplateUseText(f.Ckey, f.Cval, userVariableMap)
|
||||
switch f.Ckey {
|
||||
case models.SMTP:
|
||||
var smtp aconf.SMTPConfig
|
||||
err := toml.Unmarshal([]byte(f.Cval), &smtp)
|
||||
ginx.Dangerous(err)
|
||||
case models.IBEX:
|
||||
var ibex aconf.Ibex
|
||||
err := toml.Unmarshal([]byte(f.Cval), &ibex)
|
||||
err := toml.Unmarshal([]byte(text), &smtp)
|
||||
ginx.Dangerous(err)
|
||||
default:
|
||||
ginx.Bomb(200, "key %s can not modify", f.Ckey)
|
||||
}
|
||||
|
||||
err := models.ConfigsSet(rt.Ctx, f.Ckey, f.Cval)
|
||||
if err != nil {
|
||||
ginx.Bomb(200, err.Error())
|
||||
}
|
||||
|
||||
username := c.MustGet("username").(string)
|
||||
//insert or update build-in config
|
||||
ginx.Dangerous(models.ConfigsSetWithUname(rt.Ctx, f.Ckey, f.Cval, username))
|
||||
if f.Ckey == models.SMTP {
|
||||
// 重置邮件发送器
|
||||
var smtp aconf.SMTPConfig
|
||||
err := toml.Unmarshal([]byte(f.Cval), &smtp)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if smtp.Host == "" || smtp.Port == 0 {
|
||||
ginx.Bomb(200, "smtp host or port can not be empty")
|
||||
}
|
||||
|
||||
smtp, errSmtp := SmtpValidate(text)
|
||||
ginx.Dangerous(errSmtp)
|
||||
go sender.RestartEmailSender(smtp)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(nil)
|
||||
}
|
||||
func SmtpValidate(text string) (aconf.SMTPConfig, error) {
|
||||
var smtp aconf.SMTPConfig
|
||||
var err error
|
||||
|
||||
err = toml.Unmarshal([]byte(text), &smtp)
|
||||
if err != nil {
|
||||
return smtp, err
|
||||
}
|
||||
if smtp.Host == "" || smtp.Port == 0 {
|
||||
return smtp, fmt.Errorf("smtp host or port can not be empty")
|
||||
}
|
||||
return smtp, err
|
||||
}
|
||||
|
||||
type form struct {
|
||||
models.Configs
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// After configuring the aconf.SMTPConfig, users can choose to perform a test. In this test, the function attempts to send an email
|
||||
func (rt *Router) attemptSendEmail(c *gin.Context) {
|
||||
var f form
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
if f.Email = strings.TrimSpace(f.Email); f.Email == "" || !str.IsMail(f.Email) {
|
||||
ginx.Bomb(200, "email(%s) invalid", f.Email)
|
||||
}
|
||||
|
||||
if f.Ckey != models.SMTP {
|
||||
ginx.Bomb(200, "config(%v) invalid", f)
|
||||
}
|
||||
userVariableMap := rt.NotifyConfigCache.ConfigCache.Get()
|
||||
text := tplx.ReplaceTemplateUseText(f.Ckey, f.Cval, userVariableMap)
|
||||
smtp, err := SmtpValidate(text)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Message(sender.SendEmail("Email test", "email content", []string{f.Email}, smtp))
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/center/cconf"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
@@ -21,7 +22,7 @@ func (rt *Router) notifyTplGets(c *gin.Context) {
|
||||
for _, channel := range models.DefaultChannels {
|
||||
m[channel] = struct{}{}
|
||||
}
|
||||
m["mailsubject"] = struct{}{}
|
||||
m[models.EmailSubject] = struct{}{}
|
||||
|
||||
lst, err := models.NotifyTplGets(rt.Ctx)
|
||||
for i := 0; i < len(lst); i++ {
|
||||
@@ -34,10 +35,22 @@ func (rt *Router) notifyTplGets(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) notifyTplUpdateContent(c *gin.Context) {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
var f models.NotifyTpl
|
||||
ginx.BindJSON(c, &f)
|
||||
ginx.Dangerous(templateValidate(f))
|
||||
|
||||
notifyTpl, err := models.NotifyTplGet(rt.Ctx, f.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if notifyTpl.CreateBy != user.Username && !user.IsAdmin() {
|
||||
ginx.Bomb(403, "no permission")
|
||||
}
|
||||
|
||||
f.UpdateAt = time.Now().Unix()
|
||||
f.UpdateBy = user.Username
|
||||
|
||||
ginx.NewRender(c).Message(f.UpdateContent(rt.Ctx))
|
||||
}
|
||||
|
||||
@@ -45,8 +58,27 @@ func (rt *Router) notifyTplUpdate(c *gin.Context) {
|
||||
var f models.NotifyTpl
|
||||
ginx.BindJSON(c, &f)
|
||||
ginx.Dangerous(templateValidate(f))
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
ginx.NewRender(c).Message(f.Update(rt.Ctx))
|
||||
notifyTpl, err := models.NotifyTplGet(rt.Ctx, f.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if notifyTpl.CreateBy != user.Username && !user.IsAdmin() {
|
||||
ginx.Bomb(403, "no permission")
|
||||
}
|
||||
|
||||
// get the count of the same channel and name but different id
|
||||
count, err := models.Count(models.DB(rt.Ctx).Model(&models.NotifyTpl{}).Where("channel = ? or name = ? and id <> ?", f.Channel, f.Name, f.Id))
|
||||
ginx.Dangerous(err)
|
||||
if count != 0 {
|
||||
ginx.Bomb(200, "Refuse to create duplicate channel or name")
|
||||
}
|
||||
|
||||
notifyTpl.UpdateAt = time.Now().Unix()
|
||||
notifyTpl.UpdateBy = user.Username
|
||||
notifyTpl.Name = f.Name
|
||||
|
||||
ginx.NewRender(c).Message(notifyTpl.Update(rt.Ctx))
|
||||
}
|
||||
|
||||
func templateValidate(f models.NotifyTpl) error {
|
||||
@@ -132,7 +164,7 @@ func (rt *Router) notifyTplAdd(c *gin.Context) {
|
||||
f.Channel = strings.TrimSpace(f.Channel)
|
||||
ginx.Dangerous(templateValidate(f))
|
||||
|
||||
count, err := models.NotifyTplCountByChannel(rt.Ctx, f.Channel)
|
||||
count, err := models.Count(models.DB(rt.Ctx).Model(&models.NotifyTpl{}).Where("channel = ? or name = ?", f.Channel, f.Name))
|
||||
ginx.Dangerous(err)
|
||||
if count != 0 {
|
||||
ginx.Bomb(200, "Refuse to create duplicate channel(unique)")
|
||||
@@ -144,5 +176,14 @@ func (rt *Router) notifyTplAdd(c *gin.Context) {
|
||||
func (rt *Router) notifyTplDel(c *gin.Context) {
|
||||
f := new(models.NotifyTpl)
|
||||
id := ginx.UrlParamInt64(c, "id")
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
notifyTpl, err := models.NotifyTplGet(rt.Ctx, id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if notifyTpl.CreateBy != user.Username && !user.IsAdmin() {
|
||||
ginx.Bomb(403, "no permission")
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(f.NotifyTplDelete(rt.Ctx, id))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
@@ -12,34 +13,39 @@ import (
|
||||
"time"
|
||||
|
||||
pkgprom "github.com/ccfos/nightingale/v6/pkg/prom"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type queryFormItem struct {
|
||||
type QueryFormItem struct {
|
||||
Start int64 `json:"start" binding:"required"`
|
||||
End int64 `json:"end" binding:"required"`
|
||||
Step int64 `json:"step" binding:"required"`
|
||||
Query string `json:"query" binding:"required"`
|
||||
}
|
||||
|
||||
type batchQueryForm struct {
|
||||
type BatchQueryForm struct {
|
||||
DatasourceId int64 `json:"datasource_id" binding:"required"`
|
||||
Queries []queryFormItem `json:"queries" binding:"required"`
|
||||
Queries []QueryFormItem `json:"queries" binding:"required"`
|
||||
}
|
||||
|
||||
func (rt *Router) promBatchQueryRange(c *gin.Context) {
|
||||
var f batchQueryForm
|
||||
var f BatchQueryForm
|
||||
ginx.Dangerous(c.BindJSON(&f))
|
||||
|
||||
lst, err := PromBatchQueryRange(rt.PromClients, f)
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
func PromBatchQueryRange(pc *prom.PromClientMap, f BatchQueryForm) ([]model.Value, error) {
|
||||
var lst []model.Value
|
||||
|
||||
cli := rt.PromClients.GetCli(f.DatasourceId)
|
||||
cli := pc.GetCli(f.DatasourceId)
|
||||
if cli == nil {
|
||||
logger.Warningf("no such datasource id: %d", f.DatasourceId)
|
||||
ginx.NewRender(c).Data(lst, nil)
|
||||
return
|
||||
return lst, fmt.Errorf("no such datasource id: %d", f.DatasourceId)
|
||||
}
|
||||
|
||||
for _, item := range f.Queries {
|
||||
@@ -50,15 +56,16 @@ func (rt *Router) promBatchQueryRange(c *gin.Context) {
|
||||
}
|
||||
|
||||
resp, _, err := cli.QueryRange(context.Background(), item.Query, r)
|
||||
ginx.Dangerous(err)
|
||||
if err != nil {
|
||||
return lst, err
|
||||
}
|
||||
|
||||
lst = append(lst, resp)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(lst, nil)
|
||||
return lst, nil
|
||||
}
|
||||
|
||||
type batchInstantForm struct {
|
||||
type BatchInstantForm struct {
|
||||
DatasourceId int64 `json:"datasource_id" binding:"required"`
|
||||
Queries []InstantFormItem `json:"queries" binding:"required"`
|
||||
}
|
||||
@@ -69,26 +76,31 @@ type InstantFormItem struct {
|
||||
}
|
||||
|
||||
func (rt *Router) promBatchQueryInstant(c *gin.Context) {
|
||||
var f batchInstantForm
|
||||
var f BatchInstantForm
|
||||
ginx.Dangerous(c.BindJSON(&f))
|
||||
|
||||
lst, err := PromBatchQueryInstant(rt.PromClients, f)
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
func PromBatchQueryInstant(pc *prom.PromClientMap, f BatchInstantForm) ([]model.Value, error) {
|
||||
var lst []model.Value
|
||||
|
||||
cli := rt.PromClients.GetCli(f.DatasourceId)
|
||||
cli := pc.GetCli(f.DatasourceId)
|
||||
if cli == nil {
|
||||
logger.Warningf("no such datasource id: %d", f.DatasourceId)
|
||||
ginx.NewRender(c).Data(lst, nil)
|
||||
return
|
||||
return lst, fmt.Errorf("no such datasource id: %d", f.DatasourceId)
|
||||
}
|
||||
|
||||
for _, item := range f.Queries {
|
||||
resp, _, err := cli.Query(context.Background(), item.Query, time.Unix(item.Time, 0))
|
||||
ginx.Dangerous(err)
|
||||
if err != nil {
|
||||
return lst, err
|
||||
}
|
||||
|
||||
lst = append(lst, resp)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(lst, nil)
|
||||
return lst, nil
|
||||
}
|
||||
|
||||
func (rt *Router) dsProxy(c *gin.Context) {
|
||||
@@ -164,13 +176,24 @@ func (rt *Router) dsProxy(c *gin.Context) {
|
||||
transportPut(dsId, ds.UpdatedAt, transport)
|
||||
}
|
||||
|
||||
modifyResponse := func(r *http.Response) error {
|
||||
if r.StatusCode == http.StatusUnauthorized {
|
||||
logger.Warningf("proxy path:%s unauthorized access ", c.Request.URL.Path)
|
||||
return fmt.Errorf("unauthorized access")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
proxy := &httputil.ReverseProxy{
|
||||
Director: director,
|
||||
Transport: transport,
|
||||
ErrorHandler: errFunc,
|
||||
Director: director,
|
||||
Transport: transport,
|
||||
ErrorHandler: errFunc,
|
||||
ModifyResponse: modifyResponse,
|
||||
}
|
||||
|
||||
proxy.ServeHTTP(c.Writer, c.Request)
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
func (rt *Router) recordingRuleGets(c *gin.Context) {
|
||||
@@ -19,6 +20,30 @@ func (rt *Router) recordingRuleGets(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(ars, err)
|
||||
}
|
||||
|
||||
func (rt *Router) recordingRuleGetsByGids(c *gin.Context) {
|
||||
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
|
||||
if len(gids) > 0 {
|
||||
for _, gid := range gids {
|
||||
rt.bgroCheck(c, gid)
|
||||
}
|
||||
} else {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
if !me.IsAdmin() {
|
||||
var err error
|
||||
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if len(gids) == 0 {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ars, err := models.RecordingRuleGetsByBGIds(rt.Ctx, gids)
|
||||
ginx.NewRender(c).Data(ars, err)
|
||||
}
|
||||
|
||||
func (rt *Router) recordingRuleGetsByService(c *gin.Context) {
|
||||
ars, err := models.RecordingRuleEnabledGets(rt.Ctx)
|
||||
ginx.NewRender(c).Data(ars, err)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/center/cconf"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -17,6 +18,15 @@ func (rt *Router) rolesGets(c *gin.Context) {
|
||||
|
||||
func (rt *Router) permsGets(c *gin.Context) {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
if user.IsAdmin() {
|
||||
var lst []string
|
||||
for _, ops := range cconf.Operations.Ops {
|
||||
lst = append(lst, ops.Ops...)
|
||||
}
|
||||
ginx.NewRender(c).Data(lst, nil)
|
||||
return
|
||||
}
|
||||
|
||||
lst, err := models.OperationsOfRole(rt.Ctx, strings.Fields(user.Roles))
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ package router
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/center/cconf"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/i18n"
|
||||
)
|
||||
|
||||
func (rt *Router) operationOfRole(c *gin.Context) {
|
||||
@@ -16,6 +18,15 @@ func (rt *Router) operationOfRole(c *gin.Context) {
|
||||
ginx.Bomb(http.StatusOK, "role not found")
|
||||
}
|
||||
|
||||
if role.Name == "Admin" {
|
||||
var lst []string
|
||||
for _, ops := range cconf.Operations.Ops {
|
||||
lst = append(lst, ops.Ops...)
|
||||
}
|
||||
ginx.NewRender(c).Data(lst, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ops, err := models.OperationsOfRole(rt.Ctx, []string{role.Name})
|
||||
ginx.NewRender(c).Data(ops, err)
|
||||
}
|
||||
@@ -39,5 +50,11 @@ func (rt *Router) roleBindOperation(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) operations(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(rt.Operations.Ops, nil)
|
||||
var ops []cconf.Ops
|
||||
for _, v := range rt.Operations.Ops {
|
||||
v.Cname = i18n.Sprintf(c.GetHeader("X-Language"), v.Cname)
|
||||
ops = append(ops, v)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(ops, nil)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/flashduty"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ormx"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -29,6 +30,11 @@ func (rt *Router) selfProfilePut(c *gin.Context) {
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
user := c.MustGet("user").(*models.User)
|
||||
oldInfo := models.User{
|
||||
Username: user.Username,
|
||||
Phone: user.Phone,
|
||||
Email: user.Email,
|
||||
}
|
||||
user.Nickname = f.Nickname
|
||||
user.Phone = f.Phone
|
||||
user.Email = f.Email
|
||||
@@ -36,6 +42,10 @@ func (rt *Router) selfProfilePut(c *gin.Context) {
|
||||
user.Contacts = f.Contacts
|
||||
user.UpdateBy = user.Username
|
||||
|
||||
if flashduty.NeedSyncUser(rt.Ctx) {
|
||||
flashduty.UpdateUser(rt.Ctx, oldInfo, f.Email, f.Phone)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(user.UpdateAllFields(rt.Ctx))
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func (rt *Router) serverHeartbeat(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) serversActive(c *gin.Context) {
|
||||
datasourceId := ginx.QueryInt64(c, "dsid")
|
||||
datasourceId := ginx.QueryInt64(c, "dsid", 0)
|
||||
engineName := ginx.QueryStr(c, "engine_name", "")
|
||||
if engineName != "" {
|
||||
servers, err := models.AlertingEngineGetsInstances(rt.Ctx, "engine_cluster = ? and clock > ?", engineName, time.Now().Unix()-30)
|
||||
@@ -35,6 +35,10 @@ func (rt *Router) serversActive(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if datasourceId == 0 {
|
||||
ginx.NewRender(c).Message("dsid is required")
|
||||
return
|
||||
}
|
||||
servers, err := models.AlertingEngineGetsInstances(rt.Ctx, "datasource_id = ? and clock > ?", datasourceId, time.Now().Unix()-30)
|
||||
ginx.NewRender(c).Data(servers, err)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
type TargetQuery struct {
|
||||
@@ -42,32 +43,30 @@ func (rt *Router) targetGetsByHostFilter(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) targetGets(c *gin.Context) {
|
||||
bgid := ginx.QueryInt64(c, "bgid", -1)
|
||||
bgids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
|
||||
query := ginx.QueryStr(c, "query", "")
|
||||
limit := ginx.QueryInt(c, "limit", 30)
|
||||
downtime := ginx.QueryInt64(c, "downtime", 0)
|
||||
dsIds := queryDatasourceIds(c)
|
||||
|
||||
var bgids []int64
|
||||
var err error
|
||||
if bgid == -1 {
|
||||
// 全部对象的情况,找到用户有权限的业务组
|
||||
if len(bgids) == 0 {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
userGroupIds, err := models.MyGroupIds(rt.Ctx, user.Id)
|
||||
ginx.Dangerous(err)
|
||||
if !user.IsAdmin() {
|
||||
// 如果是非 admin 用户,全部对象的情况,找到用户有权限的业务组
|
||||
var err error
|
||||
bgids, err = models.MyBusiGroupIds(rt.Ctx, user.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
bgids, err = models.BusiGroupIds(rt.Ctx, userGroupIds)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
// 将未分配业务组的对象也加入到列表中
|
||||
bgids = append(bgids, 0)
|
||||
} else {
|
||||
bgids = append(bgids, bgid)
|
||||
// 将未分配业务组的对象也加入到列表中
|
||||
bgids = append(bgids, 0)
|
||||
}
|
||||
}
|
||||
|
||||
total, err := models.TargetTotal(rt.Ctx, bgids, dsIds, query)
|
||||
total, err := models.TargetTotal(rt.Ctx, bgids, dsIds, query, downtime)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
list, err := models.TargetGets(rt.Ctx, bgids, dsIds, query, limit, ginx.Offset(c, limit))
|
||||
list, err := models.TargetGets(rt.Ctx, bgids, dsIds, query, downtime, limit, ginx.Offset(c, limit))
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if err == nil {
|
||||
@@ -78,6 +77,12 @@ func (rt *Router) targetGets(c *gin.Context) {
|
||||
for i := 0; i < len(list); i++ {
|
||||
ginx.Dangerous(list[i].FillGroup(rt.Ctx, cache))
|
||||
keys = append(keys, models.WrapIdent(list[i].Ident))
|
||||
|
||||
if now.Unix()-list[i].UpdateAt < 60 {
|
||||
list[i].TargetUp = 2
|
||||
} else if now.Unix()-list[i].UpdateAt < 180 {
|
||||
list[i].TargetUp = 1
|
||||
}
|
||||
}
|
||||
|
||||
if len(keys) > 0 {
|
||||
@@ -103,12 +108,6 @@ func (rt *Router) targetGets(c *gin.Context) {
|
||||
// 未上报过元数据的主机,cpuNum默认为-1, 用于前端展示 unknown
|
||||
list[i].CpuNum = -1
|
||||
}
|
||||
|
||||
if now.Unix()-list[i].UnixTime/1000 < 60 {
|
||||
list[i].TargetUp = 2
|
||||
} else if now.Unix()-list[i].UnixTime/1000 < 180 {
|
||||
list[i].TargetUp = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +119,27 @@ func (rt *Router) targetGets(c *gin.Context) {
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) targetExtendInfoByIdent(c *gin.Context) {
|
||||
ident := ginx.QueryStr(c, "ident", "")
|
||||
key := models.WrapExtendIdent(ident)
|
||||
vals := storage.MGet(context.Background(), rt.Redis, []string{key})
|
||||
if len(vals) > 0 {
|
||||
extInfo := string(vals[0])
|
||||
if extInfo == "null" {
|
||||
extInfo = ""
|
||||
}
|
||||
ginx.NewRender(c).Data(gin.H{
|
||||
"extend_info": extInfo,
|
||||
"ident": ident,
|
||||
}, nil)
|
||||
return
|
||||
}
|
||||
ginx.NewRender(c).Data(gin.H{
|
||||
"extend_info": "",
|
||||
"ident": ident,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) targetGetsByService(c *gin.Context) {
|
||||
lst, err := models.TargetGetsAll(rt.Ctx)
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
@@ -312,7 +332,11 @@ func (rt *Router) targetUpdateBgid(c *gin.Context) {
|
||||
|
||||
// 机器里边存在未归组的,登录用户就需要是admin
|
||||
if len(orphans) > 0 && !user.IsAdmin() {
|
||||
ginx.Bomb(http.StatusForbidden, "No permission. Only admin can assign BG")
|
||||
can, err := user.CheckPerm(rt.Ctx, "/targets/bind")
|
||||
ginx.Dangerous(err)
|
||||
if !can {
|
||||
ginx.Bomb(http.StatusForbidden, "No permission. Only admin can assign BG")
|
||||
}
|
||||
}
|
||||
|
||||
reBelongs, err := models.IdentsFilter(rt.Ctx, f.Idents, "group_id > ?", 0)
|
||||
@@ -366,3 +390,21 @@ func (rt *Router) checkTargetPerm(c *gin.Context, idents []string) {
|
||||
ginx.Bomb(http.StatusForbidden, "No permission to operate the targets: %s", strings.Join(nopri, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *Router) targetsOfAlertRule(c *gin.Context) {
|
||||
engineName := ginx.QueryStr(c, "engine_name", "")
|
||||
m, err := models.GetTargetsOfHostAlertRule(rt.Ctx, engineName)
|
||||
ret := make(map[string]map[int64][]string)
|
||||
for en, v := range m {
|
||||
if en != engineName {
|
||||
continue
|
||||
}
|
||||
|
||||
ret[en] = make(map[int64][]string)
|
||||
for rid, idents := range v {
|
||||
ret[en][rid] = idents
|
||||
}
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(ret, err)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert/sender"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -30,10 +27,55 @@ func (rt *Router) taskGets(c *gin.Context) {
|
||||
|
||||
beginTime := time.Now().Unix() - days*24*3600
|
||||
|
||||
total, err := models.TaskRecordTotal(rt.Ctx, bgid, beginTime, creator, query)
|
||||
total, err := models.TaskRecordTotal(rt.Ctx, []int64{bgid}, beginTime, creator, query)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
list, err := models.TaskRecordGets(rt.Ctx, bgid, beginTime, creator, query, limit, ginx.Offset(c, limit))
|
||||
list, err := models.TaskRecordGets(rt.Ctx, []int64{bgid}, beginTime, creator, query, limit, ginx.Offset(c, limit))
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Data(gin.H{
|
||||
"total": total,
|
||||
"list": list,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) taskGetsByGids(c *gin.Context) {
|
||||
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
|
||||
if len(gids) > 0 {
|
||||
for _, gid := range gids {
|
||||
rt.bgroCheck(c, gid)
|
||||
}
|
||||
} else {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
if !me.IsAdmin() {
|
||||
var err error
|
||||
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if len(gids) == 0 {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mine := ginx.QueryBool(c, "mine", false)
|
||||
days := ginx.QueryInt64(c, "days", 7)
|
||||
limit := ginx.QueryInt(c, "limit", 20)
|
||||
query := ginx.QueryStr(c, "query", "")
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
creator := ""
|
||||
if mine {
|
||||
creator = user.Username
|
||||
}
|
||||
|
||||
beginTime := time.Now().Unix() - days*24*3600
|
||||
|
||||
total, err := models.TaskRecordTotal(rt.Ctx, gids, beginTime, creator, query)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
list, err := models.TaskRecordGets(rt.Ctx, gids, beginTime, creator, query, limit, ginx.Offset(c, limit))
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Data(gin.H{
|
||||
@@ -56,70 +98,6 @@ type taskForm struct {
|
||||
Hosts []string `json:"hosts" binding:"required"`
|
||||
}
|
||||
|
||||
func (f *taskForm) Verify() error {
|
||||
if f.Batch < 0 {
|
||||
return fmt.Errorf("arg(batch) should be nonnegative")
|
||||
}
|
||||
|
||||
if f.Tolerance < 0 {
|
||||
return fmt.Errorf("arg(tolerance) should be nonnegative")
|
||||
}
|
||||
|
||||
if f.Timeout < 0 {
|
||||
return fmt.Errorf("arg(timeout) should be nonnegative")
|
||||
}
|
||||
|
||||
if f.Timeout > 3600*24 {
|
||||
return fmt.Errorf("arg(timeout) longer than one day")
|
||||
}
|
||||
|
||||
if f.Timeout == 0 {
|
||||
f.Timeout = 30
|
||||
}
|
||||
|
||||
f.Pause = strings.Replace(f.Pause, ",", ",", -1)
|
||||
f.Pause = strings.Replace(f.Pause, " ", "", -1)
|
||||
f.Args = strings.Replace(f.Args, ",", ",", -1)
|
||||
|
||||
if f.Title == "" {
|
||||
return fmt.Errorf("arg(title) is required")
|
||||
}
|
||||
|
||||
if str.Dangerous(f.Title) {
|
||||
return fmt.Errorf("arg(title) is dangerous")
|
||||
}
|
||||
|
||||
if f.Script == "" {
|
||||
return fmt.Errorf("arg(script) is required")
|
||||
}
|
||||
|
||||
if str.Dangerous(f.Args) {
|
||||
return fmt.Errorf("arg(args) is dangerous")
|
||||
}
|
||||
|
||||
if str.Dangerous(f.Pause) {
|
||||
return fmt.Errorf("arg(pause) is dangerous")
|
||||
}
|
||||
|
||||
if len(f.Hosts) == 0 {
|
||||
return fmt.Errorf("arg(hosts) empty")
|
||||
}
|
||||
|
||||
if f.Action != "start" && f.Action != "pause" {
|
||||
return fmt.Errorf("arg(action) invalid")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *taskForm) HandleFH(fh string) {
|
||||
i := strings.Index(f.Title, " FH: ")
|
||||
if i > 0 {
|
||||
f.Title = f.Title[:i]
|
||||
}
|
||||
f.Title = f.Title + " FH: " + fh
|
||||
}
|
||||
|
||||
func (rt *Router) taskRecordAdd(c *gin.Context) {
|
||||
var f *models.TaskRecord
|
||||
ginx.BindJSON(c, &f)
|
||||
@@ -127,7 +105,7 @@ func (rt *Router) taskRecordAdd(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) taskAdd(c *gin.Context) {
|
||||
var f taskForm
|
||||
var f models.TaskForm
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
bgid := ginx.UrlParamInt64(c, "id")
|
||||
@@ -143,7 +121,7 @@ func (rt *Router) taskAdd(c *gin.Context) {
|
||||
rt.checkTargetPerm(c, f.Hosts)
|
||||
|
||||
// call ibex
|
||||
taskId, err := TaskCreate(f, rt.NotifyConfigCache.GetIbex())
|
||||
taskId, err := sender.TaskAdd(f, user.Username, rt.Ctx.IsCenter)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if taskId <= 0 {
|
||||
@@ -152,65 +130,20 @@ func (rt *Router) taskAdd(c *gin.Context) {
|
||||
|
||||
// write db
|
||||
record := models.TaskRecord{
|
||||
Id: taskId,
|
||||
GroupId: bgid,
|
||||
IbexAddress: rt.NotifyConfigCache.GetIbex().Address,
|
||||
IbexAuthUser: rt.NotifyConfigCache.GetIbex().BasicAuthUser,
|
||||
IbexAuthPass: rt.NotifyConfigCache.GetIbex().BasicAuthPass,
|
||||
Title: f.Title,
|
||||
Account: f.Account,
|
||||
Batch: f.Batch,
|
||||
Tolerance: f.Tolerance,
|
||||
Timeout: f.Timeout,
|
||||
Pause: f.Pause,
|
||||
Script: f.Script,
|
||||
Args: f.Args,
|
||||
CreateAt: time.Now().Unix(),
|
||||
CreateBy: f.Creator,
|
||||
Id: taskId,
|
||||
GroupId: bgid,
|
||||
Title: f.Title,
|
||||
Account: f.Account,
|
||||
Batch: f.Batch,
|
||||
Tolerance: f.Tolerance,
|
||||
Timeout: f.Timeout,
|
||||
Pause: f.Pause,
|
||||
Script: f.Script,
|
||||
Args: f.Args,
|
||||
CreateAt: time.Now().Unix(),
|
||||
CreateBy: f.Creator,
|
||||
}
|
||||
|
||||
err = record.Add(rt.Ctx)
|
||||
ginx.NewRender(c).Data(taskId, err)
|
||||
}
|
||||
|
||||
func (rt *Router) taskProxy(c *gin.Context) {
|
||||
target, err := url.Parse(rt.NotifyConfigCache.GetIbex().Address)
|
||||
if err != nil {
|
||||
ginx.NewRender(c).Message("invalid ibex address: %s", rt.NotifyConfigCache.GetIbex().Address)
|
||||
return
|
||||
}
|
||||
|
||||
director := func(req *http.Request) {
|
||||
req.URL.Scheme = target.Scheme
|
||||
req.URL.Host = target.Host
|
||||
|
||||
// fe request e.g. /api/n9e/busi-group/:id/task/*url
|
||||
index := strings.Index(req.URL.Path, "/task/")
|
||||
if index == -1 {
|
||||
panic("url path invalid")
|
||||
}
|
||||
|
||||
req.URL.Path = "/ibex/v1" + req.URL.Path[index:]
|
||||
|
||||
if target.RawQuery == "" || req.URL.RawQuery == "" {
|
||||
req.URL.RawQuery = target.RawQuery + req.URL.RawQuery
|
||||
} else {
|
||||
req.URL.RawQuery = target.RawQuery + "&" + req.URL.RawQuery
|
||||
}
|
||||
|
||||
if rt.NotifyConfigCache.GetIbex().BasicAuthUser != "" {
|
||||
req.SetBasicAuth(rt.NotifyConfigCache.GetIbex().BasicAuthUser, rt.NotifyConfigCache.GetIbex().BasicAuthPass)
|
||||
}
|
||||
}
|
||||
|
||||
errFunc := func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
ginx.NewRender(c, http.StatusBadGateway).Message(err)
|
||||
}
|
||||
|
||||
proxy := &httputil.ReverseProxy{
|
||||
Director: director,
|
||||
ErrorHandler: errFunc,
|
||||
}
|
||||
|
||||
proxy.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
@@ -18,10 +18,45 @@ func (rt *Router) taskTplGets(c *gin.Context) {
|
||||
limit := ginx.QueryInt(c, "limit", 20)
|
||||
groupId := ginx.UrlParamInt64(c, "id")
|
||||
|
||||
total, err := models.TaskTplTotal(rt.Ctx, groupId, query)
|
||||
total, err := models.TaskTplTotal(rt.Ctx, []int64{groupId}, query)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
list, err := models.TaskTplGets(rt.Ctx, groupId, query, limit, ginx.Offset(c, limit))
|
||||
list, err := models.TaskTplGets(rt.Ctx, []int64{groupId}, query, limit, ginx.Offset(c, limit))
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Data(gin.H{
|
||||
"total": total,
|
||||
"list": list,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (rt *Router) taskTplGetsByGids(c *gin.Context) {
|
||||
query := ginx.QueryStr(c, "query", "")
|
||||
limit := ginx.QueryInt(c, "limit", 20)
|
||||
|
||||
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
|
||||
if len(gids) > 0 {
|
||||
for _, gid := range gids {
|
||||
rt.bgroCheck(c, gid)
|
||||
}
|
||||
} else {
|
||||
me := c.MustGet("user").(*models.User)
|
||||
if !me.IsAdmin() {
|
||||
var err error
|
||||
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if len(gids) == 0 {
|
||||
ginx.Bomb(http.StatusForbidden, "forbidden")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total, err := models.TaskTplTotal(rt.Ctx, gids, query)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
list, err := models.TaskTplGets(rt.Ctx, gids, query, limit, ginx.Offset(c, limit))
|
||||
ginx.Dangerous(err)
|
||||
|
||||
ginx.NewRender(c).Data(gin.H{
|
||||
@@ -48,6 +83,27 @@ func (rt *Router) taskTplGet(c *gin.Context) {
|
||||
}, err)
|
||||
}
|
||||
|
||||
func (rt *Router) taskTplGetByService(c *gin.Context) {
|
||||
tid := ginx.UrlParamInt64(c, "tid")
|
||||
|
||||
tpl, err := models.TaskTplGetById(rt.Ctx, tid)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if tpl == nil {
|
||||
ginx.Bomb(404, "no such task template")
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(tpl, err)
|
||||
}
|
||||
|
||||
func (rt *Router) taskTplGetsByService(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(models.TaskTplGetAll(rt.Ctx))
|
||||
}
|
||||
|
||||
func (rt *Router) taskTplStatistics(c *gin.Context) {
|
||||
ginx.NewRender(c).Data(models.TaskTplStatistics(rt.Ctx))
|
||||
}
|
||||
|
||||
type taskTplForm struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Batch int `json:"batch"`
|
||||
|
||||
117
center/router/router_tdengine.go
Normal file
117
center/router/router_tdengine.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/center/cconf"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type databasesQueryForm struct {
|
||||
Cate string `json:"cate" form:"cate"`
|
||||
DatasourceId int64 `json:"datasource_id" form:"datasource_id"`
|
||||
}
|
||||
|
||||
func (rt *Router) tdengineDatabases(c *gin.Context) {
|
||||
var f databasesQueryForm
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
|
||||
if tdClient == nil {
|
||||
ginx.NewRender(c, http.StatusNotFound).Message("No such datasource")
|
||||
return
|
||||
}
|
||||
|
||||
databases, err := tdClient.GetDatabases()
|
||||
ginx.NewRender(c).Data(databases, err)
|
||||
}
|
||||
|
||||
type tablesQueryForm struct {
|
||||
Cate string `json:"cate"`
|
||||
DatasourceId int64 `json:"datasource_id" `
|
||||
Database string `json:"db"`
|
||||
IsStable bool `json:"is_stable"`
|
||||
}
|
||||
|
||||
// get tdengine tables
|
||||
func (rt *Router) tdengineTables(c *gin.Context) {
|
||||
var f tablesQueryForm
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
|
||||
if tdClient == nil {
|
||||
ginx.NewRender(c, http.StatusNotFound).Message("No such datasource")
|
||||
return
|
||||
}
|
||||
|
||||
tables, err := tdClient.GetTables(f.Database, f.IsStable)
|
||||
ginx.NewRender(c).Data(tables, err)
|
||||
}
|
||||
|
||||
type columnsQueryForm struct {
|
||||
Cate string `json:"cate"`
|
||||
DatasourceId int64 `json:"datasource_id" `
|
||||
Database string `json:"db"`
|
||||
Table string `json:"table"`
|
||||
}
|
||||
|
||||
// get tdengine columns
|
||||
func (rt *Router) tdengineColumns(c *gin.Context) {
|
||||
var f columnsQueryForm
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
|
||||
if tdClient == nil {
|
||||
ginx.NewRender(c, http.StatusNotFound).Message("No such datasource")
|
||||
return
|
||||
}
|
||||
|
||||
columns, err := tdClient.GetColumns(f.Database, f.Table)
|
||||
ginx.NewRender(c).Data(columns, err)
|
||||
}
|
||||
|
||||
func (rt *Router) QueryData(c *gin.Context) {
|
||||
var f models.QueryParam
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
var resp []models.DataResp
|
||||
var err error
|
||||
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
|
||||
for _, q := range f.Querys {
|
||||
datas, err := tdClient.Query(q)
|
||||
ginx.Dangerous(err)
|
||||
resp = append(resp, datas...)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(resp, err)
|
||||
}
|
||||
|
||||
func (rt *Router) QueryLog(c *gin.Context) {
|
||||
var f models.QueryParam
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
|
||||
if len(f.Querys) == 0 {
|
||||
ginx.Bomb(200, "querys is empty")
|
||||
return
|
||||
}
|
||||
|
||||
data, err := tdClient.QueryLog(f.Querys[0])
|
||||
logger.Debugf("tdengine query:%s result: %+v", f.Querys[0], data)
|
||||
ginx.NewRender(c).Data(data, err)
|
||||
}
|
||||
|
||||
// query sql template
|
||||
func (rt *Router) QuerySqlTemplate(c *gin.Context) {
|
||||
cate := ginx.QueryStr(c, "cate")
|
||||
m := make(map[string]string)
|
||||
switch cate {
|
||||
case models.TDENGINE:
|
||||
m = cconf.TDengineSQLTpl
|
||||
}
|
||||
ginx.NewRender(c).Data(m, nil)
|
||||
}
|
||||
@@ -5,12 +5,35 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/flashduty"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ormx"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
)
|
||||
|
||||
func (rt *Router) userBusiGroupsGets(c *gin.Context) {
|
||||
userid := ginx.QueryInt64(c, "userid", 0)
|
||||
username := ginx.QueryStr(c, "username", "")
|
||||
|
||||
if userid == 0 && username == "" {
|
||||
ginx.Bomb(http.StatusBadRequest, "userid or username required")
|
||||
}
|
||||
|
||||
var user *models.User
|
||||
var err error
|
||||
if userid > 0 {
|
||||
user, err = models.UserGetById(rt.Ctx, userid)
|
||||
} else {
|
||||
user, err = models.UserGetByUsername(rt.Ctx, username)
|
||||
}
|
||||
|
||||
ginx.Dangerous(err)
|
||||
|
||||
groups, err := user.BusiGroups(rt.Ctx, 10000, "")
|
||||
ginx.NewRender(c).Data(groups, err)
|
||||
}
|
||||
|
||||
func (rt *Router) userFindAll(c *gin.Context) {
|
||||
list, err := models.UserGetAll(rt.Ctx)
|
||||
ginx.NewRender(c).Data(list, err)
|
||||
@@ -57,7 +80,7 @@ func (rt *Router) userAddPost(c *gin.Context) {
|
||||
ginx.Bomb(http.StatusBadRequest, "roles empty")
|
||||
}
|
||||
|
||||
user := c.MustGet("user").(*models.User)
|
||||
username := Username(c)
|
||||
|
||||
u := models.User{
|
||||
Username: f.Username,
|
||||
@@ -68,10 +91,11 @@ func (rt *Router) userAddPost(c *gin.Context) {
|
||||
Portrait: f.Portrait,
|
||||
Roles: strings.Join(f.Roles, " "),
|
||||
Contacts: f.Contacts,
|
||||
CreateBy: user.Username,
|
||||
UpdateBy: user.Username,
|
||||
CreateBy: username,
|
||||
UpdateBy: username,
|
||||
}
|
||||
|
||||
ginx.Dangerous(u.Verify())
|
||||
ginx.NewRender(c).Message(u.Add(rt.Ctx))
|
||||
}
|
||||
|
||||
@@ -88,6 +112,30 @@ type userProfileForm struct {
|
||||
Contacts ormx.JSONObj `json:"contacts"`
|
||||
}
|
||||
|
||||
func (rt *Router) userProfilePutByService(c *gin.Context) {
|
||||
var f models.User
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
if len(f.RolesLst) == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, "roles empty")
|
||||
}
|
||||
|
||||
password, err := models.CryptoPass(rt.Ctx, f.Password)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
target := User(rt.Ctx, ginx.UrlParamInt64(c, "id"))
|
||||
target.Nickname = f.Nickname
|
||||
target.Password = password
|
||||
target.Phone = f.Phone
|
||||
target.Email = f.Email
|
||||
target.Portrait = f.Portrait
|
||||
target.Roles = strings.Join(f.RolesLst, " ")
|
||||
target.Contacts = f.Contacts
|
||||
target.UpdateBy = Username(c)
|
||||
|
||||
ginx.NewRender(c).Message(target.UpdateAllFields(rt.Ctx))
|
||||
}
|
||||
|
||||
func (rt *Router) userProfilePut(c *gin.Context) {
|
||||
var f userProfileForm
|
||||
ginx.BindJSON(c, &f)
|
||||
@@ -97,6 +145,11 @@ func (rt *Router) userProfilePut(c *gin.Context) {
|
||||
}
|
||||
|
||||
target := User(rt.Ctx, ginx.UrlParamInt64(c, "id"))
|
||||
oldInfo := models.User{
|
||||
Username: target.Username,
|
||||
Phone: target.Phone,
|
||||
Email: target.Email,
|
||||
}
|
||||
target.Nickname = f.Nickname
|
||||
target.Phone = f.Phone
|
||||
target.Email = f.Email
|
||||
@@ -104,6 +157,10 @@ func (rt *Router) userProfilePut(c *gin.Context) {
|
||||
target.Contacts = f.Contacts
|
||||
target.UpdateBy = c.MustGet("username").(string)
|
||||
|
||||
if flashduty.NeedSyncUser(rt.Ctx) {
|
||||
flashduty.UpdateUser(rt.Ctx, oldInfo, f.Email, f.Phone)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(target.UpdateAllFields(rt.Ctx))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/flashduty"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
@@ -41,8 +42,9 @@ func (rt *Router) userGroupMemberGetsByService(c *gin.Context) {
|
||||
}
|
||||
|
||||
type userGroupForm struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Note string `json:"note"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Note string `json:"note"`
|
||||
IsSyncToFlashDuty bool `json:"is_sync_to_flashduty"`
|
||||
}
|
||||
|
||||
func (rt *Router) userGroupAdd(c *gin.Context) {
|
||||
@@ -59,12 +61,19 @@ func (rt *Router) userGroupAdd(c *gin.Context) {
|
||||
}
|
||||
|
||||
err := ug.Add(rt.Ctx)
|
||||
if err == nil {
|
||||
// Even failure is not a big deal
|
||||
models.UserGroupMemberAdd(rt.Ctx, ug.Id, me.Id)
|
||||
}
|
||||
ginx.Dangerous(err)
|
||||
|
||||
// Even failure is not a big deal
|
||||
models.UserGroupMemberAdd(rt.Ctx, ug.Id, me.Id)
|
||||
|
||||
if f.IsSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
|
||||
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, &ug)
|
||||
ginx.Dangerous(err)
|
||||
err = ugs.SyncUGAdd()
|
||||
ginx.Dangerous(err)
|
||||
}
|
||||
ginx.NewRender(c).Data(ug.Id, err)
|
||||
|
||||
}
|
||||
|
||||
func (rt *Router) userGroupPut(c *gin.Context) {
|
||||
@@ -73,6 +82,7 @@ func (rt *Router) userGroupPut(c *gin.Context) {
|
||||
|
||||
me := c.MustGet("user").(*models.User)
|
||||
ug := c.MustGet("user_group").(*models.UserGroup)
|
||||
oldUGName := ug.Name
|
||||
|
||||
if ug.Name != f.Name {
|
||||
// name changed, check duplication
|
||||
@@ -88,8 +98,14 @@ func (rt *Router) userGroupPut(c *gin.Context) {
|
||||
ug.Note = f.Note
|
||||
ug.UpdateBy = me.Username
|
||||
ug.UpdateAt = time.Now().Unix()
|
||||
|
||||
if f.IsSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
|
||||
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, ug)
|
||||
ginx.Dangerous(err)
|
||||
err = ugs.SyncUGPut(oldUGName)
|
||||
ginx.Dangerous(err)
|
||||
}
|
||||
ginx.NewRender(c).Message(ug.Update(rt.Ctx, "Name", "Note", "UpdateAt", "UpdateBy"))
|
||||
|
||||
}
|
||||
|
||||
// Return all members, front-end search and paging
|
||||
@@ -109,8 +125,16 @@ func (rt *Router) userGroupGet(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) userGroupDel(c *gin.Context) {
|
||||
isSyncToFlashDuty := ginx.QueryBool(c, "is_sync_to_flashduty", false)
|
||||
ug := c.MustGet("user_group").(*models.UserGroup)
|
||||
if isSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
|
||||
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, ug)
|
||||
ginx.Dangerous(err)
|
||||
err = ugs.SyncUGDel(ug.Name)
|
||||
ginx.Dangerous(err)
|
||||
}
|
||||
ginx.NewRender(c).Message(ug.Del(rt.Ctx))
|
||||
|
||||
}
|
||||
|
||||
func (rt *Router) userGroupMemberAdd(c *gin.Context) {
|
||||
@@ -122,13 +146,21 @@ func (rt *Router) userGroupMemberAdd(c *gin.Context) {
|
||||
ug := c.MustGet("user_group").(*models.UserGroup)
|
||||
|
||||
err := ug.AddMembers(rt.Ctx, f.Ids)
|
||||
ginx.Dangerous(err)
|
||||
if err == nil {
|
||||
ug.UpdateAt = time.Now().Unix()
|
||||
ug.UpdateBy = me.Username
|
||||
ug.Update(rt.Ctx, "UpdateAt", "UpdateBy")
|
||||
}
|
||||
|
||||
if f.IsSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
|
||||
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, ug)
|
||||
ginx.Dangerous(err)
|
||||
err = ugs.SyncMembersAdd()
|
||||
ginx.Dangerous(err)
|
||||
}
|
||||
ginx.NewRender(c).Message(err)
|
||||
|
||||
}
|
||||
|
||||
func (rt *Router) userGroupMemberDel(c *gin.Context) {
|
||||
@@ -145,6 +177,12 @@ func (rt *Router) userGroupMemberDel(c *gin.Context) {
|
||||
ug.UpdateBy = me.Username
|
||||
ug.Update(rt.Ctx, "UpdateAt", "UpdateBy")
|
||||
}
|
||||
if f.IsSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
|
||||
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, ug)
|
||||
ginx.Dangerous(err)
|
||||
err = ugs.SyncMembersDel()
|
||||
ginx.Dangerous(err)
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(err)
|
||||
}
|
||||
|
||||
70
center/router/router_user_variable_config.go
Normal file
70
center/router/router_user_variable_config.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
)
|
||||
|
||||
func (rt *Router) userVariableConfigGets(context *gin.Context) {
|
||||
userVariables, err := models.ConfigsGetUserVariable(rt.Ctx)
|
||||
ginx.NewRender(context).Data(userVariables, err)
|
||||
}
|
||||
func (rt *Router) userVariableConfigAdd(context *gin.Context) {
|
||||
var f models.Configs
|
||||
ginx.BindJSON(context, &f)
|
||||
f.Ckey = strings.TrimSpace(f.Ckey)
|
||||
//insert external config. needs to make sure not plaintext for an encrypted type config
|
||||
username := context.MustGet("username").(string)
|
||||
now := time.Now().Unix()
|
||||
f.CreateBy = username
|
||||
f.UpdateBy = username
|
||||
f.CreateAt = now
|
||||
f.UpdateAt = now
|
||||
ginx.NewRender(context).Message(models.ConfigsUserVariableInsert(rt.Ctx, f))
|
||||
|
||||
}
|
||||
|
||||
func (rt *Router) userVariableConfigPut(context *gin.Context) {
|
||||
var f models.Configs
|
||||
ginx.BindJSON(context, &f)
|
||||
f.Id = ginx.UrlParamInt64(context, "id")
|
||||
f.Ckey = strings.TrimSpace(f.Ckey)
|
||||
f.UpdateBy = context.MustGet("username").(string)
|
||||
f.UpdateAt = time.Now().Unix()
|
||||
|
||||
user := context.MustGet("user").(*models.User)
|
||||
if !user.IsAdmin() && f.CreateBy != user.Username {
|
||||
// only admin or creator can update
|
||||
ginx.Bomb(403, "no permission")
|
||||
}
|
||||
|
||||
ginx.NewRender(context).Message(models.ConfigsUserVariableUpdate(rt.Ctx, f))
|
||||
}
|
||||
|
||||
func (rt *Router) userVariableConfigDel(context *gin.Context) {
|
||||
id := ginx.UrlParamInt64(context, "id")
|
||||
configs, err := models.ConfigGet(rt.Ctx, id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
user := context.MustGet("user").(*models.User)
|
||||
if !user.IsAdmin() && configs.CreateBy != user.Username {
|
||||
// only admin or creator can delete
|
||||
ginx.Bomb(403, "no permission")
|
||||
}
|
||||
|
||||
if configs != nil && configs.External == models.ConfigExternal {
|
||||
ginx.NewRender(context).Message(models.ConfigsDel(rt.Ctx, []int64{id}))
|
||||
} else {
|
||||
ginx.NewRender(context).Message(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *Router) userVariableGetDecryptByService(context *gin.Context) {
|
||||
decryptMap, decryptErr := models.ConfigUserVariableGetDecryptMap(rt.Ctx, rt.HTTP.RSA.RSAPrivateKey, rt.HTTP.RSA.RSAPassWord)
|
||||
ginx.NewRender(context).Data(decryptMap, decryptErr)
|
||||
}
|
||||
@@ -2,24 +2,30 @@ package sso
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/ccfos/nightingale/v6/center/cconf"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/cas"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ldapx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/oauth2x"
|
||||
"github.com/ccfos/nightingale/v6/pkg/oidcx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/tplx"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type SsoClient struct {
|
||||
OIDC *oidcx.SsoClient
|
||||
LDAP *ldapx.SsoClient
|
||||
CAS *cas.SsoClient
|
||||
OAuth2 *oauth2x.SsoClient
|
||||
OIDC *oidcx.SsoClient
|
||||
LDAP *ldapx.SsoClient
|
||||
CAS *cas.SsoClient
|
||||
OAuth2 *oauth2x.SsoClient
|
||||
LastUpdateTime int64
|
||||
configCache *memsto.ConfigCache
|
||||
configLastUpdateTime int64
|
||||
}
|
||||
|
||||
const LDAP = `
|
||||
@@ -29,13 +35,21 @@ Port = 389
|
||||
BaseDn = 'dc=example,dc=org'
|
||||
BindUser = 'cn=manager,dc=example,dc=org'
|
||||
BindPass = '*******'
|
||||
SyncAddUsers = false
|
||||
SyncDelUsers = false
|
||||
# unit: s
|
||||
SyncInterval = 86400
|
||||
# openldap format e.g. (&(uid=%s))
|
||||
# AD format e.g. (&(sAMAccountName=%s))
|
||||
AuthFilter = '(&(uid=%s))'
|
||||
UserFilter = '(&(uid=*))'
|
||||
CoverAttributes = true
|
||||
TLS = false
|
||||
StartTLS = true
|
||||
DefaultRoles = ['Standard']
|
||||
|
||||
[Attributes]
|
||||
Username = 'uid'
|
||||
Nickname = 'cn'
|
||||
Phone = 'mobile'
|
||||
Email = 'mail'
|
||||
@@ -44,8 +58,9 @@ Email = 'mail'
|
||||
const OAuth2 = `
|
||||
Enable = false
|
||||
DisplayName = 'OAuth2登录'
|
||||
RedirectURL = 'http://127.0.0.1:18000/callback/oauth'
|
||||
RedirectURL = 'http://n9e.com/callback/oauth'
|
||||
SsoAddr = 'https://sso.example.com/oauth2/authorize'
|
||||
SsoLogoutAddr = 'https://sso.example.com/oauth2/authorize/session/end'
|
||||
TokenAddr = 'https://sso.example.com/oauth2/token'
|
||||
UserInfoAddr = 'https://api.example.com/api/v1/user/info'
|
||||
TranTokenMethod = 'header'
|
||||
@@ -58,7 +73,7 @@ UserinfoPrefix = 'data'
|
||||
Scopes = ['profile', 'email', 'phone']
|
||||
|
||||
[Attributes]
|
||||
Username = 'username'
|
||||
Username = 'sub'
|
||||
Nickname = 'nickname'
|
||||
Phone = 'phone_number'
|
||||
Email = 'email'
|
||||
@@ -66,34 +81,41 @@ Email = 'email'
|
||||
|
||||
const CAS = `
|
||||
Enable = false
|
||||
SsoAddr = 'https://cas.example.com/cas/'
|
||||
RedirectURL = 'http://127.0.0.1:18000/callback/cas'
|
||||
DisplayName = 'CAS登录'
|
||||
CoverAttributes = false
|
||||
RedirectURL = 'http://n9e.com/callback/cas'
|
||||
SsoAddr = 'https://cas.example.com/cas/'
|
||||
SsoLogoutAddr = 'https://cas.example.com/cas/session/end'
|
||||
# LoginPath = ''
|
||||
CoverAttributes = true
|
||||
DefaultRoles = ['Standard']
|
||||
|
||||
[Attributes]
|
||||
Username = 'sub'
|
||||
Nickname = 'nickname'
|
||||
Phone = 'phone_number'
|
||||
Email = 'email'
|
||||
`
|
||||
|
||||
const OIDC = `
|
||||
Enable = false
|
||||
DisplayName = 'OIDC登录'
|
||||
RedirectURL = 'http://n9e.com/callback'
|
||||
SsoAddr = 'http://sso.example.org'
|
||||
SsoLogoutAddr = 'http://sso.example.org/session/end'
|
||||
ClientId = ''
|
||||
ClientSecret = ''
|
||||
CoverAttributes = true
|
||||
DefaultRoles = ['Standard']
|
||||
Scopes = ['openid', 'profile', 'email', 'phone']
|
||||
|
||||
[Attributes]
|
||||
Username = 'sub'
|
||||
Nickname = 'nickname'
|
||||
Phone = 'phone_number'
|
||||
Email = 'email'
|
||||
`
|
||||
|
||||
func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
|
||||
func Init(center cconf.Center, ctx *ctx.Context, configCache *memsto.ConfigCache) *SsoClient {
|
||||
ssoClient := new(SsoClient)
|
||||
m := make(map[string]string)
|
||||
m["LDAP"] = LDAP
|
||||
@@ -122,6 +144,11 @@ func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
if configCache == nil {
|
||||
logger.Error("configCache is nil, sso initialization failed")
|
||||
}
|
||||
ssoClient.configCache = configCache
|
||||
userVariableMap := configCache.Get()
|
||||
|
||||
configs, err := models.SsoConfigGets(ctx)
|
||||
if err != nil {
|
||||
@@ -129,6 +156,7 @@ func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
|
||||
}
|
||||
|
||||
for _, cfg := range configs {
|
||||
cfg.Content = tplx.ReplaceTemplateUseText(cfg.Name, cfg.Content, userVariableMap)
|
||||
switch cfg.Name {
|
||||
case "LDAP":
|
||||
var config ldapx.Config
|
||||
@@ -143,6 +171,7 @@ func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
|
||||
if err != nil {
|
||||
log.Fatalln("init oidc failed:", err)
|
||||
}
|
||||
logger.Info("init oidc..")
|
||||
oidcClient, err := oidcx.New(config)
|
||||
if err != nil {
|
||||
logger.Error("init oidc failed:", err)
|
||||
@@ -165,5 +194,84 @@ func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
|
||||
ssoClient.OAuth2 = oauth2x.New(config)
|
||||
}
|
||||
}
|
||||
|
||||
go ssoClient.SyncSsoUsers(ctx)
|
||||
go ssoClient.Reload(ctx)
|
||||
return ssoClient
|
||||
}
|
||||
|
||||
// 定期更新sso配置
|
||||
func (s *SsoClient) reload(ctx *ctx.Context) error {
|
||||
lastUpdateTime, err := models.SsoConfigLastUpdateTime(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastCacheUpdateTime := s.configCache.GetLastUpdateTime()
|
||||
if lastUpdateTime == s.LastUpdateTime && lastCacheUpdateTime == s.configLastUpdateTime {
|
||||
return nil
|
||||
}
|
||||
|
||||
configs, err := models.SsoConfigGets(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userVariableMap := s.configCache.Get()
|
||||
for _, cfg := range configs {
|
||||
cfg.Content = tplx.ReplaceTemplateUseText(cfg.Name, cfg.Content, userVariableMap)
|
||||
switch cfg.Name {
|
||||
case "LDAP":
|
||||
var config ldapx.Config
|
||||
err := toml.Unmarshal([]byte(cfg.Content), &config)
|
||||
if err != nil {
|
||||
logger.Warning("reload ldap failed", err)
|
||||
continue
|
||||
}
|
||||
s.LDAP.Reload(config)
|
||||
case "OIDC":
|
||||
var config oidcx.Config
|
||||
err := toml.Unmarshal([]byte(cfg.Content), &config)
|
||||
if err != nil {
|
||||
logger.Warning("reload oidc failed:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("reload oidc..")
|
||||
err = s.OIDC.Reload(config)
|
||||
if err != nil {
|
||||
logger.Error("reload oidc failed:", err)
|
||||
continue
|
||||
}
|
||||
case "CAS":
|
||||
var config cas.Config
|
||||
err := toml.Unmarshal([]byte(cfg.Content), &config)
|
||||
if err != nil {
|
||||
logger.Warning("reload cas failed:", err)
|
||||
continue
|
||||
}
|
||||
s.CAS.Reload(config)
|
||||
case "OAuth2":
|
||||
var config oauth2x.Config
|
||||
err := toml.Unmarshal([]byte(cfg.Content), &config)
|
||||
if err != nil {
|
||||
logger.Warning("reload oauth2 failed:", err)
|
||||
continue
|
||||
}
|
||||
s.OAuth2.Reload(config)
|
||||
}
|
||||
}
|
||||
|
||||
s.LastUpdateTime = lastUpdateTime
|
||||
s.configLastUpdateTime = lastCacheUpdateTime
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SsoClient) Reload(ctx *ctx.Context) {
|
||||
duration := time.Duration(9000) * time.Millisecond
|
||||
for {
|
||||
time.Sleep(duration)
|
||||
if err := s.reload(ctx); err != nil {
|
||||
logger.Warning("reload sso client err:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
37
center/sso/sync.go
Normal file
37
center/sso/sync.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package sso
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
func (s *SsoClient) SyncSsoUsers(ctx *ctx.Context) {
|
||||
if err := s.LDAP.SyncAddAndDelUsers(ctx); err != nil {
|
||||
fmt.Println("failed to sync the addition and deletion of ldap users:", err)
|
||||
}
|
||||
|
||||
if err := s.LDAP.SyncDelUsers(ctx); err != nil {
|
||||
fmt.Println("failed to sync deletion of ldap users:", err)
|
||||
}
|
||||
|
||||
go s.loopSyncSsoUsers(ctx)
|
||||
}
|
||||
|
||||
func (s *SsoClient) loopSyncSsoUsers(ctx *ctx.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-s.LDAP.Ticker.C:
|
||||
lc := s.LDAP.Copy()
|
||||
|
||||
if err := lc.SyncAddAndDelUsers(ctx); err != nil {
|
||||
logger.Warningf("failed to sync the addition and deletion of ldap users: %v", err)
|
||||
}
|
||||
|
||||
if err := lc.SyncDelUsers(ctx); err != nil {
|
||||
logger.Warningf("failed to sync deletion of ldap users: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/osx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/version"
|
||||
|
||||
"github.com/toolkits/pkg/net/tcpx"
|
||||
"github.com/toolkits/pkg/runner"
|
||||
)
|
||||
|
||||
@@ -31,6 +32,8 @@ func main() {
|
||||
|
||||
printEnv()
|
||||
|
||||
tcpx.WaitHosts()
|
||||
|
||||
cleanFunc, err := center.Initialize(*configDir, *cryptoKey)
|
||||
if err != nil {
|
||||
log.Fatalln("failed to initialize:", err)
|
||||
|
||||
@@ -2,11 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert"
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/alert/process"
|
||||
alertrt "github.com/ccfos/nightingale/v6/alert/router"
|
||||
"github.com/ccfos/nightingale/v6/center/metas"
|
||||
"github.com/ccfos/nightingale/v6/conf"
|
||||
"github.com/ccfos/nightingale/v6/dumper"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
@@ -15,10 +18,12 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/logx"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/idents"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/writer"
|
||||
|
||||
alertrt "github.com/ccfos/nightingale/v6/alert/router"
|
||||
pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/writer"
|
||||
"github.com/ccfos/nightingale/v6/storage"
|
||||
"github.com/ccfos/nightingale/v6/tdengine"
|
||||
|
||||
"github.com/flashcatcloud/ibex/src/cmd/ibex"
|
||||
)
|
||||
|
||||
func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
@@ -31,36 +36,54 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//check CenterApi is default value
|
||||
if len(config.CenterApi.Addrs) < 1 {
|
||||
return nil, errors.New("failed to init config: the CenterApi configuration is missing")
|
||||
}
|
||||
ctx := ctx.NewContext(context.Background(), nil, false, config.CenterApi)
|
||||
|
||||
var redis storage.Redis
|
||||
redis, err = storage.NewRedis(config.Redis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
syncStats := memsto.NewSyncStats()
|
||||
|
||||
targetCache := memsto.NewTargetCache(ctx, syncStats, nil)
|
||||
busiGroupCache := memsto.NewBusiGroupCache(ctx, syncStats)
|
||||
idents := idents.New(ctx)
|
||||
idents := idents.New(ctx, redis)
|
||||
metas := metas.New(redis)
|
||||
writers := writer.NewWriters(config.Pushgw)
|
||||
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, targetCache, busiGroupCache, idents, writers, ctx)
|
||||
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, config.Alert, targetCache, busiGroupCache, idents, metas, writers, ctx)
|
||||
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
|
||||
pushgwRouter.Config(r)
|
||||
|
||||
if !config.Alert.Disable {
|
||||
configCache := memsto.NewConfigCache(ctx, syncStats, nil, "")
|
||||
alertStats := astats.NewSyncStats()
|
||||
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
|
||||
alertMuteCache := memsto.NewAlertMuteCache(ctx, syncStats)
|
||||
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
|
||||
notifyConfigCache := memsto.NewNotifyConfigCache(ctx)
|
||||
notifyConfigCache := memsto.NewNotifyConfigCache(ctx, configCache)
|
||||
userCache := memsto.NewUserCache(ctx, syncStats)
|
||||
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
|
||||
taskTplsCache := memsto.NewTaskTplCache(ctx)
|
||||
|
||||
promClients := prom.NewPromClient(ctx, config.Alert.Heartbeat)
|
||||
promClients := prom.NewPromClient(ctx)
|
||||
tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat)
|
||||
externalProcessors := process.NewExternalProcessors()
|
||||
|
||||
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, userCache, userGroupCache)
|
||||
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache,
|
||||
alertRuleCache, notifyConfigCache, taskTplsCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache)
|
||||
|
||||
alertrtRouter := alertrt.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
|
||||
|
||||
alertrtRouter.Config(r)
|
||||
|
||||
if config.Ibex.Enable {
|
||||
ibex.ServerStart(false, nil, redis, config.HTTP.APIForService.BasicAuth, config.Alert.Heartbeat, &config.CenterApi, r, nil, config.Ibex, config.HTTP.Port)
|
||||
}
|
||||
}
|
||||
|
||||
dumper.ConfigRouter(r)
|
||||
|
||||
14
conf/conf.go
14
conf/conf.go
@@ -27,6 +27,7 @@ type ConfigType struct {
|
||||
Pushgw pconf.Pushgw
|
||||
Alert aconf.Alert
|
||||
Center cconf.Center
|
||||
Ibex Ibex
|
||||
}
|
||||
|
||||
type CenterApi struct {
|
||||
@@ -40,6 +41,17 @@ type GlobalConfig struct {
|
||||
RunMode string
|
||||
}
|
||||
|
||||
type Ibex struct {
|
||||
Enable bool
|
||||
RPCListen string
|
||||
Output Output
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
ComeFrom string
|
||||
AgtdPort int
|
||||
}
|
||||
|
||||
func InitConfig(configDir, cryptoKey string) (*ConfigType, error) {
|
||||
var config = new(ConfigType)
|
||||
|
||||
@@ -48,7 +60,7 @@ func InitConfig(configDir, cryptoKey string) (*ConfigType, error) {
|
||||
}
|
||||
|
||||
config.Pushgw.PreCheck()
|
||||
config.Alert.PreCheck()
|
||||
config.Alert.PreCheck(configDir)
|
||||
config.Center.PreCheck()
|
||||
|
||||
err := decryptConfig(config, cryptoKey)
|
||||
|
||||
@@ -77,4 +77,3 @@ Committer 记录并公示于 **[COMMITTERS](https://github.com/ccfos/nightingale
|
||||
2. 提问之前请先搜索 [Github Issues](https://github.com/ccfos/nightingale/issues "Github Issue");
|
||||
3. 我们优先推荐通过提交 [Github Issue](https://github.com/ccfos/nightingale/issues "Github Issue") 来提问,如果[有问题点击这里](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&template=bug_report.yml "有问题点击这里") | [有需求建议点击这里](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Ffeature&template=enhancement.md "有需求建议点击这里");
|
||||
|
||||
最后,我们推荐你加入微信群,针对相关开放式问题,相互交流咨询 (请先加好友:[UlricGO](https://www.gitlink.org.cn/UlricQin/gist/tree/master/self.jpeg "UlricGO") 备注:夜莺加群+姓名+公司,交流群里会有开发者团队和专业、热心的群友回答问题)。
|
||||
|
||||
BIN
doc/img/Nightingale_L_V.png
Normal file
BIN
doc/img/Nightingale_L_V.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
@@ -1,7 +1,5 @@
|
||||
ibexetc
|
||||
compose-host-network
|
||||
compose-postgres
|
||||
compose-bridge
|
||||
initsql
|
||||
mysqletc
|
||||
n9eetc
|
||||
prometc
|
||||
build.sh
|
||||
docker-compose.yaml
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM python:3-slim
|
||||
#FROM ubuntu:21.04
|
||||
|
||||
WORKDIR /app
|
||||
ADD n9e /app
|
||||
ADD http://download.flashcat.cloud/wait /wait
|
||||
RUN chmod +x /wait
|
||||
RUN chmod +x n9e
|
||||
|
||||
EXPOSE 17000
|
||||
|
||||
CMD ["/app/n9e", "-h"]
|
||||
@@ -3,10 +3,9 @@ FROM --platform=$TARGETPLATFORM python:3-slim
|
||||
|
||||
WORKDIR /app
|
||||
ADD n9e /app/
|
||||
ADD etc /app/
|
||||
ADD etc /app/etc/
|
||||
ADD integrations /app/integrations/
|
||||
ADD --chmod=755 https://github.com/ufoscout/docker-compose-wait/releases/download/2.11.0/wait_x86_64 /wait
|
||||
RUN chmod +x /wait && pip install requests
|
||||
RUN pip install requests
|
||||
|
||||
EXPOSE 17000
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
FROM flashcatcloud/toolbox:v0.0.1 as toolbox
|
||||
FROM --platform=$TARGETPLATFORM python:3-slim
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
ADD n9e /app/
|
||||
ADD etc /app/
|
||||
ADD etc /app/etc/
|
||||
ADD integrations /app/integrations/
|
||||
COPY --chmod=755 --from=toolbox /toolbox/wait_aarch64 /wait
|
||||
|
||||
EXPOSE 17000
|
||||
|
||||
|
||||
112
docker/compose-bridge/docker-compose.yaml
Normal file
112
docker/compose-bridge/docker-compose.yaml
Normal file
@@ -0,0 +1,112 @@
|
||||
version: "3.7"
|
||||
|
||||
networks:
|
||||
nightingale:
|
||||
driver: bridge
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: "mysql:8"
|
||||
container_name: mysql
|
||||
hostname: mysql
|
||||
restart: always
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
MYSQL_ROOT_PASSWORD: 1234
|
||||
volumes:
|
||||
- ./mysqldata:/var/lib/mysql/
|
||||
- ../initsql:/docker-entrypoint-initdb.d/
|
||||
- ./etc-mysql/my.cnf:/etc/my.cnf
|
||||
networks:
|
||||
- nightingale
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
redis:
|
||||
image: "redis:6.2"
|
||||
container_name: redis
|
||||
hostname: redis
|
||||
restart: always
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
networks:
|
||||
- nightingale
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
# prometheus:
|
||||
# image: prom/prometheus
|
||||
# container_name: prometheus
|
||||
# hostname: prometheus
|
||||
# restart: always
|
||||
# environment:
|
||||
# TZ: Asia/Shanghai
|
||||
# volumes:
|
||||
# - ./etc-prometheus:/etc/prometheus
|
||||
# command:
|
||||
# - "--config.file=/etc/prometheus/prometheus.yml"
|
||||
# - "--storage.tsdb.path=/prometheus"
|
||||
# - "--web.console.libraries=/usr/share/prometheus/console_libraries"
|
||||
# - "--web.console.templates=/usr/share/prometheus/consoles"
|
||||
# - "--enable-feature=remote-write-receiver"
|
||||
# - "--query.lookback-delta=2m"
|
||||
# networks:
|
||||
# - nightingale
|
||||
# ports:
|
||||
# - "9090:9090"
|
||||
|
||||
victoriametrics:
|
||||
image: victoriametrics/victoria-metrics:v1.79.12
|
||||
container_name: victoriametrics
|
||||
hostname: victoriametrics
|
||||
restart: always
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- "8428:8428"
|
||||
networks:
|
||||
- nightingale
|
||||
command:
|
||||
- "--loggerTimezone=Asia/Shanghai"
|
||||
|
||||
nightingale:
|
||||
image: flashcatcloud/nightingale:latest
|
||||
container_name: nightingale
|
||||
hostname: nightingale
|
||||
restart: always
|
||||
environment:
|
||||
GIN_MODE: release
|
||||
TZ: Asia/Shanghai
|
||||
WAIT_HOSTS: mysql:3306, redis:6379
|
||||
volumes:
|
||||
- ./etc-nightingale:/app/etc
|
||||
networks:
|
||||
- nightingale
|
||||
ports:
|
||||
- "17000:17000"
|
||||
- "20090:20090"
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
- victoriametrics
|
||||
command: >
|
||||
sh -c "/app/n9e"
|
||||
|
||||
categraf:
|
||||
image: "flashcatcloud/categraf:latest"
|
||||
container_name: "categraf"
|
||||
hostname: "categraf01"
|
||||
restart: always
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
HOST_PROC: /hostfs/proc
|
||||
HOST_SYS: /hostfs/sys
|
||||
HOST_MOUNT_PREFIX: /hostfs
|
||||
WAIT_HOSTS: nightingale:17000, nightingale:20090
|
||||
volumes:
|
||||
- ./etc-categraf:/etc/categraf/conf
|
||||
- /:/hostfs
|
||||
networks:
|
||||
- nightingale
|
||||
depends_on:
|
||||
- nightingale
|
||||
83
docker/compose-bridge/etc-categraf/config.toml
Normal file
83
docker/compose-bridge/etc-categraf/config.toml
Normal file
@@ -0,0 +1,83 @@
|
||||
[global]
|
||||
# whether print configs
|
||||
print_configs = false
|
||||
|
||||
# add label(agent_hostname) to series
|
||||
# "" -> auto detect hostname
|
||||
# "xx" -> use specified string xx
|
||||
# "$hostname" -> auto detect hostname
|
||||
# "$ip" -> auto detect ip
|
||||
# "$hostname-$ip" -> auto detect hostname and ip to replace the vars
|
||||
hostname = "$HOSTNAME"
|
||||
|
||||
# will not add label(agent_hostname) if true
|
||||
omit_hostname = false
|
||||
|
||||
# s | ms
|
||||
precision = "ms"
|
||||
|
||||
# global collect interval
|
||||
interval = 15
|
||||
|
||||
[global.labels]
|
||||
source="categraf"
|
||||
# region = "shanghai"
|
||||
# env = "localhost"
|
||||
|
||||
[writer_opt]
|
||||
# default: 2000
|
||||
batch = 2000
|
||||
# channel(as queue) size
|
||||
chan_size = 10000
|
||||
|
||||
[[writers]]
|
||||
url = "http://nightingale:17000/prometheus/v1/write"
|
||||
|
||||
# Basic auth username
|
||||
basic_auth_user = ""
|
||||
|
||||
# Basic auth password
|
||||
basic_auth_pass = ""
|
||||
|
||||
# timeout settings, unit: ms
|
||||
timeout = 5000
|
||||
dial_timeout = 2500
|
||||
max_idle_conns_per_host = 100
|
||||
|
||||
[http]
|
||||
enable = false
|
||||
address = ":9100"
|
||||
print_access = false
|
||||
run_mode = "release"
|
||||
|
||||
[heartbeat]
|
||||
enable = true
|
||||
|
||||
# report os version cpu.util mem.util metadata
|
||||
url = "http://nightingale:17000/v1/n9e/heartbeat"
|
||||
|
||||
# interval, unit: s
|
||||
interval = 10
|
||||
|
||||
# Basic auth username
|
||||
basic_auth_user = ""
|
||||
|
||||
# Basic auth password
|
||||
basic_auth_pass = ""
|
||||
|
||||
## Optional headers
|
||||
# headers = ["X-From", "categraf", "X-Xyz", "abc"]
|
||||
|
||||
# timeout settings, unit: ms
|
||||
timeout = 5000
|
||||
dial_timeout = 2500
|
||||
max_idle_conns_per_host = 100
|
||||
|
||||
[ibex]
|
||||
enable = true
|
||||
## ibex flush interval
|
||||
interval = "1000ms"
|
||||
## n9e ibex server rpc address
|
||||
servers = ["nightingale:20090"]
|
||||
## temp script dir
|
||||
meta_dir = "./meta"
|
||||
10
docker/compose-bridge/etc-categraf/input.disk/disk.toml
Normal file
10
docker/compose-bridge/etc-categraf/input.disk/disk.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
# # collect interval
|
||||
# interval = 15
|
||||
|
||||
# # By default stats will be gathered for all mount points.
|
||||
# # Set mount_points will restrict the stats to only the specified mount points.
|
||||
mount_points = ["/"]
|
||||
|
||||
# Ignore mount points by filesystem type.
|
||||
ignore_fs = ["tmpfs", "devtmpfs", "devfs", "iso9660", "overlay", "aufs", "squashfs", "nsfs"]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[[instances]]
|
||||
urls = [
|
||||
"http://nightingale:17000/metrics"
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user